Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,29 @@
"@modelcontextprotocol/codemod": "2.0.0-alpha.0",
"@modelcontextprotocol/test-conformance": "2.0.0-alpha.0",
"@modelcontextprotocol/test-helpers": "2.0.0-alpha.0",
"@modelcontextprotocol/test-integration": "2.0.0-alpha.0"
"@modelcontextprotocol/test-integration": "2.0.0-alpha.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Merging this PR triggers the first npm publish of @modelcontextprotocol/codemod@2.0.0-alpha.0 (added as a 'draft' by #1950 with no private:true, no changeset, no CHANGELOG, and not in the changesets ignore list — pnpm changeset publish publishes any non-private workspace package whose version isn't on the registry). The codemod that would ship still maps StreamableHTTPError → SdkError and tells users to read error.data?.status, which contradicts the SdkHttpError migration introduced by #2049 and documented in docs/migration.md/docs/migration-SKILL.md shipping in this same alpha.3, and produces output that won't compile (SdkError has no .status accessor; .data is typed unknown). Either mark packages/codemod/package.json "private": true until the codemod catches up with #2049, or land a changeset for it and update removedApis.ts/importPaths.ts/the test to target SdkHttpError.

Extended reasoning...

Two compounding issues anchored at the publish gate

1. Silent first publish of a draft package

PR #1950 (48251fe, "feat: add v2 codemod draft", merged 2026-05-21) added packages/codemod with name: @modelcontextprotocol/codemod at version 2.0.0-alpha.0. That package.json has no "private": true and no publishConfig restricting access; the package is not in .changeset/config.json's ignore list (only the five examples-* packages are ignored); and the package has no CHANGELOG.md and no changeset describing its initial release.

The release workflow (.github/workflows/release.yml) runs ci:publishpnpm run build:all && pnpm changeset publish after this Version Packages PR merges. changeset publish publishes every non-private, non-ignored workspace package whose package.json version is not already on the npm registry — regardless of whether a changeset exists for it. @modelcontextprotocol/codemod@2.0.0-alpha.0 has never been published (it was added by the same commit that set its version), so merging this PR will publish it as a side effect.

The visible artifact in this PR's diff is exactly that: .changeset/pre.json line 22 records "@modelcontextprotocol/codemod": "2.0.0-alpha.0" in initialVersions — the bot has noticed the new package but has nothing to bump it with, so it stays at the version that will be auto-published. This is inconsistent with repo convention: every other internal/non-publishable package (packages/core, test/helpers, test/integration, test/conformance) carries "private": true. Publishing under the @modelcontextprotocol scope on npm is hard to reverse (24-hour unpublish window; the version string can never be reused), so this is the right place to make a deliberate decision rather than ship by accident.

2. The codemod that would ship is stale relative to #2049's SdkHttpError

PR #2049 (4f226c1, also shipping in this same alpha.3 — see the @modelcontextprotocol/client@2.0.0-alpha.3 and @modelcontextprotocol/core@2.0.0-alpha.2 CHANGELOG entries in this diff) introduced SdkHttpError extends SdkError (packages/core/src/errors/sdkErrors.ts:94-110) with typed .status/.statusText accessors, and rewrote both migration docs to say StreamableHTTPErrorSdkHttpError (docs/migration.md:729, docs/migration-SKILL.md:98,119).

The codemod added by #1950 (merged ~8 hours after #2049 but written before SdkHttpError existed) still targets the old name everywhere:

  • packages/codemod/src/migrations/v1-to-v2/transforms/removedApis.ts:157renameAllReferences(sourceFile, localName, 'SdkError'). Migrated v1 code like if (e instanceof StreamableHTTPError) { use e.code } becomes if (e instanceof SdkError) — a broader check that loses the typed HTTP status accessors only the subclass has.
  • removedApis.ts:151-152 — constructor diagnostic recommends new SdkError(code, message, data?); the v2 replacement is new SdkHttpError(code, message, { status, statusText }).
  • removedApis.ts:175-176 — import diagnostic says "HTTP status is now in error.data?.status". SdkError.data is typed unknown (sdkErrors.ts:62), so following that diagnostic produces a compile error; the typed accessor is SdkHttpError.status.
  • removedApis.ts:167 adds SdkError/SdkErrorCode to imports, never SdkHttpError.
  • importPaths.ts:16-17 — re-export diagnostic also says "renamed to SdkError in v2".
  • packages/codemod/test/v1-to-v2/transforms/removedApis.test.ts:166-200 — tests assert the SdkError mapping, locking the stale behavior in.

Step-by-step proof

  1. This PR merges → release.yml triggers → changeset publish runs.
  2. @modelcontextprotocol/codemod is non-private, non-ignored, and 2.0.0-alpha.0 is not on the registry → it is published.
  3. A user runs the published codemod on v1 code containing if (err instanceof StreamableHTTPError) { console.log(err.code) }.
  4. The codemod rewrites it to if (err instanceof SdkError) { console.log(err.code) } and emits a diagnostic suggesting the user read err.data?.status for the HTTP status.
  5. The user reads the simultaneously-published docs/migration.md ("HTTP transport errors are now thrown as SdkHttpError") and the codemod output disagree. If they follow the codemod's error.data?.status suggestion, it fails to type-check (Property 'status' does not exist on type 'unknown'). The typed accessor lives only on SdkHttpError, which the codemod never imports or references.

Why this is the right PR to flag it on

The defective code lives in packages/codemod, which is not in this diff — but this Version Packages PR is the publish gate. Merging it is what triggers the first npm publish of the codemod, and the addition of @modelcontextprotocol/codemod to initialVersions at line 22 of .changeset/pre.json is the diff's record of that. There is no later checkpoint where this can be caught.

How to fix

Either:

  • Hold the publish: add "private": true to packages/codemod/package.json (or add it to the changesets ignore list) until the StreamableHTTPError migration is updated for SdkHttpError and a changeset/CHANGELOG exists. One-line change, matches the packages/core precedent.
  • Ship it deliberately: land a changeset for @modelcontextprotocol/codemod on main so the bot regenerates this PR with a proper alpha bump and CHANGELOG; and update removedApis.ts:151-176, importPaths.ts:16-17, and removedApis.test.ts:166-200 to target SdkHttpError and recommend error.status instead of error.data?.status.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Partially addressed. The "silent first publish" half is resolved: #2206 (e03bca9) landed a changeset for the codemod (.changeset/codemod-resolve-legacy-imports.md, @modelcontextprotocol/codemod: minor), so this Version Packages PR now bumps the package deliberately to 2.0.0-alpha.1 with a generated packages/codemod/CHANGELOG.md — the publish is no longer an accidental side effect of an undocumented draft.

The stale-codemod half remains. At HEAD the codemod still targets the pre-#2049 error model:

  • packages/codemod/src/migrations/v1-to-v2/transforms/removedApis.ts:151-152,157,167 — still renames StreamableHTTPError → SdkError and only ever imports SdkError/SdkErrorCode, never SdkHttpError.
  • removedApis.ts:175-176 — diagnostic still says "HTTP status is now in error.data?.status", which doesn't type-check (SdkError.data is unknown); the typed accessor is SdkHttpError.status.
  • packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts:16-17 — re-export diagnostic still says "renamed to SdkError in v2". (v2 backcompat: server legacy package #2206 only changed the auth-import resolution in this file, not this mapping.)
  • packages/codemod/test/v1-to-v2/transforms/removedApis.test.ts:166-200 — tests still lock in the SdkError mapping.

So merging this PR still publishes a codemod whose output contradicts docs/migration.md:737 / docs/migration-SKILL.md:98,119 (StreamableHTTPError → SdkHttpError) shipping in the same alpha.3. To fully close this, update the three files above to target SdkHttpError and recommend error.status (a follow-up on main would regenerate this PR), or accept the known gap for alpha.1 of the codemod and track it separately.

"@modelcontextprotocol/test-e2e": "2.0.0-alpha.0"
},
"changesets": [
"abort-handlers-on-close",
"add-consumer-sse-e2e",
"add-e2e-test-suite",
"add-fastify-middleware",
"add-hono-peer-dep",
"add-resource-size-field",
"add-sdk-http-error",
"add-server-legacy-package",
"brave-lions-glow",
"busy-rice-smoke",
"busy-weeks-hang",
"cfworker-out-of-barrel",
"codemod-resolve-legacy-imports",
"custom-methods-minimal",
"cyan-cycles-pump",
"drop-zod-peer-dep",
"export-inmemory-transport",
"expose-auth-server-discovery",
"express-resource-server-auth",
"extract-task-manager",
"fast-dragons-lead",
"finish-sdkerror-capability",
Expand All @@ -44,27 +54,42 @@
"fix-session-status-codes",
"fix-stdio-epipe-crash",
"fix-stdio-windows-hide",
"fix-streamable-close-reentrant",
"fix-streamable-http-error-response",
"fix-task-session-isolation",
"fix-transport-exact-optional-property-types",
"fix-unknown-tool-protocol-error",
"fix-validate-client-metadata-url",
"funky-baths-attack",
"gentle-planets-rest",
"heavy-walls-swim",
"hono-peer-optional",
"legacy-module-resolution-types",
"oauth-error-http200",
"odd-forks-enjoy",
"quick-islands-occur",
"reconnection-scheduler",
"register-rawshape-compat",
"remove-websocket-transport",
"respect-capability-negotiation",
"restore-task-wire-types",
"rich-hounds-report",
"schema-object-type-for-unions",
"sep-2663-tasks-removal",
"shy-times-learn",
"spec-type-schema",
"spotty-cats-tickle",
"stdio-skip-non-json",
"stdio-subpath-export",
"support-standard-json-schema",
"tame-camels-greet",
"tender-snails-fold",
"token-provider-composable-auth",
"twelve-dodos-taste",
"use-scopes-supported-in-dcr"
"use-scopes-supported-in-dcr",
"workerd-shim-vendors-cfworker",
"wraphandler-hook",
"zod-json-schema-compat",
"zod-jsonschema-fallback"
]
}
79 changes: 79 additions & 0 deletions packages/client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,84 @@
# @modelcontextprotocol/client

## 2.0.0-alpha.3

### Major Changes

- [#2128](https://github.com/modelcontextprotocol/typescript-sdk/pull/2128) [`c8d7401`](https://github.com/modelcontextprotocol/typescript-sdk/commit/c8d7401b46f34b6c49b7cfb7b321714d0d4048f6) Thanks [@felixweinberger](https://github.com/felixweinberger)! - SEP-2663: remove
2025-11 experimental tasks (TaskManager, experimental.tasks.\* accessors). Tasks are now Extensions Track.

### Minor Changes

- [#2049](https://github.com/modelcontextprotocol/typescript-sdk/pull/2049) [`4f226c1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/4f226c1e35200616d62f1d7e46a2daa33d91172a) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Add `SdkHttpError` subclass
with typed `.status` / `.statusText` accessors for HTTP transport failures. `StreamableHTTPClientTransport` now throws `SdkHttpError` (which extends `SdkError`) for non-OK HTTP responses; `SSEClientTransport` throws `SdkHttpError` for 401-after-reauth (circuit breaker).
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 PR #2049 (shipping in this alpha.3) introduced SdkHttpError and the migration docs it rewrote describe it categorically — docs/migration.md:729 says "HTTP transport errors are now thrown as SdkHttpError" and docs/migration-SKILL.md:98,119 map the wildcard SdkErrorCode.ClientHttp* to SdkHttpError — but streamableHttp.ts:683 still throws plain SdkError for ClientHttpUnexpectedContent (which fires on an OK response with the wrong content type), and migration-SKILL.md:123 four rows below correctly says SdkError for that code, contradicting rows 98/119 of the same file. Qualify the wildcard claims (e.g. "except ClientHttpUnexpectedContent, thrown as plain SdkError") or convert the line-683 throw to SdkHttpError.

Extended reasoning...

What the bug is

PR #2049 (commit 4f226c1, the most recent commit before this version-bump and shipping in @modelcontextprotocol/client@2.0.0-alpha.3) introduced SdkHttpError and converted six of the seven SdkErrorCode.ClientHttp* throw sites in packages/client/src/client/streamableHttp.ts from SdkError to SdkHttpError — but left the seventh, SdkErrorCode.ClientHttpUnexpectedContent at packages/client/src/client/streamableHttp.ts:683, as plain SdkError (its data carries only { contentType }, no status/statusText). That carve-out is intentional: the ClientHttpUnexpectedContent branch fires on an OK response whose Content-Type is unexpected, so there is no HTTP error status to attach.

The migration docs that #2049 also rewrote do not consistently note this exception:

  • docs/migration.md:729"The StreamableHTTPError class has been removed. HTTP transport errors are now thrown as SdkHttpError … with specific SdkErrorCode values…" — categorical claim, no exception, immediately following a table (lines 710–725) that lists ClientHttpUnexpectedContent among the codes.
  • docs/migration-SKILL.md:98"StreamableHTTPError → REMOVED (use SdkHttpError with SdkErrorCode.ClientHttp*)" — the wildcard sweeps in ClientHttpUnexpectedContent.
  • docs/migration-SKILL.md:119"HTTP transport error → SdkHttpError with SdkErrorCode.ClientHttp*" — same wildcard.
  • docs/migration-SKILL.md:123 (four rows below 119, same table) — "Unexpected content type → SdkError with SdkErrorCode.ClientHttpUnexpectedContent" — this row is correct and internally contradicts rows 98 and 119.

Why nothing prevents it

There is no automated check that migration-doc prose matches the implementation, and the contradiction lives between lines of the same hand-written table. The CHANGELOG entry being added in this PR is itself accurately scoped ("throws SdkHttpError … for non-OK HTTP responses"), and the worked code examples in both migration docs only switch over the SdkHttpError-producing codes — so a copy-paste migrator is not bitten. The defect is strictly in the surrounding prose at migration.md:729 and the wildcard rows at migration-SKILL.md:98,119.

Step-by-step proof

  1. git show 4f226c1 -- packages/client/src/client/streamableHttp.ts → six SdkErrorSdkHttpError conversions; the ClientHttpUnexpectedContent throw at line 683 is unchanged.
  2. At HEAD, packages/client/src/client/streamableHttp.ts:683 reads throw new SdkError(SdkErrorCode.ClientHttpUnexpectedContent, …, { contentType }) — not SdkHttpError, no status/statusText.
  3. docs/migration.md:729 and docs/migration-SKILL.md:98,119 (rewritten by 4f226c1) all assert SdkHttpError for ClientHttp* without exception.
  4. docs/migration-SKILL.md:123 (rewritten in the same commit) asserts SdkError for ClientHttpUnexpectedContent, contradicting step 3.
  5. A migrator who follows the wildcard rows writes if (error instanceof SdkHttpError) { switch (error.code) { case SdkErrorCode.ClientHttpUnexpectedContent: … } } — that branch never fires, because the actual instance is SdkError, not SdkHttpError. The migrator must read the per-scenario row at line 123 to find the carve-out.

Impact

Docs-precision drift introduced by a commit shipping in this alpha.3 release. The blast radius is narrow (the worked examples in the docs are correct, and ClientHttpUnexpectedContent is already the rarest of the seven codes), so this is a nit. But the internal contradiction within migration-SKILL.md — rows 98/119 vs. row 123 of the same table — is exactly the kind of docs-vs-implementation gap the REVIEW.md checklist asks reviewers to flag, and a wrong instanceof branch fails silently.

How to fix

Either:

  • Docs side (1–2 lines): qualify the wildcard claims at migration.md:729 and migration-SKILL.md:98,119 with "except ClientHttpUnexpectedContent, which occurs on an OK response and is thrown as plain SdkError"; or
  • Code side: convert streamableHttp.ts:683 to SdkHttpError and pass status: response.status, statusText: response.statusText (both are in scope at that site), so the wildcard claims become true.


- [#1974](https://github.com/modelcontextprotocol/typescript-sdk/pull/1974) [`db83829`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db83829c5bd5d6659c5e7b96638b11953b0e262d) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add custom (non-spec)
method support: a 3-arg `setRequestHandler(method, schemas, handler)` / `setNotificationHandler(method, schemas, handler)` form for vendor-prefixed methods, and a `request(req, resultSchema)` overload (also on `ctx.mcpReq.send`) for typed custom-method results. Spec-method
calls are unchanged.

Response result-schema validation failure now rejects with `SdkError(InvalidResult)` instead of a raw `ZodError`. Adds `SdkErrorCode.InvalidResult`.

- [#1653](https://github.com/modelcontextprotocol/typescript-sdk/pull/1653) [`6bec24a`](https://github.com/modelcontextprotocol/typescript-sdk/commit/6bec24a14cb7e3dbe9f5e04aeb893cd0d6e8cb83) Thanks [@rechedev9](https://github.com/rechedev9)! - Add `validateClientMetadataUrl()`
utility for early validation of `clientMetadataUrl`

Exports a `validateClientMetadataUrl()` function that `OAuthClientProvider` implementations can call in their constructors to fail fast on invalid URL-based client IDs, instead of discovering the error deep in the auth flow.
Comment on lines +3 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 PR #1875 (commit 9ed62fe) added a public claims?: Record<string, unknown> option to PrivateKeyJwtProviderOptions but landed without a changeset, so this user-facing feature is absent from the @modelcontextprotocol/client alpha.3 CHANGELOG even though it ships in this release. Either land a changeset on main and let the bot regenerate this PR, or manually add a Minor-change entry crediting #1875 under 2.0.0-alpha.3 before merging.

Extended reasoning...

PR #1875 custom-claims feature absent from alpha.3 CHANGELOG

What the bug is and how it manifests

PR #1875 (commit 9ed62fe, merged 2026-04-14 — after the most recent bot review at 10:29 UTC) added a new public claims?: Record<string, unknown> option to PrivateKeyJwtProviderOptions, letting callers inject custom JWT claims into the private_key_jwt client-assertion flow. PrivateKeyJwtProvider and PrivateKeyJwtProviderOptions are both re-exported from packages/client/src/index.ts (lines ~45–53), so this is a user-facing public-API addition. However, the commit touched only packages/client/src/client/authExtensions.ts and its test file — it created no .changeset/*.md file. As a result, the Changesets action generated no CHANGELOG entry for it.

The specific code path

The Version Packages commit 91dceb0 (this PR's HEAD) sits directly on top of 9ed62fe in git log, so the claims option WILL be published as part of @modelcontextprotocol/client@2.0.0-alpha.3. Yet .changeset/pre.json in this PR adds only four new slugs (fix-streamable-close-reentrant, fix-validate-client-metadata-url, odd-forks-enjoy, zod-json-schema-compat) — none corresponding to #1875 — and packages/client/CHANGELOG.md lines 3–19 list only #1653 and #1655 under the alpha.3 section.

Why existing checks don't catch it

There is no CI gate that diffs the set of merged commits against the set of changeset slugs. Changesets faithfully renders whatever .changeset/*.md files exist; commits that should have included one but did not pass through silently. This is the same root cause already flagged on this PR for #1842 (guard methods) and #1843 (CF Workers validator), but those comments do not cover #1875, which landed afterward.

Impact

Documentation-only: the feature ships and works, but alpha users reading the npm release notes for client@2.0.0-alpha.3 have no way to discover that PrivateKeyJwtProvider now accepts custom claims. Alpha users actively track release notes to find new APIs, so undocumented additions reduce the value of the alpha program.

Step-by-step proof

  1. git show 9ed62fe --stat → only packages/client/src/client/authExtensions.ts and packages/client/src/client/authExtensions.test.ts changed.
  2. git show 9ed62fe -- .changeset/ → empty; no changeset file added.
  3. git log --oneline -2 91dceb091dceb0 Version Packages (alpha) followed by 9ed62fe feat(client): support custom claims in PrivateKeyJwtProvider (#1875)9ed62fe is an ancestor of this release.
  4. grep -r "claims\|1875\|PrivateKeyJwt" .changeset/ → no matches.
  5. packages/client/CHANGELOG.md lines 3–19 (alpha.3 section): only fix: validate clientMetadataUrl at construction time (fail-fast) #1653 and fix(client): preserve custom Accept headers in StreamableHTTPClientTransport #1655 listed; no mention of claims / PrivateKeyJwtProvider / feat(client): support custom claims in PrivateKeyJwtProvider #1875.

How to fix

Preferred: land a minor changeset on main targeting @modelcontextprotocol/client and let the Changesets bot regenerate this PR. Alternatively, hand-edit packages/client/CHANGELOG.md to add under ### Minor Changes for 2.0.0-alpha.3:

- [#1875] Support custom claims in `PrivateKeyJwtProvider` via a new `claims?: Record<string, unknown>` option on `PrivateKeyJwtProviderOptions`.

Comment thread
claude[bot] marked this conversation as resolved.

- [#2248](https://github.com/modelcontextprotocol/typescript-sdk/pull/2248) [`db28156`](https://github.com/modelcontextprotocol/typescript-sdk/commit/db28156a23032290b3ce3bae00a17544c4807b8f) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Restore the 2025-11-25
task wire types that were removed together with the task feature: the task schemas and inferred types, task members of the request/result/notification unions, the `task` request-params augmentation, the `tasks` capability key, the `isTaskAugmentedRequestParams` guard, and
`RELATED_TASK_META_KEY`. The task feature itself remains removed — servers do not advertise the `tasks` capability and inbound `tasks/*` requests receive `-32601` — but the wire surface stays so SDKs interoperate cleanly with peers on the 2025-11-25 revision.

- [#1887](https://github.com/modelcontextprotocol/typescript-sdk/pull/1887) [`96db044`](https://github.com/modelcontextprotocol/typescript-sdk/commit/96db044fe965f0b7d5109e6d68598eaddce961c9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Export `isSpecType` and
`specTypeSchemas` records for runtime validation of any MCP spec type by name. `isSpecType.ContentBlock(value)` is a type predicate; `specTypeSchemas.ContentBlock` is a `StandardSchemaV1Sync<ContentBlock>` validator — `validate()` returns the result synchronously. Guards are
standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName`, `SpecTypes`, and `StandardSchemaV1Sync` types.

- [#1871](https://github.com/modelcontextprotocol/typescript-sdk/pull/1871) [`9fc9070`](https://github.com/modelcontextprotocol/typescript-sdk/commit/9fc9070b7b8e18227127aaee9869f8809a87fdb1) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Move stdio transports
to a `./stdio` subpath export. Import `StdioClientTransport`, `getDefaultEnvironment`, `DEFAULT_INHERITED_ENV_VARS`, and `StdioServerParameters` from `@modelcontextprotocol/client/stdio`, and `StdioServerTransport` from `@modelcontextprotocol/server/stdio`. The
`@modelcontextprotocol/client` root entry no longer pulls in `node:child_process`, `node:stream`, or `cross-spawn`, fixing bundling for browser and Cloudflare Workers targets; the `@modelcontextprotocol/server` root entry drops its `node:stream` reference. Node.js, Bun, and
Deno consumers update the import path; runtime behavior is unchanged.

### Patch Changes

- [#1897](https://github.com/modelcontextprotocol/typescript-sdk/pull/1897) [`434b2f1`](https://github.com/modelcontextprotocol/typescript-sdk/commit/434b2f11ecec452f3dca0199f68afccd8b119dd4) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Stop bundling
`@cfworker/json-schema` into the main package barrel. Previously `CfWorkerJsonSchemaValidator` was re-exported from the core internal barrel, so tsdown inlined the `@cfworker/json-schema` dependency into every consumer's bundle even when it was never used. The named validator
classes are now reachable only via the explicit `@modelcontextprotocol/{client,server}/validators/{ajv,cf-worker}` subpaths and the runtime `_shims` conditional, so consumers that import only from the root entry point no longer ship the validator dep.

- [#1834](https://github.com/modelcontextprotocol/typescript-sdk/pull/1834) [`42cb6b2`](https://github.com/modelcontextprotocol/typescript-sdk/commit/42cb6b2b728347d8b58a0d1940b7e63366a29ab9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Export
`InMemoryTransport` for in-process testing.
Comment on lines +45 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 PR #1834 (shipping in this release) updated docs/migration.md to define the v2 import path for InMemoryTransport (@modelcontextprotocol/{client,server}), but did not add the corresponding row to docs/migration-SKILL.md — that file has zero matches for InMemoryTransport/inMemory. CLAUDE.md §Breaking Changes and the REVIEW.md checklist both require breaking changes to be documented in both migration files, so the two are now out of sync. One-row fix: add @modelcontextprotocol/sdk/inMemory.js@modelcontextprotocol/client or @modelcontextprotocol/server to the "Types / shared imports" table.

Extended reasoning...

What the gap is

PR #1834 (commit 42cb6b2, published in this alpha.3 release per the CHANGELOG entries being added here) changed docs/migration.md from "InMemoryTransport — removed from public API" to "InMemoryTransport — moved; now exported from @modelcontextprotocol/client and @modelcontextprotocol/server". That established a clean, mechanical v1→v2 import mapping: @modelcontextprotocol/sdk/inMemory.js@modelcontextprotocol/{client,server}.

However, docs/migration-SKILL.md was not touched in that commit (git show 42cb6b2 --stat confirms only 4 files changed, none of them migration-SKILL.md), and grepping that file for InMemoryTransport or inMemory returns zero matches. The §3 "Import Mapping" tables — which already map e.g. @modelcontextprotocol/sdk/shared/transport.js → "@modelcontextprotocol/client or @modelcontextprotocol/server" — have no row for @modelcontextprotocol/sdk/inMemory.js.

Why this violates a stated repo convention

CLAUDE.md (lines ~27–30) explicitly requires: "When making breaking changes, document them in both: docs/migration.mddocs/migration-SKILL.md". REVIEW.md's Tests & docs checklist repeats: "Breaking changes documented in docs/migration.md and docs/migration-SKILL.md". The v1→v2 inMemory.js import-path change is exactly the kind of mechanical mapping the SKILL file's import tables are designed to encode, and #1834 updated one file but not the other.

Before #1834 there was arguably no clean SKILL row to write — migration.md said "removed from public API", so there was no v2 target to map to. #1834 is precisely the commit that created the canonical v2 target, so it's the commit that should have added the SKILL row. The omission therefore originates with #1834 rather than being strictly pre-existing.

Impact

An LLM-driven migration that follows docs/migration-SKILL.md verbatim (the file's stated purpose) would have no rule for import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js' and would leave it unresolved or guess. The human-readable docs/migration.md is correct, so users following that guide are unaffected — the impact is limited to automated/SKILL-driven migrations and to keeping the two docs in sync per the repo's own policy.

Step-by-step proof

  1. git show 42cb6b2 --stat → 4 files touched; docs/migration-SKILL.md is not among them.
  2. grep -n 'InMemoryTransport\|inMemory' docs/migration-SKILL.md → zero matches.
  3. docs/migration-SKILL.md §3 "Import Mapping" → "Types / shared imports" table (lines ~62–69) maps @modelcontextprotocol/sdk/shared/transport.js → "@modelcontextprotocol/client or @modelcontextprotocol/server" — the exact pattern the inMemory.js row would follow, but no such row exists.
  4. docs/migration.md (post-42cb6b2) → InMemoryTransport section says "moved — now exported from @modelcontextprotocol/client and @modelcontextprotocol/server".
  5. CLAUDE.md:27–30 → requires both files updated for breaking changes.
  6. Result: the two migration docs are out of sync for a change shipping in this release.

Relationship to existing PR comments

None of the existing timeline comments mention migration-SKILL.md — this is not a duplicate. #1834 landed on 2026-04-27, after the most recent review round (2026-04-20).

Fix

Add one row to the "Types / shared imports" table in docs/migration-SKILL.md §3:

v1 import v2 import
@modelcontextprotocol/sdk/inMemory.js @modelcontextprotocol/client or @modelcontextprotocol/server

This is a docs-only, one-line addition; it can land on main alongside this publish gate.


- [#1898](https://github.com/modelcontextprotocol/typescript-sdk/pull/1898) [`2a7611d`](https://github.com/modelcontextprotocol/typescript-sdk/commit/2a7611d46b0d4f4e7bd4147c7a3ad3da00e57e52) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Add top-level `types`
field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries
for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist.

- [#1655](https://github.com/modelcontextprotocol/typescript-sdk/pull/1655) [`1eb3123`](https://github.com/modelcontextprotocol/typescript-sdk/commit/1eb31236e707c4f4ab9234d87db21ab3f34bf0bc) Thanks [@nielskaspers](https://github.com/nielskaspers)! - fix(client): append custom
Accept headers to spec-required defaults in StreamableHTTPClientTransport

Comment thread
claude[bot] marked this conversation as resolved.
Custom Accept headers provided via `requestInit.headers` are now appended to the spec-mandated Accept types instead of being overwritten. This ensures the required media types (`application/json, text/event-stream` for POST; `text/event-stream` for GET SSE) are always present
while allowing users to include additional types for proxy/gateway routing.

Comment on lines +53 to +57
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 CHANGELOG entry says custom Accept headers are "appended to the spec-mandated Accept types", implying spec-required types come first, but the actual implementation does the reverse: user-provided types are placed first and spec-required types are appended last. The description should be corrected to accurately reflect the ordering, e.g., "spec-mandated Accept types are appended to any custom Accept headers".

Extended reasoning...

CHANGELOG Wording Inverts Actual Accept Header Ordering

What the bug is and how it manifests

The new CHANGELOG entry (line 10) states: "Custom Accept headers provided via requestInit.headers are now appended to the spec-mandated Accept types instead of being overwritten." In standard English, "X appended to Y" means Y is the base and X is added after — so this sentence implies spec-required types come first, with user custom types added afterward.

However, the actual implementation in streamableHttp.ts does the opposite. The GET SSE path uses: [...(userAccept?.split(',').map(s => s.trim().toLowerCase()) ?? []), 'text/event-stream'] and the POST path uses [...(userAccept?.split(',') ?? []), 'application/json', 'text/event-stream']. User-provided types come first, and spec-required types are appended last.

The specific code path and why it matters

Per RFC 7231, when multiple Accept types share equal q-values, earlier entries carry higher implicit preference. So the actual behavior gives user-provided types higher preference than spec-required types. The CHANGELOG implies the opposite preference ordering. While this ordering may be intentional (user routing hints for proxies need higher preference), the documentation still misrepresents which type comes first.

Addressing the refutation

The refutation argues that user-types-first is intentional for proxy/gateway routing, that CHANGELOG entries are not formal specs, and that this is a version-bump PR. These points are valid context, but they do not resolve the wording inaccuracy. The key claim in the CHANGELOG — that spec-required types are always present — is correct, but the described ordering is backwards. The fix is a one-line wording update, not a code change. Since this CHANGELOG entry appears in this PR's diff, this is the appropriate place to catch it.

Step-by-step proof of the discrepancy

  1. User sets requestInit.headers = { Accept: 'application/vnd.proxy-hint' }
  2. Code computes types = ['application/vnd.proxy-hint', 'text/event-stream']
  3. Final header: Accept: application/vnd.proxy-hint, text/event-stream
  4. application/vnd.proxy-hint has higher implicit preference (appears first)
  5. CHANGELOG says custom types are "appended to" spec types, which would produce text/event-stream, application/vnd.proxy-hint (spec first, custom second)
  6. The actual and described orderings are reversed.

Impact and fix

This is a documentation-only inaccuracy — the functional behavior is correct (spec-required types are always present). The fix is to update the CHANGELOG wording to something like: "spec-mandated Accept types are appended to any custom Accept headers provided via requestInit.headers", which accurately reflects user types appearing first with spec types appended after. The secondary q-value deduplication issue (e.g., text/event-stream; q=0.9 not deduped against text/event-stream) is a pre-existing edge case from PR #1655 and not introduced by this PR.

- [#2088](https://github.com/modelcontextprotocol/typescript-sdk/pull/2088) [`16d13ab`](https://github.com/modelcontextprotocol/typescript-sdk/commit/16d13abf78b5dba5de73dfa284325b13d4219bb2) Thanks [@mattzcarey](https://github.com/mattzcarey)! - Bundle automatic JSON Schema
validator defaults in `@modelcontextprotocol/client` and `@modelcontextprotocol/server` runtime shims.

Client and server pick the right validator automatically based on the runtime: the Node shim uses AJV, the browser/workerd shim uses `@cfworker/json-schema`. Both backends are bundled into the shim chunks that select them, so the default code path needs no extra installs —
`import { McpServer } from '@modelcontextprotocol/server'` does not pull `ajv` or `@cfworker/json-schema` into the root entry chunk.

The named validator classes remain part of the public surface for consumers who want to customize the built-in backend (pre-register schemas by `$id`, register custom AJV formats, switch dialects, change `@cfworker/json-schema` draft). They are exposed through explicit
subpaths so they do not bloat the root index chunk:
- `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/ajv'`
- `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/{client,server}/validators/cf-worker'`

Importing from one of these subpaths means the corresponding peer dep (`ajv` + `ajv-formats`, or `@cfworker/json-schema`) must be in your `package.json`. The shim keeps its own vendored copy for the default path, so a project can use the subpath in some files and rely on the
default in others.

The `jsonSchemaValidator` interface remains the public extension point for replacing validation entirely with a custom implementation.
Comment on lines +57 to +72
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 #2088 entry (here and in packages/server/CHANGELOG.md and packages/core/CHANGELOG.md, generated from .changeset/workerd-shim-vendors-cfworker.md) misdescribes the change in two ways: the "the corresponding peer dep ... must be in your package.json" sentence is false — neither package declares any peerDependencies and both tsdown configs bundle ajv/ajv-formats/@cfworker/json-schema into the /validators/* subpath chunks (the in-source JSDoc added by the same commit says "no extra installs") — and the entry is filed as a purely-additive Patch Change even though #2088 also changed the root export of AjvJsonSchemaValidator to type-only, breaking the previously documented import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server' pattern from alpha.2. Suggest amending the entry in both CHANGELOGs and the changeset on main: replace the peer-dep sentence with one matching the bundled-copy behavior, and add e.g. "Breaking (alpha): AjvJsonSchemaValidator is no longer a value export from the package root — import it from @modelcontextprotocol/{client,server}/validators/ajv" (mirroring the wording already requested for the CfWorker entry in earlier review comments).

Extended reasoning...

Two prose-vs-implementation mismatches in the #2088 entry

1. The peer-dep sentence describes a contract that does not exist. The entry says importing from @modelcontextprotocol/{client,server}/validators/{ajv,cf-worker} "means the corresponding peer dep (ajv + ajv-formats, or @cfworker/json-schema) must be in your package.json." But neither published package declares any peerDependencies block — ajv, ajv-formats, and @cfworker/json-schema appear only in devDependencies (packages/client/package.json:102-104, packages/server/package.json:95-97), so no package manager will ever surface a peer requirement. More importantly, the same commit (16d13ab) made the subpaths self-contained: both packages/{client,server}/tsdown.config.ts now set noExternal: ['@modelcontextprotocol/core', 'ajv', 'ajv-formats', '@cfworker/json-schema'] plus dts.resolve: ['ajv', 'ajv-formats'], with src/validators/ajv.ts and src/validators/cfWorker.ts as build entries — so dist/validators/ajv.mjs / dist/validators/cfWorker.mjs ship with the validator libraries inlined (runtime and types). A consumer importing the subpath needs no extra install.

2. The same commit's own JSDoc says the opposite. packages/server/src/validators/ajv.ts (and the client twin), added by 16d13ab, reads: "Re-exports Ajv + addFormats from the SDK's bundled copy, so customising the validator needs no extra installs." That is the correct description of the implementation and directly contradicts the CHANGELOG sentence. A user reading the alpha.3 release notes will believe they must add ajv/ajv-formats (or @cfworker/json-schema) before using the subpaths, when nothing extra is needed; conversely, the "peer dep" wording implies a peerDependencies declaration the published packages do not carry. (The same misleading sentence also landed in docs/migration.md ~line 971 in the same commit, so the fix should cover that too.)

3. The entry omits the breaking root-export removal. At the alpha.2 publish commit (0021561), packages/core/src/exports/public/index.ts:138 had export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js' — a value export — and both packages/client/src/index.ts and packages/server/src/index.ts wildcard-re-export the public barrel, so import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server' compiled and ran in alpha.2 (and was the customization pattern the pre-#2088 docs/migration.md showed). Commit 16d13ab changed that line to export type { AjvJsonSchemaValidator } (now public/index.ts:147, with a comment that the runtime classes must come from the /validators/{ajv,cf-worker} subpaths). So an alpha.2 user doing new AjvJsonSchemaValidator(...) from a root import gets a compile error ("only refers to a type") / undefined at runtime in alpha.3. Yet the changeset declares patch for client/server, the entry lands under "### Patch Changes", and its prose ("The named validator classes remain part of the public surface ... exposed through explicit subpaths") reads as purely additive — the word "breaking" never appears.

Step-by-step proof (export removal):

  1. alpha.2 user code: import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server'; const v = new AjvJsonSchemaValidator(); — compiles and runs against 2.0.0-alpha.2 (value export at public/index.ts:138, re-exported via export * from '@modelcontextprotocol/core/public').
  2. User upgrades to 2.0.0-alpha.3 (this PR) and reads the feat(client,server): bundle default validators, expose customisation via subpaths #2088 entry, which says the validator classes "remain part of the public surface" — nothing suggests their existing import broke.
  3. tsc now fails with "'AjvJsonSchemaValidator' only refers to a type, but is being used as a value here" (or the runtime import is undefined), because the root export is export type only.
  4. Following the entry's peer-dep sentence, the user might also npm install ajv ajv-formats — an unnecessary step, since the /validators/ajv chunk already bundles them.

Why nothing catches it: Changesets renders changeset prose verbatim; there is no check that CHANGELOG text matches the dependency metadata, the bundling config, or the export diff. This is the same class of issue the maintainers already accepted for the parallel CfWorkerJsonSchemaValidator removal (comments 3093006800/3161003783 asked for a "Breaking (alpha)" note on that entry); the Ajv removal affects more users since AJV is the Node default and the previously documented customization import. Comment 3326383258 mentions the Ajv type-only change only in passing, as a correction to the old alpha.1 #1689 example — it does not flag the #2088 entry's wording, which is the actionable item here.

How to fix (docs-only): In .changeset/workerd-shim-vendors-cfworkers.md (so the stable CHANGELOG regenerates correctly — actual file: workerd-shim-vendors-cfworker.md) and in the three generated #2088 paragraphs in packages/client/CHANGELOG.md, packages/server/CHANGELOG.md, and packages/core/CHANGELOG.md (plus the matching sentence in docs/migration.md): (a) replace the peer-dep sentence with something like "These subpaths re-export the SDK's bundled copy of the validator backend, so no additional dependencies are required"; (b) add "Breaking (alpha): AjvJsonSchemaValidator is no longer a value export from the package root — import it from @modelcontextprotocol/{client,server}/validators/ajv", mirroring the note already requested for the CfWorker entry. Filed as a nit: alpha-to-alpha breaks are expected and the entry already names the new subpath, but the release notes should not actively misdirect consumers at the publish gate.


- [#1976](https://github.com/modelcontextprotocol/typescript-sdk/pull/1976) [`55b1f06`](https://github.com/modelcontextprotocol/typescript-sdk/commit/55b1f06cd4569e334f3435b7971f0446f1ef9be9) Thanks [@felixweinberger](https://github.com/felixweinberger)! - refactor: subclasses
override `_wrapHandler` hook instead of redeclaring `setRequestHandler`.

- [#1895](https://github.com/modelcontextprotocol/typescript-sdk/pull/1895) [`b256546`](https://github.com/modelcontextprotocol/typescript-sdk/commit/b256546750277faeb7c886792aae5ed26e6904d5) Thanks [@felixweinberger](https://github.com/felixweinberger)! - Fix runtime crash on
`tools/list` when a tool's `inputSchema` comes from zod 4.0–4.1. The SDK requires `~standard.jsonSchema` (StandardJSONSchemaV1, added in zod 4.2.0); previously a missing `jsonSchema` crashed at `undefined[io]`. `standardSchemaToJsonSchema` now detects zod 4 schemas lacking
`jsonSchema` and falls back to the SDK-bundled `z.toJSONSchema()`, emitting a one-time console warning. zod 3 schemas (which the bundled zod 4 converter cannot introspect) and non-zod schema libraries without `jsonSchema` get a clear error pointing to `fromJsonSchema()`. The
workspace zod catalog is also bumped to `^4.2.0`.

## 2.0.0-alpha.2

Comment on lines +3 to 83
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 PR #1842 (commit 1eb80c4, 'v2: add guard methods') introduced new public API type guards — at minimum isCallToolResult and isJSONRPCResponse — to @modelcontextprotocol/client and @modelcontextprotocol/server without a changeset file, so these exports are absent from all alpha.3 CHANGELOGs. Before merging, manually add CHANGELOG entries for the new guard functions in packages/client/CHANGELOG.md and packages/server/CHANGELOG.md under 2.0.0-alpha.3.

Extended reasoning...

Guard methods from PR #1842 absent from alpha.3 CHANGELOGs

What the bug is and how it manifests

PR #1842 (commit 1eb80c4, 'v2: add guard methods') added new type guard functions to packages/core/src/types/guards.ts and exported them via packages/core/src/exports/public/index.ts. Because both packages/client/src/index.ts and packages/server/src/index.ts do export * from '@modelcontextprotocol/core/public', these guards became part of the published public API of both @modelcontextprotocol/client@2.0.0-alpha.3 and @modelcontextprotocol/server@2.0.0-alpha.3. However, PR #1842 did not create a changeset file, so the Changesets action generated no CHANGELOG entries for these additions.

The specific code path

The guards flow: packages/core/src/types/guards.ts → re-exported from packages/core/src/exports/public/index.ts (lines 107–115) → consumed via export * from '@modelcontextprotocol/core/public' in both packages/client/src/index.ts and packages/server/src/index.ts. Verifiers confirmed that at minimum isCallToolResult and isJSONRPCResponse are newly introduced in this commit; most other JSONRPC guards in the list may have pre-existed. No .changeset/ file appears in the commit, confirmed by git show 1eb80c4 -- .changeset/ returning empty.

Why existing code does not prevent it

There is no CI check that validates CHANGELOG completeness against the set of merged commits. The Changesets action only generates entries from changeset files it finds; it cannot detect commits that should have had a changeset but did not. This is the same root cause as the CF Workers CHANGELOG omission (PR #1843).

Impact

Users upgrading from alpha.2 to alpha.3 who read the release notes have no way to discover these new type guard exports. Alpha users actively track release notes precisely to find new APIs, so undocumented public additions reduce the practical value of the alpha program. The omission is a documentation gap with no functional impact.

Step-by-step proof

  1. git show 1eb80c4 -- .changeset/ returns nothing — PR v2: add guard methods #1842 created no changeset file.
  2. Open packages/client/CHANGELOG.md lines 3–14: the only alpha.3 entry is for PR fix(client): preserve custom Accept headers in StreamableHTTPClientTransport #1655 (Accept headers). No mention of any guard function.
  3. Open packages/server/CHANGELOG.md lines 3–11: the only alpha.3 entry is for PR fix: prevent stack overflow in transport close with re-entrancy guard #1788 (stack overflow fix). Same omission.
  4. Open packages/core/src/exports/public/index.ts lines 107–115: isCallToolResult, isJSONRPCResponse, and related guards are exported.
  5. A user reading the alpha.3 release notes finds zero indication that type guards were added to either published package.

How to fix

Manually add a patch-change entry to both packages/client/CHANGELOG.md and packages/server/CHANGELOG.md under 2.0.0-alpha.3, crediting PR #1842. The CHANGELOG files are plain text and can be amended before this PR is merged, as was done for other missed changeset entries.

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/client",
"version": "2.0.0-alpha.2",
"version": "2.0.0-alpha.3",
Comment thread
claude[bot] marked this conversation as resolved.
Comment thread
claude[bot] marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 PR #1875 (commit 9ed62fe, shipping in this alpha.3 release) added a claims option to PrivateKeyJwtProvider whose JSDoc at packages/client/src/client/authExtensions.ts:239-240 promises "custom claims taking precedence for any overlapping keys" — but the implementation at lines 82–89 chains .setIssuer/.setSubject/.setAudience/.setIssuedAt/.setExpirationTime/.setJti after constructing SignJWT(claims), and each jose setter unconditionally overwrites the corresponding payload field. So a user passing e.g. claims: { aud: 'https://custom-audience' } per the docs silently gets aud reset to the SDK-computed value. Either drop the redundant setter chain so the merged claims object is authoritative, or fix the JSDoc to say standard claims cannot be overridden.

Extended reasoning...

What the bug is

PR #1875 (commit 9ed62fe — direct parent of this version-bump commit, so it ships in @modelcontextprotocol/client@2.0.0-alpha.3) added a claims option to PrivateKeyJwtProviderOptions. The JSDoc at packages/client/src/client/authExtensions.ts:238-240 reads:

Optional custom claims to include in the JWT assertion. These are merged with the standard claims (iss, sub, aud, exp, iat, jti), with custom claims taking precedence for any overlapping keys.

The implementation does spread { ...baseClaims, ...options.claims } at line 60, so the merged claims object initially has the user's values winning. But lines 82–89 then build the JWT with:

new jose.SignJWT(claims)
    .setProtectedHeader({ alg, typ: 'JWT' })
    .setIssuer(options.issuer)
    .setSubject(options.subject)
    .setAudience(audience)
    .setIssuedAt(now)
    .setExpirationTime(now + lifetimeSeconds)
    .setJti(jti)
    .sign(...)

Each of those setters writes back into the payload after the constructor copies claims, so the SDK-computed values overwrite whatever the user passed for all six listed standard claims. The actual behavior is the exact opposite of the JSDoc promise.

Why existing code doesn't prevent it

Verified against jose@6.2.2 source (node_modules/.pnpm/jose@6.2.2/.../jwt/sign.js and lib/jwt_claims_set.js): the SignJWT constructor passes the payload to JWTClaimsBuilder which does this.#payload = structuredClone(payload), and each setter (set iss, set sub, set aud, set jti, set exp, set iat) does an unconditional this.#payload.<claim> = value assignment — no "only if absent" guard. So .setIssuer(options.issuer) always replaces claims.iss, etc.

The tests added in 9ed62fe only exercise non-standard claims (tenant_id, role), which do flow through correctly because no setter touches them. There is no test that passes a standard claim via options.claims and asserts it appears in the signed JWT, so the contradiction is not caught.

Step-by-step proof

  1. User constructs the provider with claims: { aud: 'https://custom-token-endpoint', jti: 'fixed-id' }, relying on the JSDoc precedence guarantee.
  2. Line 60 produces claims = { iss, sub, aud: 'https://custom-token-endpoint', exp, iat, jti: 'fixed-id' } — user values win in the merged object.
  3. Line 82 passes that object to new jose.SignJWT(claims); jose clones it into #payload.
  4. Line 86 .setAudience(audience) runs this.#payload.aud = audience (the SDK-computed metadata?.issuer ?? url), overwriting 'https://custom-token-endpoint'.
  5. Line 89 .setJti(jti) runs this.#payload.jti = jti (the SDK-generated random string), overwriting 'fixed-id'.
  6. The signed assertion contains the SDK values for aud and jti; the user's values are silently discarded with no warning. Same applies to iss, sub, iat, exp.

Impact

This is a docs-vs-implementation contradiction in a public API surface that ships to npm in this release. A user who follows the JSDoc to override aud (a real use case — some authorization servers require a token-endpoint-specific audience that differs from the issuer URL) will silently get the wrong audience in the assertion and a 401 from the AS, with nothing pointing them at the cause. Per REVIEW.md → Documentation & Changesets, prose that promises behavior the code doesn't ship misleads consumers and should be flagged at the publish gate.

The spread at line 60 is effectively dead code for the six standard claims — it only matters for additional, non-standard claims.

How to fix

Two options, pick one:

  • Honor the JSDoc (recommended, since the spread at line 60 already shows this was the intent): drop the redundant .setIssuer/.setSubject/.setAudience/.setIssuedAt/.setExpirationTime/.setJti chain at lines 84–89, leaving just new jose.SignJWT(claims).setProtectedHeader({ alg, typ: 'JWT' }).sign(key). The merged claims object already contains all six standard claims with the correct precedence.
  • Honor the implementation: change the JSDoc at lines 239–240 to state that the standard claims (iss, sub, aud, exp, iat, jti) cannot be overridden via claims and that only additional claims are merged. In that case the baseClaims spread at line 60 can also be dropped since it's a no-op for those keys.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟣 🟣 Pre-existing — and a correction to the five prior runtime-dependency comments (3035194589 / 3073682357 / 3073682371 / 3073783213 / 3078795515): I built dist and verified that ajv, ajv-formats, and @cfworker/json-schema are fully inlined into the .mjs bundles — the only external import in dist/src-*.mjs is zod/v4, and shimsNode/Browser/Workerd.mjs all import only from the local chunk, so there is no runtime ERR_MODULE_NOT_FOUND and the JSDoc "Bundled" claim is correct. The actual residual issue is type-only: packages/{client,server}/dist/index-*.d.mts lines 2–3 contain import { Ajv } from 'ajv' and import { JSONSchema } from 'json-schema-typed' (referenced by AjvJsonSchemaValidator(ajv?: Ajv) and type JsonSchemaType), and neither package is in dependencies/peerDependencies, so consumers with skipLibCheck: false get TS2307 — a config #1766 explicitly intends to support. Fix by adding ajv + json-schema-typed to both packages' dependencies (zero runtime cost; JS already bundled), or stop exposing the raw Ajv type / inline JSONSchema.Interface.

Extended reasoning...

Correction to prior comments + actual residual issue

Prior comments are factually wrong about runtime. Five earlier comments on this PR (3035194589, 3073682357, 3073682371, 3073783213, 3078795515) assert that dist/shimsNode.mjs / shimsWorkerd.mjs / shimsBrowser.mjs contain bare external import statements for ajv, ajv-formats, and @cfworker/json-schema, causing ERR_MODULE_NOT_FOUND at module load. I verified by running pnpm --filter @modelcontextprotocol/{client,server} build and inspecting the output, and that claim does not hold:

  • packages/client/dist/src-5RQ50XKB.mjs (387 KB shared chunk) has exactly one external import on line 1: import * as z from "zod/v4". ajv (~122 inline string occurrences), ajv-formats, and @cfworker/json-schema (the CfWorkerJsonSchemaValidator class definition lives at ~line 10343) are all inlined. The two require("ajv/...") strings inside the chunk are part of ajv's own codegen template literals, not real import/require statements.
  • shimsNode.mjs, shimsBrowser.mjs, shimsWorkerd.mjs, and validators/cfWorker.mjs all import only from ./src-5RQ50XKB.mjsgrep -E 'from "ajv|from "@cfworker|from "ajv-formats' dist/**/*.mjs returns zero matches.
  • Same for packages/server/dist/src-*.mjs.

Why the earlier reasoning was wrong: those comments assumed tsdown/rolldown externalises every bare import not listed in noExternal. It doesn't — it auto-externalises only packages listed in the building package's own dependencies/peerDependencies. Since ajv/ajv-formats/@cfworker/json-schema are absent from client's and server's package.json (they live only in core's deps, and core is a devDependency), tsdown bundles them regardless of noExternal. So the JSDoc "Bundled — no additional dependencies required" at packages/core/src/index.ts:31,35 is correct for runtime, and the recommended "extend noExternal" fix in 3078795515 is a no-op for the .mjs output.

The actual residual issue (type-only)

What the dist inspection did surface is that the bundled type declarations still reference external packages. packages/client/dist/index-KFyi7eTI.d.mts and packages/server/dist/index-BIf1K4vK.d.mts both contain on lines 2–3:

import { Ajv } from 'ajv';
import { JSONSchema } from 'json-schema-typed';

These are used at type JsonSchemaType = JSONSchema.Interface (~line 9799) and declare class AjvJsonSchemaValidator { constructor(ajv?: Ajv); } (~line 9883). The dts bundler (tsdown's dts.resolver: 'tsc' with paths mapping only @modelcontextprotocol/core) inlines core's declarations but leaves third-party type references external. None of the five prior comments mention json-schema-typed at all, and none distinguish .mjs from .d.mts behaviour.

Neither ajv nor json-schema-typed is in dependencies or peerDependencies of packages/client/package.json or packages/server/package.json. A consumer with "skipLibCheck": false therefore gets TS2307: Cannot find module 'ajv' / 'json-schema-typed' on any import from @modelcontextprotocol/client or /server — not just when using the validators. Per the alpha.1 CHANGELOG entry for #1766, the SDK explicitly intends to support skipLibCheck: false, so this is a supported-config break.

Step-by-step proof

  1. npm install @modelcontextprotocol/client@2.0.0-alpha.3 in a fresh project → node_modules contains zod, cross-spawn, eventsource, etc., but not ajv or json-schema-typed.
  2. node -e "import('@modelcontextprotocol/client').then(m => console.log(typeof m.AjvJsonSchemaValidator))" → prints function. Runtime works — the prior comments' ERR_MODULE_NOT_FOUND does not reproduce.
  3. tsconfig.json with "skipLibCheck": false; source file: import { McpClient } from '@modelcontextprotocol/client';.
  4. tsc --noEmitnode_modules/@modelcontextprotocol/client/dist/index-KFyi7eTI.d.mts(2,21): error TS2307: Cannot find module 'ajv' or its corresponding type declarations. and the same for json-schema-typed on line 3.
  5. Type-check fails for every consumer file that imports anything from the package.

Impact and fix

Runtime is fine; this is a compile-time-only break for the skipLibCheck: false population (which #1766 explicitly targets). Pre-existing since alpha.1 (#1689 introduced AjvJsonSchemaValidator and JsonSchemaType); this PR doesn't change it but is the publish gate for alpha.3.

Two fix options:

  • Add ajv and json-schema-typed to dependencies of both packages/client/package.json and packages/server/package.json. This satisfies TypeScript's module resolution for the .d.mts imports at zero additional runtime cost (the JS is already bundled; the packages are pulled in only so tsc can find their .d.ts).
  • Stop leaking the third-party types: change constructor(ajv?: Ajv) to constructor(ajv?: unknown) (or a locally-declared structural type) and inline/re-declare JSONSchema.Interface instead of re-exporting it. This keeps the dependency list minimal but is a small public-API surface change.

Filed primarily so the maintainers don't act on the incorrect runtime-crash framing / noExternal recommendation above before publish — extending noExternal would not address the .d.mts emission, and re-adding peer deps is unnecessary for runtime.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Partially addressed by #2088 (16d13ab, now shipping in this alpha.3): the ajv half of this issue is fixed — ajv + ajv-formats were added to dependencies of both packages (packages/client/package.json:103-104, packages/server/package.json:96-97), and the tsdown configs now set dts.resolve: ['ajv', 'ajv-formats'], so ajv's declarations are inlined into the dist chunks and no from \"ajv\" import remains in any built .d.mts (the AjvJsonSchemaValidator constructor was also retyped to a structural AjvLike, so the raw Ajv type is no longer leaked).

The json-schema-typed half remains. After a fresh build at HEAD:

  • packages/client/dist/types-BWG5c6KB*.d.mts:1 and packages/server/dist/types-*.d.mts:1 still contain import { JSONSchema } from \"json-schema-typed\" (sourced from packages/core/src/validators/types.ts:2, used by type JsonSchemaType = JSONSchema.Interface).
  • That chunk is imported by dist/index.d.mts line 3 of both packages, so it is loaded on any import from the root entry.
  • json-schema-typed is still not in dependencies or peerDependencies of either published package (it lives only in private core's deps), so a consumer with skipLibCheck: false still gets TS2307: Cannot find module 'json-schema-typed' — the same supported-config break described above, now only for this one specifier.

Either of the original fixes still applies and is now a one-liner given the #2088 precedent: add json-schema-typed to dts.resolve in packages/{client,server}/tsdown.config.ts (inlines the type, mirrors what was done for ajv), or add \"json-schema-typed\": \"catalog:runtimeShared\" to both packages' dependencies (zero runtime cost; type-only).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟣 Pre-existing: the alpha.1 #1710 entry in packages/client/CHANGELOG.md advertises eight new exports — adaptOAuthProvider, handleOAuthUnauthorized, isOAuthClientProvider, UnauthorizedContext, applyBasicAuth, applyPostAuth, applyPublicAuth, executeTokenRequest — but none of them are re-exported from packages/client/src/index.ts (named re-exports only, lines 9–39) nor from @modelcontextprotocol/core/public, so import { adaptOAuthProvider } from '@modelcontextprotocol/client' fails with "has no exported member" in every published alpha including the alpha.3 being cut here. Either add the eight names to the from './client/auth.js' re-export block in index.ts (they're already exported from auth.ts, so it's a barrel-only addition), or strike the "New … export" / "Exported …" bullets from .changeset/token-provider-composable-auth.md and the alpha.1 CHANGELOG section.

Extended reasoning...

What the bug is

The #1710 entry in packages/client/CHANGELOG.md under ## 2.0.0-alpha.1 (the token-provider-composable-auth changeset) makes five explicit export claims:

  • New adaptOAuthProvider(provider) export for explicit adaptation.
  • New handleOAuthUnauthorized(provider, ctx) helper …
  • New isOAuthClientProvider() type guard.
  • New UnauthorizedContext type.
  • Exported previously-internal auth helpers for building custom flows: applyBasicAuth, applyPostAuth, applyPublicAuth, executeTokenRequest.

All eight of those symbols are defined with export in packages/client/src/client/auth.ts (lines 42, 92, 103, 122, 483, 495, 505, 1461), so the changeset's intent is clear. But none of them ever reach the package root: packages/client/src/index.ts re-exports from './client/auth.js' by explicit name only (lines 9–39 list AuthProvider, auth, discoverOAuthServerInfo, OAuthClientProvider, etc.), and none of the eight names appear in those lists. The only wildcard re-export at index.ts:81 is export * from '@modelcontextprotocol/core/public', and grepping packages/core/src/exports/public/ for any of these names returns zero matches. git log --all -S 'adaptOAuthProvider' -- packages/client/src/index.ts is empty, confirming they were never added at any point. The package.json exports map has no deep path to auth.js, so the root barrel is the only public entry point.

Why nothing prevents it

The Changesets action renders changeset prose verbatim; there is no check that "New … export" claims in a CHANGELOG correspond to actual entries in the package barrel. auth.ts does export all eight symbols at the module level, so within the workspace (tests, sibling packages importing via deep paths) they resolve fine — only the published-package surface is missing them. Per REVIEW.md → Documentation & Changesets: "prose that promises behavior the code no longer ships misleads consumers… Flag any claim the diff doesn't back."

Step-by-step proof

  1. packages/client/CHANGELOG.md alpha.1 section, feat: introduce minimal AuthProvider interface with OAuthClientProvider adapter #1710 entry → bullets explicitly say "New adaptOAuthProvider(provider) export", "New handleOAuthUnauthorized(provider, ctx) helper", "New isOAuthClientProvider() type guard", "New UnauthorizedContext type", and "Exported previously-internal auth helpers … applyBasicAuth, applyPostAuth, applyPublicAuth, executeTokenRequest".
  2. rg -n 'adaptOAuthProvider|handleOAuthUnauthorized|isOAuthClientProvider|UnauthorizedContext|applyBasicAuth|applyPostAuth|applyPublicAuth|executeTokenRequest' packages/client/src/index.tszero matches.
  3. packages/client/src/index.ts:9-39 → named re-export blocks from './client/auth.js' list ~28 other symbols (auth, AuthProvider, OAuthClientProvider, discoverOAuthServerInfo, validateClientMetadataUrl, …) but none of the eight.
  4. rg -n 'adaptOAuthProvider|executeTokenRequest|applyBasicAuth' packages/core/src/exports/public/zero matches, so the wildcard at index.ts:81 doesn't carry them either.
  5. Consumer writes import { adaptOAuthProvider } from '@modelcontextprotocol/client' per the CHANGELOG → TypeScript fails with "Module '"@modelcontextprotocol/client"' has no exported member 'adaptOAuthProvider'". Same for the other seven.

Impact

A user following the published alpha.1 release notes to use any of these eight helpers — e.g. adaptOAuthProvider() for explicit OAuthClientProvider → AuthProvider adaptation, or applyBasicAuth/executeTokenRequest for a custom token flow — gets a compile error with no signal that the symbols live only in an unexported internal module. This has been live on npm since alpha.1 and carries forward unchanged into the alpha.3 being cut here (the file is one of the files this PR modifies, though only the alpha.3 section is in the diff hunk).

Relationship to this PR / existing comments

Filed as pre-existing: the false claims live in the ## 2.0.0-alpha.1 section, which is not in this PR's diff hunk (only the alpha.3 section is added above it), and packages/client/src/index.ts is not touched by this PR at all. Surfaced here because packages/client/CHANGELOG.md is one of the files this PR modifies and this is the alpha.3 publish gate — matching the established pattern on this PR of flagging carried-forward CHANGELOG-vs-implementation contradictions. None of the 25+ prior comments on this PR mention #1710 or any of these eight symbols (grep for adaptOAuthProvider|handleOAuthUnauthorized|applyBasicAuth|executeTokenRequest|1710 across the timeline → 0 matches), so this is not a duplicate.

How to fix

Two options, pick one:

  • Honor the changeset (recommended — matches the stated feat: introduce minimal AuthProvider interface with OAuthClientProvider adapter #1710 intent): add the eight names to the from './client/auth.js' re-export blocks in packages/client/src/index.ts. They are already exported from auth.ts, so this is a barrel-only addition with no implementation change:
    export {
        adaptOAuthProvider,
        handleOAuthUnauthorized,
        isOAuthClientProvider,
        applyBasicAuth,
        applyPostAuth,
        applyPublicAuth,
        executeTokenRequest,
    } from './client/auth.js';
    export type { UnauthorizedContext } from './client/auth.js';
  • Honor the implementation: strike the "New adaptOAuthProvider(provider) export" / "New handleOAuthUnauthorized helper" / "New isOAuthClientProvider()" / "New UnauthorizedContext" / "Exported previously-internal auth helpers …" bullets from .changeset/token-provider-composable-auth.md and from the alpha.1 section of packages/client/CHANGELOG.md.

"description": "Model Context Protocol implementation for TypeScript - Client package",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
Expand Down
8 changes: 8 additions & 0 deletions packages/codemod/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# @modelcontextprotocol/codemod

## 2.0.0-alpha.1

### Minor Changes

- [#2206](https://github.com/modelcontextprotocol/typescript-sdk/pull/2206) [`e03bca9`](https://github.com/modelcontextprotocol/typescript-sdk/commit/e03bca90c1f925f80843dc27fb4eb2421408a0c1) Thanks [@KKonstantinov](https://github.com/KKonstantinov)! - Codemod now resolves SSE
server and OAuth auth imports to @modelcontextprotocol/server-legacy sub-paths instead of removing them. An info diagnostic suggests eventual migration to v2 equivalents.
2 changes: 1 addition & 1 deletion packages/codemod/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/codemod",
"version": "2.0.0-alpha.0",
"version": "2.0.0-alpha.1",
"description": "Codemod to migrate MCP TypeScript SDK code from v1 to v2",
Comment on lines +3 to 4
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Merging this PR triggers the first npm publish of @modelcontextprotocol/codemod@2.0.0-alpha.1, but packages/codemod/ has no README.md and the mcp-codemod CLI isn't documented anywhere in the root README or docs/, so the package will publish with a blank npm page and no install/usage instructions (npm will also emit a "no README data" warning). Every other published package in the workspace ships a README that becomes its npm page — consider adding a short packages/codemod/README.md (npx usage, what it migrates, caveats) before this publish gate merges, or holding the codemod out of the release until docs exist. This is separate from the earlier comments about the codemod's stale StreamableHTTPErrorSdkError mapping, which concern migration content rather than missing package documentation.

Extended reasoning...

Codemod publishes without any package documentation

What the gap is. This Version Packages PR bumps @modelcontextprotocol/codemod to 2.0.0-alpha.1 (with a generated packages/codemod/CHANGELOG.md from the #2206 changeset) and is the publish gate that triggers the package's first npm publish. However, packages/codemod/ contains no README.md — the directory holds only CHANGELOG.md, src/, test/, scripts/, batch-test/, and build configs. The only README anywhere in the package is batch-test/README.md, which is internal test-harness documentation and is not part of the publish anyway (files: ["dist"]). npm renders the package page from a README* file in the package directory; it does not fall back to the monorepo root README, so the published page for @modelcontextprotocol/codemod will show "no README data".

Why nothing else covers it. The mcp-codemod CLI (bin: { "mcp-codemod": "./dist/cli.mjs" }) is not documented anywhere else either: grepping the root README.md and docs/ (including docs/migration.md and docs/migration-SKILL.md) for codemod / mcp-codemod returns zero matches outside packages/codemod itself and the changeset file. There is also no build or release step that generates or copies a README — the package scripts and .github/workflows/release.yml have no such step. So the first published version of a user-facing CLI tool ships with zero prose documentation, neither on its npm page nor in the repo docs.

Why this PR is the right place to flag it. The package is not private, is not in .changeset/config.json's ignore list, and #2206 added the changeset that bumps it here — so merging this PR is what publishes it. Every other published package in the workspace (client, server, server-legacy, express, fastify, hono, node) ships a README.md that becomes its npm package page, so the codemod is the only published package without one. The repo's review checklist also asks that new features come with prose documentation.

Concrete walk-through.

  1. This PR merges → release.yml runs pnpm changeset publish.
  2. @modelcontextprotocol/codemod@2.0.0-alpha.1 is non-private and not on the registry → it publishes; npm emits npm warn publish no README data because packages/codemod/ has no README and files: ["dist"] includes nothing else readable.
  3. A user finds @modelcontextprotocol/codemod on npm (or sees it referenced in the release) → the package page is blank: no description of what the codemod migrates, no npx @modelcontextprotocol/codemod / mcp-codemod invocation example, no caveats about the known stale mappings.
  4. Searching docs/ or the root README for "codemod" yields nothing, so there is no fallback documentation path.

Distinct from prior comments. The earlier publish-gate comments on this PR (the .changeset/pre.json thread) cover (a) the formerly-silent first publish — now resolved by the #2206 changeset — and (b) the codemod's stale StreamableHTTPError → SdkError migration mapping. Neither mentions the missing package README / user-facing usage docs, which is the gap here.

How to fix. Add a short packages/codemod/README.md covering: how to run it (npx @modelcontextprotocol/codemod / the mcp-codemod bin), what it migrates (v1 → v2 import paths, removed APIs, the new server-legacy resolution from #2206), and known caveats — and ensure it ships (npm includes README.md automatically even with files: ["dist"]). Alternatively, hold the codemod out of this release until docs exist. Filed as a nit: it's a docs/packaging completeness gap on an alpha pre-release with no functional impact, but the publish gate is the last point at which it can be addressed before the blank npm page goes live.

"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
Expand Down
Loading