Research guide · MCP migration · updated 13 September 2026
MCP 2026-07-28 migration: subscriptions/listen, serverless timeouts and server/discover failures
A modern MCP server can be technically reachable and still fail before useful tool work begins. The current migration traps include server/discover mismatches, removed initialize/Mcp-Session-Id assumptions and a newer operational failure: subscriptions/listen can pin serverless invocations when change capabilities are advertised and clients open a long-lived stream that the platform cannot cheaply sustain.
Published 13 September 2026 · updated with current Python and TypeScript SDK behavior checked 13 September 2026
2026-07-28
Stateless protocol revision covered by this migration guide
subscriptions/listen
Long-lived POST-response stream for opted-in change notifications
server/discover
Modern capability and version discovery boundary
No session ID
Mcp-Session-Id is removed on the modern wire path
Fresh production failure
A valid subscriptions/listen stream can still be the wrong runtime shape for serverless
The 2026-07-28 protocol deliberately replaces the old GET/SSE notification path with subscriptions/listen: a client opens one long-lived request, the server acknowledges the subset of notifications it will honor, and list/resource change events flow back on that response stream. The TypeScript SDK documentation says clients can automatically open this stream when configured list-change handlers intersect with capabilities the server advertises.
A Python SDK issue opened 11 September 2026 documents the expensive edge case. The reporter says MCPServer advertised tools/prompts/resources.listChanged = true and resources.subscribe = true even though the application never published a change. Modern clients then opened subscriptions/listen. Behind AWS Lambda response streaming, client disconnects did not reliably reach the ASGI app, so each stream remained alive until the platform timeout.
That report is implementation evidence, not a new protocol rule. The protocol expects the stream to be long-lived. The operational bug is the combination of unnecessary capability advertisement, automatic client listening and infrastructure that treats every open response as a billable pinned invocation.
Fast test
Separate a healthy long-lived subscription from a capability/runtime mismatch
- 1. Read
server/discover. Record whether the server advertisestools.listChanged,prompts.listChanged,resources.listChangedor resource subscription support. - 2. Ask whether the application actually publishes those events. A server that never changes its catalog may not need a change stream at all.
- 3. Inspect the client. Determine whether its configured change handlers automatically open
subscriptions/listenafter discovery. - 4. Compare durations. If
tools/listandtools/callfinish in milliseconds while onlysubscriptions/listenruns to the platform limit, the tool implementation is probably not the bottleneck. - 5. Check disconnect propagation. A client closing locally is not useful if the serverless adapter never tells the application that the stream ended.
Fast diagnosis
Map the symptom to the protocol boundary before rewriting tools
| Signal | Likely boundary | First action |
|---|---|---|
subscriptions/listen runs until platform timeout | A modern change-notification stream is open on infrastructure that bills or pins each live invocation. | Inspect server/discover capabilities, confirm whether change notifications are actually required, and measure stream lifetime separately from ordinary MCP calls. |
server advertises listChanged=true but never publishes changes | Capability declaration and actual server behavior are out of sync. | Do not add a client retry loop. Fix or configure the capability boundary when the SDK/runtime exposes a supported control. |
server/discover -> HTTP 500 | The server dispatcher or SDK likely does not understand the modern discovery method. | Check the server SDK revision and 2026 request path before debugging tools/list or OAuth. |
hosted client reports 424 external_connector_error | An upstream modern MCP request may have failed before any tool invocation. | Inspect remote logs for server/discover and protocol-version handling instead of assuming the tool itself failed. |
server says Mcp-Session-Id is missing | A 2025-era session requirement is leaking into a 2026-07-28 request path. | Branch transport behavior by negotiated revision rather than requiring a session header globally. |
server waits forever for initialize | A modern client may never send the removed handshake. | Accept self-describing 2026-07-28 requests or use a documented dual-era transport. |
GET /mcp returns 405 | This can be correct on a modern stateless endpoint because the legacy GET listening stream was replaced. | Check the negotiated revision before restoring a legacy GET/SSE endpoint. |
Migration map
Replace connection-bound assumptions one boundary at a time
| Boundary | 2025-era pattern | 2026-07-28 pattern | Migration action |
|---|---|---|---|
| Connection setup | initialize -> notifications/initialized | No handshake; request metadata carries protocol and client context | Do not wait for initialize before accepting a modern request. |
| Protocol session | Optional Mcp-Session-Id bound later requests to a session | No protocol-level session or Mcp-Session-Id | Move durable workflow state to explicit application handles. |
| Capability discovery | Learned during initialize | server/discover advertises versions, capabilities and identity | Keep advertised capabilities aligned with what the server will actually serve. |
| Notifications | Legacy GET/SSE stream and unsolicited delivery | subscriptions/listen for explicitly requested notification classes | Treat the stream as intentionally long-lived and deploy it on infrastructure that can support that lifecycle. |
| Server-to-client interaction | Connection-bound sampling, elicitation and roots patterns | Multi Round-Trip Requests return input_required and retry | Preserve requestState and correlate the follow-up with the original operation. |
| Horizontal scaling | Sticky sessions/shared session stores were common | Any request can land on any capable replica | Remove protocol-session affinity while keeping business state explicit and authorized. |
Repair sequence
Nine checks for production MCP 2026-07-28
- 1. Capture the actual protocol revision. Do not infer it from a package name or URL.
- 2. Test
server/discoverindependently. Confirm modern discovery returns capabilities instead of falling into an unknown-method HTTP 500 path. - 3. Stop requiring
initializeon the modern path. Keep the handshake only for clients that negotiate an older revision. - 4. Remove global
Mcp-Session-Idassumptions. Application state can survive through explicit authorized handles without reviving protocol sessions. - 5. Audit advertised change capabilities. If tools, prompts and resources are immutable for the deployment, do not intentionally advertise change support merely because a framework has a default handler.
- 6. Treat
subscriptions/listenas a real streaming workload. If notifications are required, choose infrastructure where long-lived response streams and disconnects are supported predictably. - 7. Do not depend on private SDK internals as the permanent fix. The current Python report uses a private-handler removal workaround because no public opt-out is exposed there. Prefer a documented SDK/runtime control or upstream fix when available.
- 8. Move interaction loops to MRTR. For supported server requests, preserve
requestState, collect required input through the host and retry the original operation. - 9. Run a dual-era matrix. Test at least one current 2026-07-28 client and one initialize-based client if backward compatibility matters.
What not to conclude
A long listen stream is not proof that MCP itself is hung
Do not rewrite healthy tools
If tool calls complete normally and only subscriptions/listen remains open, isolate the notification path first.
Do not restore legacy GET blindly
A modern endpoint can correctly return 405 on the old GET listening path because subscriptions/listen replaced it.
Do not force sessions back in
The modern protocol intentionally removed Mcp-Session-Id. Serverless cost is a stream/runtime problem, not evidence that stateful sessions should return.
Do not count connection traffic as demand
Long-lived MCP streams, probes and crawler requests are technical activity, not qualified visitors or commercial conversion.
Related zFinia guides
Continue from transport migration into cache, headers, output and authorization
- MCP tools/list cache and ttlMs
Separate stale catalog behavior from a broken transport or missing tool registration.
- MCP HeaderMismatch (-32020)
Debug Mcp-Method, Mcp-Name, x-mcp-header and Mcp-Param failures after the modern path is established.
- MCP structuredContent and outputSchema
Diagnose results that reach the transport but disappear before the model or client consumes them.
- zFinia machine services
Browse machine-readable services and current contracts for agent and machine-economy workflows.
Sources
Protocol, SDK and implementation references
- Model Context Protocol — 2026-07-28 specification release
- MCP TypeScript SDK — 2026-07-28 migration and subscriptions/listen behavior
- MCP Python SDK issue #3493 — subscriptions/listen pinning serverless invocations
- MCP Python SDK — 2026-07-28 migration notes
- MCP Go SDK — protocol notes for stateless requests and subscriptions