refactor: finish MCP SDK v2 migration
This commit is contained in:
parent
a76e3a7691
commit
f94cb38b0b
7 changed files with 684 additions and 540 deletions
|
|
@ -150,7 +150,7 @@ registerAppTool(
|
|||
"get_account_balance",
|
||||
{
|
||||
description: "Get account balance",
|
||||
inputSchema: { accountId: z.string() },
|
||||
inputSchema: z.object({ accountId: z.string() }),
|
||||
},
|
||||
async ({ accountId }) => {
|
||||
if (!authInfo) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ The server-side changes involve updating metadata structure and using helper fun
|
|||
#### Before (OpenAI)
|
||||
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import { z } from "zod";
|
||||
|
||||
function createServer() {
|
||||
|
|
@ -78,7 +78,7 @@ function createServer() {
|
|||
{
|
||||
title: "Shopping Cart",
|
||||
description: "Display the user's shopping cart",
|
||||
inputSchema: { userId: z.string() },
|
||||
inputSchema: z.object({ userId: z.string() }),
|
||||
annotations: { readOnlyHint: true },
|
||||
_meta: {
|
||||
"openai/outputTemplate": "ui://view/cart.html",
|
||||
|
|
@ -126,7 +126,7 @@ function createServer() {
|
|||
#### After (MCP Apps)
|
||||
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import {
|
||||
registerAppTool,
|
||||
registerAppResource,
|
||||
|
|
@ -144,7 +144,7 @@ function createServer() {
|
|||
{
|
||||
title: "Shopping Cart",
|
||||
description: "Display the user's shopping cart",
|
||||
inputSchema: { userId: z.string() },
|
||||
inputSchema: z.object({ userId: z.string() }),
|
||||
annotations: { readOnlyHint: true },
|
||||
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ 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 acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server.
|
||||
The View's `App` subclasses the base MCP SDK `Client`. The Host's `AppBridge`
|
||||
subclasses the base MCP SDK `Server` for the inner iframe channel, while a
|
||||
separate outer `Client` connects the Host to the actual MCP Server. Keeping
|
||||
those two connections separate prevents iframe negotiation and capabilities
|
||||
from leaking into the server connection.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
|
|
@ -93,7 +97,7 @@ sequenceDiagram
|
|||
```
|
||||
|
||||
1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context.
|
||||
5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ registerAppTool(
|
|||
"update-quantity",
|
||||
{
|
||||
description: "Update item quantity in cart",
|
||||
inputSchema: { itemId: z.string(), quantity: z.number() },
|
||||
inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
|
||||
_meta: {
|
||||
ui: {
|
||||
resourceUri: "ui://shop/cart.html",
|
||||
|
|
@ -127,14 +127,14 @@ registerAppTool(
|
|||
{
|
||||
title: "Read Data Bytes",
|
||||
description: "Load binary data in chunks",
|
||||
inputSchema: {
|
||||
inputSchema: z.object({
|
||||
id: z.string().describe("Resource identifier"),
|
||||
offset: z.number().min(0).default(0).describe("Byte offset"),
|
||||
byteCount: z
|
||||
.number()
|
||||
.default(MAX_CHUNK_BYTES)
|
||||
.describe("Bytes to read"),
|
||||
},
|
||||
}),
|
||||
outputSchema: DataChunkSchema,
|
||||
// Hidden from model - only callable by the App
|
||||
_meta: { ui: { visibility: ["app"] } },
|
||||
|
|
@ -281,10 +281,7 @@ server.registerResource(
|
|||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="./patterns.tsx#binaryBlobResourceClient"
|
||||
const result = await app.request(
|
||||
{ method: "resources/read", params: { uri: `video://${videoId}` } },
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
const result = await app.readServerResource({ uri: `video://${videoId}` });
|
||||
|
||||
const content = result.contents[0];
|
||||
if (!content || !("blob" in content)) {
|
||||
|
|
|
|||
|
|
@ -17,16 +17,12 @@ import { randomUUID } from "node:crypto";
|
|||
import type {
|
||||
CallToolResult,
|
||||
ReadResourceResult,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
} from "@modelcontextprotocol/server";
|
||||
import type { McpUiHostContext } from "../src/types.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useApp } from "../src/react/index.js";
|
||||
import { registerAppTool } from "../src/server/index.js";
|
||||
import {
|
||||
McpServer,
|
||||
ResourceTemplate,
|
||||
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
|
@ -117,14 +113,14 @@ function chunkedDataServer(server: McpServer) {
|
|||
{
|
||||
title: "Read Data Bytes",
|
||||
description: "Load binary data in chunks",
|
||||
inputSchema: {
|
||||
inputSchema: z.object({
|
||||
id: z.string().describe("Resource identifier"),
|
||||
offset: z.number().min(0).default(0).describe("Byte offset"),
|
||||
byteCount: z
|
||||
.number()
|
||||
.default(MAX_CHUNK_BYTES)
|
||||
.describe("Bytes to read"),
|
||||
},
|
||||
}),
|
||||
outputSchema: DataChunkSchema,
|
||||
// Hidden from model - only callable by the App
|
||||
_meta: { ui: { visibility: ["app"] } },
|
||||
|
|
@ -252,10 +248,7 @@ function binaryBlobResourceServer(
|
|||
*/
|
||||
async function binaryBlobResourceClient(app: App, videoId: string) {
|
||||
//#region binaryBlobResourceClient
|
||||
const result = await app.request(
|
||||
{ method: "resources/read", params: { uri: `video://${videoId}` } },
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
const result = await app.readServerResource({ uri: `video://${videoId}` });
|
||||
|
||||
const content = result.contents[0];
|
||||
if (!content || !("blob" in content)) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ This tutorial assumes you've built an MCP server before and are comfortable with
|
|||
|
||||
We'll use the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) to build the server.
|
||||
|
||||
You'll also need Node.js 18+.
|
||||
You'll also need Node.js 20+.
|
||||
|
||||
## 1. Set up the project
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ Install the dependencies you'll need:
|
|||
|
||||
```bash
|
||||
npm init -y
|
||||
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors
|
||||
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
|
||||
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
|
||||
```
|
||||
|
||||
|
|
@ -178,9 +178,10 @@ import {
|
|||
registerAppTool,
|
||||
RESOURCE_MIME_TYPE,
|
||||
} from "@modelcontextprotocol/ext-apps/server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
|
||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||
|
||||
|
|
@ -205,7 +206,7 @@ export function createServer(): McpServer {
|
|||
{
|
||||
title: "Get Time",
|
||||
description: "Returns the current server time.",
|
||||
inputSchema: {},
|
||||
inputSchema: z.object({}),
|
||||
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
|
||||
},
|
||||
async () => {
|
||||
|
|
@ -240,10 +241,10 @@ export function createServer(): McpServer {
|
|||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="../examples/quickstart/main.ts"
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/express";
|
||||
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
||||
import type { McpServer } from "@modelcontextprotocol/server";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
||||
import cors from "cors";
|
||||
import type { Request, Response } from "express";
|
||||
import { createServer } from "./server.js";
|
||||
|
|
@ -263,7 +264,7 @@ export async function startStreamableHTTPServer(
|
|||
|
||||
app.all("/mcp", async (req: Request, res: Response) => {
|
||||
const server = createServer();
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
const transport = new NodeStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ The [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/ex
|
|||
cd examples/basic-host
|
||||
```
|
||||
|
||||
The root install runs the package build, including type-checking the
|
||||
documentation snippets against the exact base MCP SDK beta.4 packages. Do
|
||||
not bypass a failed build before testing the host.
|
||||
|
||||
2. Start basic-host, pointing it to your MCP server:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
Loading…
Reference in a new issue