At a glance
Config blocks for both are in MCP config.
stdio
Your client spawns the control plane as a child process and talks to it over standard input and output. There is no port and no token, because the trust boundary is the process boundary: a process your own client spawned is already running as you. Three consequences follow, and all three are deliberate. Local mode only. A process configured for hosted tenancy refuses to start over stdio. If it did start, it would be a second control channel that bypasses every check the HTTP endpoint performs, and the guarantee that all authentication runs through one pipeline would stop being true. Nothing but the transport may write to standard output. Standard output is the wire. Diagnostics go to standard error, which is where the startup line you see comes from. One control plane per device set. The receipt ledger takes a lock at startup, so a second process over the same devices is refused immediately and loudly rather than surfacing later as an opaque failure mid action. If you are already running an HTTP control plane over the same devices, stop it, or give the stdio process its own ledger path. The stdio process also binds an ephemeral loopback port for the agent tier’s device channel. It does so unconditionally at startup, even in a session that never starts a run, so it is not accurate to say an unused feature adds no surface. What is accurate is that the port is loopback only and every request to it is guarded by a run token.HTTP
The endpoint isPOST /mcp. It speaks JSON-RPC, in JSON response mode, and it
is stateless.
Stateless means a fresh server and transport are built for each request, and
that leases travel as explicit parameters rather than living in a session.
There is nothing to resume and nothing to tear down, which is why GET and
DELETE on the same path answer 405 with an Allow: POST header.
Two protections run on every request, and a client that trips either is
refused:
- Host checking. In hosted mode only the deployment’s public host is accepted. Locally only loopback hosts are.
- Origin refusal. Any request carrying a browser
Originheader is refused. Without this, a web page could rebind its own hostname to loopback and drive your device with no cross origin preflight standing in its way. Loopback binding stops callers on other machines; it does not stop this.
What stateless HTTP cannot carry
Two protocol features need a live bidirectional session, and this is the part worth reading before you design a loop around them. Progress notifications. An action blocks until the device commits, and on the physical tier that takes as long as a physical movement takes. Over stdio, a client that sent a progress token is told the action was dispatched and is awaiting device commit. Over stateless HTTP there is no channel for a mid request notification, so nothing arrives. The server does not fail the action over it: progress is a transport capability, never a promise the tool surface makes. Human takeover. Elicitation is a request from the server to the client, so it also needs a live session. Over stateless HTTP it never happens, and the caller sees a terminal outcome instead. The substitute for both is the same one, and it is not a workaround: pass anidempotencyKey with your action and use get_receipt to find out what
happened. That path works on every transport, survives a lost response, and
survives a restarted client. See
Progress and takeover.