Connecting an instrument to a Pouchy agent

For device vendors and labs. If your instrument exposes an MCP endpoint, a Pouchy agent can read from it and command it — with a human approving every physical write, and each approved write sent exactly once.

Nothing here is a new protocol. Pouchy is an ordinary MCP client. If your device speaks MCP, you are already done at the protocol layer, and the rest of this page is about reachability, credentials and what the agent will and will not do on your behalf.

What Pouchy guarantees, and what it does not

Two properties, stated narrowly because they are the two that are actually enforced in code and pinned by tests:

  • Every physical write is approved by a person. A tool call reaching an MCP server is never auto-run by default; it surfaces a confirmation card naming the tool and its argument values, and waits. An operator may add specific tools to an auto-run list, per agent — that is a deliberate, per-tool decision, never a default.
  • An approved write is sent at most once. If a call fails ambiguously — a timeout, a dropped connection, a session the server no longer recognises — Pouchy reports the failure. It does not re-send. A timeout is precisely the case where the instrument may already have moved, and re-sending is how one authorised command becomes two.

What Pouchy does not provide:

  • It does not enforce your limits. If your server declares a range, that declaration is a statement by the device being commanded. Pouchy shows it to the operator and labels it as the device's own claim. The enforcement that matters is the one inside your driver, in front of the actuator, where a refusal is physically meaningful. Design as if the caller were hostile, because your safety layer is the only one that is real.
  • It does not serialise callers. Two agent sessions can each hold an approval for the same instrument. Human approval is a practical upper bound, not a mutex. If simultaneous commands are dangerous for your hardware, refuse the second one in the driver.
  • It does not read readOnlyHint as a safety signal. A hint that a tool is read-only is a claim by the server under examination. We treat it as documentation, not as a permission.

What your server has to answer

Standard MCP over HTTP. Pouchy speaks both eras and negotiates:

protocol versions 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, 2026-07-28
required initialize, notifications/initialized, tools/list, tools/call
optional, and used when present resources/list, resources/read, prompts/list, prompts/get
transport JSON response or text/event-stream, both accepted on POST

A stateless 2026-07-28 server is answered in a single request with no handshake. A session-based server gets initialize + notifications/initialized before every write, by design: session reuse is what makes a stale-session error ambiguous, and ambiguity is what a caller must not resolve by re-sending.

Refuse a write in-band, not out of band. An out-of-range command should come back as a tool result with isError: true naming the limit — the exchange succeeded, the instrument declined. Reserve JSON-RPC protocol errors for calls that never reached your device logic. The agent can explain the first to a user; the second reads as a broken connection.

The reference document

If your server publishes a resource describing the instrument — what it measures, what is adjustable, in what units, within what limits — Pouchy fetches it at install time and stores the first 4,000 characters with the skill, so the operator sees it in the dashboard and the agent can quote it. Longer documents are marked as truncated rather than silently cut, and the agent can read the full bytes on demand through the resource-reading tool.

The document is presented as text from the server, attributed to the server. It is not parsed into policy, and it does not grant anything.

Path A — your device is already on the public internet

If your instrument (or a gateway in front of it) terminates HTTPS on a public name, there is nothing to install:

  1. In the Pouchy dashboard, open the agent → Connect MCP server and paste the endpoint URL.
  2. If the endpoint needs a bearer token, store it in the project vault under the slug the dashboard derives from the URL, before installing — discovery authenticates with it.
  3. Discovery runs initialize + tools/list (+ resources and prompts when offered) and installs the tools as a skill. Rate limits, daily call caps and the auto-run list are per skill.

Private and loopback addresses are refused. That guard is not negotiable and not worked around; it is what keeps a URL an operator pastes from becoming a way to reach inside the network the app runs in.

Path B — your device accepts no inbound connection

The usual case for a lab or a plant floor. One small process you run, plus one Pouchy operates, turn an outbound-only device into an ordinary public MCP endpoint — without any of the above becoming an exception.

lab network                        public                       Pouchy
┌────────────────┐       ┌──────────────────┐          ┌──────────────┐
│ driver (MCP)   │◄─────│  bridge (yours)   │─dials out►│ relay(Pouchy)│◄── ordinary
│ 10.0.0.5:8930  │local  │  (you run it)     │          │              │    MCP client
└────────────────┘       └──────────────────┘          └──────────────┘
  • The bridge runs beside the instrument and opens no port. It is a local MCP client of your driver and a polling client of the relay. The driver's address is configured at the lab and is never accepted from upstream.
  • The relay terminates a public HTTPS endpoint, …/d/<deviceId>/mcp, and matches each request to its answer. Pouchy sees a normal MCP server. This one is operated by Pouchy — it is not something you stand up.

Pairing a device in the dashboard mints two tokens, shown exactly once:

token held by used for
ingress Pouchy (project vault) authenticating calls into the relay
device your bridge authenticating the bridge to the relay

They are separate so that handing a lab its device token hands them their own instrument and nothing else. Rotation reissues both together; revocation is immediate and is recorded rather than deleted. The relay is fed hashes of these tokens, never the tokens themselves, so a copy of that feed is a set of verifiers rather than a set of credentials.

Three behaviours worth designing against, all enforced at the relay and again at the bridge:

  • A device that is offline is refused at once (503). Nothing is queued for it and no caller is left hanging.
  • One tools/call per device at a time (409). A second command that lands while a stage is moving was composed against a world-state that no longer holds, so it is refused rather than queued behind it. Discovery and reads are not serialised.
  • A timeout is reported as an error, never synthesised into a success. This is load-bearing given the at-most-once rule above: nothing upstream will correct an answer the bridge invents.

MCP protocol headers cross the tunnel in both directions — Mcp-Session-Id, MCP-Protocol-Version, Accept, Last-Event-ID inbound; Mcp-Session-Id, MCP-Protocol-Version, Content-Type back — so a session-based driver works unchanged, and a text/event-stream reply survives as one. Nothing else crosses: in particular the token authenticating Pouchy to the relay is never forwarded to your driver, so it is not a credential your lab ends up holding.

Request and response bodies are never logged by the relay. They carry actuation parameters and can carry credentials.

Who runs what, and where to get it

You run the bridge. Pouchy runs the relay. You do not deploy a relay of your own — pairing a device registers it on the one Pouchy operates at relay.pouchy.ai, which is where the endpoint the dashboard hands you points.

The bridge is two files of dependency-free TypeScript that Node 22+ runs directly, with no build step. Its complete source is published at Reference: the device bridge — read every line before you run it next to an actuator, and port it if Node is not what your lab runs.

Pairing

In the Pouchy dashboard, HardwarePair hardware. Name the device, pick the agent that should be able to drive it, and pair.

You only carry one of the two tokens. Picking an agent tells the server to store the ingress token itself, in the project vault, at the one moment it has the plaintext — so it never travels through a clipboard, a screenshot, or you. The device token is the one you carry, because it has to reach a machine you own.

The panel shows both tokens once. There is no "show it again": a registry that can re-display a credential only has to be read once to be defeated.

Two normal not-yet states, both of which say so rather than reporting a credential problem:

  • the relay has not picked up this device yet — it polls for new devices every 30 seconds. Wait a moment and press Finish the install.
  • no bridge is connected to it — start the bridge beside your hardware with the device token, then press the same button.

Rotation reissues both tokens, updates the stored one for you, and shows you the new device token. Revocation is immediate and recorded rather than deleted; a revoked device stops answering on the relay's next sync. The API call is DELETE /v1/projects/{projectId}/devices/{deviceId}{ revoked: true, skillsHidden, skillsMarkingFailed? }skillsHidden counts the MCP skills that device had registered (their MCP path names it) and that are now hidden from every agent's tool list and from mcpBundleFor, their credentials withheld (T-11, 2026-09-05). skillsMarkingFailed: true means the revoke landed but that marking did NOT, so this device's skills are still offered to your agents with their stored credentials — hide them by hand. It rides only on the fault, because skillsHidden: [] on its own says both "this device had no skills" and "the cascade never ran", which need opposite responses; the audit row's older skillsHidden: 'marking_failed' says the same thing, but an audit row is not what an API caller or the dashboard reads.

Verifying an integration

Before wiring a device to a live agent, run the Device-driver probe in the Pouchy Ops Dashboard (Tools tab). Given an endpoint and an optional bearer token it reports, as copyable JSON: whether the URL passes the public-host guard, the negotiated protocol version and server identity, the tool list with its annotations, the resource URIs, the opening of the first resource, and the result of executing one tool you name explicitly.

The probe never chooses a tool itself. A write tool cannot be told from a read tool by its name, and the server's own hints are claims by the thing being probed — so an empty tool box executes nothing at all. The named tool is called with no arguments, so pick one that needs none; anything else comes back as the server's own argument error, which is a real answer about your validation but not a reachability result.

A tool-level refusal and a transport failure are reported separately. Your isError: true shows up as a call that succeeded and was declined, which is what it is.

Run the same probe against a relay endpoint after pairing. A correct tunnel returns the same JSON as a direct connection; the whole point of the design is that the client cannot tell the difference.

On the Model Hardware Standard

Anthropic announced MHS on 2026-08-27 as a waitlist-gated research preview. Its published description puts a device driver behind a protocol layer that is standard MCP.

No specification, schema, SDK, repository, license, conformance suite or version number has been published. So this page makes no claim about MHS, in either direction. What is stated above is about MCP, which is public, implemented here, and tested. If a driver of any origin exposes an MCP endpoint, the two paths on this page apply to it unchanged — and that is the only sentence about it we can honestly write today.