Stdio Transport
Purpose and Scope
Stdio serving is the local-process transport for MCP servers. In this mode, a host launches the server command as a child process, writes JSON-RPC requests to standard input, and reads JSON-RPC responses and notifications from standard output. It is the right fit when the host owns the server lifecycle, such as a desktop host starting a tool server on demand. It is not the right fit for a shared endpoint that many independent clients connect to over the network; the same server factory should be exposed over Streamable HTTP for that deployment shape instead.
Sources: docs/serving/stdio.md, docs/serving/http.md
The important design idea is that the application code still builds an ordinary MCP server. Tools, resources, prompts, and other capabilities are registered inside a factory, and the transport-specific entry point decides how that instance is connected. For stdio, the entry point pins one server instance to the process and waits on stdin until a host sends protocol messages. For HTTP, the handler creates a fresh server instance per request. This difference affects state, logging, testing, shutdown, and how you reason about concurrent callers.
Sources: docs/serving/stdio.md, docs/serving/http.md
Relevant Source Files
- docs/serving/stdio.md — Primary how-to for serving a server factory over stdio, including the entry point, logging rule, Inspector command, and shutdown handle.
- docs/serving/http.md — Explains the Streamable HTTP alternative and the per-request factory model that contrasts with stdio's local child-process shape.
- docs/serving/express.md — Shows how the same server factory is mounted behind Express when a network endpoint is needed instead of stdio.
- docs/serving/fastify.md — Shows the Fastify mounting path and reinforces that framework adapters wrap the HTTP handler rather than changing MCP behavior.
- docs/serving/hono.md — Shows the web-standard Hono path, including body parsing and pass-through request context for HTTP serving.
- docs/serving/authorization.md — Describes bearer-token enforcement for HTTP routes, which is a separate concern from local stdio process communication.
System-to-Code Mapping
The stdio page presents serveStdio as the server package's stdio entry point. It takes a factory, owns the stdio transport, and calls the factory to build the server instance that serves the connection. That replaces the older style of manually constructing a stdio server transport and connecting it to the server. The HTTP page presents createMcpHandler as the parallel entry point for Streamable HTTP. Both APIs keep protocol registration code inside the factory, but they differ in how often the factory runs and who owns the underlying channel.
Sources: docs/serving/stdio.md, docs/serving/http.md
| Concern | Stdio serving | HTTP serving |
|---|---|---|
| Entry point | serveStdio(factory) | createMcpHandler(factory) |
| Process model | Host starts a local child process | Runtime exposes a network endpoint |
| Instance lifetime | One pinned instance for the stdio connection | Fresh instance per HTTP request |
| Channel | stdin and stdout carry JSON-RPC | HTTP request and SSE response carry messages |
| Typical verification | Inspector launches the command | curl or a framework route posts to /mcp |
Server Execution Flow
A minimal stdio server imports McpServer from @modelcontextprotocol/server and serveStdio from @modelcontextprotocol/server/stdio. The factory creates the server, registers capabilities, and returns it. Once the entry point is called, the process is an MCP server even though no network port is open. A host that spawns the command can initialize the session, list capabilities, and invoke whatever the factory registered. Until a host connects and sends data on stdin, the process simply waits for protocol input.
Sources: docs/serving/stdio.md
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
const handle = serveStdio(() => {
const server = new McpServer({ name: 'notes', version: '1.0.0' });
// server.registerTool(...)
return server;
});The factory boundary is useful because it keeps the same application registration code portable across transports. If you later need to expose the notes server to many clients, move the factory behind the HTTP handler and mount that handler through Node, Express, Fastify, Hono, or a web-standard runtime. If you keep local process integration, keep the stdio entry point. This separation also makes migration clearer: the stdio guide says serveStdio replaces the v1-era manual StdioServerTransport plus server.connect wiring.
Sources: docs/serving/stdio.md, docs/serving/http.md
Client Connection Pattern
A client connects to a stdio server by choosing the stdio client transport instead of an HTTP transport. The documented client flow keeps the same high-level client object and changes only the transport: the client transport spawns a command, then speaks JSON-RPC over the child's stdin and stdout. That means the server program must behave like a protocol process from its first stdout byte onward. When the client closes, the stdio client shuts the child down in order by closing stdin, then sending termination signals if needed.
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StdioClientTransport({ command: 'node', args: ['server.js'] });
await client.connect(transport);This pairing is especially helpful for local tools that should not expose a port or accept cross-process HTTP traffic. The host remains responsible for the command, arguments, environment, and lifecycle policy. The server remains responsible for registering capabilities and keeping stdout clean. If the same code is used in tests, a fully in-process transport can avoid both child processes and network sockets, but stdio is the realistic integration path when validating how a host will actually launch the server.
Sources: docs/serving/stdio.md
Operational Rules and Edge Cases
The most important stdio rule is simple: never write logs to stdout. The stdio guide uses console.error for readiness messages because it writes to stderr, while stdout is reserved for JSON-RPC. A host parses every stdout line as a protocol message. One debug line sent with console.log appears before or between protocol frames and can make an otherwise correct server fail during initialization. Treat stdout as a binary contract with the host, not as an application log stream.
Sources: docs/serving/stdio.md
console.error('notes server is listening on stdio');A corrupted stdout stream is difficult to diagnose if the server also appears to start normally. The stderr banner still shows in the host's server log, so the operator may see readiness while the protocol parser rejects the first stdout line. For this reason, route all diagnostics, progress notes, and startup banners to stderr or to another logging sink. If third-party libraries print to stdout during import or initialization, disable that output before the host sends initialize, because the first valid response must be a JSON-RPC response, not ordinary text.
Sources: docs/serving/stdio.md
Testing and Shutdown
The MCP Inspector provides the quickest manual test for stdio because it launches the command itself and connects over the same transport a host would use. The stdio guide shows the Inspector command with a built server file. After the browser opens, the Connect button initializes the server, and the Tools tab lists and calls tools registered by the factory. This catches the two failures that matter most for stdio integration: the command does not start correctly, or stdout contains something other than protocol messages.
Sources: docs/serving/stdio.md
npx @modelcontextprotocol/inspector node ./build/server.jsserveStdio returns a StdioServerHandle, and its close() method tears down both the pinned server instance and the underlying transport. Wire this into process signals so local hosts and terminal users can stop the server cleanly. The close promise resolves after the factory-built instance and the transport are shut down, which is the point where application resources owned by that instance should no longer receive protocol traffic. Long-lived module-scope resources should have their own shutdown policy if the process owns them.
Sources: docs/serving/stdio.md
process.on('SIGINT', () => {
void handle.close();
});Relationship to HTTP, Frameworks, and Authorization
Stdio deliberately avoids the web-serving concerns that appear in the HTTP and framework guides. There is no Host header to validate, no Origin header to defend against DNS rebinding, no parsed HTTP body to forward, and no bearer-token middleware mounted in front of a route. Those concerns belong to Streamable HTTP deployments. Express, Fastify, and Hono helpers all show the same pattern: create an MCP handler from the factory, adapt it to the framework's request model, and mount it at an MCP route such as /mcp.
Sources: docs/serving/express.md, docs/serving/fastify.md, docs/serving/hono.md, docs/serving/authorization.md
Choose HTTP when multiple clients need one endpoint, when browser-adjacent DNS rebinding protection matters, or when bearer authorization must be enforced at the resource-server boundary. The authorization guide describes requireBearerAuth for Express and token verification that produces AuthInfo; Hono and Fastify examples show equivalent request-context forwarding. Choose stdio when the host is local, owns the child process, and can trust that command boundary. In both cases, keep capability registration in the factory so the transport choice does not leak into tool and resource implementation.
Sources: docs/serving/stdio.md, docs/serving/http.md, docs/serving/authorization.md
Next Steps
Start with stdio if you are building a local server for a host that launches commands. Implement the factory, keep stdout protocol-only, test with the Inspector, and add clean shutdown handling before integrating with a real host. If the server must become a shared service, move the same factory to Streamable HTTP and then pick the runtime-specific mounting page for Express, Fastify, Hono, or a web-standard environment. For authentication, read the authorization guide after the HTTP serving path is in place.
Sources: docs/serving/stdio.md, docs/serving/http.md, docs/serving/express.md, docs/serving/fastify.md, docs/serving/hono.md, docs/serving/authorization.md