SDK 2.0 fixups: lockfile, peers, handler guards, docs, wire-compat tests (#768)

Follow-up to #720 and #705: regenerated lockfile, ^2.0.0 peers with client required, types from @modelcontextprotocol/client, restored double-registration guard, deprecated raw-shape registerAppTool overload, migration guide, wire-compat tests, MCPB build from the local package.
This commit is contained in:
Olivier Chafik 2026-09-08 14:20:31 +01:00 • committed by GitHub
commit 4cd4273947
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 2147 additions and 645 deletions

View file

@ -114,6 +114,21 @@ jobs:
grep -q "Ready" /tmp/mcpb-smoke.log
working-directory: examples/pdf-server
dependency-isolation:
name: Dependency isolation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run test:dependency-isolation
e2e:
runs-on: ubuntu-latest
steps:

View file

@ -60,8 +60,8 @@ rm -fR package-lock.json node_modules && \
### Key Source Files
- `src/app.ts` - `App` subclasses the base MCP SDK `Protocol`, handles View initialization, tool calls, and messaging
- `src/app-bridge.ts` - `AppBridge` subclasses the base MCP SDK `Protocol` for the iframe channel and proxies through a separate outer `Client`
- `src/app.ts` - `App` subclasses `Protocol` from `@modelcontextprotocol/client`, handles View initialization, tool calls, and messaging
- `src/app-bridge.ts` - `AppBridge` subclasses `Protocol` from `@modelcontextprotocol/client` for the iframe channel and proxies through a separate outer `Client`
- `src/server/index.ts` - Helpers for MCP servers to register tools/resources with UI metadata
- `src/types.ts` - Protocol types re-exported from `spec.types.ts` and Zod schemas from `generated/schema.ts` (auto-generated during build)
- `src/message-transport.ts` - `PostMessageTransport` for iframe communication

View file

@ -122,27 +122,33 @@ resources:
## Getting Started
Requires Node.js 20+. The base MCP SDK packages are `^2.0.0` peers of
`ext-apps` (`@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it for you).
For a View or host:
```bash
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@2.0.0-beta.5 \
@modelcontextprotocol/core@2.0.0-beta.5 \
@modelcontextprotocol/client@^2.0.0 \
zod@^4.2.0
```
For an MCP server:
For an MCP server, add the server package (and, for HTTP transports, the Node
and Express adapters):
```bash
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/server@2.0.0-beta.5 \
@modelcontextprotocol/core@2.0.0-beta.5 \
@modelcontextprotocol/client@^2.0.0 \
@modelcontextprotocol/server@^2.0.0 \
@modelcontextprotocol/node@^2.0.0 \
@modelcontextprotocol/express@^2.0.0 \
zod@^4.2.0
```
Applications that implement both roles should install both `client` and
`server`. Keep all installed base MCP SDK packages on the exact same published
beta so they share one compatible protocol implementation.
The wire protocol is unchanged between `ext-apps` 1.x and 2.x: a 2.x View works
in a 1.x host and a 2.x host renders 1.x Views. See the
[migration guide](https://apps.extensions.modelcontextprotocol.io/api/documents/migrate-to-v2.html)
when upgrading from 1.x.
**New here?** Start with the
[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html)

102
docs/migrate-to-2.md Normal file
View file

@ -0,0 +1,102 @@
---
title: Migrate to v2
group: Getting Started
description: Upgrade from ext-apps 1.x to 2.x — new base MCP SDK peer packages, API changes for Views and hosts, and what stays wire-compatible.
---
# Migrating from ext-apps 1.x to 2.x
ext-apps 2.x is built on the base MCP TypeScript SDK 2.x, which replaced the
single `@modelcontextprotocol/sdk` package with `@modelcontextprotocol/client`,
`server`, `core`, `node` and `express`. The MCP Apps wire protocol did not
change; the breaking changes are in dependencies and in the TypeScript API.
## Host compatibility
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).
## 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) |
`@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;
`@modelcontextprotocol/server` stays optional and is only needed for the
`./server` helpers. Node.js 20+ is required.
## Breaking changes
- **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.
- **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
`registerAppTool` but are deprecated; wrap them with `z.object({...})`.
- **`App` / `AppBridge` extend `Protocol` from `@modelcontextprotocol/client`.**
`ProtocolWithEvents`, `AppRequest`, `AppNotification` and `AppResult` are
gone; use the SDK's `Protocol`, `Request`, `Notification` and `Result`.
- **Handler context.** Custom handlers receive the SDK 2.x context:
`extra.signal` → `extra.mcpReq.signal`, `extra.requestId` →
`extra.mcpReq.id`.
- **`setRequestHandler` / `setNotificationHandler` take method names.**
`app.setRequestHandler(SomeRequestSchema, handler)` becomes
`app.setRequestHandler("some/method", { params: SomeParamsSchema }, (params, ctx) => …)`
for custom methods (the handler receives the parsed params); the two-argument
`setRequestHandler("tools/call", handler)` form exists only for spec-defined
method names.
- **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.
## Host-side wire deltas
Observed when a 2.x `AppBridge` answers a View; a 1.x View sees these too.
| Situation | 1.x host | 2.x host |
| --------------------------------------------------------- | ------------------------------ | --------------------------------------------------- |
| A handler throws `-32002` (resource not found) | `error.code: -32002` | `error.code: -32602` (the SDK never emits `-32002`) |
| Invalid params on a `ui/*` request | `-32603` with a zod issue dump | `-32602 Invalid params for <method>: …` |
| Error message text | `MCP error -32602: …` prefix | plain message |
| `tools/call` to an unknown tool through a 2.x `McpServer` | `result.isError: true` | JSON-RPC error `-32602` (`callServerTool` rejects) |
## `schema.json`
The published `./schema.json` export is regenerated from the 2.x core schemas:
- `McpUiToolResultNotification.params.structuredContent` is any JSON value (was
`type: "object"`).
- `McpUiToolResultNotification.params._meta` documents
`io.modelcontextprotocol/serverInfo` and no longer lists `progressToken` /
`related-task` (both still pass through).
- `McpUiHostContext.toolInfo.tool.outputSchema` is a loose object (only
`$schema` is documented); `inputSchema.properties` values are now typed as
JSON values.
- A recursive JSON-value definition (`__schema0`) is added under the `$defs` of
`McpUiHostContext`, `McpUiHostContextChangedNotification` and
`McpUiInitializeResult`.
## Checklist
1. `npm uninstall @modelcontextprotocol/sdk` and install the packages for your
role from the table above.
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,
`@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
`setRequestHandler` calls.
5. Replace `McpError` / numeric-code checks with `ProtocolError` / `SdkError`.

View file

@ -56,8 +56,8 @@ flowchart LR
- **Host** — The chat client (e.g., Claude Desktop) that connects to servers, embeds Views in iframes, and proxies communication between them.
- **View** — The UI running inside a sandboxed iframe. It receives tool data from the Host and can call server tools or send messages back to the chat.
The View's `App` and the Host's `AppBridge` both subclass the base MCP SDK's
public `Protocol` for the iframe channel. A separate outer `Client` connects
The View's `App` and the Host's `AppBridge` both subclass the `Protocol` class
exported by `@modelcontextprotocol/client` for the iframe channel. A separate outer `Client` connects
the Host to the actual MCP Server. Keeping those two connections separate
preserves the Apps-only iframe handshake and prevents iframe capabilities from
leaking into the server connection.

View file

@ -37,7 +37,7 @@ Install the dependencies you'll need:
```bash
npm init -y
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@2.0.0-beta.5 @modelcontextprotocol/core@2.0.0-beta.5 @modelcontextprotocol/server@2.0.0-beta.5 @modelcontextprotocol/node@2.0.0-beta.5 @modelcontextprotocol/express@2.0.0-beta.5 zod@^4.2.0 express cors
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@^2.0.0 @modelcontextprotocol/server@^2.0.0 @modelcontextprotocol/node@^2.0.0 @modelcontextprotocol/express@^2.0.0 zod@^4.2.0 express cors
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
```

View file

@ -29,7 +29,7 @@ The [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/ex
```
The root install runs the package build, including type-checking the
documentation snippets against the exact base MCP SDK beta.4 packages. Do
documentation snippets against the pinned base MCP SDK 2.x packages. Do
not bypass a failed build before testing the host.
2. Start basic-host, pointing it to your MCP server:

View file

@ -1,7 +1,7 @@
{
"homepage": "https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host",
"name": "@modelcontextprotocol/ext-apps-basic-host",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"scripts": {
"build": "tsc --noEmit && concurrently \"cross-env INPUT=index.html vite build\" \"cross-env INPUT=sandbox.html vite build\"",
@ -11,11 +11,11 @@
"dev": "cross-env NODE_ENV=development concurrently \"npm run watch\" \"npm run serve\""
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/express": "^5.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-basic-preact",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Preact",
"repository": {
@ -25,14 +25,14 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"preact": "^10.0.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@preact/preset-vite": "^2.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-basic-react",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using React",
"repository": {
@ -35,15 +35,15 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-basic-solid",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Solid",
"repository": {
@ -25,14 +25,14 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"solid-js": "1.9.10",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-basic-svelte",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Svelte",
"repository": {
@ -25,14 +25,14 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"svelte": "^5.0.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-basic-vanillajs",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using vanilla JavaScript",
"repository": {
@ -25,13 +25,13 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-basic-vue",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Vue",
"repository": {
@ -25,14 +25,14 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"vue": "^3.5.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-budget-allocator",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Budget allocator MCP App Server with interactive visualization",
"repository": {
@ -26,14 +26,14 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"chart.js": "^4.4.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-cohort-heatmap",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Cohort heatmap MCP App Server for retention analysis",
"repository": {
@ -26,15 +26,15 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-customer-segmentation",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Customer segmentation MCP App Server with filtering",
"repository": {
@ -26,14 +26,14 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"chart.js": "^4.4.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-debug",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "Debug MCP App Server for testing all SDK capabilities",
"repository": {
@ -34,11 +34,11 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "integration-server",
"version": "1.7.5",
"version": "2.0.0",
"private": true,
"type": "module",
"scripts": {
@ -16,15 +16,15 @@
},
"dependencies": {
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/express": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/server-lazy-auth",
"version": "1.7.5",
"version": "2.0.0",
"type": "module",
"description": "MCP App example demonstrating lazy (on-demand) OAuth: public tools work unauthenticated, protected tools return 401 + WWW-Authenticate so the host runs the OAuth flow only when needed",
"repository": {
@ -22,13 +22,13 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.7.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"jose": "^6.0.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",

Some files were not shown because too many files have changed in this diff Show more