Skip to content

Fix streamable HTTP server-to-client MCP routing#1514

Merged
threepointone merged 1 commit into
mainfrom
fix/mcp-streamable-post-routing
May 12, 2026
Merged

Fix streamable HTTP server-to-client MCP routing#1514
threepointone merged 1 commit into
mainfrom
fix/mcp-streamable-post-routing

Conversation

@threepointone
Copy link
Copy Markdown
Contributor

@threepointone threepointone commented May 12, 2026

Summary

Fixes the Streamable HTTP routing failure described in #1510 for clients that do not keep a standalone GET/SSE stream open.

The MCP Streamable HTTP spec permits clients to rely on the originating POST response stream for related server-to-client messages. Previously, StreamableHTTPServerTransport.send() treated server-to-client requests and notifications without an explicit relatedRequestId as standalone messages. If no connection had _standaloneSse set, those messages were silently dropped. For request/response flows such as elicitation/create, the caller then waited until the MCP SDK timeout and surfaced an opaque request timeout.

This PR makes the transport route related server-to-client messages through POST when that is the only correct available channel.

What changed

  • Track the currently dispatched MCP request id with transport-local AsyncLocalStorage while invoking the SDK onmessage handler.
  • When send() is called with no explicit relatedRequestId and no standalone SSE stream exists:
    • route JSON-RPC requests and notifications to the active request id when available;
    • otherwise, conservatively fall back only if there is exactly one active POST request id across live connections;
    • reject unroutable server-to-client JSON-RPC requests instead of silently dropping them.
  • Preserve existing Inspector-style behavior when a standalone SSE stream is present.
  • Continue treating completely unroutable notifications as best-effort, since standalone notifications are optional by spec.
  • Add a patch changeset for agents.

Why this approach

The full semantic fix belongs upstream in @modelcontextprotocol/sdk: SDK convenience APIs such as Server.elicitInput(), Server.createMessage(), Server.listRoots(), and notification helpers should automatically inherit relatedRequestId when called from inside a request handler.

This transport-side fix is still needed so agents works with spec-compliant Streamable HTTP clients today. It avoids unsafe “pick any POST stream” behavior by using the active request context when possible and falling back only when there is exactly one active POST request.

Follow-up tracking issue for the SDK work: #1513.

Coverage

Added regression coverage for:

  • McpAgent.elicitInput() over the originating POST stream without standalone SSE.
  • SDK Server.elicitInput() over the originating POST stream without standalone SSE.
  • Concurrent elicitation requests staying on their own POST streams.
  • Request-scoped logging notifications delivered over POST when no standalone SSE exists.
  • Lost async-context fallback to the only active POST stream.
  • Ambiguous server-to-client requests rejecting instead of being silently dropped.

Test plan

  • npm --workspace packages/agents run test:workers -- src/tests/mcp/elicitation.test.ts src/tests/mcp/transports/streamable-http.test.ts
  • npm --workspace packages/agents run test:workers -- src/tests/mcp

Both passed locally.

Known remaining limitations

  • If a connection is still marked _standaloneSse but the outer HTTP SSE writer is already dead, the transport will still prefer the standalone connection. That lifecycle race is not fully observable from send().
  • If async context is lost while multiple POST streams are active, the transport intentionally refuses to guess. The SDK-level relatedRequestId propagation tracked in Track MCP SDK fix for implicit relatedRequestId on server-to-client requests #1513 is the correct fix for that case.
  • Durable recovery of pending SDK-level server-to-client requests across hibernation is outside this patch and likely needs SDK design work.

Made with Cursor


Open in Devin Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

🦋 Changeset detected

Latest commit: 9b21dd4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agents Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

@threepointone threepointone force-pushed the fix/mcp-streamable-post-routing branch from 9cbd152 to df2c294 Compare May 12, 2026 19:37
Streamable HTTP clients are not required to keep a standalone GET/SSE stream open. When a server emits a related server-to-client request or notification while handling an in-flight client request, prefer the originating POST response stream when no standalone stream is available.

Track the active MCP request id during transport dispatch so SDK convenience calls such as Server.elicitInput() can still be delivered to clients that only hold the POST stream. If async context is unavailable, conservatively fall back only when there is exactly one active POST request id, and reject unroutable server-to-client requests instead of silently dropping them until the SDK timeout fires.

Clean up completed POST request ids once the stream has received all final responses so fallback routing does not consider stale request state.

Add regression coverage for McpAgent and SDK elicitation without standalone SSE, concurrent elicitation routing, request-scoped logging notifications over POST, context-loss fallback, completed request cleanup, and ambiguous request rejection.

Co-authored-by: Cursor <cursoragent@cursor.com>
@threepointone threepointone force-pushed the fix/mcp-streamable-post-routing branch from df2c294 to 9b21dd4 Compare May 12, 2026 19:43
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 12, 2026

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1514

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1514

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1514

hono-agents

npm i https://pkg.pr.new/hono-agents@1514

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1514

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1514

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1514

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1514

commit: 9b21dd4

@threepointone threepointone merged commit 0371a6f into main May 12, 2026
4 checks passed
@threepointone threepointone deleted the fix/mcp-streamable-post-routing branch May 12, 2026 19:55
@github-actions github-actions Bot mentioned this pull request May 12, 2026
threepointone added a commit that referenced this pull request May 15, 2026
Bring back the server-only MCP elicitation and raw transport examples that were lost during the full-stack example migration. Also document that focused MCP protocol examples may stay Wrangler-only, refresh the workspace lockfile for the restored mcp-server workspace, and add a patch changeset noting the PR #1514 routing revert.

Co-authored-by: Cursor <cursoragent@cursor.com>
mattzcarey pushed a commit that referenced this pull request May 15, 2026
* Revert "Fix streamable HTTP server-to-client routing (#1514)"

This reverts commit 0371a6f.

* Restore minimal MCP server examples

Bring back the server-only MCP elicitation and raw transport examples that were lost during the full-stack example migration. Also document that focused MCP protocol examples may stay Wrangler-only, refresh the workspace lockfile for the restored mcp-server workspace, and add a patch changeset noting the PR #1514 routing revert.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot mentioned this pull request May 15, 2026
cjol pushed a commit that referenced this pull request May 20, 2026
* Revert "Fix streamable HTTP server-to-client routing (#1514)"

This reverts commit 0371a6f.

* Restore minimal MCP server examples

Bring back the server-only MCP elicitation and raw transport examples that were lost during the full-stack example migration. Also document that focused MCP protocol examples may stay Wrangler-only, refresh the workspace lockfile for the restored mcp-server workspace, and add a patch changeset noting the PR #1514 routing revert.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant