Skip to content

docs(migration): align four passages with the implementation#2388

Merged
felixweinberger merged 1 commit into
mainfrom
fweinberger/docs-dualrole-instanceof-caveat
Jun 29, 2026
Merged

docs(migration): align four passages with the implementation#2388
felixweinberger merged 1 commit into
mainfrom
fweinberger/docs-dualrole-instanceof-caveat

Conversation

@felixweinberger

Copy link
Copy Markdown
Contributor

Follow-up to #2382: the final automated review round landed after the merge, leaving four findings unanswered. Each was checked against the implementation before rewording (the other five findings from that round were already addressed by pre-merge pushes).

Motivation and Context

  • SEP-2352 / saveTokens(): the guide described the wrong consequence for a provider that drops the issuer field. Actual behavior: reads still succeed and refresh keeps working; the per-AS issuer check does not apply to that credential and a [mcp-sdk] warning is logged on each read (with re-stamping on first use). Also removes a duplicated clause.
  • zod-3 failure path: registration does not throw for z.object() schemas — the server starts and connects normally, and the first tools/list answers with an error pointing at fromJsonSchema(). Only the deprecated unwrapped raw-shape form errors at registration (pointing at zod/v4). The previous text described a registration-time error that does not occur on that path.
  • DiscoverResult cache fields: hidden at the type level only — the runtime object still carries ttlMs/cacheScope (readable via a cast); raw frames are needed only to distinguish an omitted hint from the defaulted 0/'private'.
  • connect() sessionId skip: the same guard exists in v1 since 1.10.0, so the bullet now presents it as a long-standing gotcha rather than a v1→v2 behavior change.

How Has This Been Tested?

Docs-only; each claim verified against the source with file:line evidence before rewording. Prettier clean.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Corrections verified against source after the final automated review round
landed post-merge on #2382: the SEP-2352 paragraph now describes what a
missing issuer stamp actually does (reads succeed, refresh keeps working,
the per-AS check does not apply to that credential, a warning is logged on
each read) and drops a duplicated clause; the zod-3 paragraph now matches
the real failure path (registration does not throw for z.object() schemas -
the server starts normally and the first tools/list answers with an error
pointing at fromJsonSchema(); only the deprecated raw-shape form errors at
registration, pointing at zod/v4); the DiscoverResult bullet now says the
cache fields are hidden at the type level only (the runtime object carries
both, a cast suffices, raw frames only distinguish omitted hints from
defaulted values); and the connect() sessionId-skip bullet notes the same
guard exists in v1 since 1.10.0, so it is a gotcha rather than a migration
delta.
@felixweinberger felixweinberger requested a review from a team as a code owner June 29, 2026 15:30
@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 805892b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2388

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2388

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2388

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2388

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2388

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2388

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2388

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2388

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2388

commit: 805892b

@felixweinberger felixweinberger merged commit 7c42d47 into main Jun 29, 2026
19 checks passed
@felixweinberger felixweinberger deleted the fweinberger/docs-dualrole-instanceof-caveat branch June 29, 2026 15:32
Comment on lines +305 to +311
- **`DiscoverResult` hides its cache fields at the type level only.** `ttlMs` /
`cacheScope` on `server/discover` are read by the client's response-cache layer and
are absent from the public `DiscoverResult` type returned by `getDiscoverResult()` —
but they are not removed at runtime: the returned object still carries both, readable
via a cast. The wire parse defaults absent or malformed hints to `0` / `'private'`,
so only tooling that must distinguish an omitted hint from an advertised default
needs raw frames.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The new claims that the returned object "still carries both" cache fields, that "the wire parse defaults absent or malformed hints to 0/'private'", and that they are "read by the client's response-cache layer" only hold for the DiscoverResult populated by the connect-time version-negotiation probe — the public client.discover() path (which equally backs getDiscoverResult()) goes through codec.decodeResult(), which returns the raw object un-defaulted, and the response-cache layer never reads server/discover hints at all. Consider scoping the runtime-presence/defaulting claims to the probe-populated result and dropping or correcting the response-cache-layer clause.

Extended reasoning...

What the passage claims vs. what the code does

The rewritten bullet (docs/migration/support-2026-07-28.md:305-311) makes three runtime claims about the object behind getDiscoverResult():

  1. ttlMs / cacheScope "are read by the client's response-cache layer";
  2. "the returned object still carries both, readable via a cast";
  3. "The wire parse defaults absent or malformed hints to 0 / 'private', so only tooling that must distinguish an omitted hint from an advertised default needs raw frames."

Claims (2) and (3) are accurate only for one of the two sources of _discoverResult, and claim (1) is not backed by the implementation.

The probe path (where the claims hold). During connect-time version negotiation, probeClassifier.ts:143 routes the probe answer through codecForVersion(MODERN_WIRE_REVISION).validateResult('server/discover', ...). validateResult is triState() (core-internal/src/wire/rev2026-07-28/codec.ts:62-66), which returns parsed.data from the dispatch schema — and that schema's ttlMs/cacheScope carry .catch(0) / .catch('private') (rev2026-07-28/schemas.ts ~1119-1128). The parsed, defaults-applied object is what client.ts:1093 stores as _discoverResult. For this source, "still carries both" and "defaults absent or malformed hints" are true.

The discover() path (where they don't). client.discover() (client.ts:1447-1450) also populates _discoverResult and is one of the documented sources of getDiscoverResult(). It goes through _requestWithSchema_requestWithSchemaViaCodeccodec.decodeResult(), which only safeParses the catch-defaulted wire schema as a validity check and then returns {...raw} with just resultType deleted (rev2026-07-28/codec.ts:247-261) — the defaulted parse output is discarded. The result is then validated against the public DiscoverResultSchema (types/schemas.ts:577), a looseObject with no cache fields, which neither adds absent keys nor normalizes malformed ones.

Step-by-step proof (discover() against a server that omits the hints):

  1. A non-SDK 2026-era server answers server/discover with a result that omits ttlMs and cacheScope.
  2. codec.decodeResult('server/discover', raw) safeParses the wire schema (the .catch defaults exist only inside that throwaway parse), then returns the raw object minus resultType — still with no ttlMs/cacheScope.
  3. The looseObject DiscoverResultSchema passes it through unchanged; _discoverResult is set to it.
  4. Tooling that follows the doc and casts getDiscoverResult() to read ttlMs/cacheScope gets undefined, not the documented 0/'private'. Likewise a malformed hint (e.g. ttlMs: "soon") passes through verbatim instead of being defaulted.

The response-cache clause. The "read by the client's response-cache layer" wording (carried over from the pre-PR text) is also not what the code does: the only freshness reads are the list verbs' aggregate write and resources/read (client.ts:1654, 1672-1680, 1789-1794), each reading hints off its own result body via _freshness(). 'server/discover' appears in responseCache.ts only inside the CAP_EXEMPT_METHODS set (lines 142-148); there is no cache write or read for it anywhere in the client, so its hints are never consumed by that layer.

Why this matters here. This PR's stated purpose is aligning these exact sentences with the implementation, with file:line verification, and the audience the sentence addresses is precisely the tooling that would rely on the cast/defaulting behavior. No verifier disputed the finding (4/4 confirmed).

Suggested fix. Scope the runtime-presence and 0/'private'-defaulting claims to the connect-probe-populated DiscoverResult (or describe the discover() behavior: omitted hints stay omitted, malformed hints pass through verbatim), and drop or correct the "read by the client's response-cache layer" clause — e.g. "appear in the response-cache store only as a cap-exempt method; the cache reads hints off the cacheable list/read results, not server/discover".

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