Repository navigation
Conversation
Batch handling ignored the negotiated protocol version, and the two transports disagreed: HTTP refused every batch, even from clients on 2025-03-26 (the revision that spelled batching out), while stdio accepted batches after 2025-06-18 had removed them. Both transports now decide from the negotiated revision: - 2024-11-05 and 2025-03-26: batches are processed per JSON-RPC 2.0. Responses come back as an array; notifications and client responses add no entry; an all-notification batch gets 202 (stdio writes nothing); an empty batch is a single -32600; each invalid entry gets its own -32600; initialize inside a batch is refused for that entry, as 2025-03-26 forbids batching it. Entries run concurrently. - 2025-06-18 and later: a batch is refused with a single -32600. On HTTP the version is the one already resolved for the request (the session's negotiated version, else MCP-Protocol-Version, else the 2025-03-26 default). An initialize inside a batch no longer opens a session or exempts the batch from the session/header version check. stdio now forwards a batch line to the HTTP handler whole, with the version negotiated at initialize, instead of splitting it itself, so both transports share one implementation. The raw line is forwarded so an id like 1e400 is still refused rather than re-serialized to null. Fixes #319 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmhsFjk8W96X2PvmShmDm9
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Batch handling ignored the negotiated protocol version, and the two transports disagreed:
2025-03-26, the revision that spelled batching out.2025-06-18or later, which removed batching.Fix
Both transports now decide from the negotiated revision. I went with the version-aware option from the issue, since it fit cleanly into the existing per-request version resolution.
2024-11-05,2025-03-262025-06-18,2025-11-25-32600,id: null202, no body; stdio writes nothing-326001,{}, unsafe numeric id)-32600for that entry onlyinitializeinside a batch-32600for that entry (2025-03-26 lifecycle: MUST NOT be batched)Design decisions
processMessageinsrc/handlers.tshandles arrays through a newprocessBatch. The predicate issupportsBatching()insrc/protocol-version.ts, next to the other revision helpers. stdio no longer splits batches. It forwards the batch line to/mcpin oneinject, carrying the version negotiated atinitializeinMCP-Protocol-Version(the same as single messages), so both transports behave the same by construction.MCP-Protocol-Versionheader, otherwise the spec's2025-03-26default for an absent header. So a header-less, session-less batch is accepted, as the spec's default implies. A session negotiated at2025-06-18refuses batches even without a header.initializein a batch is refused for that entry.isInitializeRequestno longer looks inside arrays, so a batchedinitializecannot open a session. It also cannot exempt the batch from the session/header version reconciliation, which would otherwise let a2025-06-18session opt back into batching with a2025-03-26header.2024-11-05accepts batches. That revision is plain JSON-RPC 2.0, which has always defined batches. Only2025-06-18took them out.Promise.all, which JSON-RPC allows). One long-running entry, such as a blockingtasks/result, does not hold up the others, and response order follows request order. All entries share the one HTTP reply, and nothing in the request handlers hijacks or streams it, so this is safe.1e400intonullbefore the per-entry safe-integer check could refuse it.Tests
test/jsonrpc-batch.test.ts(22 tests): HTTP with each version via header, via session, and with no header; notifications-only, empty, invalid entries, unsafe ids,initializein a batch, and the version-override attempt. stdio after negotiating each version, notifications-only,initializein a batch, and1e400ids.test/index.test.tsnow sendsMCP-Protocol-Version: 2025-06-18to test the refusal.test/stdio.test.tsnow expects a batch after2025-11-25to be refused.npm run lint,npm run typecheckandnpm run testall pass: 788/788 with Redis.Fixes #319
🤖 Generated with Claude Code
https://claude.ai/code/session_01LmhsFjk8W96X2PvmShmDm9