From 853c9f749c2c04e9b8391919905935dee411300b Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Tue, 8 Sep 2026 18:01:18 +0100 Subject: [PATCH] Migration guide fixes and editor-visible deprecation for the 1.x handler form Guide: name @modelcontextprotocol/express in the server-author row, say the two SDKs share no types rather than "do not interoperate" (they speak the same wire protocol), note zod 4.0/4.1 lack ~standard.jsonSchema, list the full 1.x-shaped extra, cover tool callbacks in the handler-context bullet, document that the double-registration guard now covers the base Protocol's notifications/progress and notifications/cancelled handlers, mention ResourceNotFoundError, add the Express row to the import map, and add a "What stays the same" section. The @deprecated tag on the legacy setRequestHandler / setNotificationHandler overload sat on the parameter inside an intersection type, which TypeScript does not report. The overload now comes from LegacyRequestHandlerSetter / LegacyNotificationHandlerSetter, named types with a documented call signature, so editors flag the 1.x call and not the 2.x forms. README links to the quickstart use the lowercase slug typedoc emits. --- README.md | 6 ++--- docs/migrate-to-2.md | 55 ++++++++++++++++++++++++++++++------------ src/app-bridge.ts | 23 ++++++------------ src/app.ts | 25 ++++++++----------- src/legacy-handlers.ts | 27 +++++++++++++++++++++ 5 files changed, 87 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index acfc6617b..ab4004d66 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Why · - Quickstart + Quickstart · API Docs · @@ -151,7 +151,7 @@ in a 1.x host and a 2.x host renders 1.x Views. See the when upgrading from 1.x. **New here?** Start with the -[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html) +[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html) to build your first MCP App. ## Using the SDK @@ -272,7 +272,7 @@ point your client at a local build: ## Resources -- [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html) +- [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html) - [API Documentation](https://apps.extensions.modelcontextprotocol.io/api/) - [Specification (2026-01-26)](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx) ([Draft](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)) diff --git a/docs/migrate-to-2.md b/docs/migrate-to-2.md index 515c36384..f9341d069 100644 --- a/docs/migrate-to-2.md +++ b/docs/migrate-to-2.md @@ -17,14 +17,22 @@ The `ui/*` messages exchanged over the iframe channel are byte-identical to 1.x. A 2.x View runs in a 1.x host and a 2.x host renders 1.x Views. The only host-side deltas are in error responses (see below). +## What stays the same + +`App` / `AppBridge` constructors, the `on*` setters, `addEventListener`, +`callServerTool`, `readServerResource`, `registerAppResource`, the React hooks +and the `_meta.ui.resourceUri` handling are unchanged. Published `.d.ts` files +now resolve under `moduleResolution: NodeNext` / `Node16` without workarounds +(#705). + ## Peer dependencies by role -| Role | Install | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| View author | `@modelcontextprotocol/ext-apps`, `@modelcontextprotocol/client@^2.0.0`, `zod@^4.2.0` (+ `react`/`react-dom` for `./react`) | -| Host author | same as View author | -| MCP server author | View author packages + `@modelcontextprotocol/server@^2.0.0`; `@modelcontextprotocol/node` and `express` for HTTP transports | -| CDN / `*-with-deps` | nothing extra: `./app-with-deps` and `./react-with-deps` bundle client, core and zod (about 25% larger than the 1.x bundles) | +| Role | Install | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| View author | `@modelcontextprotocol/ext-apps`, `@modelcontextprotocol/client@^2.0.0`, `zod@^4.2.0` (+ `react`/`react-dom` for `./react`) | +| Host author | same as View author | +| MCP server author | View author packages + `@modelcontextprotocol/server@^2.0.0`; for HTTP transports also `@modelcontextprotocol/node@^2.0.0` (`NodeStreamableHTTPServerTransport`) and `@modelcontextprotocol/express@^2.0.0` (`createMcpExpressApp`, which peers on `express`) | +| CDN / `*-with-deps` | nothing extra: `./app-with-deps` and `./react-with-deps` bundle client, core and zod (about 25% larger than the 1.x bundles) | `@modelcontextprotocol/client` is a required peer (`App` and `AppBridge` extend its `Protocol` class); `@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it without you listing it; @@ -35,18 +43,23 @@ its `Protocol` class); `@modelcontextprotocol/core` is a required peer that `cli - **Peer packages.** `@modelcontextprotocol/sdk@^1` is replaced by the split 2.x packages above, all at `^2.0.0`. Remove the 1.x package from your - project; the two SDKs do not interoperate. + project: the two SDKs share no classes or types (a 1.x `McpError` or + `Client` cannot be passed to 2.x code), even though they speak the same + wire protocol. - **zod 3 dropped.** The peer range is `zod@^4.2.0`. Tool schemas must - implement Standard JSON Schema (`~standard.jsonSchema`): zod v4, ArkType, - Valibot. Raw zod shapes (`{ q: z.string() }`) still work with + implement Standard JSON Schema (`~standard.jsonSchema`): zod 4.2+, ArkType, + Valibot. zod 4.0 and 4.1 do not expose `~standard.jsonSchema` and are + rejected too. Raw zod shapes (`{ q: z.string() }`) still work with `registerAppTool` but are deprecated; wrap them with `z.object({...})`. - **`App` / `AppBridge` extend `Protocol` from `@modelcontextprotocol/client`.** `ProtocolWithEvents` is gone; use the SDK's `Protocol`. The `AppRequest`, `AppNotification` and `AppResult` unions are kept as deprecated type aliases (nothing in 2.x consumes them) and will be removed in 3.0. -- **Handler context.** Custom handlers receive the SDK 2.x context: - `extra.signal` → `extra.mcpReq.signal`, `extra.requestId` → - `extra.mcpReq.id`. +- **Handler context.** Custom request handlers, `registerAppTool` callbacks + (server) and `app.registerTool` callbacks (View) receive the SDK 2.x + context: `extra.signal` → `extra.mcpReq.signal`, `extra.requestId` → + `extra.mcpReq.id`; `extra.sessionId` is unchanged and `extra.authInfo` + moved to `extra.http?.authInfo`. - **`setRequestHandler` / `setNotificationHandler` take method names.** `app.setRequestHandler(SomeRequestSchema, handler)` becomes `app.setRequestHandler("some/method", { params: SomeParamsSchema }, (params, ctx) => …)` @@ -55,14 +68,23 @@ its `Protocol` class); `@modelcontextprotocol/core` is a required peer that `cli method names. The 1.x `(Schema, handler)` form still works on `App` and `AppBridge` as a deprecated overload: it logs a one-time warning, hands the handler the whole `{ method, params }` message as before, and gives request - handlers a 1.x-shaped `extra` (`signal`, `requestId`, `sessionId`, `_meta`). - It will be removed in 3.0. + handlers a 1.x-shaped `extra` (`signal`, `requestId`, `sessionId`, `_meta`, + `sendRequest`, `sendNotification`, `authInfo`; exported as + `LegacyRequestHandlerExtra`). It will be removed in 3.0. +- **Double-registration guard covers the SDK's own handlers.** Registering a + handler for `notifications/progress` or `notifications/cancelled` (which + the base `Protocol` already owns) now throws `already registered`, as + `ping` and the `on*`-owned methods did in 1.x. Use `addEventListener` or + the SDK's progress callbacks instead of replacing these. - **Errors.** Remote JSON-RPC errors are `ProtocolError` (numeric `code`); local conditions are `SdkError` with a string `code`: request timeout → `"REQUEST_TIMEOUT"`, connection closed → `"CONNECTION_CLOSED"`. Cancelling a request with an `AbortSignal` also rejects with `"REQUEST_TIMEOUT"` (the message is the abort reason). Messages no longer carry the `MCP error N:` prefix. + A resource-not-found reply that carries `data.uri` arrives as + `ResourceNotFoundError` (a `ProtocolError` subclass, `code` `-32602`, with a + `.uri` getter). ## Host-side wire deltas @@ -98,8 +120,9 @@ The published `./schema.json` export is regenerated from the 2.x core schemas: 2. Replace `sdk/...` imports with the split packages (`sdk/server/mcp.js` → `@modelcontextprotocol/server`, `sdk/server/streamableHttp.js` → `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`, - `sdk/server/stdio.js` → `@modelcontextprotocol/server/stdio`, `sdk/types.js` - → `@modelcontextprotocol/client` or `server` for the types, + `sdk/server/stdio.js` → `@modelcontextprotocol/server/stdio`, hand-written + Express routing → `createMcpExpressApp` from `@modelcontextprotocol/express`, + `sdk/types.js` → `@modelcontextprotocol/client` or `server` for the types, `@modelcontextprotocol/core` for the zod schemas). 3. Wrap raw zod shapes with `z.object({...})`. 4. Update custom handlers to the `extra.mcpReq.*` context and method-keyed diff --git a/src/app-bridge.ts b/src/app-bridge.ts index c61dfa4df..1ac4db485 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -37,9 +37,8 @@ import { import { EventDispatcher, MethodRegistry } from "./events.js"; import { toModernArgs, - type LegacyMethodSchema, - type LegacyNotificationHandler, - type LegacyRequestHandler, + type LegacyNotificationHandlerSetter, + type LegacyRequestHandlerSetter, } from "./legacy-handlers.js"; import type { ZodLiteral, ZodObject, ZodType } from "zod/v4"; @@ -330,12 +329,9 @@ export class AppBridge extends Protocol { * * @throws {Error} if a handler for this method is already registered. */ - override setRequestHandler: Protocol["setRequestHandler"] & - (( - /** @deprecated Pass the method name and `{ params }` instead. */ - schema: S, - handler: LegacyRequestHandler, - ) => void) = (...args: unknown[]) => { + override setRequestHandler: LegacyRequestHandlerSetter< + Protocol["setRequestHandler"] + > = (...args: unknown[]) => { const [method, ...rest] = toModernArgs("request", args) ?? args; this._methods.claim(method as string, "setRequestHandler"); (super.setRequestHandler as unknown as UntypedHandlerSetter).call( @@ -352,12 +348,9 @@ export class AppBridge extends Protocol { * * @throws {Error} if a handler for this method is already registered. */ - override setNotificationHandler: Protocol["setNotificationHandler"] & - (( - /** @deprecated Pass the method name and `{ params }` instead. */ - schema: S, - handler: LegacyNotificationHandler, - ) => void) = (...args: unknown[]) => { + override setNotificationHandler: LegacyNotificationHandlerSetter< + Protocol["setNotificationHandler"] + > = (...args: unknown[]) => { const [method, ...rest] = toModernArgs("notification", args) ?? args; this._methods.claim(method as string, "setNotificationHandler"); (super.setNotificationHandler as unknown as UntypedHandlerSetter).call( diff --git a/src/app.ts b/src/app.ts index 0ab51accd..89c6c35e7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -26,15 +26,16 @@ export { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "./constants.js"; import { EventDispatcher, MethodRegistry } from "./events.js"; import { toModernArgs, - type LegacyMethodSchema, - type LegacyNotificationHandler, - type LegacyRequestHandler, + type LegacyNotificationHandlerSetter, + type LegacyRequestHandlerSetter, } from "./legacy-handlers.js"; export type { LegacyMethodSchema, LegacyNotificationHandler, + LegacyNotificationHandlerSetter, LegacyRequestHandler, LegacyRequestHandlerExtra, + LegacyRequestHandlerSetter, } from "./legacy-handlers.js"; export { EventDispatcher } from "./events.js"; @@ -328,12 +329,9 @@ export class App extends Protocol { * * @throws {Error} if a handler for this method is already registered. */ - override setRequestHandler: Protocol["setRequestHandler"] & - (( - /** @deprecated Pass the method name and `{ params }` instead. */ - schema: S, - handler: LegacyRequestHandler, - ) => void) = (...args: unknown[]) => { + override setRequestHandler: LegacyRequestHandlerSetter< + Protocol["setRequestHandler"] + > = (...args: unknown[]) => { const [method, ...rest] = toModernArgs("request", args) ?? args; this._methods.claim(method as string, "setRequestHandler"); (super.setRequestHandler as unknown as UntypedHandlerSetter).call( @@ -350,12 +348,9 @@ export class App extends Protocol { * * @throws {Error} if a handler for this method is already registered. */ - override setNotificationHandler: Protocol["setNotificationHandler"] & - (( - /** @deprecated Pass the method name and `{ params }` instead. */ - schema: S, - handler: LegacyNotificationHandler, - ) => void) = (...args: unknown[]) => { + override setNotificationHandler: LegacyNotificationHandlerSetter< + Protocol["setNotificationHandler"] + > = (...args: unknown[]) => { const [method, ...rest] = toModernArgs("notification", args) ?? args; this._methods.claim(method as string, "setNotificationHandler"); (super.setNotificationHandler as unknown as UntypedHandlerSetter).call( diff --git a/src/legacy-handlers.ts b/src/legacy-handlers.ts index b4d115754..237b8eba9 100644 --- a/src/legacy-handlers.ts +++ b/src/legacy-handlers.ts @@ -36,6 +36,33 @@ export type LegacyNotificationHandler = ( notification: S["_output"], ) => void | Promise; +/** + * `setRequestHandler` with the 1.x `(Schema, handler)` form kept as a + * deprecated overload next to the 2.x forms in `Modern`. + */ +export type LegacyRequestHandlerSetter = Modern & { + /** + * @deprecated 1.x form. Pass the method name and `{ params }` instead; + * removed in 3.0. + */ + ( + schema: S, + handler: LegacyRequestHandler, + ): void; +}; + +/** `setNotificationHandler` counterpart of {@link LegacyRequestHandlerSetter}. */ +export type LegacyNotificationHandlerSetter = Modern & { + /** + * @deprecated 1.x form. Pass the method name and `{ params }` instead; + * removed in 3.0. + */ + ( + schema: S, + handler: LegacyNotificationHandler, + ): void; +}; + /** * Arguments for the 2.x three-argument `setRequestHandler` / * `setNotificationHandler` form.