Migration guide fixes and editor-visible deprecation for the 1.x handler form (#771)
Guide fact-checked claim by claim against the built package: three wording fixes, seven coverage gaps (including the double-registration guard now covering the base Protocol's progress/cancelled handlers), and the legacy setRequestHandler overload's @deprecated moved onto a documented call signature so editors report it.
This commit is contained in:
commit
ae0fe55b84
5 changed files with 87 additions and 49 deletions
|
|
@ -11,7 +11,7 @@
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<a href="#why-mcp-apps">Why</a>
|
<a href="#why-mcp-apps">Why</a>
|
||||||
·
|
·
|
||||||
<a href="https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html">Quickstart</a>
|
<a href="https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html">Quickstart</a>
|
||||||
·
|
·
|
||||||
<a href="https://apps.extensions.modelcontextprotocol.io/api/">API Docs</a>
|
<a href="https://apps.extensions.modelcontextprotocol.io/api/">API Docs</a>
|
||||||
·
|
·
|
||||||
|
|
@ -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.
|
when upgrading from 1.x.
|
||||||
|
|
||||||
**New here?** Start with the
|
**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.
|
to build your first MCP App.
|
||||||
|
|
||||||
## Using the SDK
|
## Using the SDK
|
||||||
|
|
@ -272,7 +272,7 @@ point your client at a local build:
|
||||||
|
|
||||||
## Resources
|
## 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/)
|
- [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)
|
- [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))
|
([Draft](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx))
|
||||||
|
|
|
||||||
|
|
@ -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
|
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).
|
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
|
## Peer dependencies by role
|
||||||
|
|
||||||
| Role | Install |
|
| Role | Install |
|
||||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| View author | `@modelcontextprotocol/ext-apps`, `@modelcontextprotocol/client@^2.0.0`, `zod@^4.2.0` (+ `react`/`react-dom` for `./react`) |
|
| 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 |
|
| Host author | same as View author |
|
||||||
| MCP server author | View author packages + `@modelcontextprotocol/server@^2.0.0`; `@modelcontextprotocol/node` and `express` for HTTP transports |
|
| 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) |
|
| 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
|
`@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;
|
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
|
- **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
|
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
|
- **zod 3 dropped.** The peer range is `zod@^4.2.0`. Tool schemas must
|
||||||
implement Standard JSON Schema (`~standard.jsonSchema`): zod v4, ArkType,
|
implement Standard JSON Schema (`~standard.jsonSchema`): zod 4.2+, ArkType,
|
||||||
Valibot. Raw zod shapes (`{ q: z.string() }`) still work with
|
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({...})`.
|
`registerAppTool` but are deprecated; wrap them with `z.object({...})`.
|
||||||
- **`App` / `AppBridge` extend `Protocol` from `@modelcontextprotocol/client`.**
|
- **`App` / `AppBridge` extend `Protocol` from `@modelcontextprotocol/client`.**
|
||||||
`ProtocolWithEvents` is gone; use the SDK's `Protocol`. The `AppRequest`,
|
`ProtocolWithEvents` is gone; use the SDK's `Protocol`. The `AppRequest`,
|
||||||
`AppNotification` and `AppResult` unions are kept as deprecated type
|
`AppNotification` and `AppResult` unions are kept as deprecated type
|
||||||
aliases (nothing in 2.x consumes them) and will be removed in 3.0.
|
aliases (nothing in 2.x consumes them) and will be removed in 3.0.
|
||||||
- **Handler context.** Custom handlers receive the SDK 2.x context:
|
- **Handler context.** Custom request handlers, `registerAppTool` callbacks
|
||||||
`extra.signal` → `extra.mcpReq.signal`, `extra.requestId` →
|
(server) and `app.registerTool` callbacks (View) receive the SDK 2.x
|
||||||
`extra.mcpReq.id`.
|
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.**
|
- **`setRequestHandler` / `setNotificationHandler` take method names.**
|
||||||
`app.setRequestHandler(SomeRequestSchema, handler)` becomes
|
`app.setRequestHandler(SomeRequestSchema, handler)` becomes
|
||||||
`app.setRequestHandler("some/method", { params: SomeParamsSchema }, (params, ctx) => …)`
|
`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
|
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
|
`AppBridge` as a deprecated overload: it logs a one-time warning, hands the
|
||||||
handler the whole `{ method, params }` message as before, and gives request
|
handler the whole `{ method, params }` message as before, and gives request
|
||||||
handlers a 1.x-shaped `extra` (`signal`, `requestId`, `sessionId`, `_meta`).
|
handlers a 1.x-shaped `extra` (`signal`, `requestId`, `sessionId`, `_meta`,
|
||||||
It will be removed in 3.0.
|
`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`);
|
- **Errors.** Remote JSON-RPC errors are `ProtocolError` (numeric `code`);
|
||||||
local conditions are `SdkError` with a string `code`: request timeout →
|
local conditions are `SdkError` with a string `code`: request timeout →
|
||||||
`"REQUEST_TIMEOUT"`, connection closed → `"CONNECTION_CLOSED"`. Cancelling a
|
`"REQUEST_TIMEOUT"`, connection closed → `"CONNECTION_CLOSED"`. Cancelling a
|
||||||
request with an `AbortSignal` also rejects with `"REQUEST_TIMEOUT"` (the
|
request with an `AbortSignal` also rejects with `"REQUEST_TIMEOUT"` (the
|
||||||
message is the abort reason). Messages no longer carry the `MCP error N:`
|
message is the abort reason). Messages no longer carry the `MCP error N:`
|
||||||
prefix.
|
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
|
## 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` →
|
2. Replace `sdk/...` imports with the split packages (`sdk/server/mcp.js` →
|
||||||
`@modelcontextprotocol/server`, `sdk/server/streamableHttp.js` →
|
`@modelcontextprotocol/server`, `sdk/server/streamableHttp.js` →
|
||||||
`NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`,
|
`NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node`,
|
||||||
`sdk/server/stdio.js` → `@modelcontextprotocol/server/stdio`, `sdk/types.js`
|
`sdk/server/stdio.js` → `@modelcontextprotocol/server/stdio`, hand-written
|
||||||
→ `@modelcontextprotocol/client` or `server` for the types,
|
Express routing → `createMcpExpressApp` from `@modelcontextprotocol/express`,
|
||||||
|
`sdk/types.js` → `@modelcontextprotocol/client` or `server` for the types,
|
||||||
`@modelcontextprotocol/core` for the zod schemas).
|
`@modelcontextprotocol/core` for the zod schemas).
|
||||||
3. Wrap raw zod shapes with `z.object({...})`.
|
3. Wrap raw zod shapes with `z.object({...})`.
|
||||||
4. Update custom handlers to the `extra.mcpReq.*` context and method-keyed
|
4. Update custom handlers to the `extra.mcpReq.*` context and method-keyed
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,8 @@ import {
|
||||||
import { EventDispatcher, MethodRegistry } from "./events.js";
|
import { EventDispatcher, MethodRegistry } from "./events.js";
|
||||||
import {
|
import {
|
||||||
toModernArgs,
|
toModernArgs,
|
||||||
type LegacyMethodSchema,
|
type LegacyNotificationHandlerSetter,
|
||||||
type LegacyNotificationHandler,
|
type LegacyRequestHandlerSetter,
|
||||||
type LegacyRequestHandler,
|
|
||||||
} from "./legacy-handlers.js";
|
} from "./legacy-handlers.js";
|
||||||
import type { ZodLiteral, ZodObject, ZodType } from "zod/v4";
|
import type { ZodLiteral, ZodObject, ZodType } from "zod/v4";
|
||||||
|
|
||||||
|
|
@ -330,12 +329,9 @@ export class AppBridge extends Protocol<BaseContext> {
|
||||||
*
|
*
|
||||||
* @throws {Error} if a handler for this method is already registered.
|
* @throws {Error} if a handler for this method is already registered.
|
||||||
*/
|
*/
|
||||||
override setRequestHandler: Protocol<BaseContext>["setRequestHandler"] &
|
override setRequestHandler: LegacyRequestHandlerSetter<
|
||||||
(<S extends LegacyMethodSchema>(
|
Protocol<BaseContext>["setRequestHandler"]
|
||||||
/** @deprecated Pass the method name and `{ params }` instead. */
|
> = (...args: unknown[]) => {
|
||||||
schema: S,
|
|
||||||
handler: LegacyRequestHandler<S>,
|
|
||||||
) => void) = (...args: unknown[]) => {
|
|
||||||
const [method, ...rest] = toModernArgs("request", args) ?? args;
|
const [method, ...rest] = toModernArgs("request", args) ?? args;
|
||||||
this._methods.claim(method as string, "setRequestHandler");
|
this._methods.claim(method as string, "setRequestHandler");
|
||||||
(super.setRequestHandler as unknown as UntypedHandlerSetter).call(
|
(super.setRequestHandler as unknown as UntypedHandlerSetter).call(
|
||||||
|
|
@ -352,12 +348,9 @@ export class AppBridge extends Protocol<BaseContext> {
|
||||||
*
|
*
|
||||||
* @throws {Error} if a handler for this method is already registered.
|
* @throws {Error} if a handler for this method is already registered.
|
||||||
*/
|
*/
|
||||||
override setNotificationHandler: Protocol<BaseContext>["setNotificationHandler"] &
|
override setNotificationHandler: LegacyNotificationHandlerSetter<
|
||||||
(<S extends LegacyMethodSchema>(
|
Protocol<BaseContext>["setNotificationHandler"]
|
||||||
/** @deprecated Pass the method name and `{ params }` instead. */
|
> = (...args: unknown[]) => {
|
||||||
schema: S,
|
|
||||||
handler: LegacyNotificationHandler<S>,
|
|
||||||
) => void) = (...args: unknown[]) => {
|
|
||||||
const [method, ...rest] = toModernArgs("notification", args) ?? args;
|
const [method, ...rest] = toModernArgs("notification", args) ?? args;
|
||||||
this._methods.claim(method as string, "setNotificationHandler");
|
this._methods.claim(method as string, "setNotificationHandler");
|
||||||
(super.setNotificationHandler as unknown as UntypedHandlerSetter).call(
|
(super.setNotificationHandler as unknown as UntypedHandlerSetter).call(
|
||||||
|
|
|
||||||
25
src/app.ts
25
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 { EventDispatcher, MethodRegistry } from "./events.js";
|
||||||
import {
|
import {
|
||||||
toModernArgs,
|
toModernArgs,
|
||||||
type LegacyMethodSchema,
|
type LegacyNotificationHandlerSetter,
|
||||||
type LegacyNotificationHandler,
|
type LegacyRequestHandlerSetter,
|
||||||
type LegacyRequestHandler,
|
|
||||||
} from "./legacy-handlers.js";
|
} from "./legacy-handlers.js";
|
||||||
export type {
|
export type {
|
||||||
LegacyMethodSchema,
|
LegacyMethodSchema,
|
||||||
LegacyNotificationHandler,
|
LegacyNotificationHandler,
|
||||||
|
LegacyNotificationHandlerSetter,
|
||||||
LegacyRequestHandler,
|
LegacyRequestHandler,
|
||||||
LegacyRequestHandlerExtra,
|
LegacyRequestHandlerExtra,
|
||||||
|
LegacyRequestHandlerSetter,
|
||||||
} from "./legacy-handlers.js";
|
} from "./legacy-handlers.js";
|
||||||
export { EventDispatcher } from "./events.js";
|
export { EventDispatcher } from "./events.js";
|
||||||
|
|
||||||
|
|
@ -328,12 +329,9 @@ export class App extends Protocol<BaseContext> {
|
||||||
*
|
*
|
||||||
* @throws {Error} if a handler for this method is already registered.
|
* @throws {Error} if a handler for this method is already registered.
|
||||||
*/
|
*/
|
||||||
override setRequestHandler: Protocol<BaseContext>["setRequestHandler"] &
|
override setRequestHandler: LegacyRequestHandlerSetter<
|
||||||
(<S extends LegacyMethodSchema>(
|
Protocol<BaseContext>["setRequestHandler"]
|
||||||
/** @deprecated Pass the method name and `{ params }` instead. */
|
> = (...args: unknown[]) => {
|
||||||
schema: S,
|
|
||||||
handler: LegacyRequestHandler<S>,
|
|
||||||
) => void) = (...args: unknown[]) => {
|
|
||||||
const [method, ...rest] = toModernArgs("request", args) ?? args;
|
const [method, ...rest] = toModernArgs("request", args) ?? args;
|
||||||
this._methods.claim(method as string, "setRequestHandler");
|
this._methods.claim(method as string, "setRequestHandler");
|
||||||
(super.setRequestHandler as unknown as UntypedHandlerSetter).call(
|
(super.setRequestHandler as unknown as UntypedHandlerSetter).call(
|
||||||
|
|
@ -350,12 +348,9 @@ export class App extends Protocol<BaseContext> {
|
||||||
*
|
*
|
||||||
* @throws {Error} if a handler for this method is already registered.
|
* @throws {Error} if a handler for this method is already registered.
|
||||||
*/
|
*/
|
||||||
override setNotificationHandler: Protocol<BaseContext>["setNotificationHandler"] &
|
override setNotificationHandler: LegacyNotificationHandlerSetter<
|
||||||
(<S extends LegacyMethodSchema>(
|
Protocol<BaseContext>["setNotificationHandler"]
|
||||||
/** @deprecated Pass the method name and `{ params }` instead. */
|
> = (...args: unknown[]) => {
|
||||||
schema: S,
|
|
||||||
handler: LegacyNotificationHandler<S>,
|
|
||||||
) => void) = (...args: unknown[]) => {
|
|
||||||
const [method, ...rest] = toModernArgs("notification", args) ?? args;
|
const [method, ...rest] = toModernArgs("notification", args) ?? args;
|
||||||
this._methods.claim(method as string, "setNotificationHandler");
|
this._methods.claim(method as string, "setNotificationHandler");
|
||||||
(super.setNotificationHandler as unknown as UntypedHandlerSetter).call(
|
(super.setNotificationHandler as unknown as UntypedHandlerSetter).call(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,33 @@ export type LegacyNotificationHandler<S extends LegacyMethodSchema> = (
|
||||||
notification: S["_output"],
|
notification: S["_output"],
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `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> = Modern & {
|
||||||
|
/**
|
||||||
|
* @deprecated 1.x form. Pass the method name and `{ params }` instead;
|
||||||
|
* removed in 3.0.
|
||||||
|
*/
|
||||||
|
<S extends LegacyMethodSchema>(
|
||||||
|
schema: S,
|
||||||
|
handler: LegacyRequestHandler<S>,
|
||||||
|
): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** `setNotificationHandler` counterpart of {@link LegacyRequestHandlerSetter}. */
|
||||||
|
export type LegacyNotificationHandlerSetter<Modern> = Modern & {
|
||||||
|
/**
|
||||||
|
* @deprecated 1.x form. Pass the method name and `{ params }` instead;
|
||||||
|
* removed in 3.0.
|
||||||
|
*/
|
||||||
|
<S extends LegacyMethodSchema>(
|
||||||
|
schema: S,
|
||||||
|
handler: LegacyNotificationHandler<S>,
|
||||||
|
): void;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arguments for the 2.x three-argument `setRequestHandler` /
|
* Arguments for the 2.x three-argument `setRequestHandler` /
|
||||||
* `setNotificationHandler` form.
|
* `setNotificationHandler` form.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue