Skip to content

docs: correct migration-guide errors found in a real-world v1-to-v2 migration#2246

Open
felixweinberger wants to merge 1 commit into
mainfrom
fweinberger/migration-doc-fixes
Open

docs: correct migration-guide errors found in a real-world v1-to-v2 migration#2246
felixweinberger wants to merge 1 commit into
mainfrom
fweinberger/migration-doc-fixes

Conversation

@felixweinberger
Copy link
Copy Markdown
Contributor

Corrects errors and fills gaps in docs/migration.md and docs/migration-SKILL.md. Every item below produced incorrect code when the guides were followed literally during a large production v1→v2 migration (an MCP host application using the client, server, custom transports, and OAuth surfaces).

Motivation and Context

Each fix maps to a concrete backwards-compatibility error a migrator hits:

  1. OAuth "Before (v1)" examples import removed classes from the v2 package name (@modelcontextprotocol/client) — v1 code never imported from that package; copying the example produces imports that cannot have existed. Fixed to the real v1 path (@modelcontextprotocol/sdk/server/auth/errors.js).
  2. OAuthError code property is .code, and v1's unknown-code collapse is gone. v1 mapped unrecognized OAuth error codes onto ServerError, so instanceof ServerError retry checks also matched nonstandard codes (e.g. invalid_refresh_token). A mechanical rewrite to error.code === OAuthErrorCode.ServerError silently stops retrying those — a behavior regression with no compile error. Documented with a known-codes-set pattern that preserves v1 semantics.
  3. Custom-method handler signature change is now called out. v1 custom-schema handlers received the full request/notification envelope; v2 3-arg handlers receive parsed params. Migrated handler bodies reading request.params.x and test fakes capturing argument 2 as the handler break at runtime (handler is not a function) — observed exactly this way in real test suites.
  4. Schema→method-string tables were incomplete (migration.md listed 13 of 31 mappings; the SKILL doc 18). Migrators had to guess strings like 'notifications/elicitation/complete' (not 'elicitation/complete') and 'tasks/result' (not 'tasks/get-result') — wrong guesses register handlers that never fire or throw. Both tables now carry all 31 mappings, matching the codemod's authoritative map.
  5. Context restructure applies to client-side handlers too (ClientContext = BaseContext); all existing examples were server-side, so client elicitation/sampling/roots handlers (and their test mocks) were migrated with the wrong ctx shape. Added a client-side example.
  6. JSONRPCErrorResponse.id is now optional (v1 required it) — id-less error responses reach onmessage instead of being rejected at the transport. Undocumented behavior change; code assuming id is always present should handle absence.
  7. Stdio transports now skip non-JSON lines silently where v1 surfaced them via onerror — consumers using onerror to detect noisy servers lose that signal. Undocumented until now.
  8. Tool argument-validation failures are returned in-band (isError: true, Input validation error: ...) instead of v1's protocol-level InvalidParams — client code catching protocol errors, and tests asserting a thrown error, behave differently with no compile-time hint.
  9. Added a rule for choosing @modelcontextprotocol/client vs /server for shared types — the single most frequent point of confusion in package-split migrations; both packages re-export identical declarations, so the guide now says to pick by file role and never mix.

How Has This Been Tested?

Every claim was verified against the source in this repo before documenting (OAuthError in packages/core/src/auth/errors.ts, BaseContext/handler overloads in packages/core/src/shared/protocol.ts, JSONRPCErrorResponseSchema in packages/core/src/types/schemas.ts, ReadBuffer in packages/core/src/shared/stdio.ts, in-band tool errors in packages/server/src/server/mcp.ts, method strings against packages/core/src/types/ and the codemod's schemaToMethodMap). The failure modes themselves were each observed during a real v1→v2 migration of a large MCP application.

Breaking Changes

None — documentation only.

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

Additional context

Both guides were updated together per the repo convention that docs/migration.md and docs/migration-SKILL.md stay in sync. No changeset included: the change is docs-only and releases no package. The new table rows follow the codemod's schemaToMethodMap.ts, so docs, codemod, and spec types now agree on all 31 method strings.

@felixweinberger felixweinberger requested a review from a team as a code owner June 2, 2026 09:26
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 2, 2026

⚠️ No Changeset found

Latest commit: 6309881

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
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 2, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2246

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2246

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2246

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server-legacy@2246

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2246

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2246

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2246

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2246

commit: 6309881

Comment thread docs/migration.md
Comment thread docs/migration-SKILL.md Outdated
…-to-v2 migration

Every item here caused incorrect code when the guides were followed
literally while migrating a large production MCP application:

- OAuth: v1 example imported removed classes from the v2 package name;
  document the .code property and the unknown-error-code retry semantics
  (v1 collapsed unknown codes into ServerError, v2 preserves them)
- Context: note the restructure also applies to client-side handlers
  (ClientContext = BaseContext) with a client-side example
- Custom methods: call out the v1->v2 handler signature change (parsed
  params instead of the request envelope) that breaks handler bodies and
  test fakes
- Complete the schema-to-method-string tables (14/19 of 31 entries were
  missing, including tasks/get, tasks/result, and
  notifications/elicitation/complete)
- Document that JSONRPCErrorResponse.id is now optional
- Document that stdio transports now skip non-JSON lines instead of
  surfacing them via onerror
- Document in-band tool argument-validation failures (isError result
  instead of a thrown InvalidParams protocol error)
- Add a rule for choosing between client/server packages for shared types
@felixweinberger felixweinberger force-pushed the fweinberger/migration-doc-fixes branch from 6d76ce6 to 6309881 Compare June 2, 2026 18:06
Comment thread docs/migration.md
Comment on lines +480 to +481
Task-related schemas (`ListTasksRequestSchema`, `GetTaskRequestSchema`, `GetTaskPayloadRequestSchema`, `CancelTaskRequestSchema`, `TaskStatusNotificationSchema`) have no v2 handler-registration equivalent: the experimental task feature was removed and servers answer inbound
`tasks/*` with `-32601` (see the experimental-tasks section).
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 sentence claiming ListTasksRequestSchema/GetTaskRequestSchema/GetTaskPayloadRequestSchema/CancelTaskRequestSchema/TaskStatusNotificationSchema "have no v2 handler-registration equivalent" is contradicted by the SDK: after #2248 restored the task wire types, 'tasks/get', 'tasks/result', 'tasks/list', 'tasks/cancel', and 'notifications/tasks/status' are valid spec methods that setRequestHandler/setNotificationHandler accept and resolve schemas for — the -32601 answer only applies when no handler is registered. Either add the five mappings to the table (with a caveat that the high-level task feature/taskStore plumbing is removed and servers don't advertise the tasks capability) or reword the prose; the same sentence appears in docs/migration-SKILL.md (~lines 434-435).

Extended reasoning...

What the prose claims vs. what the SDK does. The new paragraph after the schema→method-string table states that the five task-related schemas "have no v2 handler-registration equivalent: the experimental task feature was removed and servers answer inbound tasks/* with -32601". But PR #2248 deliberately restored the task wire surface for 2025-11-25 interop, and that wire surface includes the request/notification union memberships. GetTaskRequestSchema, GetTaskPayloadRequestSchema, ListTasksRequestSchema, and CancelTaskRequestSchema are members of both ClientRequestSchema and ServerRequestSchema (packages/core/src/types/schemas.ts:2093-2096, 2124-2127), and TaskStatusNotificationSchema is in both notification unions (schemas.ts:2104, 2138).\n\nThe code path that makes the claim false. Because RequestMethod is derived from those unions (packages/core/src/types/types.ts:379) and ResultTypeMap explicitly maps 'tasks/get'GetTaskResult, 'tasks/result'Result, 'tasks/list'ListTasksResult, 'tasks/cancel'CancelTaskResult (types.ts:400-403), the typed 2-arg spec-method overload server.setRequestHandler('tasks/get', handler) compiles. At runtime the request-schema map is built from the union options (schemas.ts:2206+), so getRequestSchema('tasks/get') resolves GetTaskRequestSchema and Protocol.setRequestHandler registers the handler without hitting the "not a spec request method" error path. Server.assertRequestHandlerCapability (packages/server/src/server/server.ts:306) has no tasks/* case and no throwing default, and a grep of packages/server/src shows no other special-casing of tasks/ — nothing rejects the registration, and a registered handler will fire on inbound requests. The same holds for setNotificationHandler('notifications/tasks/status', ...) since TaskStatusNotificationSchema is in the notification unions.\n\nStep-by-step proof. (1) A v1 low-level Server user has server.setRequestHandler(GetTaskRequestSchema, handler). (2) Following the new table+prose, they conclude there is no method string to migrate to and delete the handler. (3) In reality, server.setRequestHandler('tasks/get', handler) in v2 type-checks (the method is in RequestMethod/ResultTypeMap), registers (the schema map built from ClientRequestSchema.options/ServerRequestSchema.options contains tasks/get), passes capability assertion (no tasks/* case in assertRequestHandlerCapability), and the handler is invoked when a 2025-11-25 peer sends tasks/get — the -32601 response is only the default when no handler is registered. So a direct equivalent exists for all five schemas: GetTaskRequestSchema'tasks/get', GetTaskPayloadRequestSchema'tasks/result', ListTasksRequestSchema'tasks/list', CancelTaskRequestSchema'tasks/cancel', TaskStatusNotificationSchema'notifications/tasks/status'.\n\nWhy this matters. This PR's whole purpose is making the migration guides literally followable, and this paragraph tells low-level Server/Client users that v1 task handlers cannot be ported when they can be — leading them to delete handlers that interop with 2025-11-25 peers depends on. It also sits in tension with the PR's own description, which cites 'tasks/result' as one of the method strings the real-world migration needed. The likely source of the confusion is the codemod's schemaToMethodMap, which omits the task schemas — but a codemod omission only means no automatic rewrite, not that the SDK has no equivalent. The surrounding statements (the high-level task feature and taskStore plumbing are gone, servers don't advertise the tasks capability, unhandled tasks/* gets -32601) all remain true; only the absolute "no handler-registration equivalent" claim is wrong.\n\nHow to fix. Either (a) add the five mappings to the table (GetTaskRequestSchema'tasks/get', GetTaskPayloadRequestSchema'tasks/result', ListTasksRequestSchema'tasks/list', CancelTaskRequestSchema'tasks/cancel', TaskStatusNotificationSchema'notifications/tasks/status') with a caveat that the McpServer-level task feature/taskStore plumbing is removed and servers built on this SDK don't advertise the tasks capability, or (b) reword the paragraph to say the high-level task feature was removed but the method strings remain registrable on the low-level Server/Client (and the codemod won't rewrite them automatically). Apply the same fix to the duplicated sentence in docs/migration-SKILL.md (~lines 434-435), per the repo convention that the two guides stay in sync.

Comment thread docs/migration.md
Comment on lines +435 to +436
Note this is a signature change from v1, where custom-schema handlers received the full request/notification envelope. Handler bodies must switch from `request.params.x` to `params.x`, and test doubles that capture registration arguments must account for the handler now being the
_third_ argument (the second is the schemas object).
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 PR adds the same "signature change from v1" guidance twice within the Custom (non-spec) methods subsection of docs/migration.md: once before the Before/After example (~line 397) and again here after the setNotificationHandler note (lines 435-436), with only cosmetic wording differences. Keep one of the two paragraphs (probably the first) and drop the other — the companion docs/migration-SKILL.md states this note only once.

Extended reasoning...

What the issue is. This PR inserts two near-verbatim copies of the same paragraph into the Custom (non-spec) methods subsection of docs/migration.md. The first, added right after the vendor-prefixed-methods intro (~line 397), reads: "This is a signature change from v1, where custom-schema handlers received the full request/notification envelope: handler bodies switch from request.params.x to params.x, and test doubles that capture registration arguments must account for the handler now being the third argument (the second is the schemas object)." The second, added ~40 lines later after the setNotificationHandler paragraph (lines 435-436), reads: "Note this is a signature change from v1, where custom-schema handlers received the full request/notification envelope. Handler bodies must switch from request.params.x to params.x, and test doubles that capture registration arguments must account for the handler now being the third argument (the second is the schemas object)."

Both additions come from this PR. The diff hunks @@ -380,6 +394,9 @@ and @@ -415,6 +432,9 @@ each add one of the paragraphs, and both land inside the same subsection — so this is duplication introduced by the PR, not pre-existing text being echoed.

Step-by-step proof that the two paragraphs are redundant:

  1. Paragraph 1 (line ~397): states (a) v1 custom-schema handlers received the full envelope, (b) handler bodies change request.params.xparams.x, (c) the handler is now the third registration argument and the second is the schemas object.
  2. Paragraph 2 (lines 435-436): states (a) v1 custom-schema handlers received the full envelope, (b) handler bodies change request.params.xparams.x, (c) the handler is now the third registration argument and the second is the schemas object.
  3. The only differences are the lead-in ("This is" vs. "Note this is") and "switch" vs. "must switch" — no new information is conveyed by the second occurrence.
  4. The companion docs/migration-SKILL.md (kept in sync with this guide per repo convention, and updated in the same PR) carries this guidance exactly once, as the single "Signature trap:" paragraph in section 9 — confirming a single statement was the intent and the second copy is an authoring slip rather than deliberate reinforcement.

Why nothing catches it. Documentation prose isn't linted for repetition, and each hunk reads fine in isolation; only reading the whole subsection makes the duplication visible.

Impact. Purely editorial — no code or behavior is affected. The cost is reader friction: someone working through the Custom (non-spec) methods section reads the identical caveat twice within ~40 lines, which makes the guide feel unedited and slightly dilutes the (genuinely useful) warning.

How to fix. Drop one of the two paragraphs. The first placement (before the Before/After example) is probably the better one to keep, since the reader hits the caveat before seeing the code comparison it explains; the second copy after the setNotificationHandler note can simply be deleted.

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