diff --git a/docs/migration/support-2026-07-28.md b/docs/migration/support-2026-07-28.md index 0f56f869d..1b38a0a2d 100644 --- a/docs/migration/support-2026-07-28.md +++ b/docs/migration/support-2026-07-28.md @@ -302,10 +302,13 @@ and the multi-round-trip retry fields (`inputResponses`, `requestState`). - **`resultType` is gone from every public result type** (`Result`, `CallToolResult`, `GetPromptResult`, …). The wire schemas keep parsing it, and the protocol layer consumes it before results reach your code. -- **`DiscoverResult` strips its cache fields too.** `ttlMs` / `cacheScope` on - `server/discover` are wire-only — consumed by the client's response-cache layer and - absent from the public `DiscoverResult` type returned by `getDiscoverResult()`. - Tooling that displays the server's advertised cache policy must parse raw frames. +- **`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. - **High-level methods return the named public types** (`client.callTool()` → `Promise`, etc.). Handler return positions are unaffected. - **Reserved envelope keys and retry fields appear in no public params/result type.** diff --git a/docs/migration/upgrade-to-v2.md b/docs/migration/upgrade-to-v2.md index 84b7787ed..924d3174c 100644 --- a/docs/migration/upgrade-to-v2.md +++ b/docs/migration/upgrade-to-v2.md @@ -393,9 +393,11 @@ prefer wrapping explicitly. Zod v4, ArkType, and Valibot all implement the spec. **Zod v3 is no longer supported** (v1 peer was `^3.25 || ^4.0`). Check the **declared range** in your `package.json`, not just the installed version: a zod-3 range that satisfied the v1 peer installs and typechecks cleanly under v2 and only fails at -runtime, when the first registration throws — under a spawning harness that surfaces -as an opaque child exit two hops from the cause. A Zod v3 schema -hard-errors with a pointer at `fromJsonSchema()`. Zod **≥4.2.0** self-converts via +runtime — and quietly: registration swallows the conversion failure, the server starts +and connects normally, and the first `tools/list` (so `client.listTools()`) answers +with an error pointing at `fromJsonSchema()` while the process keeps running. (Only the +deprecated unwrapped raw-shape form with zod-3 field values throws at registration, +with a message pointing at `zod/v4`.) Zod **≥4.2.0** self-converts via `~standard.jsonSchema` — the supported path. Zod **4.0–4.1** lacks it, so the SDK falls back to its bundled Zod's `z.toJSONSchema()` with a one-time `[mcp-sdk]` console warning; and because `.describe()` field descriptions live in the _authoring_ Zod's @@ -798,13 +800,12 @@ same handling as the POST send path. methods plus `tokens()` / `clientInformation()`. On read, a stored value whose `issuer` names a different AS is treated as `undefined` and the flow re-registers / re-authorizes. **Round-trip the stored object verbatim and you're protected** — single-slot storage -works. The failure modes differ: a stamp naming a **different** AS reads back as -`undefined` and the flow re-registers / re-authorizes. A **missing** stamp (a -`saveTokens()` that rebuilds the object field-by-field and drops `issuer`, or -pre-upgrade storage) is used **as-is** with a `[mcp-sdk]` console warning — SEP-2352 -isolation is silently inactive for that read; `auth()` re-stamps on first use where the -provider can persist it. If you see that warning repeatedly, your provider is not -round-tripping the stored object. To hold credentials for several authorization servers at once, key your storage +works. Dropping the stamp is easy to miss: a `saveTokens()` implementation that +rebuilds the object field-by-field and drops `issuer` leaves the value unstamped — +reads still succeed and refresh keeps working, the per-AS issuer check simply does not +apply to that credential, and every read logs an `[mcp-sdk]` warning (`auth()` +re-stamps on first use where the provider can persist it). If you see that warning +repeating after upgrading, check this first. To hold credentials for several authorization servers at once, key your storage on `ctx.issuer` (treat **`ctx === undefined` as "return the most-recently-saved token set"** — the transport's per-request `Authorization: Bearer` read calls `tokens()` with no `ctx`). New TypeScript-only aliases `StoredOAuthTokens` / `StoredOAuthClientInformation` @@ -998,8 +999,9 @@ rewrite required unless noted. #### Client connection & dispatch - **`connect()` skips the `initialize` handshake when the transport already exposes a - `sessionId`** — it assumes it is reconnecting to an existing session (v1 always - initialized). A custom or test transport that sets `sessionId` at construction + `sessionId`** — it assumes it is reconnecting to an existing session (unchanged from + v1.x, where the same guard has existed since 1.10.0; recorded here because the + far-away symptom keeps surprising migrators). A custom or test transport that sets `sessionId` at construction silently skips initialization: `getServerCapabilities()` stays `undefined` and the list verbs return empty results. Expose `sessionId` only after the first request has been sent.