Skip to content

fix(rpc,ai): conform MCP Streamable HTTP transport to 2025-06-18#6275

Open
milkyskies wants to merge 1 commit into
Effect-TS:mainfrom
milkyskies:fix/mcp-streamable-http-conformance
Open

fix(rpc,ai): conform MCP Streamable HTTP transport to 2025-06-18#6275
milkyskies wants to merge 1 commit into
Effect-TS:mainfrom
milkyskies:fix/mcp-streamable-http-conformance

Conversation

@milkyskies

@milkyskies milkyskies commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Closes #6274.

McpServer.layerHttp / layerHttpRouter serve MCP over the generic JSON-RPC RPC protocol. At the HTTP wire level this diverged from the MCP Streamable HTTP transport, so spec-conformant MCP clients reject the endpoint.

Changes

@effect/rpc — single request → single response object

The JSON-RPC serialization replied to a single request with a one-element array ([{...}]). MCP 2025-06-18 removed JSON-RPC batching, and clients built to that revision expect a single response object and choke on the array.

The parser now tracks whether the decoded payload was a batch (top-level array) and frames the response to match: a single request gets a single object, a batch still gets an array. This is correct JSON-RPC 2.0 behavior generally, not MCP-specific — RpcClient already decodes both shapes, so existing RPC-over-HTTP consumers are unaffected.

@effect/aiGET/DELETE405 instead of 404

The transport is stateless and serves MCP over POST only. GET (the optional server→client SSE stream) and DELETE (session teardown) now return 405 Method Not Allowed with Allow: POST, so the path is distinguishable from "endpoint doesn't exist."

Tests

Both fail on main and pass with this change:

  • packages/rpc/test/RpcSerialization.test.ts — unit: single request → object, batch → array.
  • packages/ai/ai/test/McpServer.test.ts — end-to-end via HttpLayerRouter.toWebHandler: initialize returns a single object; GET returns 405 with Allow: POST.

Out of scope

  • Mcp-Session-Id: deliberately not added. The transport is stateless, and omitting the header is precisely how the spec lets a server signal statelessness; returning an id that subsequent requests don't honor would be worse. Stateful session support is a separate feature.
  • OAuth / Dynamic Client Registration: separate concern (see Support MCP protocol version 2025-11-25 #6157). This matters for how you verify the fix: Claude Desktop's native URL connector additionally insists on OAuth DCR and will fail against a no-auth server regardless of this change. Verify the transport fix with Claude Code, the mcp-remote bridge, or a direct initialize request — not Desktop's native "add a URL" flow. (Confirmed against a live deployment with direct requests: initialize returns a single object, tools/list returns the full tool set, and GET405.)

🤖 Generated with Claude Code

The JSON-RPC serialization replied to a single request with a one-element
array, but MCP 2025-06-18 removed JSON-RPC batching, so conformant clients
(Claude Desktop/Code) reject the array and refuse to connect.

- @effect/rpc: reply to a single (non-batch) request with a single JSON-RPC
  object; batches still receive an array.
- @effect/ai: McpServer.layerHttp/layerHttpRouter answer GET and DELETE with
  405 (Allow: POST) instead of 404, matching the stateless POST-only transport.

Closes Effect-TS#6274

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9944ae9

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

This PR includes changesets to release 2 packages
Name Type
@effect/rpc Patch
@effect/ai 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Discussion Ongoing

Development

Successfully merging this pull request may close these issues.

McpServer.layerHttpRouter emits JSON-RPC batches (arrays) over HTTP — breaks MCP 2025-06-18 Streamable HTTP clients (Claude Desktop/Code)

1 participant