Skip to content

Chapter 18 · Transport Layer and Lifecycle ​

Chapter goals: Understand the responsibility boundary of the MCP transport layer as a "binding," master the working methods of the two standard transports — stdio and Streamable HTTP — and clarify the JSON-RPC 2.0 message format and protocol version negotiation mechanism.

18.1 The Transport Layer: Only Governs "How to Send," Not "What to Send" ​

The official spec defines the transport layer with great restraint: transport is a binding that defines three things:

  1. How messages are framed and delivered;
  2. How request metadata is carried;
  3. How cancellation and termination are signaled.

It does not define message meaning: the message pattern (request/response/notification) belongs to the core protocol and is完全 identical across all transports. This brings an important guarantee — your Server business logic doesn't need to change code when switching transports: local development with stdio, production with Streamable HTTP — only startup parameters differ.

MCP has two standard transport bindings:

TransportFormTypical Scenario
stdioSubprocess spawned by the client, newline-delimited messages on stdin/stdoutLocal Server (filesystem, local database)
Streamable HTTPEach message POSTed to a single MCP endpoint; response is a JSON object or an SSE stream scoped to that requestRemotely hosted Server (shared by multiple clients)

The spec also allows custom transport implementations, as long as they satisfy the same framing and delivery contract.

18.2 stdio Transport ​

stdio is the simplest and most commonly used local transport. How it works:

  1. The Client starts the Server program as a subprocess;
  2. Both sides exchange messages through the subprocess's stdin/stdout;
  3. Each message is one line of UTF-8 encoded JSON-RPC (newline-delimited, bare newlines inside the message are forbidden);
  4. Logs must go to stderr, never pollute stdout.
python
# server_stdio.py —— complete skeleton of a stdio Server
from mcp.server.fastmcp import FastMCP
import sys

mcp = FastMCP("local-notes")

@mcp.tool()
def add_note(text: str) -> str:
    """Add a note"""
    with open("notes.txt", "a") as f:
        f.write(text + "\n")
    return "Saved"

if __name__ == "__main__":
    mcp.run()   # FastMCP defaults to stdio transport

Client configuration (using Claude Desktop as an example):

json
{
  "mcpServers": {
    "notes": {
      "command": "python",
      "args": ["/absolute/path/to/server_stdio.py"]
    }
  }
}

The #1 pitfall during debugging: print() outputs to stdout by default, which corrupts the message flow and causes协议 parsing to fail. All debug output should use print(..., file=sys.stderr) or configure logging to stderr.

18.3 Streamable HTTP Transport ​

For remote scenarios, use Streamable HTTP: the client sends each message as an HTTP POST to a unified MCP endpoint; the server can choose to return either a JSON object directly, or an SSE stream scoped to that request (suitable for multiple progress notifications on long tasks). An optional GET SSE endpoint is also supported for server-initiated pushes.

Key design points:

  • Zero protocol semantic changes: still those same JSON-RPC messages, just the envelope changed to HTTP;
  • Metadata mirroring: Streamable HTTP mirrors key metadata from the request body into HTTP headers (e.g. MCP-Protocol-Version), letting middleware (gateways, proxies) route and inspect requests without parsing the message body;
  • Native multi-tenancy: one remote Server can serve大量 Clients simultaneously — this is the essential difference from stdio's "single Client subprocess."

18.4 Message Format: JSON-RPC 2.0 ​

Regardless of transport, the message body is JSON-RPC 2.0, in three types:

jsonc
// ① Request (has id, expects response)
{ "jsonrpc": "2.0", "id": 7,
  "method": "tools/call",
  "params": { "name": "get_weather", "arguments": { "city": "Beijing" } } }

// ② Response (corresponds to request id)
{ "jsonrpc": "2.0", "id": 7,
  "result": { "content": [{ "type": "text", "text": "Sunny, 26°C" }] } }

// ③ Notification (no id, no response needed, e.g. tool list change alert)
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }

Three iron rules (from the spec's message patterns): Client sends request, Server replies with response; Server can also send requests to Client (e.g. elicitation to ask the user for more info); notifications don't need a response.

18.5 Version Negotiation and Compatibility ​

Protocol version management is key to understanding MCP's evolution. The current spec (from 2026-07-28) uses the modern mode: no more handshake — each request declares its own protocol version in the _meta field (also placed in the MCP-Protocol-Version header on HTTP). The server accepts or rejects each request independently:

  • Version supported → normal result returned;
  • Not supported → returns error code -32022 UnsupportedProtocolVersionError, listing versions it supports, and the client can retry with a different version:
json
{
  "jsonrpc": "2.0", "id": 1,
  "error": {
    "code": -32022,
    "message": "Unsupported protocol version",
    "data": {
      "supported": ["2026-07-28", "2025-11-25"],
      "requested": "1900-01-01"
    }
  }
}

There are also clear terms for handling legacy baggage: 2025-11-25 and earlier versions belong to the Legacy mode — they establish a session via a one-time initialize handshake to negotiate versions; implementations supporting both old and new sets are called Dual-era implementations. The SDK通常 handles these differences for you, but you must know both mechanisms exist when troubleshooting cross-version compatibility issues.

18.6 Session Management and Brief Evolution History ​

Early MCP remote transport used a dual-endpoint HTTP+SSE方案, but had engineering pain points around reconnection and state retention; Streamable HTTP simplified this to a single endpoint + request-scoped SSE, becoming the current标准. At the session level, the stateless modern mode has each request自-contain (with _meta version, capabilities, and identity info), and discovery results (server/discover) are cacheable and reusable; the legacy mode establishes a stateful session via the initialize handshake.

Practical advice: new projects should一律 use the latest SDK and Streamable HTTP; when maintaining old integrations, first confirm which "protocol era" the peer is in, then decide the upgrade path.

Chapter Summary ​

  • Transport is a "binding": only governs framing/delivery, metadata carrying, and cancellation/termination; protocol semantics are完全 consistent across transports;
  • stdio = subprocess + newline-delimited JSON-RPC on stdin/stdout; logs must go to stderr;
  • Streamable HTTP = POST to a single MCP endpoint; response is JSON or request-scoped SSE stream; natively supports multiple clients;
  • Three message forms: request (has id), response (corresponds to id), notification (no id); underlying unified as JSON-RPC 2.0 UTF-8;
  • Version negotiation has evolved from "initialize handshake" to "per-request _meta declaration + server-side per-request validation"; unsupported returns -32022 with the supported list.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What is the responsibility boundary of the "transport / binding" in the MCP spec?

2. Under stdio transport, what is the correct way for a Server to print debug logs?

3. In Streamable HTTP transport, after the client sends a message, what is the server's response form?

4. What is the version negotiation mechanism in the modern MCP spec (2026-07-28)?

🛠️ Hands-on Practice ​

  1. Start the notebook Server from Chapter 11 with both stdio and Streamable HTTP transports respectively; connect with MCP Inspector separately and verify tool behavior is完全 identical.
  2. Deliberately add a line print("debug info") to stdout in the stdio Server; observe what error the client reports; then fix it by writing to stderr.
  3. Use curl to manually construct a JSON-RPC request POSTed to your Streamable HTTP endpoint; observe the raw response structure; then send an non-existent protocol version to verify the -32022 error and supported list.

After completing the exercises, proceed to Next chapter: Writing Your First MCP Server.