refactor: finish MCP SDK v2 migration · modelcontextprotocol/ext-apps@f94cb38 · GitHub
Skip to content

Commit f94cb38

Browse files
committed
refactor: finish MCP SDK v2 migration
1 parent a76e3a7 commit f94cb38

112 files changed

Lines changed: 684 additions & 540 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 4 additions & 4 deletions

‎README.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,17 @@ resources:
122122
## Getting Started
123123

124124
```bash
125-
npm install -S @modelcontextprotocol/ext-apps
125+
npm install -S @modelcontextprotocol/ext-apps \
126+
@modelcontextprotocol/client@2.0.0-beta.4 \
127+
@modelcontextprotocol/server@2.0.0-beta.4 \
128+
@modelcontextprotocol/core@2.0.0-beta.4 \
129+
zod@^4.2.0
126130
```
127131

132+
This release uses the split base MCP SDK v2 packages. Install all three at the
133+
exact published beta.4 version so the Apps SDK and your MCP client/server share
134+
one compatible protocol implementation.
135+
128136
**New here?** Start with the
129137
[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html)
130138
to build your first MCP App.

‎build.bun.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,15 @@ function buildJs(
3131
});
3232
}
3333

34-
// zod is a peerDependency — keep it external so consumers share a single
35-
// zod instance (instanceof ZodError / schema.extend() break with duplicate copies).
36-
const PEER_EXTERNALS = ["@modelcontextprotocol/sdk", "zod"];
34+
// Peer dependencies stay external in the standard entry points so consumers
35+
// share one base MCP SDK and Zod instance. The *-with-deps entry points keep
36+
// bundling these dependencies for standalone browser use.
37+
const PEER_EXTERNALS = [
38+
"@modelcontextprotocol/client",
39+
"@modelcontextprotocol/core",
40+
"@modelcontextprotocol/server",
41+
"zod",
42+
];
3743

3844
await Promise.all([
3945
buildJs("src/app.ts", {

‎docs/authorization.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ registerAppTool(
150150
"get_account_balance",
151151
{
152152
description: "Get account balance",
153-
inputSchema: { accountId: z.string() },
153+
inputSchema: z.object({ accountId: z.string() }),
154154
},
155155
async ({ accountId }) => {
156156
if (!authInfo) {

‎docs/migrate_from_openai_apps.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The server-side changes involve updating metadata structure and using helper fun
6666
#### Before (OpenAI)
6767

6868
```typescript
69-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
69+
import { McpServer } from "@modelcontextprotocol/server";
7070
import { z } from "zod";
7171

7272
function createServer() {
@@ -78,7 +78,7 @@ function createServer() {
7878
{
7979
title: "Shopping Cart",
8080
description: "Display the user's shopping cart",
81-
inputSchema: { userId: z.string() },
81+
inputSchema: z.object({ userId: z.string() }),
8282
annotations: { readOnlyHint: true },
8383
_meta: {
8484
"openai/outputTemplate": "ui://view/cart.html",
@@ -126,7 +126,7 @@ function createServer() {
126126
#### After (MCP Apps)
127127

128128
```typescript
129-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
129+
import { McpServer } from "@modelcontextprotocol/server";
130130
import {
131131
registerAppTool,
132132
registerAppResource,
@@ -144,7 +144,7 @@ function createServer() {
144144
{
145145
title: "Shopping Cart",
146146
description: "Display the user's shopping cart",
147-
inputSchema: { userId: z.string() },
147+
inputSchema: z.object({ userId: z.string() }),
148148
annotations: { readOnlyHint: true },
149149
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
150150
},

‎docs/overview.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ flowchart LR
5656
- **Host** — The chat client (e.g., Claude Desktop) that connects to servers, embeds Views in iframes, and proxies communication between them.
5757
- **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.
5858

59-
The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server.
59+
The View's `App` subclasses the base MCP SDK `Client`. The Host's `AppBridge`
60+
subclasses the base MCP SDK `Server` for the inner iframe channel, while a
61+
separate outer `Client` connects the Host to the actual MCP Server. Keeping
62+
those two connections separate prevents iframe negotiation and capabilities
63+
from leaking into the server connection.
6064

6165
## Lifecycle
6266

@@ -93,7 +97,7 @@ sequenceDiagram
9397
```
9498

9599
1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
96-
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). This handshake ensures the View is ready before receiving data.
100+
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The SDK first completes its temporary inner base MCP handshake, then the View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). `ui/initialize` remains authoritative for Apps capabilities and identity, and `ui/notifications/initialized` remains the public View-ready signal.
97101
3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering). This separation lets servers provide rich data to the UI without bloating the model's context.
98102
4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context.
99103
5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources.

‎docs/patterns.md‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ registerAppTool(
1919
"update-quantity",
2020
{
2121
description: "Update item quantity in cart",
22-
inputSchema: { itemId: z.string(), quantity: z.number() },
22+
inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
2323
_meta: {
2424
ui: {
2525
resourceUri: "ui://shop/cart.html",
@@ -127,14 +127,14 @@ registerAppTool(
127127
{
128128
title: "Read Data Bytes",
129129
description: "Load binary data in chunks",
130-
inputSchema: {
130+
inputSchema: z.object({
131131
id: z.string().describe("Resource identifier"),
132132
offset: z.number().min(0).default(0).describe("Byte offset"),
133133
byteCount: z
134134
.number()
135135
.default(MAX_CHUNK_BYTES)
136136
.describe("Bytes to read"),
137-
},
137+
}),
138138
outputSchema: DataChunkSchema,
139139
// Hidden from model - only callable by the App
140140
_meta: { ui: { visibility: ["app"] } },
@@ -281,10 +281,7 @@ server.registerResource(
281281

282282
<!-- prettier-ignore -->
283283
```ts source="./patterns.tsx#binaryBlobResourceClient"
284-
const result = await app.request(
285-
{ method: "resources/read", params: { uri: `video://${videoId}` } },
286-
ReadResourceResultSchema,
287-
);
284+
const result = await app.readServerResource({ uri: `video://${videoId}` });
288285

289286
const content = result.contents[0];
290287
if (!content || !("blob" in content)) {

‎docs/patterns.tsx‎

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,12 @@ import { randomUUID } from "node:crypto";
1717
import type {
1818
CallToolResult,
1919
ReadResourceResult,
20-
} from "@modelcontextprotocol/sdk/types.js";
21-
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
20+
} from "@modelcontextprotocol/server";
2221
import type { McpUiHostContext } from "../src/types.js";
2322
import { useEffect, useState } from "react";
2423
import { useApp } from "../src/react/index.js";
2524
import { registerAppTool } from "../src/server/index.js";
26-
import {
27-
McpServer,
28-
ResourceTemplate,
29-
} from "@modelcontextprotocol/sdk/server/mcp.js";
25+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
3026
import { z } from "zod";
3127

3228
/**
@@ -117,14 +113,14 @@ function chunkedDataServer(server: McpServer) {
117113
{
118114
title: "Read Data Bytes",
119115
description: "Load binary data in chunks",
120-
inputSchema: {
116+
inputSchema: z.object({
121117
id: z.string().describe("Resource identifier"),
122118
offset: z.number().min(0).default(0).describe("Byte offset"),
123119
byteCount: z
124120
.number()
125121
.default(MAX_CHUNK_BYTES)
126122
.describe("Bytes to read"),
127-
},
123+
}),
128124
outputSchema: DataChunkSchema,
129125
// Hidden from model - only callable by the App
130126
_meta: { ui: { visibility: ["app"] } },
@@ -252,10 +248,7 @@ function binaryBlobResourceServer(
252248
*/
253249
async function binaryBlobResourceClient(app: App, videoId: string) {
254250
//#region binaryBlobResourceClient
255-
const result = await app.request(
256-
{ method: "resources/read", params: { uri: `video://${videoId}` } },
257-
ReadResourceResultSchema,
258-
);
251+
const result = await app.readServerResource({ uri: `video://${videoId}` });
259252

260253
const content = result.contents[0];
261254
if (!content || !("blob" in content)) {

‎docs/quickstart.md‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This tutorial assumes you've built an MCP server before and are comfortable with
2121

2222
We'll use the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) to build the server.
2323

24-
You'll also need Node.js 18+.
24+
You'll also need Node.js 20+.
2525

2626
## 1. Set up the project
2727

@@ -37,7 +37,7 @@ Install the dependencies you'll need:
3737

3838
```bash
3939
npm init -y
40-
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors
40+
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@2.0.0-beta.4 @modelcontextprotocol/core@2.0.0-beta.4 @modelcontextprotocol/server@2.0.0-beta.4 @modelcontextprotocol/node@2.0.0-beta.4 @modelcontextprotocol/express@2.0.0-beta.4 zod@^4.2.0 express cors
4141
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
4242
```
4343

@@ -178,9 +178,10 @@ import {
178178
registerAppTool,
179179
RESOURCE_MIME_TYPE,
180180
} from "@modelcontextprotocol/ext-apps/server";
181-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
181+
import { McpServer } from "@modelcontextprotocol/server";
182182
import fs from "node:fs/promises";
183183
import path from "node:path";
184+
import { z } from "zod";
184185

185186
const DIST_DIR = path.join(import.meta.dirname, "dist");
186187

@@ -205,7 +206,7 @@ export function createServer(): McpServer {
205206
{
206207
title: "Get Time",
207208
description: "Returns the current server time.",
208-
inputSchema: {},
209+
inputSchema: z.object({}),
209210
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
210211
},
211212
async () => {
@@ -240,10 +241,10 @@ export function createServer(): McpServer {
240241

241242
<!-- prettier-ignore -->
242243
```ts source="../examples/quickstart/main.ts"
243-
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
244-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
245-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
246-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
244+
import { createMcpExpressApp } from "@modelcontextprotocol/express";
245+
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
246+
import type { McpServer } from "@modelcontextprotocol/server";
247+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
247248
import cors from "cors";
248249
import type { Request, Response } from "express";
249250
import { createServer } from "./server.js";
@@ -263,7 +264,7 @@ export async function startStreamableHTTPServer(
263264

264265
app.all("/mcp", async (req: Request, res: Response) => {
265266
const server = createServer();
266-
const transport = new StreamableHTTPServerTransport({
267+
const transport = new NodeStreamableHTTPServerTransport({
267268
sessionIdGenerator: undefined,
268269
});
269270

‎docs/testing-mcp-apps.md‎

Lines changed: 4 additions & 0 deletions

0 commit comments

Comments
 (0)