Transports

JSON-RPC semantics are the same on every transport. A transport only defines framing, how metadata is attached, and how cancel/teardown work. The current spec’s standard bindings are stdio and Streamable HTTP. Older HTTP+SSE is superseded, but it still shows up as a legacy option in some clients (including some Cursor configs).


Comparison

TransportHow it movesTypical useNew work?
StdioHost launches a child; newline-delimited JSON-RPC on stdin/stdoutLocal tools, debuggingDefault locally
Streamable HTTPClient POSTs to one MCP endpoint; the reply is JSON or a request-scoped SSE streamRemote, many clients, deployableDefault remotely
SSE (legacy)Old “HTTP + long-lived event stream”Clients that have not movedDo not build new servers on it

The official Python SDK matches that: mcp.run() defaults to stdio; deploy with transport="streamable-http"; transport="sse" is compatibility only.


Stdio: local, no network overhead

A host config is usually a command plus args, not a URL:

{
  "command": "python",
  "args": ["D:\\mcp-demo\\server.py"]
}

After the process starts:

  • The client writes requests to stdin
  • The server writes responses to stdout
  • stderr is where logs belong

Therefore:

# Risky: can corrupt the protocol stream (especially before run() or when buffers flush)
print("server starting")

# Correct: log to stderr
import logging
logging.basicConfig(level=logging.INFO)  # the official SDK sends logs to stderr

If you run python server.py in a terminal, no output and no return is success—it is waiting for the first protocol message on stdin. Launch it from Inspector or a host; do not stare at that window for a banner.

Windows PowerShell (it should block; stop with Ctrl+C):

python .\server.py

Unix:

python ./server.py

Cancellation: the client sends notifications/cancelled. When the process exits, the connection is gone.


Streamable HTTP: the remote default

The client POSTs to a single endpoint (commonly /mcp). The spec requires gateway-readable headers (such as Mcp-Method, Mcp-Name, and the protocol version) so routers and rate limiters need not parse the JSON body first. Auth is ordinary HTTP: bearer tokens, API keys, custom headers. OAuth is the recommended way to obtain tokens.

Listen locally with the official SDK:

if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="127.0.0.1", port=8000)

The default endpoint is http://127.0.0.1:8000/mcp. host / port / streamable_http_path are arguments to run(), not to MCPServer(...).

PowerShell probe (port only; use Inspector for a real session):

Invoke-WebRequest -Uri "http://127.0.0.1:8000/mcp" -Method POST -ContentType "application/json" -Body '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}}'

Unix:

curl -sS -X POST "http://127.0.0.1:8000/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}}'

Cancellation: the client closes that request’s response stream. The current spec does not require a long-lived session id to scale out.

Think about auth and firewalls before binding 0.0.0.0. Keep 127.0.0.1 while you learn.


Legacy SSE: awareness only

The 2025-03-26 revision replaced HTTP+SSE with Streamable HTTP. Some clients (those whose docs or UI still say SSE—Cursor is a common case) still accept an event-stream URL. That is a compatibility path, not a new API.

If you must serve a client that only speaks SSE, follow that product’s fields (sometimes url plus transport: sse) and plan a move to Streamable HTTP. Do not make SSE the primary path of a new server.


How to choose

flowchart TD
    A[Who consumes this server?] --> B{Only this local host?}
    B -->|Yes| C[Stdio]
    B -->|No / deploy it| D[Streamable HTTP]
    D --> E{Does the peer only speak old SSE?}
    E -->|Yes| F[Temporary SSE, document the migration]
    E -->|No| G[POST /mcp only]

The same tools, resources, and prompts can be developed on stdio and published by changing one run(transport=...) line. The transport changes; the primitives do not.


Next

评论