Skip to main content
The control plane speaks MCP over two transports, and both publish the same tool surface. That equality is checked rather than assumed, so a tool you can call on one you can call on the other. What differs is authentication, statefulness, and which optional protocol features can physically reach you.

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 is POST /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 Origin header 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.
Authentication happens before anything else. Local mode may require a development token; a hosted deployment requires OAuth or an API key. See Authentication.

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 an idempotencyKey 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.

Choosing

Use stdio when your client spawns local processes, when you are working against your own devices, and when you want progress and takeover. It is the better development experience. Use HTTP for anything shared, anything remote, anything with more than one caller, and anything that needs a real credential. It is the only transport a hosted deployment offers. Whichever you choose, the calls you write do not change.