Cheat sheets

MCP Clients and Transports

Probe, merge and route MCP 2026-07-28 peers over stdio and Streamable HTTP. Retry MRTR rounds. Frame JSON-RPC.

  • Tasks and commands
  • Annotated code
  • Diagrams
  • Comparisons
  • Defaults and numbers
  • Troubleshooting
  • Questions and tips

Source

Multi-server client#

canonical namelookup_meta on each request_meta on each requestone initialize probeclientHostmodel and UIprocessMultiServerClientone Peer per serverstoreregistrycanonical name to peerprocessnotesmodern peerprocessfilesmodern peerprocessarchivelegacy,allow_legacy=Truecanonical namelookup_meta on each request_meta on each requestone initialize probeclientHostmodel andUIprocessMultiServerClientone Peerper serverstoreregistrycanonicalname topeerprocessnotesmodern peerprocessfilesmodern peerprocessarchivelegacy,allow_legacy=True

Stdio era probe#

1Clientsends server/discover before any other request.2Resultmodern. Pick the highest shared version.3-32022modern. Pick from data.supported. Retry, new id.4-32020, -32021modern. Fix the request. No initialize.5Timeout, close, unknown errorambiguous. Fail closed.6Allowlisted peerone bounded initialize. Check protocolVersion,capabilities, serverInfo.7Era cachekeep it for the transport lifetime.

Client tasks in code#

TaskCode
Register a modern peerclient.add_server("notes", notes)
Permit one legacy probeclient.add_server("archive", archive, allow_legacy=True)
Probe every peer in name orderclient.connect_all()
List tools of each live peerclient.discover_tools()
Reject duplicate namesclient.merge(policy="reject")
Call by canonical nameclient.call("notes/search", {})
Mirror body fields into headershttp_headers_for(message)
Serve JSON-RPC lines until EOFserve(handler, StdioTransport(stdin, stdout))

Run the lessons#

CommandWhat it does
python3 code/main.py11.14: discovers demo-server at 2026-07-28
python3 main.py13.08 in code/: peers, merged tools, calls
python3 main.py --probe13.09: runs the HTTP probe on a free port
python3 main.py --serve13.09: serves http://127.0.0.1:8017/mcp
python3 main.py13.11 in code/: request ids [1, 2, 3]
python3 code/main.py19.22: BytesIO demo, prints server_responses
python3 -m unittest discover tests -vRuns the tests from code/

Stamp every modern request#

def modern_request(request_id, method, params, version, capabilities=None):
body_params = dict(params or {})
body_params["_meta"] = request_meta(version, capabilities)new on each call
return {"jsonrpc": "2.0", "id": request_id, "method": method, "params": body_params}
VERSION_KEY = "io.modelcontextprotocol/protocolVersion"required
CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities"required
CLIENT_INFO_KEY = "io.modelcontextprotocol/clientInfo"client identity
RECOGNIZED_MODERN_ERRORS = {-32020, -32021, -32022}never downgrade

Project: MCP Tool Discovery Workbench#

CommandWhat it does
python3 scripts/project_test.py mcp-at-scale --all --path <dir> --strictRuns the grader. Init first with --init <dir>
python3 cli.py samples/inventory.json --query "pods count" --max-chars 500Best schemas inside the budget
python3 cli.py /absolute/inventory.json --servestdio server with catalog_search
ChecksBefore initialize: -32002. Pages of 32 tools. 250 names across 8 pages

stdio or Streamable HTTP#

FactorstdioStreamable HTTP
Use it forClient-launched subprocessNetwork service
FramingOne UTF-8 JSON-RPC message per lineOne JSON-RPC message per POST
Version contextparams._metaparams._meta plus mirrored headers
ResponseOne line on stdoutJSON or request-scoped SSE
NotificationNo response line202 Accepted, no body
Diagnosticsstderr onlyHTTP status plus JSON-RPC error
Change eventsReopen after restartsubscriptions/listen POST stream
SafetyExit on stdin EOFValidate Origin, bind 127.0.0.1

MRTR round for Sampling#

1Clientcalls tools/call. Capabilities hold sampling.2Serverreturns input_required, inputRequests,requestState.3Clientchecks policy and asks its model.4Clientsame method and arguments, new id,inputResponses, exact requestState.5Serververifies HMAC, principal, arguments digest,expiry.6Serverlast round returns resultType: "complete".

Defaults in the lesson code#

SettingDefaultNote
discovery_timeout_ms1_000Must be positive
legacy_probe_timeout_ms1_000Passed to the transport adapter
merge policyprefix-on-collisionOr reject
Discovery ttl_ms3_600_000cache_scope="public"
MAX_REQUEST_BYTES1_048_576Larger body gives -32700
READ_TIMEOUT_SECONDS5.0Socket timeout on the body read
maxTokens400Per embedded sampling request
requestState expiry300Seconds after the first call

Status and error codes#

SymptomCauseFix
HTTP 400, -32020Header missing or differs from bodyBuild headers from the final body
HTTP 400, -32022Version not supportedRetry with a value from supported
-32021Client lacks a required capabilityRead data.requiredCapabilities
-32602Missing _meta or bad requestStateSend _meta and echo the state
HTTP 403Origin not in the allowlistUse an exact origin match
HTTP 404, -32601Unknown modern methodDo not treat it as legacy proof
HTTP 405GET or DELETE on /mcpUse POST and subscriptions/listen
-32700, id nullOne stdio line is broken JSONLoop continues with the next line

Questions#

Is -32601 proof of a legacy server?
No. It only lets an allowlisted peer get one probe.
Can a stream resume?
No. Last-Event-ID is ignored. Send a new request with a new id.
Where does workflow state go?
In a server-minted handle that the client passes as an argument.
Can the server call the client?
No. It returns input_required and the client retries.
Do model preferences sum to one?
No. They are independent hints.

Tips#

  • Inspect the final serialized request.
  • Send a non-ASCII Mcp-Name as =?base64?{Base64EncodedValue}?=.
  • Cap MRTR rounds. Validate each sampling response.
  • On stream loss, reissue subscriptions/listen and refetch.
  • Under stdio, write diagnostics to stderr.