fix(server): thread responseMode into the legacy stateless fallback - #2621
Open
dielduarte wants to merge 1 commit into
Open
fix(server): thread responseMode into the legacy stateless fallback#2621dielduarte wants to merge 1 commit into
dielduarte wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: 0134d9a The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
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 |
dielduarte
force-pushed
the
legacy-response-mode
branch
from
August 6, 2026 18:12
1f394ce to
2720097
Compare
dielduarte
marked this pull request as ready for review
August 6, 2026 18:15
dielduarte
force-pushed
the
legacy-response-mode
branch
from
August 6, 2026 18:17
2720097 to
8ca3df0
Compare
createLegacyStatelessFallback built a WebStandardStreamableHTTPServerTransport per legacy request but never passed it enableJsonResponse, so a legacy- classified request always answered over SSE, flushing its 200 before a tool call resolved. The modern leg already solved the equivalent problem via responseMode: 'json' (backed by PerRequestHTTPServerTransport); it just never reached the legacy leg's transport, which has the same knob under a different name. responseMode: 'json' now sets enableJsonResponse: true on the legacy leg's transport too, buffering the reply until the result is known - the same guarantee the modern leg already had. 'auto' and 'sse' are equivalent on this leg (both leave its SSE-first default unchanged), since the transport has no partial-upgrade concept to distinguish them. legacyStatelessFallback(factory, onerror, responseMode) gains a third, optional parameter mirroring the entry's option, so hand-wired compositions that use the standalone building block get the same guarantee without reimplementing the legacy transport wiring by hand.
dielduarte
force-pushed
the
legacy-response-mode
branch
from
August 6, 2026 18:25
8ca3df0 to
0134d9a
Compare
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.
Summary
createMcpHandler'sresponseModeoption lets the modern (2026-07-28) leg defer the HTTP response until a tool call's result is known (responseMode: 'json', backed byPerRequestHTTPServerTransport). The legacy leg's transport,WebStandardStreamableHTTPServerTransport, has the equivalent knob (enableJsonResponse), butcreateLegacyStatelessFallbacknever constructed it with the option set — a legacy-classified request always answered over SSE, flushing its200before the tool call resolved, with no way to defer it.This PR threads
responseModeinto the legacy leg too:responseMode: 'json'now setsenableJsonResponse: trueon the legacy leg's transport, buffering the reply until the result is known — the same guarantee the modern leg already had.'auto'and'sse'are equivalent on this leg (both leave its SSE-first default unchanged) — the transport has no partial-upgrade concept to distinguish them.legacyStatelessFallback(factory, onerror, responseMode)(the standalone building block for hand-wired compositions) gains a third, optional parameter mirroring the entry's option.Why this matters: a consumer that needs to turn a tool-call failure into a different HTTP status (e.g. a
401/403challenge instead of the SDK's default200withisError: true) does so by rewriting the response once a handler-recorded failure is known. That rewrite only has a window to fire if the response hasn't already been sent — exactly whatenableJsonResponseguarantees. Previously, getting that guarantee on the legacy leg meant bypassinglegacy: 'stateless'/legacyStatelessFallbackentirely and hand-rolling aWebStandardStreamableHTTPServerTransport+server.connect()+ manual abort-teardown. Both now support it directly.Per
CONTRIBUTING.md's note that straightforward bug fixes with tests can skip the issue-first step, I'm opening this directly: the production change is a few lines (a new parameter threaded throughcreateLegacyStatelessFallback/legacyStatelessFallback, one spread into the transport constructor, a call-site pass-through), with tests demonstrating the gap and the fix. Opening as a draft in case a maintainer would rather see this filed as an issue first.Test plan
pnpm --filter @modelcontextprotocol/server test— 474/474 pass (6 new tests covering: unchanged SSE-first behavior for the default/'auto'/'sse', buffered-JSON behavior for'json'on both the standalonelegacyStatelessFallbackand end-to-end throughcreateMcpHandleron a legacy-classified request, and dropped mid-call notifications under'json')pnpm --filter @modelcontextprotocol/server typecheckandlint— cleanpnpm check:all(typecheck + lint + docs build across the whole workspace) — cleanpnpm test:all— clean aside from one pre-existing, unrelated flaky e2e test (protocol:timeout:max-total [sse 2025-11-25], a fake-timer scenario) that reproduces identically on a cleanmaincheckoutpatchon@modelcontextprotocol/server)