Repository navigation
fix(client): fall back to initialize when the discover probe gets a completed non-modern answer #2571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
fix(client): fall back to initialize when the discover probe gets a completed non-modern answer #2571
Changes from 2 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8d5fee1
fix(client): fall back to initialize when the discover probe gets a c…
claude b226ba9
fix(client): keep mid-body network failures out of the invalid-reply …
claude 573da02
fix(client): scope the probe 202 row to probe ids and name the cause …
claude 5804b09
Merge remote-tracking branch 'origin/main' into fix/probe-malformed-e…
claude de0ef85
fix(client): tolerate custom fetch responses lacking text() in direct…
claude 066bbe1
fix(client): carry the raw body text on the 2xx JSON-parse-failure stamp
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| '@modelcontextprotocol/client': patch | ||
| --- | ||
|
|
||
| `versionNegotiation: { mode: 'auto' }` now falls back to the classic `initialize` handshake whenever the `server/discover` probe gets a completed, non-auth HTTP answer that is not a valid modern reply, instead of failing `connect()`. Newly falling back: | ||
|
|
||
| - a 2xx reply whose JSON body fails strict JSON-RPC validation — e.g. the JSON-RPC 2.0 parse-error shape `{"error":{"code":-32700,...},"id":null}` some deployed servers send for unknown methods (anomalyco/opencode#39354), or error replies with extra/unknown members; | ||
| - a 2xx `application/json` reply with an empty or unparseable body; | ||
| - a 2xx reply in a non-MCP content type (e.g. a proxy's HTML error page, `text/plain`, or a missing content-type); | ||
| - a `202 Accepted` answer to the probe — now immediate legacy evidence instead of waiting out the full probe timeout; | ||
| - any `5xx` answer, with or without a JSON-RPC error body (some deployments map JSON-RPC errors to 500). Hosts that cache era verdicts for `connect({ prior })` should date cached legacy verdicts — a 5xx can be a modern server's transient failure; the SDK itself never persists a verdict. | ||
|
|
||
| Unchanged: 401/403 remain typed auth failures (never era evidence), network-level failures and an HTTP probe timeout still reject with typed errors, and `pin` mode still never falls back. |
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * Invalid-reply provenance stamp (internal — not part of the public API). | ||
| * | ||
| * When the HTTP layer COMPLETES an exchange (2xx) but what came back is not a | ||
| * valid JSON-RPC reply — a JSON body the strict message schema rejects, an | ||
| * empty or unparseable body, or a 202 accepted-without-reply to the probe — | ||
| * the transport stamps the error at the throw boundary, carrying the offending | ||
| * body when one was parsed. The version-negotiation probe reads the stamp to | ||
| * classify the reply as era evidence — deployed servers answer the unknown | ||
| * `server/discover` probe with off-spec shapes such as the JSON-RPC 2.0 | ||
| * parse-error reply `{"error":{"code":-32700,...},"id":null}` — instead of | ||
| * misreporting a server that answered as a network failure. As with the auth | ||
| * seam, provenance is recorded where it is known, never reconstructed from | ||
| * error types downstream. | ||
| * | ||
| * `Symbol.for` uses the global symbol registry, so the stamp survives a | ||
| * duplicated SDK copy in one process (bundler double-install, version skew) | ||
| * by design: both copies resolve the same symbol. | ||
| */ | ||
| const INVALID_REPLY = Symbol.for('mcp.invalidReplyBody'); | ||
|
|
||
| /** | ||
| * Stamp `error` as an invalid-reply escape (with the reply body that failed | ||
| * validation, when one was parsed) and return it — identity-preserving (the | ||
| * same object flows on, `instanceof` and `.cause` chains intact). A | ||
| * frozen/sealed object is returned unstamped rather than replaced: identity | ||
| * outranks provenance. Primitive throws cannot carry the stamp. | ||
| */ | ||
| export function markInvalidReplyEscape<T>(error: T, body: unknown): T { | ||
| if ((typeof error === 'object' && error !== null) || typeof error === 'function') { | ||
| try { | ||
| Object.defineProperty(error, INVALID_REPLY, { value: { body }, configurable: true }); | ||
| } catch { | ||
| // Frozen/sealed: leave unstamped. | ||
| } | ||
| } | ||
| return error; | ||
| } | ||
|
|
||
| /** | ||
| * Read the reply body an invalid-reply escape was stamped with, or `undefined` | ||
| * when `error` carries no stamp. The `{ body }` wrapper keeps a stamped | ||
| * absent/`null` body distinguishable from an absent stamp. | ||
| */ | ||
| export function readInvalidReplyEscape(error: unknown): { body: unknown } | undefined { | ||
| if (((typeof error === 'object' && error !== null) || typeof error === 'function') && INVALID_REPLY in error) { | ||
| const stamp = (error as Record<PropertyKey, unknown>)[INVALID_REPLY]; | ||
| if (typeof stamp === 'object' && stamp !== null && 'body' in stamp) { | ||
| return { body: (stamp as { body: unknown }).body }; | ||
| } | ||
| } | ||
| return undefined; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.