MCP Fundamentals
MCP Streamable HTTP: A Versioned Request Walkthrough
By MCP Beast·
MCP Streamable HTTP carries client messages to an HTTP endpoint and returns either JSON or a request-scoped Server-Sent Events response. To implement or diagnose it, pin the protocol revision first. An example that uses initialization, a protocol session identifier, or a separate GET stream may describe an older revision rather than the current request model.
This walkthrough targets 2026-07-28. It uses a fictional read-only tool to show what belongs in one request and then follows the response through a client, an intermediary and a server. The example is for inspection; it is not a runnable endpoint or a claim about a specific SDK's default configuration.
Establish the revision before reading the trace
The 2026-07-28 Streamable HTTP specification removes the older GET stream endpoint and protocol-level sessions. Each request is a POST to the MCP endpoint, with support for JSON and SSE responses. Keep earlier Streamable HTTP and the older HTTP+SSE transport in separate compatibility records.
For your trace, record the host, client library, server release, endpoint and protocol revision. Include any proxy or gateway in the path. A package name alone is not enough to establish the revision being sent, and a successful request to a health endpoint does not establish MCP compatibility.
If you are deciding where routing and enforcement belong, see gateway, proxy and router boundaries. Here the question is narrower: does the intended message arrive intact, and can the client interpret the actual response?
Inspect one request as a unit
The following structural HTTP example uses a fictional lookup_article tool and a reserved example domain. Authentication, HTTP framing and deployment-specific headers are omitted, so this is not a complete executable request. The body includes the current protocol metadata rather than relying on a prior initialization exchange.
POST /mcp HTTP/1.1
Host: support.example.com
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: lookup_article
{
"jsonrpc": "2.0",
"id": 17,
"method": "tools/call",
"params": {
"name": "lookup_article",
"arguments": { "article_id": "demo-42" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
Check header/body agreement for the version, method and named operation. Do not fix a mismatch by changing only what a gateway sees while leaving the server's body unchanged. The request should express one operation consistently across the path.
The base protocol's metadata table makes protocolVersion and clientCapabilities required; clientInfo is recommended rather than required. Client identity metadata is self-reported and must not be treated as an authenticated principal. The empty capability object above advertises no optional client features.
For a real operation, use its discovered input schema and the required transport mappings. This example deliberately uses a simple ASCII tool name and no custom parameter headers; it is not an exhaustive demonstration of every header encoding rule.
Separate transport, protocol and tool outcomes
When a response arrives, inspect the HTTP status and content type before parsing the payload. Then inspect the JSON-RPC result or error and, for a tool call, its application outcome. “HTTP succeeded” is not the same statement as “the requested business action succeeded.”
| Observation | First question to investigate | Evidence to preserve |
|---|---|---|
| No HTTP response | Did the request reach the intended endpoint? | Connection phase and bounded timing |
| HTML response | Did a login page or generic proxy route answer? | Status, content type and redacted route |
| HTTP rejection | Was transport, authorization or metadata rejected? | Status and safe error fields |
| JSON-RPC error | Which protocol method or parameters were rejected? | Request ID, code and redacted message |
| Tool-reported failure | Did the operation execute and fail at its own boundary? | Tool result category and correlation reference |
| Input required | Is the client expected to gather more information? | Result type and supported interaction |
A useful reproduction changes one layer at a time. Keep the same synthetic operation while comparing the direct endpoint with the intended gateway route. If the direct request works and the routed one fails, inspect header forwarding, response handling and authorization at that boundary. Do not immediately rewrite the tool implementation.
Support the response the server actually chooses
The transport permits a single JSON response or a request-scoped SSE stream. The client's Accept header and parser must support both. For an SSE response, keep reading framed events until the relevant final response or a genuine interruption; receiving the first chunk is not completion.
An intermediary can change the observed behavior even when the server's handler is correct. Test whether small events reach the client promptly or arrive only after a buffer fills. Inspect configured request and idle timeouts separately. These are proposed deployment checks, not a claim that every proxy buffers or that disabling one setting resolves every streaming issue.
Use a controlled server response that includes an early progress event followed by a delayed final result. Complete these two blank observation rows for the same synthetic operation through approved direct and mediated test paths; do not bypass production access controls to obtain a comparison.
| Test route | Protocol revision | Method/name header-body agreement | HTTP status / content type | First event arrival at client | Final response arrival at client | Response ID / request match | Observed outcome |
|---|---|---|---|---|---|---|---|
| Approved direct path | |||||||
| Approved mediated path |
Record elapsed arrival times from each request's start and keep a restricted reference to the server's corresponding emission observations. Write unknown when timing cannot be observed; for an ordinary JSON response, mark first SSE event not applicable. Repeat with a normal JSON result without treating either a first event or an HTTP success as a final tool outcome.
If the server emits early but the client receives late, correlated observations narrow the investigation to delivery between those points, including intermediaries, networking and client buffering. They do not establish which proxy setting is faulty. Compare timing within a common clock or account for clock differences before drawing that conclusion. Keep only safe IDs and structural fields in the worksheet, never bearer headers, cookies or private request/result bodies.
Handle additional input without inventing a second channel
The current multi round-trip pattern carries supported server-to-client requests inside an input-required result. The client gathers the requested input and resumes the original operation with the corresponding response. Do not implement an unrelated callback based on a legacy trace and assume it is interchangeable.
For example, the fictional lookup operation might require the user to select an authorized workspace. That interaction should remain associated with the original lookup. A response for another open request must not satisfy it. See elicitation and safe resumption for consent, cancellation and stale-state tests.
If the host cannot support the requested interaction, report that compatibility boundary accurately. Repeating the same request indefinitely does not create a missing client capability.
Treat cancellation as a recovery decision
In this transport revision, closing an SSE response stream signals cancellation of that request. It does not prove that an external side effect has been rolled back. For a read operation, stopping work may be straightforward. For a tool that submitted an external job, investigate the job's actual state before deciding whether to retry.
Make the outcome vocabulary precise: completed, failed, cancelled before execution, or unresolved after interruption are materially different observations. Avoid claiming “not executed” solely because the client did not receive a final result. Audit evidence can help link the request to the downstream attempt and outcome.
For a retryable test operation, verify which identifiers are correlation values and which, if any, provide application-level duplicate protection. A JSON-RPC request ID identifies a protocol exchange; do not assume it is a business idempotency guarantee.
Keep authorization separate from client metadata
The MCP authorization specification describes the authorization framework for HTTP-based transports. Use the intended resource and identity configuration for the endpoint. A recognizable clientInfo.name is not evidence of permission, and adding a version header does not repair an expired or wrongly scoped credential.
When collecting a reproduction, remove bearer tokens, cookies, private document content and sensitive argument values. Retain enough structural information to identify the failing layer. If a transport log requires raw secrets to be useful, improve the diagnostic design before sharing it.
Use MCP server management to retain the working combination of client, endpoint, revision and policy. That record is more reliable than an unversioned snippet copied between teams.
Frequently Asked Questions
Does current Streamable HTTP require an MCP session ID?
No. Revision 2026-07-28 removes protocol-level sessions. Older Streamable HTTP examples may use a different lifecycle, so identify the revision before applying their setup steps.
Can a Streamable HTTP server return ordinary JSON?
Yes. A request can receive a single JSON response or a request-scoped SSE response. The client needs to support the response forms defined by its protocol revision.
Is clientInfo an authenticated identity?
No. It is self-reported implementation metadata. The current base specification recommends including it, while authorization must rely on the appropriate authenticated identity and policy.
Does a closed stream prove a tool did nothing?
No. Cancellation and loss of the final result do not establish that downstream effects were reversed or never occurred. Inspect the operation's outcome before retrying consequential work.
Capture one synthetic request through the full intended route and label each observed layer. Then test both JSON and streamed results before treating the transport integration as complete.