diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6848e4c..fb41938 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,21 +114,6 @@ 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: @@ -147,10 +132,8 @@ jobs: - run: npm ci - # Headless runs without a channel use chromium-headless-shell, so the - # full Chromium build (about 165 MB) would only be downloaded, never run. - name: Install Playwright browsers - run: npx playwright install --with-deps --only-shell chromium + run: npx playwright install --with-deps chromium - name: Run E2E tests run: npx playwright test --reporter=list @@ -182,14 +165,9 @@ jobs: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs - # The checkout lives on the Windows drive, which WSL reaches over 9P; - # writing node_modules there is what makes this job slow (npm ci alone - # took about 3 minutes). Copy the tree onto the distro's ext4 filesystem - # first, as Microsoft's WSL guidance recommends for anything I/O heavy. - name: Build and test in WSL shell: wsl-bash {0} run: | - cp -r "$(pwd)" ~/ext-apps && cd ~/ext-apps npm ci npm run build npm run examples:build diff --git a/AGENTS.md b/AGENTS.md index 219b947..6bfc8cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,8 +60,8 @@ rm -fR package-lock.json node_modules && \ ### Key Source Files -- `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/app.ts` - `App` class extends MCP Protocol, handles guest initialization, tool calls, messaging +- `src/app-bridge.ts` - `AppBridge` class for hosts, proxies MCP requests, sends tool input/results to guests - `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 @@ -70,12 +70,12 @@ rm -fR package-lock.json node_modules && \ ### Protocol Flow ``` -View (App/Protocol) <--PostMessageTransport--> Host (AppBridge/Protocol) <--separate MCP Client--> MCP Server +View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server ``` 1. Host creates iframe with view HTML 2. View creates `App` instance and calls `connect()` with `PostMessageTransport` -3. View sends `ui/initialize` (the only handshake on the iframe channel) and receives authoritative Apps capabilities and context +3. View sends `ui/initialize` request, receives host capabilities and context 4. Host sends `sendToolInput()` with tool arguments after initialization 5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()` 6. Host sends `sendToolResult()` when tool execution completes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e28dcd1..6592106 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -502,7 +502,7 @@ Alternatively, use the [workflow dispatch](https://github.com/modelcontextprotoc ## Code of Conduct -This project follows our [Code of Conduct](https://github.com/modelcontextprotocol/.github/blob/main/CODE_OF_CONDUCT.md). Please review it before contributing. +This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing. ## Reporting Issues diff --git a/README.md b/README.md index ab4004d..7348e15 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Why · - Quickstart + Quickstart · API Docs · @@ -89,8 +89,6 @@ it will guide you through the rest. Goose Postman MCPJam - mcp-use - Alpic Playground

> [!NOTE] @@ -122,36 +120,12 @@ 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 \ - zod@^4.2.0 +npm install -S @modelcontextprotocol/ext-apps ``` -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/client@^2.0.0 \ - @modelcontextprotocol/server@^2.0.0 \ - @modelcontextprotocol/node@^2.0.0 \ - @modelcontextprotocol/express@^2.0.0 \ - zod@^4.2.0 -``` - -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) +[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html) to build your first MCP App. ## Using the SDK @@ -165,7 +139,7 @@ UI metadata. | `@modelcontextprotocol/ext-apps` | Build interactive Views (App class, PostMessageTransport) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app.html) | | `@modelcontextprotocol/ext-apps/react` | React hooks for Views (useApp, useHostStyles, etc.) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/_modelcontextprotocol_ext-apps_react.html) | | `@modelcontextprotocol/ext-apps/app-bridge` | Embed and communicate with Views in your chat client | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app-bridge.html) | -| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server-helpers.html) | +| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server.html) | There's no _supported_ host implementation in this repo (beyond the [examples/basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) @@ -272,7 +246,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/build.bun.ts b/build.bun.ts index 61361e6..a129d5d 100644 --- a/build.bun.ts +++ b/build.bun.ts @@ -1,9 +1,6 @@ #!/usr/bin/env bun import { $ } from "bun"; -import { cpSync, mkdirSync, rmSync } from "node:fs"; - -// Avoid publishing artifacts left behind by an earlier build or branch. -rmSync("dist", { recursive: true, force: true }); +import { cpSync, mkdirSync } from "node:fs"; // Run TypeScript compiler for type declarations await $`tsc`; @@ -34,15 +31,9 @@ function buildJs( }); } -// Peer dependencies stay external in the standard entry points so consumers -// share one base MCP SDK and Zod instance. The *-with-deps entry points keep -// bundling these dependencies for standalone browser use. -const PEER_EXTERNALS = [ - "@modelcontextprotocol/client", - "@modelcontextprotocol/core", - "@modelcontextprotocol/server", - "zod", -]; +// zod is a peerDependency — keep it external so consumers share a single +// zod instance (instanceof ZodError / schema.extend() break with duplicate copies). +const PEER_EXTERNALS = ["@modelcontextprotocol/sdk", "zod"]; await Promise.all([ buildJs("src/app.ts", { diff --git a/docs/authorization.md b/docs/authorization.md index df7adc6..59fc26c 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -150,7 +150,7 @@ registerAppTool( "get_account_balance", { description: "Get account balance", - inputSchema: z.object({ accountId: z.string() }), + inputSchema: { accountId: z.string() }, }, async ({ accountId }) => { if (!authInfo) { diff --git a/docs/migrate-to-2.md b/docs/migrate-to-2.md deleted file mode 100644 index f9341d0..0000000 --- a/docs/migrate-to-2.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -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). - -## 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`; 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; -`@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 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 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 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) => …)` - for custom methods (the handler receives the parsed params); the two-argument - `setRequestHandler("tools/call", handler)` form exists only for spec-defined - 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`, - `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 - -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 : …` | -| 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`, 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 - `setRequestHandler` calls. -5. Replace `McpError` / numeric-code checks with `ProtocolError` / `SdkError`. diff --git a/docs/migrate_from_openai_apps.md b/docs/migrate_from_openai_apps.md index 22d5d9f..d91281b 100644 --- a/docs/migrate_from_openai_apps.md +++ b/docs/migrate_from_openai_apps.md @@ -66,7 +66,7 @@ The server-side changes involve updating metadata structure and using helper fun #### Before (OpenAI) ```typescript -import { McpServer } from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; function createServer() { @@ -78,7 +78,7 @@ function createServer() { { title: "Shopping Cart", description: "Display the user's shopping cart", - inputSchema: z.object({ userId: z.string() }), + inputSchema: { 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/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerAppTool, registerAppResource, @@ -144,7 +144,7 @@ function createServer() { { title: "Shopping Cart", description: "Display the user's shopping cart", - inputSchema: z.object({ userId: z.string() }), + inputSchema: { userId: z.string() }, annotations: { readOnlyHint: true }, _meta: { ui: { resourceUri: "ui://view/cart.html" } }, }, diff --git a/docs/overview.md b/docs/overview.md index 8eda95d..28f5b23 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -56,11 +56,7 @@ 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 `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. +The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server. ## Lifecycle @@ -97,7 +93,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` (the only handshake on the iframe channel) and receives host context (theme, capabilities, container dimensions). `ui/initialize` is authoritative for Apps capabilities and identity, and `ui/notifications/initialized` is the public View-ready signal. +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. 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. diff --git a/docs/patterns.md b/docs/patterns.md index 884de26..87a6d82 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -19,7 +19,7 @@ registerAppTool( "update-quantity", { description: "Update item quantity in cart", - inputSchema: z.object({ itemId: z.string(), quantity: z.number() }), + inputSchema: { 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: z.object({ + inputSchema: { 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,7 +281,10 @@ server.registerResource( ```ts source="./patterns.tsx#binaryBlobResourceClient" -const result = await app.readServerResource({ uri: `video://${videoId}` }); +const result = await app.request( + { method: "resources/read", params: { uri: `video://${videoId}` } }, + ReadResourceResultSchema, +); const content = result.contents[0]; if (!content || !("blob" in content)) { diff --git a/docs/patterns.tsx b/docs/patterns.tsx index bb9eaa4..6b87692 100644 --- a/docs/patterns.tsx +++ b/docs/patterns.tsx @@ -17,12 +17,16 @@ import { randomUUID } from "node:crypto"; import type { CallToolResult, ReadResourceResult, -} from "@modelcontextprotocol/server"; +} from "@modelcontextprotocol/sdk/types.js"; +import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js"; 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/server"; +import { + McpServer, + ResourceTemplate, +} from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; /** @@ -113,14 +117,14 @@ function chunkedDataServer(server: McpServer) { { title: "Read Data Bytes", description: "Load binary data in chunks", - inputSchema: z.object({ + inputSchema: { 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"] } }, @@ -248,7 +252,10 @@ function binaryBlobResourceServer( */ async function binaryBlobResourceClient(app: App, videoId: string) { //#region binaryBlobResourceClient - const result = await app.readServerResource({ uri: `video://${videoId}` }); + const result = await app.request( + { method: "resources/read", params: { uri: `video://${videoId}` } }, + ReadResourceResultSchema, + ); const content = result.contents[0]; if (!content || !("blob" in content)) { diff --git a/docs/quickstart.md b/docs/quickstart.md index a021912..3e00796 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -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 20+. +You'll also need Node.js 18+. ## 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/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 @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env ``` @@ -59,7 +59,6 @@ npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development IN ```json source="../examples/quickstart/tsconfig.json" { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", @@ -88,7 +87,6 @@ npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development IN ```json source="../examples/quickstart/tsconfig.server.json" { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", @@ -180,10 +178,9 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import fs from "node:fs/promises"; import path from "node:path"; -import { z } from "zod"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -208,7 +205,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async () => { @@ -243,10 +240,10 @@ export function createServer(): McpServer { ```ts source="../examples/quickstart/main.ts" -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +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 cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -266,7 +263,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/docs/testing-mcp-apps.md b/docs/testing-mcp-apps.md index a8eb333..df1396c 100644 --- a/docs/testing-mcp-apps.md +++ b/docs/testing-mcp-apps.md @@ -28,10 +28,6 @@ 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 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: ```bash diff --git a/examples/basic-host/package.json b/examples/basic-host/package.json index cefe9d5..1b45dcf 100644 --- a/examples/basic-host/package.json +++ b/examples/basic-host/package.json @@ -1,7 +1,7 @@ { "homepage": "https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host", "name": "@modelcontextprotocol/ext-apps-basic-host", - "version": "2.0.0", + "version": "1.7.4", "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/client": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/express": "^5.0.0", diff --git a/examples/basic-host/src/implementation.ts b/examples/basic-host/src/implementation.ts index 5f680ff..31e3698 100644 --- a/examples/basic-host/src/implementation.ts +++ b/examples/basic-host/src/implementation.ts @@ -1,12 +1,8 @@ import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions, buildAllowAttribute, type McpUiUpdateModelContextRequest, type McpUiMessageRequest } from "@modelcontextprotocol/ext-apps/app-bridge"; -import { - Client, - SSEClientTransport, - StreamableHTTPClientTransport, - type CallToolResult, - type Resource, - type Tool, -} from "@modelcontextprotocol/client"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import type { CallToolResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js"; import { getTheme, onThemeChange } from "./theme"; import { HOST_STYLE_VARIABLES } from "./host-styles"; diff --git a/examples/basic-host/src/index.tsx b/examples/basic-host/src/index.tsx index 00c0d52..3d488a7 100644 --- a/examples/basic-host/src/index.tsx +++ b/examples/basic-host/src/index.tsx @@ -1,5 +1,5 @@ import { getToolUiResourceUri, McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge"; -import type { Tool } from "@modelcontextprotocol/client"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo, type ModelContext, type AppMessage } from "./implementation"; diff --git a/examples/basic-host/tsconfig.json b/examples/basic-host/tsconfig.json index 3c143ca..9d7c3bc 100644 --- a/examples/basic-host/tsconfig.json +++ b/examples/basic-host/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-preact/main.ts b/examples/basic-server-preact/main.ts index ffdff52..7642632 100644 --- a/examples/basic-server-preact/main.ts +++ b/examples/basic-server-preact/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-preact/package.json b/examples/basic-server-preact/package.json index 60aa40d..5127202 100644 --- a/examples/basic-server-preact/package.json +++ b/examples/basic-server-preact/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-basic-preact", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Basic MCP App Server example using Preact", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/basic-server-preact" }, "license": "MIT", @@ -24,15 +24,12 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "preact": "^10.0.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@preact/preset-vite": "^2.0.0", diff --git a/examples/basic-server-preact/server.ts b/examples/basic-server-preact/server.ts index dfec94a..bf14ea5 100644 --- a/examples/basic-server-preact/server.ts +++ b/examples/basic-server-preact/server.ts @@ -1,12 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -32,7 +28,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-preact/src/mcp-app.tsx b/examples/basic-server-preact/src/mcp-app.tsx index 351a8ec..a8bdc68 100644 --- a/examples/basic-server-preact/src/mcp-app.tsx +++ b/examples/basic-server-preact/src/mcp-app.tsx @@ -8,7 +8,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { useCallback, useEffect, useState } from "preact/hooks"; import { render } from "preact"; import styles from "./mcp-app.module.css"; diff --git a/examples/basic-server-preact/tsconfig.json b/examples/basic-server-preact/tsconfig.json index 9bf9389..32182d9 100644 --- a/examples/basic-server-preact/tsconfig.json +++ b/examples/basic-server-preact/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-preact/tsconfig.server.json b/examples/basic-server-preact/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/basic-server-preact/tsconfig.server.json +++ b/examples/basic-server-preact/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/basic-server-react/main.ts b/examples/basic-server-react/main.ts index 21c5e18..ec187b6 100644 --- a/examples/basic-server-react/main.ts +++ b/examples/basic-server-react/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +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 cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-react/package.json b/examples/basic-server-react/package.json index ea51a8a..e4d1233 100644 --- a/examples/basic-server-react/package.json +++ b/examples/basic-server-react/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-basic-react", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Basic MCP App Server example using React", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/basic-server-react" }, "license": "MIT", @@ -34,16 +34,13 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index d77c908..23f6dda 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -1,12 +1,8 @@ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") @@ -33,7 +29,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-react/src/mcp-app.tsx b/examples/basic-server-react/src/mcp-app.tsx index 2b5fbef..6bb5540 100644 --- a/examples/basic-server-react/src/mcp-app.tsx +++ b/examples/basic-server-react/src/mcp-app.tsx @@ -3,7 +3,7 @@ */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { StrictMode, useCallback, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import styles from "./mcp-app.module.css"; diff --git a/examples/basic-server-react/tsconfig.json b/examples/basic-server-react/tsconfig.json index 3ecbdad..fc3c210 100644 --- a/examples/basic-server-react/tsconfig.json +++ b/examples/basic-server-react/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-react/tsconfig.server.json b/examples/basic-server-react/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/basic-server-react/tsconfig.server.json +++ b/examples/basic-server-react/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/basic-server-solid/main.ts b/examples/basic-server-solid/main.ts index 095ef8d..c8d9de2 100644 --- a/examples/basic-server-solid/main.ts +++ b/examples/basic-server-solid/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-solid/package.json b/examples/basic-server-solid/package.json index b3ca909..8f75ac8 100644 --- a/examples/basic-server-solid/package.json +++ b/examples/basic-server-solid/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-basic-solid", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Basic MCP App Server example using Solid", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/basic-server-solid" }, "license": "MIT", @@ -24,15 +24,12 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "solid-js": "1.9.10", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/basic-server-solid/server.ts b/examples/basic-server-solid/server.ts index addb522..2ed2d93 100644 --- a/examples/basic-server-solid/server.ts +++ b/examples/basic-server-solid/server.ts @@ -1,12 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -32,7 +28,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-solid/src/mcp-app.tsx b/examples/basic-server-solid/src/mcp-app.tsx index 2d78b7b..66d47f3 100644 --- a/examples/basic-server-solid/src/mcp-app.tsx +++ b/examples/basic-server-solid/src/mcp-app.tsx @@ -8,7 +8,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { createEffect, createSignal, onMount, Show } from "solid-js"; import { render } from "solid-js/web"; import styles from "./mcp-app.module.css"; diff --git a/examples/basic-server-solid/tsconfig.json b/examples/basic-server-solid/tsconfig.json index bb13a01..84f0efe 100644 --- a/examples/basic-server-solid/tsconfig.json +++ b/examples/basic-server-solid/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-solid/tsconfig.server.json b/examples/basic-server-solid/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/basic-server-solid/tsconfig.server.json +++ b/examples/basic-server-solid/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/basic-server-svelte/main.ts b/examples/basic-server-svelte/main.ts index 734ec0c..6c50a25 100644 --- a/examples/basic-server-svelte/main.ts +++ b/examples/basic-server-svelte/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-svelte/package.json b/examples/basic-server-svelte/package.json index 19fe4c9..f877bf7 100644 --- a/examples/basic-server-svelte/package.json +++ b/examples/basic-server-svelte/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-basic-svelte", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Basic MCP App Server example using Svelte", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/basic-server-svelte" }, "license": "MIT", @@ -24,15 +24,12 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "svelte": "^5.0.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", diff --git a/examples/basic-server-svelte/server.ts b/examples/basic-server-svelte/server.ts index 9414285..da603ca 100644 --- a/examples/basic-server-svelte/server.ts +++ b/examples/basic-server-svelte/server.ts @@ -1,12 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -32,7 +28,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-svelte/src/App.svelte b/examples/basic-server-svelte/src/App.svelte index cabcc31..fde128d 100644 --- a/examples/basic-server-svelte/src/App.svelte +++ b/examples/basic-server-svelte/src/App.svelte @@ -7,7 +7,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; function extractTime(result: CallToolResult): string { const { text } = result.content?.find((c) => c.type === "text")!; diff --git a/examples/basic-server-svelte/tsconfig.json b/examples/basic-server-svelte/tsconfig.json index 4fd3cd7..5edb66a 100644 --- a/examples/basic-server-svelte/tsconfig.json +++ b/examples/basic-server-svelte/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-svelte/tsconfig.server.json b/examples/basic-server-svelte/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/basic-server-svelte/tsconfig.server.json +++ b/examples/basic-server-svelte/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/basic-server-vanillajs/main.ts b/examples/basic-server-vanillajs/main.ts index 4de7331..286fa34 100644 --- a/examples/basic-server-vanillajs/main.ts +++ b/examples/basic-server-vanillajs/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-vanillajs/package.json b/examples/basic-server-vanillajs/package.json index ebfebbb..5d47f2c 100644 --- a/examples/basic-server-vanillajs/package.json +++ b/examples/basic-server-vanillajs/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-basic-vanillajs", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Basic MCP App Server example using vanilla JavaScript", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/basic-server-vanillajs" }, "license": "MIT", @@ -24,14 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 693d861..5b2daf7 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -1,9 +1,6 @@ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -32,7 +29,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: z.object({}), + inputSchema: {}, outputSchema: z.object({ time: z.string(), }), diff --git a/examples/basic-server-vanillajs/src/mcp-app.ts b/examples/basic-server-vanillajs/src/mcp-app.ts index 6b5084a..c3168c2 100644 --- a/examples/basic-server-vanillajs/src/mcp-app.ts +++ b/examples/basic-server-vanillajs/src/mcp-app.ts @@ -8,7 +8,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import "./global.css"; import "./mcp-app.css"; diff --git a/examples/basic-server-vanillajs/tsconfig.json b/examples/basic-server-vanillajs/tsconfig.json index b028ff5..535267b 100644 --- a/examples/basic-server-vanillajs/tsconfig.json +++ b/examples/basic-server-vanillajs/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-vanillajs/tsconfig.server.json b/examples/basic-server-vanillajs/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/basic-server-vanillajs/tsconfig.server.json +++ b/examples/basic-server-vanillajs/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/basic-server-vue/main.ts b/examples/basic-server-vue/main.ts index d774269..669a071 100644 --- a/examples/basic-server-vue/main.ts +++ b/examples/basic-server-vue/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/basic-server-vue/package.json b/examples/basic-server-vue/package.json index 858dd89..d27ddc9 100644 --- a/examples/basic-server-vue/package.json +++ b/examples/basic-server-vue/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-basic-vue", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Basic MCP App Server example using Vue", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/basic-server-vue" }, "license": "MIT", @@ -24,15 +24,12 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "vue": "^3.5.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/basic-server-vue/server.ts b/examples/basic-server-vue/server.ts index 9adb911..5aa72de 100644 --- a/examples/basic-server-vue/server.ts +++ b/examples/basic-server-vue/server.ts @@ -1,12 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -32,7 +28,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { diff --git a/examples/basic-server-vue/src/App.vue b/examples/basic-server-vue/src/App.vue index a45c262..196ad64 100644 --- a/examples/basic-server-vue/src/App.vue +++ b/examples/basic-server-vue/src/App.vue @@ -7,7 +7,7 @@ import { applyHostStyleVariables, type McpUiHostContext, } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; function extractTime(result: CallToolResult): string { const { text } = result.content?.find((c) => c.type === "text")!; diff --git a/examples/basic-server-vue/tsconfig.json b/examples/basic-server-vue/tsconfig.json index 4fd3cd7..5edb66a 100644 --- a/examples/basic-server-vue/tsconfig.json +++ b/examples/basic-server-vue/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/basic-server-vue/tsconfig.server.json b/examples/basic-server-vue/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/basic-server-vue/tsconfig.server.json +++ b/examples/basic-server-vue/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/budget-allocator-server/main.ts b/examples/budget-allocator-server/main.ts index 02c03cb..cc488f0 100644 --- a/examples/budget-allocator-server/main.ts +++ b/examples/budget-allocator-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/budget-allocator-server/package.json b/examples/budget-allocator-server/package.json index 6e41622..19a1d25 100644 --- a/examples/budget-allocator-server/package.json +++ b/examples/budget-allocator-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-budget-allocator", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Budget allocator MCP App Server with interactive visualization", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/budget-allocator-server" }, "license": "MIT", @@ -26,14 +26,12 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts index 01f5dbf..04fba11 100755 --- a/examples/budget-allocator-server/server.ts +++ b/examples/budget-allocator-server/server.ts @@ -4,11 +4,11 @@ * Provides budget configuration, 24 months of historical allocation data, * and industry benchmarks by company stage. */ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -270,7 +270,7 @@ export function createServer(): McpServer { title: "Get Budget Data", description: "Returns budget configuration with 24 months of historical allocations and industry benchmarks by company stage. The widget is interactive and exposes tools for reading/modifying allocations, adjusting budgets, and comparing against industry benchmarks.", - inputSchema: z.object({}), + inputSchema: {}, outputSchema: BudgetDataResponseSchema, _meta: { ui: { resourceUri } }, }, diff --git a/examples/budget-allocator-server/tsconfig.json b/examples/budget-allocator-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/budget-allocator-server/tsconfig.json +++ b/examples/budget-allocator-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/budget-allocator-server/tsconfig.server.json b/examples/budget-allocator-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/budget-allocator-server/tsconfig.server.json +++ b/examples/budget-allocator-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/cohort-heatmap-server/main.ts b/examples/cohort-heatmap-server/main.ts index 8b33603..d59903b 100644 --- a/examples/cohort-heatmap-server/main.ts +++ b/examples/cohort-heatmap-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/cohort-heatmap-server/package.json b/examples/cohort-heatmap-server/package.json index 7e2d2e2..8d1268b 100644 --- a/examples/cohort-heatmap-server/package.json +++ b/examples/cohort-heatmap-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-cohort-heatmap", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Cohort heatmap MCP App Server for retention analysis", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/cohort-heatmap-server" }, "license": "MIT", @@ -26,15 +26,13 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts index 129e2dc..6d07144 100644 --- a/examples/cohort-heatmap-server/server.ts +++ b/examples/cohort-heatmap-server/server.ts @@ -1,7 +1,5 @@ -import { - McpServer, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -180,8 +178,8 @@ export function createServer(): McpServer { title: "Get Cohort Retention Data", description: "Returns cohort retention heatmap data showing customer retention over time by signup month", - inputSchema: GetCohortDataInputSchema, - outputSchema: CohortDataSchema, + inputSchema: GetCohortDataInputSchema.shape, + outputSchema: CohortDataSchema.shape, _meta: { ui: { resourceUri } }, }, async ({ metric, periodType, cohortCount, maxPeriods }) => { diff --git a/examples/cohort-heatmap-server/tsconfig.json b/examples/cohort-heatmap-server/tsconfig.json index 3ecbdad..fc3c210 100644 --- a/examples/cohort-heatmap-server/tsconfig.json +++ b/examples/cohort-heatmap-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/cohort-heatmap-server/tsconfig.server.json b/examples/cohort-heatmap-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/cohort-heatmap-server/tsconfig.server.json +++ b/examples/cohort-heatmap-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/customer-segmentation-server/main.ts b/examples/customer-segmentation-server/main.ts index c788757..130076c 100644 --- a/examples/customer-segmentation-server/main.ts +++ b/examples/customer-segmentation-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/customer-segmentation-server/package.json b/examples/customer-segmentation-server/package.json index bac3b89..4511de9 100644 --- a/examples/customer-segmentation-server/package.json +++ b/examples/customer-segmentation-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-customer-segmentation", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Customer segmentation MCP App Server with filtering", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/customer-segmentation-server" }, "license": "MIT", @@ -26,14 +26,12 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts index 7cc1c1f..244415d 100644 --- a/examples/customer-segmentation-server/server.ts +++ b/examples/customer-segmentation-server/server.ts @@ -1,8 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -100,8 +100,8 @@ export function createServer(): McpServer { title: "Get Customer Data", description: "Returns customer data with segment information for visualization. Optionally filter by segment.", - inputSchema: GetCustomerDataInputSchema, - outputSchema: GetCustomerDataOutputSchema, + inputSchema: GetCustomerDataInputSchema.shape, + outputSchema: GetCustomerDataOutputSchema.shape, _meta: { ui: { resourceUri } }, }, async ({ segment }): Promise => { diff --git a/examples/customer-segmentation-server/tsconfig.json b/examples/customer-segmentation-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/customer-segmentation-server/tsconfig.json +++ b/examples/customer-segmentation-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/customer-segmentation-server/tsconfig.server.json b/examples/customer-segmentation-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/customer-segmentation-server/tsconfig.server.json +++ b/examples/customer-segmentation-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/debug-server/main.ts b/examples/debug-server/main.ts index 9fb0825..3c89a97 100644 --- a/examples/debug-server/main.ts +++ b/examples/debug-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/debug-server/package.json b/examples/debug-server/package.json index e28f4d3..3417a8f 100644 --- a/examples/debug-server/package.json +++ b/examples/debug-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-debug", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Debug MCP App Server for testing all SDK capabilities", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/debug-server" }, "license": "MIT", @@ -34,11 +34,9 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", - "zod": "^4.2.0" + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/debug-server/server.ts b/examples/debug-server/server.ts index 068f94d..fd3452a 100644 --- a/examples/debug-server/server.ts +++ b/examples/debug-server/server.ts @@ -3,11 +3,11 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import { appendFileSync } from "node:fs"; import path from "node:path"; diff --git a/examples/debug-server/tsconfig.json b/examples/debug-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/debug-server/tsconfig.json +++ b/examples/debug-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/debug-server/tsconfig.server.json b/examples/debug-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/debug-server/tsconfig.server.json +++ b/examples/debug-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/integration-server/main.ts b/examples/integration-server/main.ts index 9a42842..c45787e 100644 --- a/examples/integration-server/main.ts +++ b/examples/integration-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/integration-server/package.json b/examples/integration-server/package.json index 7e04f4a..f6715a4 100644 --- a/examples/integration-server/package.json +++ b/examples/integration-server/package.json @@ -1,6 +1,6 @@ { "name": "integration-server", - "version": "2.0.0", + "version": "1.7.4", "private": true, "type": "module", "scripts": { @@ -15,16 +15,13 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/integration-server/server.ts b/examples/integration-server/server.ts index 346f4f7..e44689d 100644 --- a/examples/integration-server/server.ts +++ b/examples/integration-server/server.ts @@ -3,11 +3,11 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -33,7 +33,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time.", - inputSchema: z.object({}), + inputSchema: {}, outputSchema: z.object({ time: z.string(), }), @@ -72,7 +72,7 @@ export function createServer(): McpServer { ); // Sample downloadable resource — used to demo ResourceLink in ui/download-file - server.registerResource( + server.resource( SAMPLE_DOWNLOAD_URI, SAMPLE_DOWNLOAD_URI, { diff --git a/examples/integration-server/src/mcp-app.tsx b/examples/integration-server/src/mcp-app.tsx index 281a356..7f0b18c 100644 --- a/examples/integration-server/src/mcp-app.tsx +++ b/examples/integration-server/src/mcp-app.tsx @@ -3,7 +3,7 @@ */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { StrictMode, useCallback, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import styles from "./mcp-app.module.css"; diff --git a/examples/integration-server/tsconfig.json b/examples/integration-server/tsconfig.json index 3ecbdad..fc3c210 100644 --- a/examples/integration-server/tsconfig.json +++ b/examples/integration-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/integration-server/tsconfig.server.json b/examples/integration-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/integration-server/tsconfig.server.json +++ b/examples/integration-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/lazy-auth-server/package.json b/examples/lazy-auth-server/package.json index dbd4601..f3ed6cd 100644 --- a/examples/lazy-auth-server/package.json +++ b/examples/lazy-auth-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-lazy-auth", - "version": "2.0.0", + "version": "1.7.4", "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": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/lazy-auth-server" }, "license": "MIT", @@ -22,13 +22,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "jose": "^6.0.0", - "zod": "^4.2.0" + "jose": "^6.0.0" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/lazy-auth-server/server.ts b/examples/lazy-auth-server/server.ts index 5240008..fe7b1ac 100644 --- a/examples/lazy-auth-server/server.ts +++ b/examples/lazy-auth-server/server.ts @@ -21,13 +21,13 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import { - McpServer, - UrlElicitationRequiredError, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { UrlElicitationRequiredError } from "@modelcontextprotocol/sdk/types.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import cors from "cors"; import express, { type Express, type Request, type Response } from "express"; import { SignJWT, jwtVerify } from "jose"; @@ -35,7 +35,6 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js). Derived // from import.meta.url rather than import.meta.filename/dirname, which are @@ -415,36 +414,20 @@ async function handleAuthorize(req: Request, res: Response) { if (state) denyUrl.searchParams.set("state", state); res.type("text/html").send(/*html*/ ` Authorize - +button{padding:10px 20px;margin:4px;font-size:15px;font-weight:bold;border:none;border-radius:6px;cursor:pointer} +.approve{background:#1a7a3e;color:#fff}.deny{background:#b91c1c;color:#fff}

🔑 Mock Authorization

An application is requesting access:

Client
${escapeHtml(client_id ?? "(none)")}
Scope
${escapeHtml(scope ?? "(default)")}
Redirect
${escapeHtml(redirect_uri)}
- -
- -`); + + +`); return; } @@ -622,7 +605,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Show Auth Button", description: "Public tool: shows a button that triggers the protected get_secret tool.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri: buttonUri } }, }, async (): Promise => ({ @@ -655,7 +638,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Get Secret", description: "Protected tool: returns secret data. Requires authentication.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri: secretUri } }, }, async (): Promise => { @@ -699,7 +682,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Revoke Auth Token", description: "Protected tool: revokes the caller's entire auth session (access + refresh token).", - inputSchema: z.object({}), + inputSchema: {}, }, async (): Promise => { if (!authInfo) { @@ -737,7 +720,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Elicit URL (session-callback)", description: "URL elicitation via elicitInput. Blocks until the client accepts; sends ElicitCompleteNotification before returning.", - inputSchema: z.object({}), + inputSchema: {}, }, async (_args, extra): Promise => { const eid = crypto.randomUUID(); @@ -752,7 +735,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { message: "Please open this URL to continue.", elicitationId: eid, }, - { relatedRequestId: extra.mcpReq.id, timeout: 300_000 }, + { relatedRequestId: extra.requestId, timeout: 300_000 }, ); // Send completion notification before returning — lands on the same SSE // stream as this tool response. For Streamable HTTP this is the ONLY @@ -784,7 +767,7 @@ export function createServer(authInfo?: AuthInfo, req?: Request): McpServer { title: "Elicit by -32042 Error", description: "Throws UrlElicitationRequiredError (-32042). Client must open the URL, then retry. On retry, if the last-issued elicitationId was completed, sends ElicitCompleteNotification and succeeds.", - inputSchema: z.object({}), + inputSchema: {}, }, async (): Promise => { // Retry path: check if the user completed the elicitation we last issued. @@ -1048,7 +1031,7 @@ export function createApp(): Express { } const server = createServer(authInfo, req); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); res.on("close", () => { diff --git a/examples/lazy-auth-server/src/mcp-app.css b/examples/lazy-auth-server/src/mcp-app.css index 800b224..ba4f323 100644 --- a/examples/lazy-auth-server/src/mcp-app.css +++ b/examples/lazy-auth-server/src/mcp-app.css @@ -17,17 +17,9 @@ display: flex; flex-wrap: wrap; gap: var(--spacing-sm); - /* Keep a long press on or near the buttons from starting text selection - or raising the iOS callout menu instead of a click. */ - -webkit-user-select: none; - user-select: none; - -webkit-touch-callout: none; } button { - /* Match the consent page's 56px tap targets (server.ts .btn). */ - min-height: 56px; - min-width: 56px; padding: var(--spacing-sm) var(--spacing-md); border: none; border-radius: var(--border-radius-md); @@ -35,20 +27,12 @@ button { font-weight: var(--font-weight-bold); background-color: var(--color-accent); cursor: pointer; - /* Suppress the double-tap-to-zoom gesture so a tap resolves to a click - immediately instead of waiting to see if a second tap follows. */ - touch-action: manipulation; - -webkit-tap-highlight-color: transparent; &:disabled { opacity: 0.5; cursor: not-allowed; } - &:active:not(:disabled) { - filter: brightness(0.85); - } - &:hover:not(:disabled) { background-color: color-mix( in srgb, diff --git a/examples/lazy-auth-server/tsconfig.json b/examples/lazy-auth-server/tsconfig.json index d6a42ce..6c553b5 100644 --- a/examples/lazy-auth-server/tsconfig.json +++ b/examples/lazy-auth-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/lazy-auth-server/tsconfig.server.json b/examples/lazy-auth-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/lazy-auth-server/tsconfig.server.json +++ b/examples/lazy-auth-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/map-server/README.md b/examples/map-server/README.md index e1e044a..4c4de5f 100644 --- a/examples/map-server/README.md +++ b/examples/map-server/README.md @@ -122,7 +122,7 @@ Vanilla TypeScript app that: - [`server.ts`](server.ts) - MCP server with geocode and show-map tools - [`mcp-app.html`](mcp-app.html) / [`src/mcp-app.ts`](src/mcp-app.ts) - CesiumJS globe UI -- [`main.ts`](main.ts) - HTTP and stdio server utilities +- [`server-utils.ts`](server-utils.ts) - HTTP server utilities ## Notes diff --git a/examples/map-server/main.ts b/examples/map-server/main.ts index 8038394..f91f57b 100644 --- a/examples/map-server/main.ts +++ b/examples/map-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/map-server/package.json b/examples/map-server/package.json index cc4585f..5dc23fe 100644 --- a/examples/map-server/package.json +++ b/examples/map-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-map", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "MCP App Server example with CesiumJS 3D globe and geocoding", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/map-server" }, "license": "MIT", @@ -26,14 +26,11 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/map-server/server.ts b/examples/map-server/server.ts index 5767d46..10125a6 100644 --- a/examples/map-server/server.ts +++ b/examples/map-server/server.ts @@ -5,11 +5,11 @@ * - geocode: Search for places using OpenStreetMap Nominatim * - show-map: Display an interactive 3D globe at a given location */ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -148,7 +148,7 @@ export function createServer(): McpServer { title: "Show Map", description: "Display an interactive world map zoomed to a specific bounding box. Use the GeoCode tool to find the bounding box of a location. The widget is interactive and exposes tools for navigation (fly to locations) and querying the current view.", - inputSchema: z.object({ + inputSchema: { west: z .number() .optional() @@ -173,7 +173,7 @@ export function createServer(): McpServer { .string() .optional() .describe("Optional label to display on the map"), - }), + }, _meta: { ui: { resourceUri } }, }, async ({ west, south, east, north, label }): Promise => ({ @@ -196,13 +196,13 @@ export function createServer(): McpServer { title: "Geocode", description: "Search for places using OpenStreetMap. Returns coordinates and bounding boxes for up to 5 matches.", - inputSchema: z.object({ + inputSchema: { query: z .string() .describe( "Place name or address to search for (e.g., 'Paris', 'Golden Gate Bridge', '1600 Pennsylvania Ave')", ), - }), + }, }, async ({ query }): Promise => { try { diff --git a/examples/map-server/src/mcp-app.ts b/examples/map-server/src/mcp-app.ts index d3090c7..39d7c2a 100644 --- a/examples/map-server/src/mcp-app.ts +++ b/examples/map-server/src/mcp-app.ts @@ -6,7 +6,7 @@ * a navigate-to tool for the host to control navigation. */ import { App } from "@modelcontextprotocol/ext-apps"; -import type { ContentBlock } from "@modelcontextprotocol/client"; +import type { ContentBlock } from "@modelcontextprotocol/sdk/spec.types.js"; import { z } from "zod"; // TypeScript declaration for Cesium loaded from CDN diff --git a/examples/map-server/tsconfig.json b/examples/map-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/map-server/tsconfig.json +++ b/examples/map-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/map-server/tsconfig.server.json b/examples/map-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/map-server/tsconfig.server.json +++ b/examples/map-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/pdf-server/build-mcpb.mjs b/examples/pdf-server/build-mcpb.mjs index 2917334..c1c3469 100644 --- a/examples/pdf-server/build-mcpb.mjs +++ b/examples/pdf-server/build-mcpb.mjs @@ -43,21 +43,6 @@ writeFileSync( path.join(stage, "manifest.json"), JSON.stringify(manifest, null, 2), ); -// The bundle must ship the ext-apps build from this checkout, not whatever -// the registry has: pack the repo root and point the staged manifest at it. -const repoRoot = path.resolve(here, "..", ".."); -const packed = JSON.parse( - execSync( - "npm pack --json --ignore-scripts --pack-destination " + - JSON.stringify(stage), - { - cwd: repoRoot, - encoding: "utf8", - }, - ).replace(/^[^[]*/, ""), -); -pkg.dependencies["@modelcontextprotocol/ext-apps"] = - "file:./" + packed[0].filename; writeFileSync(path.join(stage, "package.json"), JSON.stringify(pkg, null, 2)); const run = (cmd) => execSync(cmd, { cwd: stage, stdio: "inherit" }); diff --git a/examples/pdf-server/main.ts b/examples/pdf-server/main.ts index bb7b57f..1ff848a 100644 --- a/examples/pdf-server/main.ts +++ b/examples/pdf-server/main.ts @@ -6,10 +6,10 @@ import fs from "node:fs"; import path from "node:path"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { @@ -39,7 +39,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/pdf-server/package.json b/examples/pdf-server/package.json index c8d9cf8..4d960f1 100644 --- a/examples/pdf-server/package.json +++ b/examples/pdf-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-pdf", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "MCP server for loading and extracting text from PDF files with chunked pagination and interactive viewer", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/pdf-server" }, "license": "MIT", @@ -26,15 +26,12 @@ }, "dependencies": { "@cantoo/pdf-lib": "^2.6.5", - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "pdfjs-dist": "^5.0.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/pdf-server/server.test.ts b/examples/pdf-server/server.test.ts index 0b2a5eb..33a03f1 100644 --- a/examples/pdf-server/server.test.ts +++ b/examples/pdf-server/server.test.ts @@ -2,7 +2,8 @@ import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs"; import { PDFDocument } from "@cantoo/pdf-lib"; import { makeRandomJpeg } from "../../tests/helpers/range-counting-server"; diff --git a/examples/pdf-server/server.ts b/examples/pdf-server/server.ts index e67cd47..93dedfd 100644 --- a/examples/pdf-server/server.ts +++ b/examples/pdf-server/server.ts @@ -13,19 +13,18 @@ import { randomUUID } from "crypto"; import fs from "node:fs"; import path from "node:path"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; import { - McpServer, + RootsListChangedNotificationSchema, type CallToolResult, - type ElicitResult, - type PrimitiveSchemaDefinition, type ReadResourceResult, - type Server, -} from "@modelcontextprotocol/server"; +} from "@modelcontextprotocol/sdk/types.js"; // Stub DOMMatrix/ImageData/Path2D before pdfjs-dist loads — its legacy // build instantiates DOMMatrix at module scope and the @napi-rs/canvas // polyfill is unreliable under npx. See ./pdfjs-polyfill.ts for details. @@ -63,6 +62,10 @@ class FetchStandardFontDataFactory { return new Uint8Array(await res.arrayBuffer()); } } +import type { + PrimitiveSchemaDefinition, + ElicitResult, +} from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; // ============================================================================= @@ -1279,7 +1282,7 @@ export function createServer(options: CreateServerOptions = {}): McpServer { refreshRoots(server.server); }; server.server.setNotificationHandler( - "notifications/roots/list_changed", + RootsListChangedNotificationSchema, async () => { await refreshRoots(server.server); }, @@ -1289,12 +1292,10 @@ export function createServer(options: CreateServerOptions = {}): McpServer { const { readPdfRange } = sharedPdfCache; // Tool: list_pdfs - List available PDFs - server.registerTool( + server.tool( "list_pdfs", - { - description: "List available PDFs that can be displayed", - inputSchema: z.object({}), - }, + "List available PDFs that can be displayed", + {}, async (): Promise => { const seen = new Set(); const localFiles: string[] = []; @@ -1376,7 +1377,7 @@ export function createServer(options: CreateServerOptions = {}): McpServer { title: "Read PDF Bytes", description: "Read a range of bytes from a PDF (max 512KB per request). The model should NOT call this tool directly.", - inputSchema: z.object({ + inputSchema: { url: z.string().describe("PDF URL or local file path"), offset: z.number().min(0).default(0).describe("Byte offset"), byteCount: z @@ -1385,7 +1386,7 @@ export function createServer(options: CreateServerOptions = {}): McpServer { .max(MAX_CHUNK_BYTES) .default(MAX_CHUNK_BYTES) .describe("Bytes to read"), - }), + }, outputSchema: z.object({ url: z.string(), bytes: z.string().describe("Base64 encoded bytes"), @@ -1472,7 +1473,7 @@ Returns a viewUUID in structuredContent. Pass it to \`interact\`: Accepts local files (use list_pdfs), client MCP root directories, or any HTTPS URL. Set \`elicit_form_inputs\` to true to prompt the user to fill form fields before display.`, - inputSchema: z.object({ + inputSchema: { url: z .string() .default(DEFAULT_PDF) @@ -1488,7 +1489,7 @@ Set \`elicit_form_inputs\` to true to prompt the user to fill form fields before "If true and the PDF has form fields, prompt the user to fill them before displaying", ), }), - }), + }, outputSchema: z.object({ viewUUID: z .string() @@ -2425,7 +2426,7 @@ Example — add a signature image and a stamp, then screenshot to verify: **FORMS** — fill_form: fill fields with \`fields\` array of {name, value}. **SAVE** — save_as: write the annotated PDF (annotations + form values) to a file. Pass \`path\` (absolute path or file://) for a new location, or omit \`path\` to overwrite the original. Set \`overwrite: true\` to replace an existing file (always required when omitting \`path\`).`, - inputSchema: z.object({ + inputSchema: { viewUUID: z .string() .describe( @@ -2522,7 +2523,7 @@ Example — add a signature image and a stamp, then screenshot to verify: .describe( "Array of commands to execute sequentially. More efficient than separate calls. Tip: end with get_pages+getScreenshots to verify changes.", ), - }), + }, }, async ( { @@ -2599,7 +2600,7 @@ Example — add a signature image and a stamp, then screenshot to verify: const result = await processInteractCommand( uuid, commandList[i], - extra.mcpReq.signal, + extra.signal, ); if (result.isError) { const errText = result.content @@ -2656,7 +2657,7 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Submit Page Data", description: "Submit rendered page data for a get_pages request (used by viewer). The model should NOT call this tool directly.", - inputSchema: z.object({ + inputSchema: { requestId: z .string() .describe("The request ID from the get_pages command"), @@ -2669,7 +2670,7 @@ Example — add a signature image and a stamp, then screenshot to verify: }), ) .describe("Page data entries"), - }), + }, _meta: { ui: { visibility: ["app"] } }, }, async ({ requestId, pages }): Promise => { @@ -2699,7 +2700,7 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Submit Save Data", description: "Submit annotated PDF bytes for a save_as request (used by viewer). The model should NOT call this tool directly.", - inputSchema: z.object({ + inputSchema: { requestId: z .string() .describe("The request ID from the save_as command"), @@ -2708,7 +2709,7 @@ Example — add a signature image and a stamp, then screenshot to verify: .string() .optional() .describe("Error message if the viewer failed to build bytes"), - }), + }, _meta: { ui: { visibility: ["app"] } }, }, async ({ requestId, data, error }): Promise => { @@ -2738,7 +2739,7 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Submit Viewer State", description: "Submit a viewer-state snapshot for a get_viewer_state request (used by viewer). The model should NOT call this tool directly.", - inputSchema: z.object({ + inputSchema: { requestId: z .string() .describe("The request ID from the get_viewer_state command"), @@ -2750,7 +2751,7 @@ Example — add a signature image and a stamp, then screenshot to verify: .string() .optional() .describe("Error message if the viewer failed to read state"), - }), + }, _meta: { ui: { visibility: ["app"] } }, }, async ({ requestId, state, error }): Promise => { @@ -2780,9 +2781,9 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Poll PDF Commands", description: "Poll for pending commands for a PDF viewer. The model should NOT call this tool directly.", - inputSchema: z.object({ + inputSchema: { viewUUID: z.string().describe("The viewUUID of the PDF viewer"), - }), + }, _meta: { ui: { visibility: ["app"] } }, }, async ({ viewUUID: uuid }): Promise => { @@ -2827,10 +2828,10 @@ Example — add a signature image and a stamp, then screenshot to verify: title: "Save PDF", description: "Save annotated PDF bytes back to a local file. The model should NOT call this tool directly — use interact with action: save_as instead.", - inputSchema: z.object({ + inputSchema: { url: z.string().describe("Original PDF URL or local file path"), data: z.string().describe("Base64-encoded PDF bytes"), - }), + }, outputSchema: z.object({ filePath: z.string(), mtimeMs: z.number(), diff --git a/examples/pdf-server/src/mcp-app.ts b/examples/pdf-server/src/mcp-app.ts index 45e0c74..a953e24 100644 --- a/examples/pdf-server/src/mcp-app.ts +++ b/examples/pdf-server/src/mcp-app.ts @@ -12,10 +12,8 @@ import { applyDocumentTheme, applyHostStyleVariables, } from "@modelcontextprotocol/ext-apps"; -import type { - CallToolResult, - ContentBlock, -} from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import type { ContentBlock } from "@modelcontextprotocol/sdk/spec.types.js"; import { z } from "zod"; import * as pdfjsLib from "pdfjs-dist"; import { AnnotationLayer, AnnotationMode, TextLayer } from "pdfjs-dist"; diff --git a/examples/pdf-server/tsconfig.json b/examples/pdf-server/tsconfig.json index 2fe7e62..0501827 100644 --- a/examples/pdf-server/tsconfig.json +++ b/examples/pdf-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client", "bun"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/pdf-server/tsconfig.server.json b/examples/pdf-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/pdf-server/tsconfig.server.json +++ b/examples/pdf-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/qr-server/package.json b/examples/qr-server/package.json index 425728e..a5cbf5b 100644 --- a/examples/qr-server/package.json +++ b/examples/qr-server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/server-qr", - "version": "2.0.0", + "version": "1.7.4", "private": true, "scripts": { "start": "uv run server.py", @@ -8,6 +8,7 @@ "build": "echo 'No build step needed for Python server'" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "^2.0.0" + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0" } } diff --git a/examples/quickstart/main.ts b/examples/quickstart/main.ts index 7f370a8..06895a4 100644 --- a/examples/quickstart/main.ts +++ b/examples/quickstart/main.ts @@ -1,7 +1,7 @@ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +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 cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -21,7 +21,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json index 57f8444..99948cc 100644 --- a/examples/quickstart/package.json +++ b/examples/quickstart/package.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/quickstart", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "private": true, "description": "Quickstart MCP App Server example", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/quickstart" }, "license": "MIT", @@ -15,13 +15,10 @@ "start": "concurrently --raw \"cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch\" \"tsx watch main.ts\"" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", - "express": "^5.1.0", - "zod": "^4.2.0" + "express": "^5.1.0" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/quickstart/server.ts b/examples/quickstart/server.ts index 611d0f0..eb3c3b1 100644 --- a/examples/quickstart/server.ts +++ b/examples/quickstart/server.ts @@ -3,10 +3,9 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { McpServer } from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import fs from "node:fs/promises"; import path from "node:path"; -import { z } from "zod"; const DIST_DIR = path.join(import.meta.dirname, "dist"); @@ -31,7 +30,7 @@ export function createServer(): McpServer { { title: "Get Time", description: "Returns the current server time.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async () => { diff --git a/examples/quickstart/tsconfig.json b/examples/quickstart/tsconfig.json index d6a42ce..6c553b5 100644 --- a/examples/quickstart/tsconfig.json +++ b/examples/quickstart/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/quickstart/tsconfig.server.json b/examples/quickstart/tsconfig.server.json index 72552e2..7e65f5f 100644 --- a/examples/quickstart/tsconfig.server.json +++ b/examples/quickstart/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/say-server/package.json b/examples/say-server/package.json index cc12249..a0076e6 100644 --- a/examples/say-server/package.json +++ b/examples/say-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-say", - "version": "2.0.0", + "version": "1.7.4", "private": true, "description": "Streaming TTS MCP App Server with karaoke-style text highlighting", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/say-server" }, "license": "MIT", @@ -15,6 +15,7 @@ "build": "echo 'No build step needed for Python server'" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "^2.0.0" + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0" } } diff --git a/examples/scenario-modeler-server/main.ts b/examples/scenario-modeler-server/main.ts index cc20e63..dbd3ab6 100644 --- a/examples/scenario-modeler-server/main.ts +++ b/examples/scenario-modeler-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/scenario-modeler-server/package.json b/examples/scenario-modeler-server/package.json index e6f37c0..aa1e668 100644 --- a/examples/scenario-modeler-server/package.json +++ b/examples/scenario-modeler-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-scenario-modeler", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Financial scenario modeling MCP App Server", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/scenario-modeler-server" }, "license": "MIT", @@ -26,16 +26,14 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/scenario-modeler-server/server.ts b/examples/scenario-modeler-server/server.ts index c5fa05d..b59851c 100644 --- a/examples/scenario-modeler-server/server.ts +++ b/examples/scenario-modeler-server/server.ts @@ -3,11 +3,11 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -305,8 +305,8 @@ export function createServer(): McpServer { title: "Get Scenario Data", description: "Returns SaaS scenario templates and optionally computes custom projections for given inputs", - inputSchema: GetScenarioDataInputSchema, - outputSchema: GetScenarioDataOutputSchema, + inputSchema: GetScenarioDataInputSchema.shape, + outputSchema: GetScenarioDataOutputSchema.shape, _meta: { ui: { resourceUri } }, }, async (args: { diff --git a/examples/scenario-modeler-server/tsconfig.json b/examples/scenario-modeler-server/tsconfig.json index 3ecbdad..fc3c210 100644 --- a/examples/scenario-modeler-server/tsconfig.json +++ b/examples/scenario-modeler-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/scenario-modeler-server/tsconfig.server.json b/examples/scenario-modeler-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/scenario-modeler-server/tsconfig.server.json +++ b/examples/scenario-modeler-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/shadertoy-server/main.ts b/examples/shadertoy-server/main.ts index 7e388f0..ac8f6ad 100644 --- a/examples/shadertoy-server/main.ts +++ b/examples/shadertoy-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/shadertoy-server/package.json b/examples/shadertoy-server/package.json index aec56c6..ccf15a4 100644 --- a/examples/shadertoy-server/package.json +++ b/examples/shadertoy-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-shadertoy", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "MCP App Server example for rendering ShaderToy-compatible GLSL shaders", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/shadertoy-server" }, "license": "MIT", @@ -24,13 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/shadertoy-server/server.ts b/examples/shadertoy-server/server.ts index 50c27bc..5238c40 100644 --- a/examples/shadertoy-server/server.ts +++ b/examples/shadertoy-server/server.ts @@ -3,11 +3,11 @@ import { registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; diff --git a/examples/shadertoy-server/tsconfig.json b/examples/shadertoy-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/shadertoy-server/tsconfig.json +++ b/examples/shadertoy-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/shadertoy-server/tsconfig.server.json b/examples/shadertoy-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/shadertoy-server/tsconfig.server.json +++ b/examples/shadertoy-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/sheet-music-server/main.ts b/examples/sheet-music-server/main.ts index d7bf3a0..8b143a7 100644 --- a/examples/sheet-music-server/main.ts +++ b/examples/sheet-music-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/sheet-music-server/package.json b/examples/sheet-music-server/package.json index 85ca6bb..ca9fa90 100644 --- a/examples/sheet-music-server/package.json +++ b/examples/sheet-music-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-sheet-music", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "MCP App Server for rendering and playing sheet music from ABC notation", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/sheet-music-server" }, "license": "MIT", @@ -24,14 +24,12 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "abcjs": "^6.4.4", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/sheet-music-server/server.ts b/examples/sheet-music-server/server.ts index be7214a..cfd319b 100644 --- a/examples/sheet-music-server/server.ts +++ b/examples/sheet-music-server/server.ts @@ -1,8 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; diff --git a/examples/sheet-music-server/tsconfig.json b/examples/sheet-music-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/sheet-music-server/tsconfig.json +++ b/examples/sheet-music-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/sheet-music-server/tsconfig.server.json b/examples/sheet-music-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/sheet-music-server/tsconfig.server.json +++ b/examples/sheet-music-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/system-monitor-server/main.ts b/examples/system-monitor-server/main.ts index c55c799..365a560 100644 --- a/examples/system-monitor-server/main.ts +++ b/examples/system-monitor-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/system-monitor-server/package.json b/examples/system-monitor-server/package.json index 2dc3ef1..12e8cea 100644 --- a/examples/system-monitor-server/package.json +++ b/examples/system-monitor-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-system-monitor", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "System monitor MCP App Server with real-time stats", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/system-monitor-server" }, "license": "MIT", @@ -26,15 +26,13 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", "systeminformation": "^5.31.6", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/system-monitor-server/server.ts b/examples/system-monitor-server/server.ts index a17bcf6..8fac5f2 100644 --- a/examples/system-monitor-server/server.ts +++ b/examples/system-monitor-server/server.ts @@ -3,11 +3,11 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; @@ -135,8 +135,8 @@ export function createServer(): McpServer { title: "Get System Info", description: "Returns system information, including hostname, platform, CPU info, and memory.", - inputSchema: z.object({}), - outputSchema: SystemInfoSchema, + inputSchema: {}, + outputSchema: SystemInfoSchema.shape, _meta: { ui: { resourceUri } }, }, (): CallToolResult => { @@ -156,8 +156,8 @@ export function createServer(): McpServer { title: "Poll System Stats", description: "Returns dynamic system metrics for polling: per-core CPU timing, memory usage, and uptime. App-only.", - inputSchema: z.object({}), - outputSchema: PollStatsSchema, + inputSchema: {}, + outputSchema: PollStatsSchema.shape, _meta: { ui: { visibility: ["app"] } }, }, async (): Promise => { diff --git a/examples/system-monitor-server/tsconfig.json b/examples/system-monitor-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/system-monitor-server/tsconfig.json +++ b/examples/system-monitor-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/system-monitor-server/tsconfig.server.json b/examples/system-monitor-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/system-monitor-server/tsconfig.server.json +++ b/examples/system-monitor-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/threejs-server/main.ts b/examples/threejs-server/main.ts index ef16e26..13a2084 100644 --- a/examples/threejs-server/main.ts +++ b/examples/threejs-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/threejs-server/package.json b/examples/threejs-server/package.json index 68864d9..cfb5686 100644 --- a/examples/threejs-server/package.json +++ b/examples/threejs-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-threejs", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Three.js 3D visualization MCP App Server", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/threejs-server" }, "license": "MIT", @@ -26,17 +26,14 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", "three": "^0.181.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index 0a13c19..ae320df 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -8,10 +8,8 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -163,7 +161,7 @@ export function createServer(): McpServer { title: "Show Three.js Scene", description: "Render an interactive 3D scene with custom Three.js code. Supports transparent backgrounds (alpha: true) for seamless host UI integration. Available globals: THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass, canvas, width, height.", - inputSchema: z.object({ + inputSchema: { code: z .string() .default(DEFAULT_THREEJS_CODE) @@ -174,7 +172,7 @@ export function createServer(): McpServer { .positive() .default(400) .describe("Height in pixels"), - }), + }, outputSchema: z.object({ success: z.boolean(), }), @@ -194,7 +192,7 @@ export function createServer(): McpServer { { title: "Learn Three.js", description: "Get documentation and examples for using the Three.js View", - inputSchema: z.object({}), + inputSchema: {}, }, async () => { return { diff --git a/examples/threejs-server/src/mcp-app-wrapper.tsx b/examples/threejs-server/src/mcp-app-wrapper.tsx index 44fee0c..34194b1 100644 --- a/examples/threejs-server/src/mcp-app-wrapper.tsx +++ b/examples/threejs-server/src/mcp-app-wrapper.tsx @@ -6,7 +6,7 @@ */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { StrictMode, useState, useCallback, useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; import { z } from "zod"; diff --git a/examples/threejs-server/tsconfig.json b/examples/threejs-server/tsconfig.json index 3ecbdad..fc3c210 100644 --- a/examples/threejs-server/tsconfig.json +++ b/examples/threejs-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/threejs-server/tsconfig.server.json b/examples/threejs-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/threejs-server/tsconfig.server.json +++ b/examples/threejs-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/transcript-server/README.md b/examples/transcript-server/README.md index 559ffad..3b3fa42 100644 --- a/examples/transcript-server/README.md +++ b/examples/transcript-server/README.md @@ -56,7 +56,7 @@ To test local modifications, use this configuration (replace `~/code/ext-apps` w ### Prerequisites -- Node.js 20+ +- Node.js 18+ - Chrome, Edge, or Safari (Web Speech API support) ### Installation diff --git a/examples/transcript-server/main.ts b/examples/transcript-server/main.ts index e86ad7c..68db8dc 100644 --- a/examples/transcript-server/main.ts +++ b/examples/transcript-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -25,7 +25,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/transcript-server/package.json b/examples/transcript-server/package.json index 46a8f98..dcba3d7 100644 --- a/examples/transcript-server/package.json +++ b/examples/transcript-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-transcript", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "MCP App Server for live speech transcription", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/transcript-server" }, "license": "MIT", @@ -24,13 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/transcript-server/server.ts b/examples/transcript-server/server.ts index 863eb29..56bf88d 100644 --- a/examples/transcript-server/server.ts +++ b/examples/transcript-server/server.ts @@ -1,8 +1,8 @@ -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { @@ -10,7 +10,6 @@ import { registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import { z } from "zod"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") @@ -34,7 +33,7 @@ export function createServer(): McpServer { title: "Transcribe Speech", description: "Opens a live speech transcription interface using the Web Speech API.", - inputSchema: z.object({}), + inputSchema: {}, _meta: { ui: { resourceUri } }, }, async (): Promise => { diff --git a/examples/transcript-server/tsconfig.json b/examples/transcript-server/tsconfig.json index 00e4801..535267b 100644 --- a/examples/transcript-server/tsconfig.json +++ b/examples/transcript-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client", "dom-speech-recognition"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/transcript-server/tsconfig.server.json b/examples/transcript-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/transcript-server/tsconfig.server.json +++ b/examples/transcript-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/video-resource-server/main.ts b/examples/video-resource-server/main.ts index 1983373..082fa5a 100644 --- a/examples/video-resource-server/main.ts +++ b/examples/video-resource-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/video-resource-server/package.json b/examples/video-resource-server/package.json index 4a7a61f..b0d04af 100644 --- a/examples/video-resource-server/package.json +++ b/examples/video-resource-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-video-resource", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "MCP App Server demonstrating video resources served as base64 blobs", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/video-resource-server" }, "license": "MIT", @@ -24,14 +24,11 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/video-resource-server/server.ts b/examples/video-resource-server/server.ts index 8ae6240..2cb317e 100644 --- a/examples/video-resource-server/server.ts +++ b/examples/video-resource-server/server.ts @@ -12,9 +12,11 @@ import { import { McpServer, ResourceTemplate, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +} from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; @@ -133,14 +135,14 @@ Available videos: ${Object.entries(VIDEO_LIBRARY) .map(([id, v]) => `- ${id}: ${v.description}`) .join("\n")}`, - inputSchema: z.object({ + inputSchema: { videoId: z .enum(Object.keys(VIDEO_LIBRARY) as [string, ...string[]]) .default("bunny-1mb") .describe( `Video ID to play. Available: ${Object.keys(VIDEO_LIBRARY).join(", ")}`, ), - }), + }, outputSchema: z.object({ videoUri: z.string(), description: z.string(), diff --git a/examples/video-resource-server/src/mcp-app.ts b/examples/video-resource-server/src/mcp-app.ts index 2a3f8f9..88ed913 100644 --- a/examples/video-resource-server/src/mcp-app.ts +++ b/examples/video-resource-server/src/mcp-app.ts @@ -5,7 +5,7 @@ * The video is served as a base64 blob and converted to a data URI for playback. */ import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import "./global.css"; import "./mcp-app.css"; diff --git a/examples/video-resource-server/tsconfig.json b/examples/video-resource-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/video-resource-server/tsconfig.json +++ b/examples/video-resource-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/video-resource-server/tsconfig.server.json b/examples/video-resource-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/video-resource-server/tsconfig.server.json +++ b/examples/video-resource-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/examples/wiki-explorer-server/main.ts b/examples/wiki-explorer-server/main.ts index bfeeffb..cc7b3a2 100644 --- a/examples/wiki-explorer-server/main.ts +++ b/examples/wiki-explorer-server/main.ts @@ -4,10 +4,10 @@ * Or: node dist/index.js [--stdio] */ -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; -import { createMcpExpressApp } from "@modelcontextprotocol/express"; -import type { McpServer } from "@modelcontextprotocol/server"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; @@ -27,7 +27,7 @@ export async function startStreamableHTTPServer( app.all("/mcp", async (req: Request, res: Response) => { const server = createServer(); - const transport = new NodeStreamableHTTPServerTransport({ + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); diff --git a/examples/wiki-explorer-server/package.json b/examples/wiki-explorer-server/package.json index 52304cf..fe511ad 100644 --- a/examples/wiki-explorer-server/package.json +++ b/examples/wiki-explorer-server/package.json @@ -1,11 +1,11 @@ { "name": "@modelcontextprotocol/server-wiki-explorer", - "version": "2.0.0", + "version": "1.7.4", "type": "module", "description": "Wikipedia link explorer MCP App Server with graph visualization", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git", + "url": "https://github.com/modelcontextprotocol/ext-apps", "directory": "examples/wiki-explorer-server" }, "license": "MIT", @@ -26,15 +26,12 @@ "serve": "bun --watch main.ts" }, "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cheerio": "^1.0.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/examples/wiki-explorer-server/server.ts b/examples/wiki-explorer-server/server.ts index 8bc4eb7..59d5e18 100644 --- a/examples/wiki-explorer-server/server.ts +++ b/examples/wiki-explorer-server/server.ts @@ -3,11 +3,11 @@ import { registerAppResource, registerAppTool, } from "@modelcontextprotocol/ext-apps/server"; -import { - McpServer, - type CallToolResult, - type ReadResourceResult, -} from "@modelcontextprotocol/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + CallToolResult, + ReadResourceResult, +} from "@modelcontextprotocol/sdk/types.js"; import * as cheerio from "cheerio"; import fs from "node:fs/promises"; import path from "node:path"; diff --git a/examples/wiki-explorer-server/src/mcp-app.ts b/examples/wiki-explorer-server/src/mcp-app.ts index 9833d9c..291292d 100644 --- a/examples/wiki-explorer-server/src/mcp-app.ts +++ b/examples/wiki-explorer-server/src/mcp-app.ts @@ -2,7 +2,7 @@ * Wiki Explorer - Force-directed graph visualization of Wikipedia link networks */ import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps"; -import type { CallToolResult } from "@modelcontextprotocol/client"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { forceCenter, diff --git a/examples/wiki-explorer-server/tsconfig.json b/examples/wiki-explorer-server/tsconfig.json index b028ff5..535267b 100644 --- a/examples/wiki-explorer-server/tsconfig.json +++ b/examples/wiki-explorer-server/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "vite/client"], "target": "ESNext", "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", diff --git a/examples/wiki-explorer-server/tsconfig.server.json b/examples/wiki-explorer-server/tsconfig.server.json index 27eb28f..05ddd8e 100644 --- a/examples/wiki-explorer-server/tsconfig.server.json +++ b/examples/wiki-explorer-server/tsconfig.server.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node"], "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", diff --git a/package-lock.json b/package-lock.json index 0189ca2..29e9058 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/ext-apps", - "version": "2.0.0", + "version": "1.7.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/ext-apps", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "workspaces": [ "examples/*" @@ -16,10 +16,7 @@ }, "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/core": "2.0.0", - "@modelcontextprotocol/sdk": "1.30.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -34,7 +31,6 @@ "electron-to-chromium": "1.5.267", "esbuild": "^0.25.12", "express": "^5.1.0", - "ext-apps-v1": "npm:@modelcontextprotocol/ext-apps@1.7.5", "husky": "^9.1.7", "nodemon": "^3.1.0", "playwright": "1.57.0", @@ -42,29 +38,24 @@ "prettier": "^3.6.2", "react": "^19.2.0", "react-dom": "^19.2.0", - "sharp": "^0.35.3", + "sharp": "^0.34.5", "ts-to-zod": "^5.1.0", "tsx": "^4.21.0", "typedoc": "^0.28.14", "typedoc-github-theme": "^0.4.0", "typescript": "^5.9.3", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "engines": { "node": ">=20" }, "peerDependencies": { - "@modelcontextprotocol/client": "^2.0.0", - "@modelcontextprotocol/core": "^2.0.0", - "@modelcontextprotocol/server": "^2.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^4.2.0" + "zod": "^3.25.0 || ^4.0.0" }, "peerDependenciesMeta": { - "@modelcontextprotocol/server": { - "optional": true - }, "react": { "optional": true }, @@ -75,13 +66,13 @@ }, "examples/basic-host": { "name": "@modelcontextprotocol/ext-apps-basic-host", - "version": "2.0.0", + "version": "1.7.4", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "devDependencies": { "@types/express": "^5.0.0", @@ -117,18 +108,15 @@ }, "examples/basic-server-preact": { "name": "@modelcontextprotocol/server-basic-preact", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "preact": "^10.0.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-server-basic-preact": "dist/index.js" @@ -164,19 +152,16 @@ }, "examples/basic-server-react": { "name": "@modelcontextprotocol/server-basic-react", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-server-basic-react": "dist/index.js" @@ -214,18 +199,15 @@ }, "examples/basic-server-solid": { "name": "@modelcontextprotocol/server-basic-solid", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "solid-js": "1.9.10", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-server-basic-solid": "dist/index.js" @@ -261,18 +243,15 @@ }, "examples/basic-server-svelte": { "name": "@modelcontextprotocol/server-basic-svelte", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "svelte": "^5.0.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-server-basic-svelte": "dist/index.js" @@ -308,17 +287,14 @@ }, "examples/basic-server-vanillajs": { "name": "@modelcontextprotocol/server-basic-vanillajs", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-server-basic-vanillajs": "dist/index.js" @@ -353,18 +329,15 @@ }, "examples/basic-server-vue": { "name": "@modelcontextprotocol/server-basic-vue", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "vue": "^3.5.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-server-basic-vue": "dist/index.js" @@ -400,17 +373,15 @@ }, "examples/budget-allocator-server": { "name": "@modelcontextprotocol/server-budget-allocator", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-budget-allocator-server": "dist/index.js" @@ -445,18 +416,16 @@ }, "examples/cohort-heatmap-server": { "name": "@modelcontextprotocol/server-cohort-heatmap", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-cohort-heatmap-server": "dist/index.js" @@ -494,17 +463,15 @@ }, "examples/customer-segmentation-server": { "name": "@modelcontextprotocol/server-customer-segmentation", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-customer-segmentation-server": "dist/index.js" @@ -539,14 +506,12 @@ }, "examples/debug-server": { "name": "@modelcontextprotocol/server-debug", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", - "zod": "^4.2.0" + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "zod": "^4.1.13" }, "bin": { "mcp-server-debug": "dist/index.js" @@ -582,18 +547,15 @@ "license": "MIT" }, "examples/integration-server": { - "version": "2.0.0", + "version": "1.7.4", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-integration-server": "dist/index.js" @@ -630,16 +592,14 @@ }, "examples/lazy-auth-server": { "name": "@modelcontextprotocol/server-lazy-auth", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "jose": "^6.0.0", - "zod": "^4.2.0" + "jose": "^6.0.0" }, "bin": { "mcp-server-lazy-auth": "dist/index.js" @@ -674,17 +634,14 @@ }, "examples/map-server": { "name": "@modelcontextprotocol/server-map", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-map-server": "dist/index.js" @@ -719,19 +676,16 @@ }, "examples/pdf-server": { "name": "@modelcontextprotocol/server-pdf", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { "@cantoo/pdf-lib": "^2.6.5", - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "pdfjs-dist": "^5.0.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-pdf-server": "dist/index.js" @@ -766,23 +720,21 @@ }, "examples/qr-server": { "name": "@modelcontextprotocol/server-qr", - "version": "2.0.0", + "version": "1.7.4", "dependencies": { - "@modelcontextprotocol/ext-apps": "^2.0.0" + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0" } }, "examples/quickstart": { "name": "@modelcontextprotocol/quickstart", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", - "express": "^5.1.0", - "zod": "^4.2.0" + "express": "^5.1.0" }, "devDependencies": { "@types/cors": "^2.8.19", @@ -808,27 +760,26 @@ }, "examples/say-server": { "name": "@modelcontextprotocol/server-say", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/ext-apps": "^2.0.0" + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0" } }, "examples/scenario-modeler-server": { "name": "@modelcontextprotocol/server-scenario-modeler", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-scenario-modeler-server": "dist/index.js" @@ -866,16 +817,14 @@ }, "examples/shadertoy-server": { "name": "@modelcontextprotocol/server-shadertoy", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-shadertoy-server": "dist/index.js" @@ -910,17 +859,15 @@ }, "examples/sheet-music-server": { "name": "@modelcontextprotocol/server-sheet-music", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "abcjs": "^6.4.4", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-sheet-music-server": "dist/index.js" @@ -955,18 +902,16 @@ }, "examples/system-monitor-server": { "name": "@modelcontextprotocol/server-system-monitor", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "chart.js": "^4.4.0", "cors": "^2.8.5", "express": "^5.1.0", "systeminformation": "^5.31.6", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-system-monitor-server": "dist/index.js" @@ -1001,20 +946,17 @@ }, "examples/threejs-server": { "name": "@modelcontextprotocol/server-threejs", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", "three": "^0.181.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-threejs-server": "dist/index.js" @@ -1053,16 +995,14 @@ }, "examples/transcript-server": { "name": "@modelcontextprotocol/server-transcript", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-transcript-server": "dist/index.js" @@ -1098,17 +1038,14 @@ }, "examples/video-resource-server": { "name": "@modelcontextprotocol/server-video-resource", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-video-resource-server": "dist/index.js" @@ -1143,18 +1080,15 @@ }, "examples/wiki-explorer-server": { "name": "@modelcontextprotocol/server-wiki-explorer", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "dependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/express": "2.0.0", - "@modelcontextprotocol/ext-apps": "^2.0.0", - "@modelcontextprotocol/node": "2.0.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/ext-apps": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.29.0", "cheerio": "^1.0.0", "cors": "^2.8.5", "express": "^5.1.0", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "bin": { "mcp-wiki-explorer-server": "dist/index.js" @@ -1189,13 +1123,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -1204,9 +1138,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, "license": "MIT", "engines": { @@ -1214,21 +1148,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -1255,14 +1189,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1285,14 +1219,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -1312,9 +1246,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", "engines": { @@ -1322,29 +1256,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1364,27 +1298,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -1392,26 +1326,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -1505,33 +1439,33 @@ } }, "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", "debug": "^4.3.1" }, "engines": { @@ -1539,13 +1473,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1616,9 +1550,9 @@ "license": "Apache-2.0" }, "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, @@ -2102,9 +2036,9 @@ } }, "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "dev": true, "license": "MIT", "engines": { @@ -2112,9 +2046,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", - "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], @@ -2125,19 +2059,19 @@ "darwin" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.2" + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", - "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], @@ -2148,39 +2082,19 @@ "darwin" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.2" - } - }, - "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", - "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "dependencies": { - "@img/sharp-wasm32": "0.35.3" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", - "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], @@ -2195,9 +2109,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", - "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], @@ -2212,9 +2126,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", - "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], @@ -2229,9 +2143,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", - "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], @@ -2246,9 +2160,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", - "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ "ppc64" ], @@ -2263,9 +2177,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", - "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ "riscv64" ], @@ -2280,9 +2194,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", - "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], @@ -2297,9 +2211,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", - "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], @@ -2314,9 +2228,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", - "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], @@ -2331,9 +2245,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", - "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], @@ -2348,9 +2262,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", - "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], @@ -2361,19 +2275,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.2" + "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", - "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], @@ -2384,19 +2298,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.2" + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", - "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ "ppc64" ], @@ -2407,19 +2321,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.2" + "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", - "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ "riscv64" ], @@ -2430,19 +2344,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.2" + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", - "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], @@ -2453,19 +2367,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.2" + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", - "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], @@ -2476,19 +2390,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.2" + "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", - "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], @@ -2499,19 +2413,19 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", - "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], @@ -2522,56 +2436,39 @@ "linux" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", - "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.11.1" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", - "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], "dev": true, - "license": "Apache-2.0", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@emnapi/runtime": "^1.7.0" }, "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", - "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" ], @@ -2582,16 +2479,16 @@ "win32" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", - "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], @@ -2602,16 +2499,16 @@ "win32" ], "engines": { - "node": "^20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", - "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -2622,7 +2519,7 @@ "win32" ], "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -2679,93 +2576,50 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, - "node_modules/@modelcontextprotocol/client": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0.tgz", - "integrity": "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==", + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.1.tgz", + "integrity": "sha512-J3WdG1A4JSSKnSWKyU+895dBVYBV2Utgtf7fUsUK45mlkETm53a/1DR6Pm3hUGKqLLQthZLmpxOg8VPzJi/lyg==", "license": "MIT", + "workspaces": [ + "examples/*" + ], "dependencies": { - "@modelcontextprotocol/core": "2.0.0", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "jose": "^6.1.3", - "pkce-challenge": "^5.0.0", - "zod": "^4.2.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@modelcontextprotocol/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz", - "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==", - "license": "MIT", - "dependencies": { - "zod": "^4.2.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@modelcontextprotocol/express": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/express/-/express-2.0.0.tgz", - "integrity": "sha512-Snlr8j9FR9LcVvEJPF7qJ7d5zTL4Bes2dk7RcacN9eSZ7OLohwxqhEvWu1+UxELyScgLbLLOdeVEGdwKI1iwVQ==", - "license": "MIT", - "dependencies": { - "cors": "^2.8.5" + "@standard-schema/spec": "^1.1.0" }, "engines": { "node": ">=20" }, "peerDependencies": { - "@modelcontextprotocol/server": "^2.0.0", - "express": "^4.18.0 || ^5.0.0" + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/@modelcontextprotocol/ext-apps": { - "resolved": "", - "link": true - }, "node_modules/@modelcontextprotocol/ext-apps-basic-host": { "resolved": "examples/basic-host", "link": true }, - "node_modules/@modelcontextprotocol/node": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.0.0.tgz", - "integrity": "sha512-Y4hAC2XdGDUdDOCbLDOCA4+aL3NUldjsOWlDL/YwpAxrPhRm1xHd7lZ+mLacvZ9t3PaH28wgNoaLQGrIk1P2pg==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@modelcontextprotocol/server": "^2.0.0", - "hono": "^4.11.4" - }, - "peerDependenciesMeta": { - "hono": { - "optional": true - } - } - }, "node_modules/@modelcontextprotocol/quickstart": { "resolved": "examples/quickstart", "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "dev": true, + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -2799,19 +2653,6 @@ } } }, - "node_modules/@modelcontextprotocol/server": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz", - "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/core": "2.0.0", - "zod": "^4.2.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/@modelcontextprotocol/server-basic-preact": { "resolved": "examples/basic-server-preact", "link": true @@ -3185,9 +3026,9 @@ } }, "node_modules/@oclif/core/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -4419,10 +4260,9 @@ } }, "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -4439,7 +4279,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -4621,16 +4460,13 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", - "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", + "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "baseline-browser-mapping": "dist/cli.js" } }, "node_modules/bezier-js": { @@ -4658,20 +4494,20 @@ } }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^1.0.5", "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" @@ -4681,19 +4517,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -4717,9 +4540,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -4741,9 +4564,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", - "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -4761,11 +4584,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.44", - "caniuse-lite": "^1.0.30001806", - "electron-to-chromium": "^1.5.393", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -4774,34 +4597,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/browserslist/node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/browserslist/node_modules/electron-to-chromium": { - "version": "1.5.395", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.395.tgz", - "integrity": "sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==", - "dev": true, - "license": "ISC" - }, "node_modules/bun": { "version": "1.3.10", "resolved": "https://registry.npmjs.org/bun/-/bun-1.3.10.tgz", @@ -5210,15 +5005,15 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", - "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, "license": "MIT", "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", - "shell-quote": "1.9.0", + "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" @@ -6016,13 +5811,11 @@ } }, "node_modules/express-rate-limit": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", - "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", - "dev": true, + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", "dependencies": { - "debug": "^4.4.3", "ip-address": "^10.2.0" }, "engines": { @@ -6035,49 +5828,16 @@ "express": ">= 4.11" } }, - "node_modules/ext-apps-v1": { - "name": "@modelcontextprotocol/ext-apps", - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.5.tgz", - "integrity": "sha512-TjPH2S2y5UEGKhmI6+XGFuqfqOV4ppe1x6DA3txnUaEWkgtA4G5vo14jGKFZmegdkZ1H4QMLyujLvoU1BEdnAg==", - "dev": true, - "license": "MIT", - "workspaces": [ - "examples/*" - ], - "dependencies": { - "@standard-schema/spec": "^1.1.0" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", - "dev": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -6108,9 +5868,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -6411,9 +6171,9 @@ } }, "node_modules/hono": { - "version": "4.12.31", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.31.tgz", - "integrity": "sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==", + "version": "4.12.23", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", + "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -6552,10 +6312,9 @@ } }, "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", - "dev": true, + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -6752,14 +6511,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/json5": { @@ -6819,20 +6576,10 @@ } }, "node_modules/linkify-it": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", - "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" @@ -7044,25 +6791,15 @@ } }, "node_modules/markdown-it": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", - "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "^4.5.0", - "linkify-it": "^5.0.2", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" @@ -7250,14 +6987,11 @@ } }, "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "license": "MIT" }, "node_modules/nodemon": { "version": "3.1.11", @@ -7758,7 +7492,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7882,9 +7615,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -7967,53 +7700,48 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", - "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "dev": true, + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@img/colour": "^1.1.0", + "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", - "semver": "^7.8.5" + "semver": "^7.7.3" }, "engines": { - "node": ">=20.9.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.3", - "@img/sharp-darwin-x64": "0.35.3", - "@img/sharp-freebsd-wasm32": "0.35.3", - "@img/sharp-libvips-darwin-arm64": "1.3.2", - "@img/sharp-libvips-darwin-x64": "1.3.2", - "@img/sharp-libvips-linux-arm": "1.3.2", - "@img/sharp-libvips-linux-arm64": "1.3.2", - "@img/sharp-libvips-linux-ppc64": "1.3.2", - "@img/sharp-libvips-linux-riscv64": "1.3.2", - "@img/sharp-libvips-linux-s390x": "1.3.2", - "@img/sharp-libvips-linux-x64": "1.3.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", - "@img/sharp-libvips-linuxmusl-x64": "1.3.2", - "@img/sharp-linux-arm": "0.35.3", - "@img/sharp-linux-arm64": "0.35.3", - "@img/sharp-linux-ppc64": "0.35.3", - "@img/sharp-linux-riscv64": "0.35.3", - "@img/sharp-linux-s390x": "0.35.3", - "@img/sharp-linux-x64": "0.35.3", - "@img/sharp-linuxmusl-arm64": "0.35.3", - "@img/sharp-linuxmusl-x64": "0.35.3", - "@img/sharp-webcontainers-wasm32": "0.35.3", - "@img/sharp-win32-arm64": "0.35.3", - "@img/sharp-win32-ia32": "0.35.3", - "@img/sharp-win32-x64": "0.35.3" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/shebang-command": { @@ -8038,9 +7766,9 @@ } }, "node_modules/shell-quote": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", - "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "license": "MIT", "engines": { @@ -9329,34 +9057,17 @@ } }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "content-type": "^2.0.0", + "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/typedoc": { @@ -9397,9 +9108,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -9451,9 +9162,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -9516,9 +9227,9 @@ } }, "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9904,13 +9615,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25.28 || ^4" + "zod": "^3.25 || ^4" } } } diff --git a/package.json b/package.json index c86efa9..8c6ead5 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "name": "@modelcontextprotocol/ext-apps", "repository": { "type": "git", - "url": "git+https://github.com/modelcontextprotocol/ext-apps.git" + "url": "https://github.com/modelcontextprotocol/ext-apps" }, "homepage": "https://github.com/modelcontextprotocol/ext-apps", - "version": "2.0.0", + "version": "1.7.4", "license": "MIT", "description": "MCP Apps SDK — Enable MCP servers to display interactive user interfaces in conversational clients.", "type": "module", @@ -54,7 +54,6 @@ "prepack": "npm run build", "build:all": "npm run examples:build", "test": "bun test src examples", - "test:dependency-isolation": "npm run build && node scripts/check-dependency-isolation.mjs", "test:e2e": "playwright test", "test:e2e:update": "playwright test --update-snapshots", "test:e2e:ui": "playwright test --ui", @@ -76,13 +75,9 @@ "update-lock:docker": "rm -rf node_modules package-lock.json examples/*/node_modules && docker run --rm --platform linux/amd64 -v $(pwd):/work -w /work -e HOME=/tmp node:latest npm i --registry=https://registry.npmjs.org/ --ignore-scripts && rm -rf node_modules examples/*/node_modules && npm i --registry=https://registry.npmjs.org/" }, "author": "Olivier Chafik", - "//interop": "The @modelcontextprotocol/sdk 1.x and ext-apps-v1 devDependencies exist only for src/interop-v1.test.ts, which runs the published 1.7.5 package against this build. The library does not use them.", "devDependencies": { "@boneskull/typedoc-plugin-mermaid": "^0.2.0", - "@modelcontextprotocol/client": "2.0.0", - "@modelcontextprotocol/core": "2.0.0", - "@modelcontextprotocol/sdk": "1.30.0", - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", "@playwright/test": "1.57.0", "@types/bun": "^1.3.2", "@types/node": "20.19.27", @@ -97,7 +92,6 @@ "electron-to-chromium": "1.5.267", "esbuild": "^0.25.12", "express": "^5.1.0", - "ext-apps-v1": "npm:@modelcontextprotocol/ext-apps@1.7.5", "husky": "^9.1.7", "nodemon": "^3.1.0", "playwright": "1.57.0", @@ -105,26 +99,21 @@ "prettier": "^3.6.2", "react": "^19.2.0", "react-dom": "^19.2.0", - "sharp": "^0.35.3", + "sharp": "^0.34.5", "ts-to-zod": "^5.1.0", "tsx": "^4.21.0", "typedoc": "^0.28.14", "typedoc-github-theme": "^0.4.0", "typescript": "^5.9.3", - "zod": "^4.2.0" + "zod": "^4.1.13" }, "peerDependencies": { - "@modelcontextprotocol/client": "^2.0.0", - "@modelcontextprotocol/core": "^2.0.0", - "@modelcontextprotocol/server": "^2.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "zod": "^4.2.0" + "zod": "^3.25.0 || ^4.0.0" }, "peerDependenciesMeta": { - "@modelcontextprotocol/server": { - "optional": true - }, "react": { "optional": true }, diff --git a/plugins/mcp-apps/skills/add-app-to-server/SKILL.md b/plugins/mcp-apps/skills/add-app-to-server/SKILL.md index 5a473f0..02d09a4 100644 --- a/plugins/mcp-apps/skills/add-app-to-server/SKILL.md +++ b/plugins/mcp-apps/skills/add-app-to-server/SKILL.md @@ -77,28 +77,13 @@ Before writing any code, analyze the server's existing tools and determine which ## Step 2: Add Dependencies ```bash -npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@^2.0.0 @modelcontextprotocol/server@^2.0.0 zod@^4.2.0 +npm install @modelcontextprotocol/ext-apps npm install -D vite vite-plugin-singlefile ``` -Plus framework-specific dependencies if needed (e.g., `react`, `react-dom`, `@vitejs/plugin-react` for React), and `@modelcontextprotocol/node` / `@modelcontextprotocol/express` if the server uses HTTP transports. +Plus framework-specific dependencies if needed (e.g., `react`, `react-dom`, `@vitejs/plugin-react` for React). -ext-apps 2.x requires the split base MCP SDK packages at `^2.0.0` -(`@modelcontextprotocol/core` comes in transitively). Existing servers that -still import `@modelcontextprotocol/sdk` v1 must migrate those imports and -handler schemas before adding the App integration: - -| SDK v1 (`@modelcontextprotocol/sdk`) | SDK v2 | -|---|---| -| `sdk/server/mcp.js` (`McpServer`) | `@modelcontextprotocol/server` | -| `sdk/server/streamableHttp.js` (`StreamableHTTPServerTransport`) | `NodeStreamableHTTPServerTransport` from `@modelcontextprotocol/node` | -| Express wiring by hand | `createMcpExpressApp` from `@modelcontextprotocol/express` | -| `sdk/server/stdio.js` | `@modelcontextprotocol/server/stdio` | -| `sdk/types.js` types (`CallToolResult`, …) | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `sdk/types.js` zod schemas (`CallToolResultSchema`, …) | `@modelcontextprotocol/core` | -| Raw zod shapes: `inputSchema: { q: z.string() }` | `inputSchema: z.object({ q: z.string() })` | -| `extra.signal` in tool callbacks | `extra.mcpReq.signal` | -| `setRequestHandler(SomeRequestSchema, handler)` | `setRequestHandler("some/method", { params: ParamsSchema }, handler)` (the 2-arg form is only for spec methods such as `"tools/call"`) | +Use `npm install` to add dependencies rather than manually writing version numbers. This lets npm resolve the latest compatible versions. Never specify version numbers from memory. ## Step 3: Set Up the Build Pipeline @@ -161,14 +146,10 @@ Transform plain MCP tools into App tools with UI. **Before** (plain MCP tool): ```typescript -server.registerTool( - "my-tool", - { inputSchema: z.object({ param: z.string() }) }, - async ({ param }) => { - const data = await fetchData(param); - return { content: [{ type: "text", text: JSON.stringify(data) }] }; - }, -); +server.tool("my-tool", { param: z.string() }, async (args) => { + const data = await fetchData(args.param); + return { content: [{ type: "text", text: JSON.stringify(data) }] }; +}); ``` **After** (App tool with UI): @@ -177,22 +158,17 @@ import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@model const resourceUri = "ui://my-tool/mcp-app.html"; -registerAppTool( - server, - "my-tool", - { - description: "Shows data with an interactive UI", - inputSchema: z.object({ param: z.string() }), - _meta: { ui: { resourceUri } }, - }, - async ({ param }) => { - const data = await fetchData(param); - return { - content: [{ type: "text", text: JSON.stringify(data) }], // text fallback for non-UI hosts - structuredContent: { data }, // structured data for the UI - }; - }, -); +registerAppTool(server, "my-tool", { + description: "Shows data with an interactive UI", + inputSchema: { param: z.string() }, + _meta: { ui: { resourceUri } }, +}, async (args) => { + const data = await fetchData(args.param); + return { + content: [{ type: "text", text: JSON.stringify(data) }], // text fallback for non-UI hosts + structuredContent: { data }, // structured data for the UI + }; +}); ``` Key guidance: @@ -211,21 +187,17 @@ import path from "node:path"; const resourceUri = "ui://my-tool/mcp-app.html"; -registerAppResource( - server, - "My Tool UI", - resourceUri, - {}, - async () => { - const html = await fs.readFile( - path.resolve(import.meta.dirname, "dist", "mcp-app.html"), - "utf-8", - ); - return { - contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }], - }; - }, -); +registerAppResource(server, { + uri: resourceUri, + name: "My Tool UI", + mimeType: RESOURCE_MIME_TYPE, +}, async () => { + const html = await fs.readFile( + path.resolve(import.meta.dirname, "dist", "mcp-app.html"), + "utf-8", + ); + return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }] }; +}); ``` If multiple tools share the same UI, they can reference the same `resourceUri` and the same resource registration. @@ -299,32 +271,25 @@ registerAppTool(server, "poll-data", { }); ``` -The UI calls these via -`app.callServerTool({ name: "poll-data", arguments: {} })`. +The UI calls these via `app.callServerTool("poll-data", {})`. ### CSP Configuration If the UI needs to load external resources (fonts, APIs, CDNs), declare the domains: ```typescript -registerAppResource(server, "My Tool UI", resourceUri, {}, async () => ({ - contents: [ - { - uri: resourceUri, - mimeType: RESOURCE_MIME_TYPE, - text: html, - _meta: { - ui: { - csp: { - connectDomains: ["https://api.example.com"], // fetch/XHR targets - resourceDomains: ["https://cdn.example.com"], // scripts, styles, images - frameDomains: ["https://embed.example.com"], // nested iframes - }, - }, - }, +registerAppResource(server, { + uri: resourceUri, + name: "My Tool UI", + mimeType: RESOURCE_MIME_TYPE, + _meta: { + ui: { + connectDomains: ["api.example.com"], // fetch/XHR targets + resourceDomains: ["cdn.example.com"], // scripts, styles, images + frameDomains: ["embed.example.com"], // nested iframes }, - ], -})); + }, +}, async () => { /* ... */ }); ``` ### Streaming Partial Input @@ -361,14 +326,7 @@ server.server.oninitialized = () => { }, appToolHandler); } else { // Text-only client — register plain tool - server.registerTool( - "my-tool", - { - description: "Shows data", - inputSchema: z.object({ param: z.string() }), - }, - plainToolHandler, - ); + server.tool("my-tool", "Shows data", { param: z.string() }, plainToolHandler); } }; ``` diff --git a/plugins/mcp-apps/skills/convert-web-app/SKILL.md b/plugins/mcp-apps/skills/convert-web-app/SKILL.md index 8fb7fa0..5db2d94 100644 --- a/plugins/mcp-apps/skills/convert-web-app/SKILL.md +++ b/plugins/mcp-apps/skills/convert-web-app/SKILL.md @@ -115,22 +115,19 @@ Create a new MCP server with tool and resource registration. This wraps the exis ### Dependencies ```bash -npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@^2.0.0 @modelcontextprotocol/server@^2.0.0 zod@^4.2.0 +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod npm install -D tsx vite vite-plugin-singlefile ``` -Use `npm install` so the package manager resolves versions; ext-apps 2.x needs -the split base MCP SDK packages at `^2.0.0` (`@modelcontextprotocol/core` comes -in transitively). Do not add the legacy `@modelcontextprotocol/sdk` v1 package -or substitute unpublished local packages. +Use `npm install` to add dependencies rather than manually writing version numbers. This lets npm resolve the latest compatible versions. Never specify version numbers from memory. ### Server Code Create `server.ts`: ```typescript -import { McpServer } from "@modelcontextprotocol/server"; -import { StdioServerTransport } from "@modelcontextprotocol/server/stdio"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; import fs from "node:fs/promises"; import path from "node:path"; @@ -143,9 +140,7 @@ const resourceUri = "ui://my-app/mcp-app.html"; // Register the tool — inputSchema maps to the app's data sources registerAppTool(server, "show-app", { description: "Displays the app with the given parameters", - inputSchema: z.object({ - query: z.string().describe("The search query"), - }), + inputSchema: { query: z.string().describe("The search query") }, _meta: { ui: { resourceUri } }, }, async (args) => { // Process args server-side if needed @@ -156,29 +151,19 @@ registerAppTool(server, "show-app", { }); // Register the HTML resource -registerAppResource( - server, - "My App UI", - resourceUri, - {}, - async () => { - const html = await fs.readFile( - path.resolve(import.meta.dirname, "dist", "mcp-app.html"), - "utf-8", - ); - return { - contents: [ - { - uri: resourceUri, - mimeType: RESOURCE_MIME_TYPE, - text: html, - // Add CSP domains from Step 2 if needed: - // _meta: { ui: { csp: { connectDomains: ["https://api.example.com"], resourceDomains: ["https://cdn.example.com"] } } }, - }, - ], - }; - }, -); +registerAppResource(server, { + uri: resourceUri, + name: "My App UI", + mimeType: RESOURCE_MIME_TYPE, + // Add CSP domains from Step 2 if needed: + // _meta: { ui: { connectDomains: ["api.example.com"], resourceDomains: ["cdn.example.com"] } }, +}, async () => { + const html = await fs.readFile( + path.resolve(import.meta.dirname, "dist", "mcp-app.html"), + "utf-8", + ); + return { contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }] }; +}); // Start the server const transport = new StdioServerTransport(); @@ -325,10 +310,7 @@ const data = await fetch("/api/data").then(r => r.json()); // After (hybrid): async function fetchData(): Promise { if (isMcpApp) { - const result = await app.callServerTool({ - name: "fetch-data", - arguments: {}, - }); + const result = await app.callServerTool("fetch-data", {}); return result.structuredContent; } return fetch("/api/data").then(r => r.json()); @@ -455,8 +437,7 @@ registerAppTool(server, "refresh-data", { }); ``` -The UI calls these via -`app.callServerTool({ name: "refresh-data", arguments: {} })`. +The UI calls these via `app.callServerTool("refresh-data", {})`. ### Streaming Partial Input diff --git a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md index d73dc90..5bd1219 100644 --- a/plugins/mcp-apps/skills/create-mcp-app/SKILL.md +++ b/plugins/mcp-apps/skills/create-mcp-app/SKILL.md @@ -110,14 +110,11 @@ See `/tmp/mcp-ext-apps/docs/patterns.md` for detailed recipes: **Always** use `npm install` to add dependencies rather than manually writing version numbers: ```bash -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 @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod express cors npm install -D typescript vite vite-plugin-singlefile concurrently cross-env @types/node @types/express @types/cors ``` -ext-apps 2.x requires the split base MCP SDK packages (`@modelcontextprotocol/client`, -`server`, `node`, `express`) at `^2.0.0`; `@modelcontextprotocol/core` comes in -transitively. Do not add the legacy `@modelcontextprotocol/sdk` v1 package or -substitute unpublished local packages. +This lets npm resolve the latest compatible versions. **Never** specify version numbers from memory. ### TypeScript Server Execution diff --git a/scripts/check-dependency-isolation.mjs b/scripts/check-dependency-isolation.mjs deleted file mode 100644 index 3809224..0000000 --- a/scripts/check-dependency-isolation.mjs +++ /dev/null @@ -1,209 +0,0 @@ -import { execFileSync } from "node:child_process"; -import { - mkdtempSync, - mkdirSync, - readdirSync, - readFileSync, - rmSync, - writeFileSync, -} from "node:fs"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; - -const root = resolve(import.meta.dirname, ".."); -const packageJson = JSON.parse( - readFileSync(join(root, "package.json"), "utf8"), -); -const client = "@modelcontextprotocol/client"; -const server = "@modelcontextprotocol/server"; - -// App and AppBridge extend the client package's Protocol class, so client is -// a required peer for every consumer. The server helpers are only needed by -// server authors, so server stays optional: View-only consumers must not have -// it installed or bundled. -for (const role of [client, server]) { - if (!packageJson.peerDependencies?.[role]) { - throw new Error(`${role} must remain a peer dependency`); - } -} -if (packageJson.peerDependenciesMeta?.[client]?.optional) { - throw new Error(`${client} must be a required peer dependency`); -} -if (packageJson.peerDependenciesMeta?.[server]?.optional !== true) { - throw new Error(`${server} must be an optional peer dependency`); -} - -/** - * Exact version for a synthetic consumer dependency. Read from - * devDependencies so the consumers exercise the same SDK version the - * repository tests against; the check would silently drift if a range were - * allowed here. - */ -function exactDevDependency(name) { - const version = packageJson.devDependencies?.[name]; - if (!/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(version ?? "")) { - throw new Error( - `devDependencies["${name}"] must be an exact version, got ${JSON.stringify(version)}`, - ); - } - return version; -} - -/** Exact version of a package as installed in this repository's node_modules. */ -function installedVersion(name) { - return JSON.parse( - readFileSync(join(root, "node_modules", name, "package.json"), "utf8"), - ).version; -} - -const temporaryRoot = mkdtempSync(join(tmpdir(), "ext-apps-role-peers-")); -try { - // Minimal environment: nothing inherited from the caller's npm config - // (registry overrides, auth tokens, npm_config_* set by an outer `npm run`) - // can leak into the synthetic consumers. - const npmEnvironment = { - PATH: process.env.PATH ?? process.env.Path, - HOME: process.env.HOME ?? process.env.USERPROFILE, - npm_config_cache: join(temporaryRoot, "npm-cache"), - }; - const packDestination = join(temporaryRoot, "pack"); - mkdirSync(packDestination); - // `npm pack` runs the package's `prepare` script even with --ignore-scripts - // (pacote's directory fetcher), and its output can pollute stdout, so do not - // rely on `--json`: locate the tarball on disk instead. - execFileSync( - "npm", - ["pack", "--ignore-scripts", "--pack-destination", packDestination], - { cwd: root, stdio: "pipe", env: npmEnvironment }, - ); - const tarballs = readdirSync(packDestination).filter((name) => - name.endsWith(".tgz"), - ); - if (tarballs.length !== 1) { - throw new Error(`expected exactly one tarball, found ${tarballs}`); - } - const tarball = join(packDestination, tarballs[0]); - - const consumers = [ - { - // View / host author: ext-apps + client (+ react for the hooks entry). - name: "app-only", - dependencies: { - "@types/node": exactDevDependency("@types/node"), - "@types/react": installedVersion("@types/react"), - [client]: exactDevDependency(client), - "@modelcontextprotocol/ext-apps": `file:${tarball}`, - react: installedVersion("react"), - }, - absent: server, - // server must be neither installed (it is an optional peer) nor bundled. - mustNotInstall: true, - entry: - 'import { App } from "@modelcontextprotocol/ext-apps"; import { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; console.log(App, AppBridge, useApp);', - }, - { - // Server author: ext-apps + server. npm auto-installs client as a - // required peer (its types back the shared wire types), but the server - // entry must not pull it into a runtime bundle. - name: "server-only", - dependencies: { - "@types/node": exactDevDependency("@types/node"), - "@modelcontextprotocol/ext-apps": `file:${tarball}`, - [server]: exactDevDependency(server), - }, - absent: client, - mustNotInstall: false, - entry: - 'import { registerAppTool } from "@modelcontextprotocol/ext-apps/server"; console.log(registerAppTool);', - }, - ]; - - for (const consumer of consumers) { - const directory = join(temporaryRoot, consumer.name); - mkdirSync(directory); - writeFileSync( - join(directory, "package.json"), - JSON.stringify({ - private: true, - type: "module", - dependencies: consumer.dependencies, - }), - ); - execFileSync( - "npm", - [ - "install", - "--ignore-scripts", - "--package-lock=false", - "--no-audit", - "--no-fund", - ], - { cwd: directory, stdio: "inherit", env: npmEnvironment }, - ); - - if (consumer.mustNotInstall) { - const absentPath = join( - directory, - "node_modules", - ...consumer.absent.split("/"), - "package.json", - ); - try { - readFileSync(absentPath); - throw new Error( - `${consumer.name} unexpectedly installed ${consumer.absent}`, - ); - } catch (error) { - if (error?.code !== "ENOENT") throw error; - } - } - - writeFileSync(join(directory, "entry.ts"), consumer.entry); - writeFileSync( - join(directory, "tsconfig.json"), - JSON.stringify({ - compilerOptions: { - jsx: "react-jsx", - lib: ["ES2020", "DOM"], - module: "ESNext", - moduleResolution: "bundler", - noEmit: true, - skipLibCheck: false, - strict: true, - target: "ES2020", - }, - include: ["entry.ts"], - }), - ); - execFileSync( - process.execPath, - [join(root, "node_modules", "typescript", "bin", "tsc")], - { cwd: directory, stdio: "inherit" }, - ); - - const metafile = join(directory, "bundle-meta.json"); - execFileSync( - join(root, "node_modules", ".bin", "esbuild"), - [ - "entry.ts", - "--bundle", - "--platform=browser", - "--outfile=bundle.js", - `--metafile=${metafile}`, - ], - { cwd: directory, stdio: "inherit" }, - ); - const bundleInputs = Object.keys( - JSON.parse(readFileSync(metafile, "utf8")).inputs, - ).join("\n"); - if (bundleInputs.includes(`/node_modules/${consumer.absent}/`)) { - throw new Error( - `${consumer.name} unexpectedly bundled ${consumer.absent}`, - ); - } - } -} finally { - rmSync(temporaryRoot, { recursive: true, force: true }); -} - -console.log("Role peer dependency isolation checks passed."); diff --git a/scripts/generate-schemas.ts b/scripts/generate-schemas.ts index 0ff9f04..109f352 100644 --- a/scripts/generate-schemas.ts +++ b/scripts/generate-schemas.ts @@ -12,7 +12,7 @@ * * ts-to-zod generates `import { z } from "zod"`. We rewrite this to * `import { z } from "zod/v4"` because the generated schemas compose with - * schemas imported from `@modelcontextprotocol/core`, which the SDK + * schemas imported from `@modelcontextprotocol/sdk/types.js`, which the SDK * constructs via `zod/v4`. Mixing schemas from the v3 and v4 APIs at runtime * fails with errors like `keyValidator._parse is not a function` (v3 internals * calling into v4 objects, or vice versa). The `zod/v4` subpath is exported by @@ -22,12 +22,10 @@ * * **Problem**: ts-to-zod cannot resolve types imported from external packages. * When it encounters types like `ContentBlock`, `CallToolResult`, `Implementation`, - * `RequestId`, and `Tool` (imported by spec.types.ts from - * `@modelcontextprotocol/client`), it generates `z.any()` as a placeholder. + * `RequestId`, and `Tool` from `@modelcontextprotocol/sdk`, it generates `z.any()` + * as a placeholder. * - * **Solution**: Import the corresponding Zod schemas from - * `@modelcontextprotocol/core` (the role-neutral package both client and - * server re-export) and remove the z.any() placeholders. + * **Solution**: Import the schemas from MCP SDK and remove the z.any() placeholders. * * ### 3. Index Signatures (`z.record().and()` → `z.object().passthrough()`) * @@ -195,7 +193,7 @@ function postProcess(content: string): string { `import { z } from "zod/v4"; import { ${mcpImports}, -} from "@modelcontextprotocol/core";`, +} from "@modelcontextprotocol/sdk/types.js";`, ); // 2. Remove z.any() placeholders for external types (now imported from MCP SDK) diff --git a/scripts/typedoc-plugin-base-sdk-links.mjs b/scripts/typedoc-plugin-base-sdk-links.mjs deleted file mode 100644 index 6098918..0000000 --- a/scripts/typedoc-plugin-base-sdk-links.mjs +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Resolve module-local links inherited from the base MCP SDK declarations. - * - * TypeDoc's externalSymbolLinkMappings handles package-qualified symbols, but - * the base SDK writes this particular reference as `index.inputRequired`. - */ - -const BASE_SDK_DOCS = "https://ts.sdk.modelcontextprotocol.io/v2/"; - -/** @param {import("typedoc").Application} app */ -export function load(app) { - app.converter.addUnknownSymbolResolver((ref) => { - const name = ref.symbolReference?.path?.map((part) => part.path).join("."); - if (name === "index.inputRequired") return BASE_SDK_DOCS; - }); -} diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index f2523f2..1a14d3f 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -289,7 +289,6 @@ When `_meta.ui` is present on **both**, the content-item value takes precedence. style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:; - object-src 'none'; connect-src 'none'; ``` @@ -548,8 +547,8 @@ When the View sends an `ui/initialize` request to the Host, it MUST include its ```typescript interface McpUiAppCapabilities { - /** Experimental features keyed by identifier. */ - experimental?: Record; + /** Experimental features (structure TBD). */ + experimental?: {}; /** App exposes MCP-style tools that the host can call. */ tools?: { /** App supports tools/list_changed notifications. */ @@ -660,25 +659,9 @@ Example: They describe the features and capabilities that the Host supports. ```typescript -/** Content block modalities that the host supports for a given capability. */ -interface SupportedContentBlockModalities { - /** Host supports text content blocks. */ - text?: {}; - /** Host supports image content blocks. */ - image?: {}; - /** Host supports audio content blocks. */ - audio?: {}; - /** Host supports resource content blocks. */ - resource?: {}; - /** Host supports resource link content blocks. */ - resourceLink?: {}; - /** Host supports structured content. */ - structuredContent?: {}; -} - interface HostCapabilities { - /** Experimental features keyed by identifier. */ - experimental?: Record; + /** Experimental features (structure TBD). */ + experimental?: {}; /** Host supports opening external URLs. */ openLinks?: {}; /** Host supports file downloads via ui/download-file. */ @@ -695,6 +678,14 @@ interface HostCapabilities { }; /** Host accepts log messages. */ logging?: {}; + /** + * Host supports LLM sampling (sampling/createMessage) from the view. + * Mirrors MCP ClientCapabilities.sampling so hosts can pass it through. + */ + sampling?: { + /** Host supports tool use via `tools` and `toolChoice` params (SEP-1577). */ + tools?: {}; + }; /** Sandbox configuration applied by the host. */ sandbox?: { /** Permissions granted by the host (camera, microphone, geolocation, clipboard-write). */ @@ -716,21 +707,6 @@ interface HostCapabilities { baseUriDomains?: string[]; }; }; - /** - * Host accepts context updates (ui/update-model-context) to be included in - * the model's context for future turns. - */ - updateModelContext?: SupportedContentBlockModalities; - /** Host supports receiving content messages (ui/message) from the view. */ - message?: SupportedContentBlockModalities; - /** - * Host supports LLM sampling (sampling/createMessage) from the view. - * Mirrors MCP ClientCapabilities.sampling so hosts can pass it through. - */ - sampling?: { - /** Host supports tool use via `tools` and `toolChoice` params (SEP-1577). */ - tools?: {}; - }; } ``` diff --git a/src/app-bridge.examples.ts b/src/app-bridge.examples.ts index 47ad623..d5dc5bc 100644 --- a/src/app-bridge.examples.ts +++ b/src/app-bridge.examples.ts @@ -7,11 +7,17 @@ * @module */ -import { Client, type Transport } from "@modelcontextprotocol/client"; -import type { +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { + CallToolResult, + CallToolResultSchema, CreateMessageRequest, CreateMessageResult, -} from "@modelcontextprotocol/server"; + ListResourcesResultSchema, + ReadResourceResultSchema, + ListPromptsResultSchema, +} from "@modelcontextprotocol/sdk/types.js"; import { AppBridge, PostMessageTransport } from "./app-bridge.js"; import type { McpUiDisplayMode } from "./types.js"; @@ -217,7 +223,8 @@ function AppBridge_oncalltool_forwardToServer( bridge.oncalltool = async (params, extra) => { return mcpClient.request( { method: "tools/call", params }, - { signal: extra.mcpReq.signal }, + CallToolResultSchema, + { signal: extra.signal }, ); }; //#endregion AppBridge_oncalltool_forwardToServer @@ -238,9 +245,7 @@ function AppBridge_oncreatesamplingmessage_forwardToLlm( //#region AppBridge_oncreatesamplingmessage_forwardToLlm bridge.oncreatesamplingmessage = async (params, extra) => { // Apply rate limiting, user approval, cost controls here - return await myLlmProvider.complete(params, { - signal: extra.mcpReq.signal, - }); + return await myLlmProvider.complete(params, { signal: extra.signal }); }; //#endregion AppBridge_oncreatesamplingmessage_forwardToLlm } @@ -256,7 +261,8 @@ function AppBridge_onlistresources_returnResources( bridge.onlistresources = async (params, extra) => { return mcpClient.request( { method: "resources/list", params }, - { signal: extra.mcpReq.signal }, + ListResourcesResultSchema, + { signal: extra.signal }, ); }; //#endregion AppBridge_onlistresources_returnResources @@ -273,7 +279,8 @@ function AppBridge_onreadresource_returnResource( bridge.onreadresource = async (params, extra) => { return mcpClient.request( { method: "resources/read", params }, - { signal: extra.mcpReq.signal }, + ReadResourceResultSchema, + { signal: extra.signal }, ); }; //#endregion AppBridge_onreadresource_returnResource @@ -290,7 +297,8 @@ function AppBridge_onlistprompts_returnPrompts( bridge.onlistprompts = async (params, extra) => { return mcpClient.request( { method: "prompts/list", params }, - { signal: extra.mcpReq.signal }, + ListPromptsResultSchema, + { signal: extra.signal }, ); }; //#endregion AppBridge_onlistprompts_returnPrompts @@ -436,10 +444,10 @@ async function AppBridge_sendToolResult_afterExecution( args: Record, ) { //#region AppBridge_sendToolResult_afterExecution - const result = await mcpClient.request({ - method: "tools/call", - params: { name: "get_weather", arguments: args }, - }); + const result = await mcpClient.request( + { method: "tools/call", params: { name: "get_weather", arguments: args } }, + CallToolResultSchema, + ); bridge.sendToolResult(result); //#endregion AppBridge_sendToolResult_afterExecution } diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index a87b0e7..a4fe1de 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -1,19 +1,29 @@ import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test"; -import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; -import { Server, type ServerCapabilities } from "@modelcontextprotocol/server"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import type { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import type { ServerCapabilities } from "@modelcontextprotocol/sdk/types.js"; +import { + EmptyResultSchema, + ListPromptsResultSchema, + ListResourcesResultSchema, + ListResourceTemplatesResultSchema, + PromptListChangedNotificationSchema, + ReadResourceResultSchema, + ResourceListChangedNotificationSchema, + ToolListChangedNotificationSchema, +} from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod/v4"; -import { App } from "./app.js"; -import { LATEST_PROTOCOL_VERSION } from "./types.js"; +import { App } from "./app"; +import { LATEST_PROTOCOL_VERSION } from "./types"; import { AppBridge, buildAllowAttribute, getToolUiResourceUri, isToolVisibilityModelOnly, isToolVisibilityAppOnly, - McpUiOpenLinkResultSchema, type McpUiHostCapabilities, -} from "./app-bridge.js"; +} from "./app-bridge"; /** Wait for pending microtasks to complete */ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); @@ -24,27 +34,17 @@ const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); */ function createMockClient( serverCapabilities: ServerCapabilities = {}, -): Pick< - Client, - | "getServerCapabilities" - | "request" - | "notification" - | "setNotificationHandler" -> { +): Pick { return { getServerCapabilities: () => serverCapabilities, request: async () => ({}) as never, notification: async () => {}, - setNotificationHandler: () => {}, }; } const testHostInfo = { name: "TestHost", version: "1.0.0" }; const testAppInfo = { name: "TestApp", version: "1.0.0" }; const testHostCapabilities: McpUiHostCapabilities = { - experimental: { - "com.example/host-extension": { version: 1 }, - }, openLinks: {}, serverTools: {}, logging: {}, @@ -85,53 +85,6 @@ describe("App <-> AppBridge integration", () => { expect(initializedFired).toBe(true); }); - it("sends only the Apps handshake on the wire and gates the Apps-ready callback", async () => { - const methods: string[] = []; - let releaseAppsInitialized!: () => void; - let reachedAppsInitialized!: () => void; - const appsInitializedGate = new Promise((resolve) => { - releaseAppsInitialized = resolve; - }); - const reachedGate = new Promise((resolve) => { - reachedAppsInitialized = resolve; - }); - const originalSend = appTransport.send.bind(appTransport); - appTransport.send = async (message, options) => { - if ("method" in message) methods.push(message.method); - if ( - "method" in message && - message.method === "ui/notifications/initialized" - ) { - reachedAppsInitialized(); - await appsInitializedGate; - } - return originalSend(message, options); - }; - - let singularCalls = 0; - let listenerCalls = 0; - bridge.oninitialized = () => singularCalls++; - bridge.addEventListener("initialized", () => listenerCalls++); - - await bridge.connect(bridgeTransport); - const connecting = app.connect(appTransport); - await reachedGate; - - expect(methods).toEqual([ - "ui/initialize", - "ui/notifications/initialized", - ]); - expect(bridge.getAppVersion()).toEqual(testAppInfo); - expect(singularCalls).toBe(0); - expect(listenerCalls).toBe(0); - - releaseAppsInitialized(); - await connecting; - - expect(singularCalls).toBe(1); - expect(listenerCalls).toBe(1); - }); - it("App receives host info and capabilities after connect", async () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -141,16 +94,10 @@ describe("App <-> AppBridge integration", () => { const hostCaps = app.getHostCapabilities(); expect(hostCaps).toEqual(testHostCapabilities); - expect(bridge.getCapabilities()).toEqual(testHostCapabilities); }); it("Bridge receives app info and capabilities after initialization", async () => { - const appCapabilities = { - experimental: { - "com.example/app-extension": { version: 1 }, - }, - tools: { listChanged: true }, - }; + const appCapabilities = { tools: { listChanged: true } }; app = new App(testAppInfo, appCapabilities, { autoResize: false }); await bridge.connect(bridgeTransport); @@ -667,30 +614,6 @@ describe("App <-> AppBridge integration", () => { expect(result.isError).toBe(true); }); - - it("rejects invalid params for a custom ui request", async () => { - bridge.onopenlink = async () => ({}); - await app.connect(appTransport); - - await expect( - app.request( - { - method: "ui/open-link", - params: { url: 42 }, - }, - McpUiOpenLinkResultSchema, - ), - ).rejects.toThrow(); - }); - - it("rejects invalid results from a custom ui handler", async () => { - bridge.onopenlink = async () => ({ isError: "yes" }) as never; - await app.connect(appTransport); - - await expect( - app.openLink({ url: "https://example.com" }), - ).rejects.toThrow(); - }); }); describe("deprecated method aliases", () => { @@ -760,7 +683,10 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); // Bridge can send ping via the protocol's request method - const result = await bridge.request({ method: "ping", params: {} }); + const result = await bridge.request( + { method: "ping", params: {} }, + EmptyResultSchema, + ); expect(result).toEqual({}); }); @@ -875,7 +801,7 @@ describe("App <-> AppBridge integration", () => { }); await bridge.callTool({ name: "noargs", arguments: {} }); expect(receivedExtra).toBeDefined(); - expect(receivedExtra.mcpReq.signal).toBeInstanceOf(AbortSignal); + expect(receivedExtra.signal).toBeInstanceOf(AbortSignal); }); it("isError result skips output schema validation", async () => { @@ -1551,9 +1477,40 @@ describe("App <-> AppBridge integration", () => { ).rejects.toThrow(/Invalid input for tool translate/); }); - it("rejects listTools when a tool schema does not implement Standard JSON Schema", async () => { + it("falls back to z.toJSONSchema for zod schemas lacking ~standard.jsonSchema (zod v3.25.x)", async () => { + // zod v3.25 implements ~standard.validate but not ~standard.jsonSchema. + // Simulate by stripping jsonSchema from a real zod schema. + const v4Schema = z.object({ q: z.string() }); + const zod3LikeSchema = Object.assign(Object.create(v4Schema), { + "~standard": { + version: 1 as const, + vendor: "zod", + validate: v4Schema["~standard"].validate, + types: undefined as + | undefined + | { + readonly input: { q: string }; + readonly output: { q: string }; + }, + // no jsonSchema + }, + }); + const appCapabilities = { tools: { listChanged: true } }; app = new App(testAppInfo, appCapabilities, { autoResize: false }); + app.registerTool( + "search", + { inputSchema: zod3LikeSchema }, + async ({ q }: { q: string }) => ({ + content: [{ type: "text" as const, text: q }], + }), + ); + await app.connect(appTransport); + + const list = await bridge.listTools({}); + expect(list.tools[0].inputSchema.properties).toHaveProperty("q"); + + // Non-zod schema without jsonSchema → listTools rejects with guidance. app.registerTool( "broken", { @@ -1567,8 +1524,6 @@ describe("App <-> AppBridge integration", () => { }, async () => ({ content: [] }), ); - await app.connect(appTransport); - expect(bridge.listTools({})).rejects.toThrow( /does not implement Standard JSON Schema/, ); @@ -2228,10 +2183,10 @@ describe("App <-> AppBridge integration", () => { await app.connect(appTransport); // App sends resources/list request via the protocol's request method - const result = await app.request({ - method: "resources/list", - params: requestParams, - }); + const result = await app.request( + { method: "resources/list", params: requestParams }, + ListResourcesResultSchema, + ); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2272,10 +2227,10 @@ describe("App <-> AppBridge integration", () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); - const result = await app.request({ - method: "resources/read", - params: requestParams, - }); + const result = await app.request( + { method: "resources/read", params: requestParams }, + ReadResourceResultSchema, + ); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2323,10 +2278,10 @@ describe("App <-> AppBridge integration", () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); - const result = await app.request({ - method: "resources/templates/list", - params: requestParams, - }); + const result = await app.request( + { method: "resources/templates/list", params: requestParams }, + ListResourceTemplatesResultSchema, + ); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2346,10 +2301,10 @@ describe("App <-> AppBridge integration", () => { await bridge.connect(bridgeTransport); await app.connect(appTransport); - const result = await app.request({ - method: "prompts/list", - params: requestParams, - }); + const result = await app.request( + { method: "prompts/list", params: requestParams }, + ListPromptsResultSchema, + ); expect(receivedRequests).toHaveLength(1); expect(receivedRequests[0]).toMatchObject(requestParams); @@ -2358,13 +2313,9 @@ describe("App <-> AppBridge integration", () => { it("sendToolListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; - bridge.oncalltool = async () => ({ content: [] }); - app.setNotificationHandler( - "notifications/tools/list_changed", - (notification) => { - receivedNotifications.push(notification.params); - }, - ); + app.setNotificationHandler(ToolListChangedNotificationSchema, (n) => { + receivedNotifications.push(n.params); + }); await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -2377,13 +2328,9 @@ describe("App <-> AppBridge integration", () => { it("sendResourceListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; - bridge.onlistresources = async () => ({ resources: [] }); - app.setNotificationHandler( - "notifications/resources/list_changed", - (notification) => { - receivedNotifications.push(notification.params); - }, - ); + app.setNotificationHandler(ResourceListChangedNotificationSchema, (n) => { + receivedNotifications.push(n.params); + }); await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -2396,13 +2343,9 @@ describe("App <-> AppBridge integration", () => { it("sendPromptListChanged sends notification to app", async () => { const receivedNotifications: unknown[] = []; - bridge.onlistprompts = async () => ({ prompts: [] }); - app.setNotificationHandler( - "notifications/prompts/list_changed", - (notification) => { - receivedNotifications.push(notification.params); - }, - ); + app.setNotificationHandler(PromptListChangedNotificationSchema, (n) => { + receivedNotifications.push(n.params); + }); await bridge.connect(bridgeTransport); await app.connect(appTransport); @@ -2415,184 +2358,6 @@ describe("App <-> AppBridge integration", () => { }); }); -describe("AppBridge outer v2 Client proxy", () => { - let outerClient: Client; - let outerServer: Server; - let outerClientTransport: InMemoryTransport; - let outerServerTransport: InMemoryTransport; - let outerInitializeCount: number; - - beforeEach(async () => { - [outerClientTransport, outerServerTransport] = - InMemoryTransport.createLinkedPair(); - outerInitializeCount = 0; - const originalSend = outerClientTransport.send.bind(outerClientTransport); - outerClientTransport.send = async (message, options) => { - if ("method" in message && message.method === "initialize") { - outerInitializeCount++; - } - return originalSend(message, options); - }; - - outerServer = new Server( - { name: "ActualServer", version: "1.0.0" }, - { - capabilities: { - tools: { listChanged: true }, - resources: { listChanged: true }, - prompts: { listChanged: true }, - }, - supportedProtocolVersions: ["2025-11-25"], - }, - ); - outerServer.setRequestHandler("tools/call", async (request) => ({ - content: [ - { - type: "text", - text: `outer:${request.params.name}`, - }, - ], - })); - - outerClient = new Client( - { name: "HostOuterClient", version: "1.0.0" }, - { - capabilities: {}, - versionNegotiation: { mode: "legacy" }, - supportedProtocolVersions: ["2025-11-25"], - }, - ); - await outerServer.connect(outerServerTransport); - await outerClient.connect(outerClientTransport); - }); - - afterEach(async () => { - await outerClient.close().catch(() => {}); - await outerServer.close().catch(() => {}); - }); - - it("proxies through the outer Client without reconnecting it", async () => { - const bridge = new AppBridge( - outerClient, - testHostInfo, - testHostCapabilities, - ); - const app = new App(testAppInfo, {}, { autoResize: false }); - let [appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair(); - - await bridge.connect(bridgeTransport); - await app.connect(appTransport); - expect( - await app.callServerTool({ name: "first", arguments: {} }), - ).toMatchObject({ - content: [{ type: "text", text: "outer:first" }], - }); - expect(outerInitializeCount).toBe(1); - - await app.close(); - await bridge.close(); - [appTransport, bridgeTransport] = InMemoryTransport.createLinkedPair(); - await bridge.connect(bridgeTransport); - await app.connect(appTransport); - - expect( - await app.callServerTool({ name: "second", arguments: {} }), - ).toMatchObject({ - content: [{ type: "text", text: "outer:second" }], - }); - expect(outerInitializeCount).toBe(1); - expect(outerClient.getServerVersion()).toMatchObject({ - name: "ActualServer", - version: "1.0.0", - }); - - await app.close(); - await bridge.close(); - }); - - it("propagates View cancellation to the actual outer Server", async () => { - let started!: () => void; - let aborted!: () => void; - const startedPromise = new Promise((resolve) => { - started = resolve; - }); - const abortedPromise = new Promise((resolve) => { - aborted = resolve; - }); - outerServer.setRequestHandler("tools/call", async (_request, context) => { - started(); - await new Promise((resolve) => { - context.mcpReq.signal.addEventListener( - "abort", - () => { - aborted(); - resolve(); - }, - { once: true }, - ); - }); - return { content: [] }; - }); - - const bridge = new AppBridge( - outerClient, - testHostInfo, - testHostCapabilities, - ); - const app = new App(testAppInfo, {}, { autoResize: false }); - const [appTransport, bridgeTransport] = - InMemoryTransport.createLinkedPair(); - await bridge.connect(bridgeTransport); - await app.connect(appTransport); - - const controller = new AbortController(); - const result = app.callServerTool( - { name: "cancel-me", arguments: {} }, - { signal: controller.signal }, - ); - await startedPromise; - controller.abort("test cancellation"); - await abortedPromise; - await expect(result).rejects.toThrow(); - - await app.close(); - await bridge.close(); - }); - - it("forwards outer list-changed notifications to the App", async () => { - const bridge = new AppBridge( - outerClient, - testHostInfo, - testHostCapabilities, - ); - const app = new App(testAppInfo, {}, { autoResize: false }); - const [appTransport, bridgeTransport] = - InMemoryTransport.createLinkedPair(); - const received: string[] = []; - app.setNotificationHandler("notifications/tools/list_changed", () => { - received.push("tools"); - }); - app.setNotificationHandler("notifications/resources/list_changed", () => { - received.push("resources"); - }); - app.setNotificationHandler("notifications/prompts/list_changed", () => { - received.push("prompts"); - }); - - await bridge.connect(bridgeTransport); - await app.connect(appTransport); - await outerServer.sendToolListChanged(); - await outerServer.sendResourceListChanged(); - await outerServer.sendPromptListChanged(); - await flush(); - - expect(received).toEqual(["tools", "resources", "prompts"]); - - await app.close(); - await bridge.close(); - }); -}); - describe("getToolUiResourceUri", () => { describe("new nested format (_meta.ui.resourceUri)", () => { it("extracts resourceUri from _meta.ui.resourceUri", () => { @@ -2948,7 +2713,7 @@ describe("isToolVisibilityAppOnly", () => { expect(a).toEqual([]); }); - it("App merges hostcontext before listeners fire", async () => { + it("App.onEventDispatch merges hostcontext before listeners fire", async () => { let seen: unknown; app.addEventListener("hostcontextchanged", () => { seen = app.getHostContext(); @@ -2993,90 +2758,33 @@ describe("isToolVisibilityAppOnly", () => { testHostInfo, testHostCapabilities, ); - const params = z.object({}); - bridge2.setRequestHandler("test/method", { params }, () => ({})); - expect(() => { - bridge2.setRequestHandler("test/method", { params }, () => ({})); - }).toThrow(/already registered/); - }); - - it("direct setRequestHandler cannot silently replace an on* host handler", () => { - const bridge2 = new AppBridge( - createMockClient() as Client, - testHostInfo, - testHostCapabilities, + bridge2.setRequestHandler( + // @ts-expect-error — exercising throw path with raw schema + { shape: { method: { value: "test/method" } } }, + () => ({}), ); - bridge2.onopenlink = async () => ({}); expect(() => { bridge2.setRequestHandler( - "ui/open-link", - { params: z.object({}) }, + // @ts-expect-error — exercising throw path with raw schema + { shape: { method: { value: "test/method" } } }, () => ({}), ); }).toThrow(/already registered/); }); it("direct setNotificationHandler throws for event-mapped methods", () => { - const bridge2 = new AppBridge( - createMockClient() as Client, - testHostInfo, - testHostCapabilities, - ); - bridge2.onsizechange = () => {}; + const app2 = new App(testAppInfo, {}, { autoResize: false }); + app2.addEventListener("toolinput", () => {}); expect(() => { - bridge2.setNotificationHandler( - "ui/notifications/size-changed", - { params: z.object({}) }, + app2.setNotificationHandler( + // @ts-expect-error — exercising throw path with raw schema + { + shape: { method: { value: "ui/notifications/tool-input" } }, + }, () => {}, ); }).toThrow(/already registered/); }); - - it("removeRequestHandler releases the method so an on* setter can re-register", () => { - const bridge2 = new AppBridge( - createMockClient() as Client, - testHostInfo, - testHostCapabilities, - ); - bridge2.removeRequestHandler("tools/call"); - expect(() => { - bridge2.setRequestHandler("tools/call", async () => ({ content: [] })); - }).not.toThrow(); - expect(() => { - bridge2.oncalltool = async () => ({ content: [] }); - }).not.toThrow(); - }); - - it("oncreatesamplingmessage has a getter, replace semantics, and a replace warning", () => { - const bridge2 = new AppBridge( - createMockClient() as Client, - testHostInfo, - testHostCapabilities, - ); - expect(bridge2.oncreatesamplingmessage).toBeUndefined(); - const first = async () => ({ - role: "assistant" as const, - content: { type: "text" as const, text: "" }, - model: "m", - }); - bridge2.oncreatesamplingmessage = first; - expect(bridge2.oncreatesamplingmessage).toBe(first); - - const warn = spyOn(console, "warn").mockImplementation(() => {}); - try { - expect(() => { - bridge2.oncreatesamplingmessage = first; - }).not.toThrow(); - expect(warn).toHaveBeenCalledWith( - expect.stringContaining("oncreatesamplingmessage handler replaced"), - ); - } finally { - warn.mockRestore(); - } - - bridge2.oncreatesamplingmessage = undefined; - expect(bridge2.oncreatesamplingmessage).toBeUndefined(); - }); }); }); diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 1ac4db4..23383c4 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -1,53 +1,58 @@ +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { + CallToolRequest, + CallToolRequestSchema, + CallToolResult, + CallToolResultSchema, + CreateMessageRequest, + CreateMessageRequestSchema, + CreateMessageResult, + CreateMessageResultWithTools, + EmptyResult, + Implementation, + ListPromptsRequest, + ListPromptsRequestSchema, + ListPromptsResult, + ListPromptsResultSchema, + ListResourcesRequest, + ListResourcesRequestSchema, + ListResourcesResult, + ListResourcesResultSchema, + ListResourceTemplatesRequest, + ListResourceTemplatesRequestSchema, + ListResourceTemplatesResult, + ListResourceTemplatesResultSchema, + ListToolsRequest, + ListToolsRequestSchema, + ListToolsResultSchema, + LoggingMessageNotification, + LoggingMessageNotificationSchema, + PingRequest, + PingRequestSchema, + PromptListChangedNotification, + PromptListChangedNotificationSchema, + ReadResourceRequest, + ReadResourceRequestSchema, + ReadResourceResult, + ReadResourceResultSchema, + ResourceListChangedNotification, + ResourceListChangedNotificationSchema, + Tool, + ToolListChangedNotification, + ToolListChangedNotificationSchema, +} from "@modelcontextprotocol/sdk/types.js"; import { Protocol, - type BaseContext, - type Client, - type ProtocolOptions, - type CallToolRequest, - type CallToolResult, - type CreateMessageRequest, - type CreateMessageResult, - type CreateMessageResultWithTools, - type EmptyResult, - type Implementation, - type JSONRPCRequest, - type ListPromptsRequest, - type ListPromptsResult, - type ListResourcesRequest, - type ListResourcesResult, - type ListResourceTemplatesRequest, - type ListResourceTemplatesResult, - type ListToolsRequest, - type LoggingMessageNotification, - type PingRequest, - type PromptListChangedNotification, - type ReadResourceRequest, - type ReadResourceResult, - type RequestOptions, - type ResourceListChangedNotification, - type Result, - type Tool, - type ToolListChangedNotification, - type Transport, -} from "@modelcontextprotocol/client"; -import { - EmptyResultSchema, - LoggingMessageNotificationSchema, -} from "@modelcontextprotocol/core"; -import { EventDispatcher, MethodRegistry } from "./events.js"; -import { - toModernArgs, - type LegacyNotificationHandlerSetter, - type LegacyRequestHandlerSetter, -} from "./legacy-handlers.js"; -import type { ZodLiteral, ZodObject, ZodType } from "zod/v4"; - -type MethodSchema = ZodObject<{ - method: ZodLiteral; - params: ZodType; -}>; + ProtocolOptions, + RequestOptions, +} from "@modelcontextprotocol/sdk/shared/protocol.js"; +import { ProtocolWithEvents } from "./events"; import { + type AppNotification, + type AppRequest, + type AppResult, type McpUiSandboxResourceReadyNotification, type McpUiSizeChangedNotification, type McpUiToolCancelledNotification, @@ -66,19 +71,15 @@ import { McpUiInitializeRequest, McpUiInitializeRequestSchema, McpUiInitializeResult, - McpUiInitializeResultSchema, McpUiMessageRequest, McpUiMessageRequestSchema, McpUiMessageResult, - McpUiMessageResultSchema, McpUiOpenLinkRequest, McpUiOpenLinkRequestSchema, McpUiOpenLinkResult, - McpUiOpenLinkResultSchema, McpUiDownloadFileRequest, McpUiDownloadFileRequestSchema, McpUiDownloadFileResult, - McpUiDownloadFileResultSchema, McpUiResourceTeardownRequest, McpUiResourceTeardownResultSchema, McpUiRequestTeardownNotification, @@ -89,16 +90,13 @@ import { McpUiRequestDisplayModeRequest, McpUiRequestDisplayModeRequestSchema, McpUiRequestDisplayModeResult, - McpUiRequestDisplayModeResultSchema, McpUiResourcePermissions, McpUiToolMeta, -} from "./types.js"; -export * from "./types.js"; -export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./constants.js"; -import { RESOURCE_URI_META_KEY } from "./constants.js"; - -type UntypedHandlerSetter = (this: unknown, ...args: unknown[]) => void; -export { PostMessageTransport } from "./message-transport.js"; +} from "./types"; +export * from "./types"; +export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app"; +import { RESOURCE_URI_META_KEY } from "./app"; +export { PostMessageTransport } from "./message-transport"; /** * Extract UI resource URI from tool metadata. @@ -205,10 +203,10 @@ export function buildAllowAttribute( * * @property hostContext - Optional initial host context to provide to the view * - * @see `ProtocolOptions` from @modelcontextprotocol/client for available options + * @see `ProtocolOptions` from @modelcontextprotocol/sdk for available options * @see {@link McpUiHostContext `McpUiHostContext`} for the hostContext structure */ -export type HostOptions = Omit & { +export type HostOptions = ProtocolOptions & { hostContext?: McpUiHostContext; }; @@ -223,12 +221,15 @@ export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION]; /** * Extra metadata passed to request handlers. * - * This type represents the additional context (`BaseContext`) provided by the - * base MCP SDK `Protocol` when handling requests. + * This type represents the additional context provided by the `Protocol` class + * when handling requests, including abort signals and session information. + * It is extracted from the MCP SDK's request handler signature. * * @internal */ -type RequestHandlerExtra = BaseContext; +type RequestHandlerExtra = Parameters< + Parameters[1] +>[1]; /** * Maps DOM-style event names to their notification `params` types. @@ -248,11 +249,11 @@ export type AppBridgeEventMap = { /** * Host-side bridge for communicating with a single View ({@link app!App `App`}). * - * `AppBridge` extends the base MCP SDK's public `Protocol` class and acts as a - * proxy between the host application and a view running in an iframe. When a - * separate MCP client is provided to the constructor, it automatically forwards - * MCP server capabilities (tools, resources, prompts) to the view. It also - * handles the Apps initialization handshake. + * `AppBridge` extends the MCP SDK's `Protocol` class and acts as a proxy between + * the host application and a view running in an iframe. When an MCP client + * is provided to the constructor, it automatically forwards MCP server capabilities + * (tools, resources, prompts) to the view. It also handles the initialization + * handshake. * * ## Architecture * @@ -302,125 +303,47 @@ export type AppBridgeEventMap = { * await bridge.connect(transport); * ``` */ -export class AppBridge extends Protocol { +export class AppBridge extends ProtocolWithEvents< + AppRequest, + AppNotification, + AppResult, + AppBridgeEventMap +> { private _appCapabilities?: McpUiAppCapabilities; private _hostContext: McpUiHostContext = {}; private _appInfo?: Implementation; private _initializedReceived = false; - private readonly _registeredEvents = new Set(); - private readonly _events = new EventDispatcher(); - private readonly _methods = new MethodRegistry(); - - // ── Handler registration with double-set protection ───────────────── - // - // The base SDK `Protocol` silently replaces an existing handler. The four - // overrides below restore the v1 behaviour: a direct `setRequestHandler` / - // `setNotificationHandler` for a method that already has a handler throws, - // so a stray registration cannot silently replace a host's `on*` handler - // (for example the URL allow-listing in `onopenlink`) or disconnect - // `addEventListener` listeners. They are arrow-function class fields rather - // than prototype methods so that `Protocol`'s constructor — which registers - // its own ping/cancelled/progress handlers before our fields initialize — - // hits the base implementation and skips tracking. /** - * Registers a request handler. Throws if a handler for the same method has - * already been registered — use the `on*` setter for replace semantics. - * - * @throws {Error} if a handler for this method is already registered. - */ - 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( - this, - method, - ...rest, - ); - }; - - /** - * Registers a notification handler. Throws if a handler for the same method - * has already been registered — use the `on*` setter (replace semantics) or - * `addEventListener` (multi-listener) for mapped events. - * - * @throws {Error} if a handler for this method is already registered. - */ - 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( - this, - method, - ...rest, - ); - }; - - override removeRequestHandler: Protocol["removeRequestHandler"] = - (method: string) => { - this._methods.release(method); - super.removeRequestHandler(method); - }; - - override removeNotificationHandler: Protocol["removeNotificationHandler"] = - (method: string) => { - this._methods.release(method); - super.removeNotificationHandler(method); - }; - - /** - * Register a request handler with replace semantics, bypassing the - * double-set protection of {@link setRequestHandler `setRequestHandler`}. - * Used by the `on*` request-handler setters. - */ - protected replaceRequestHandler: Protocol["setRequestHandler"] = - (method: string, ...rest: unknown[]) => { - this._methods.replace(method); - (super.setRequestHandler as unknown as UntypedHandlerSetter).call( - this, - method, - ...rest, - ); - }; - - /** - * The base MCP SDK calls this hook for every standard and custom request - * handler registration. AppBridge uses it to preserve the existing warning - * when a View calls host methods before `ui/notifications/initialized`, - * without adding overload-forwarding adapters around `setRequestHandler`. - * `ui/initialize` and `ping` are exempt because they are valid before the - * Apps-ready gate. This wraps registered callbacks, not JSON-RPC dispatch. + * Wrap every handler registered via `replaceRequestHandler` with a check + * that the View has sent `ui/notifications/initialized`. Warns (never + * throws) so lenient hosts keep working while still surfacing the + * misordering that leaves strict hosts with a permanently hidden iframe. + * `ui/initialize` and `ping` use `setRequestHandler` directly and are + * intentionally exempt. * * @see {@link https://github.com/anthropics/claude-ai-mcp/issues/149 claude-ai-mcp#149} */ - protected override _wrapHandler( - method: string, - handler: (request: JSONRPCRequest, context: BaseContext) => Promise, - ): (request: JSONRPCRequest, context: BaseContext) => Promise { - const wrapped = super._wrapHandler(method, handler); - if (method === "ui/initialize" || method === "ping") { - return wrapped; - } - return async (request, context) => { + private _baseReplaceRequestHandler = this.replaceRequestHandler; + protected override replaceRequestHandler: Protocol< + AppRequest, + AppNotification, + AppResult + >["setRequestHandler"] = (schema, handler) => { + this._baseReplaceRequestHandler(schema, (request, extra) => { if (!this._initializedReceived) { console.warn( - `[ext-apps] AppBridge received '${method}' before ` + + `[ext-apps] AppBridge received '${request.method}' before ` + `ui/notifications/initialized. The View is calling host ` + `methods before completing the handshake; it should await ` + `app.connect() first.`, ); } - return wrapped(request, context); - }; - } + return handler(request, extra); + }); + }; - private readonly eventSchemas: { - [K in keyof AppBridgeEventMap]: MethodSchema; - } = { + protected readonly eventSchemas = { sizechange: McpUiSizeChangedNotificationSchema, sandboxready: McpUiSandboxProxyReadyNotificationSchema, initialized: McpUiInitializedNotificationSchema, @@ -428,72 +351,6 @@ export class AppBridge extends Protocol { loggingmessage: LoggingMessageNotificationSchema, }; - protected setEventHandler( - event: K, - handler: ((params: AppBridgeEventMap[K]) => void) | undefined, - ): void { - this._ensureEventSlot(event); - this._events.setHandler(event, handler); - } - - protected getEventHandler( - event: K, - ): ((params: AppBridgeEventMap[K]) => void) | undefined { - return this._events.getHandler(event); - } - - addEventListener( - event: K, - handler: (params: AppBridgeEventMap[K]) => void, - ): void { - this._ensureEventSlot(event); - this._events.addEventListener(event, handler); - } - - removeEventListener( - event: K, - handler: (params: AppBridgeEventMap[K]) => void, - ): void { - this._events.removeEventListener(event, handler); - } - - private _ensureEventSlot(event: K): void { - if (this._registeredEvents.has(event)) return; - const schema = this.eventSchemas[event]; - if (!schema) throw new Error(`Unknown event: ${String(event)}`); - - const dispatch = (params: AppBridgeEventMap[K]) => { - if (event === "initialized") this._initializedReceived = true; - this._events.dispatch(event, params); - }; - - if (event === "loggingmessage") { - this.setNotificationHandler("notifications/message", (notification) => { - dispatch(notification.params as AppBridgeEventMap[K]); - }); - } else { - this.setNotificationHandler( - schema.shape.method.value, - { params: schema.shape.params }, - (params) => dispatch(params as AppBridgeEventMap[K]), - ); - } - this._registeredEvents.add(event); - } - - protected warnIfRequestHandlerReplaced( - name: string, - previous: unknown, - next: unknown, - ): void { - if (previous && next) { - console.warn( - `[MCP Apps] ${name} handler replaced. ` + - `Previous handler will no longer be called.`, - ); - } - } - /** * Create a new AppBridge instance. * @@ -502,8 +359,8 @@ export class AppBridge extends Protocol { * between the View and the server. When `null`, you must register handlers * manually using the {@link oncalltool `oncalltool`}, {@link onlistresources `onlistresources`}, etc. setters. * @param _hostInfo - Host application identification (name and version) - * @param _hostCapabilities - Features and capabilities the host supports - * @param options - Configuration options (`ProtocolOptions` from the base MCP SDK plus `hostContext`) + * @param _capabilities - Features and capabilities the host supports + * @param options - Configuration options (inherited from Protocol) * * @example With MCP client (automatic forwarding) * ```ts source="./app-bridge.examples.ts#AppBridge_constructor_withMcpClient" @@ -530,27 +387,22 @@ export class AppBridge extends Protocol { constructor( private _client: Client | null, private _hostInfo: Implementation, - private _hostCapabilities: McpUiHostCapabilities, + private _capabilities: McpUiHostCapabilities, options?: HostOptions, ) { super(options); - this._methods.replace("notifications/cancelled"); - this._methods.replace("notifications/progress"); - this._ensureEventSlot("initialized"); + this.addEventListener("initialized", () => { + this._initializedReceived = true; + }); this._hostContext = options?.hostContext || {}; - this.replaceRequestHandler( - "ui/initialize", - { - params: McpUiInitializeRequestSchema.shape.params, - result: McpUiInitializeResultSchema, - }, - (params) => this._onAppsInitialize(params), + this.setRequestHandler(McpUiInitializeRequestSchema, (request) => + this._oninitialize(request), ); - this.replaceRequestHandler("ping", (request, extra) => { + this.setRequestHandler(PingRequestSchema, (request, extra) => { this.onping?.(request.params, extra); return {}; }); @@ -558,47 +410,14 @@ export class AppBridge extends Protocol { // Default handler for requestDisplayMode - returns current mode from host context. // Hosts can override this by setting bridge.onrequestdisplaymode = ... this.replaceRequestHandler( - "ui/request-display-mode", - { - params: McpUiRequestDisplayModeRequestSchema.shape.params, - result: McpUiRequestDisplayModeResultSchema, - }, - () => { + McpUiRequestDisplayModeRequestSchema, + (request) => { const currentMode = this._hostContext.displayMode ?? "inline"; return { mode: currentMode }; }, ); } - /** @internal */ - protected buildContext(ctx: BaseContext): BaseContext { - return ctx; - } - - /** - * Verify that the view supports the capability required for the given request method. - * @internal - */ - protected assertCapabilityForMethod(_method: string): void { - // Host-to-view requests are not capability-gated. - } - - /** - * Verify that a request handler is registered and supported for the given method. - * @internal - */ - protected assertRequestHandlerCapability(_method: string): void { - // Host handler registration is not capability-gated. - } - - /** - * Verify that the host supports the capability required for the given notification method. - * @internal - */ - protected assertNotificationCapability(_method: string): void { - // Host notifications are not capability-gated. - } - /** * Get the view's capabilities discovered during initialization. * @@ -722,7 +541,8 @@ export class AppBridge extends Protocol { * ```typescript * bridge.onsandboxready = async () => { * const resource = await mcpClient.request( - * { method: "resources/read", params: { uri: "ui://my-app" } } + * { method: "resources/read", params: { uri: "ui://my-app" } }, + * ReadResourceResultSchema * ); * * bridge.sendSandboxResourceReady({ @@ -769,13 +589,13 @@ export class AppBridge extends Protocol { * @deprecated Use {@link addEventListener `addEventListener("initialized", handler)`} instead — it composes with other listeners and supports cleanup via {@link removeEventListener `removeEventListener`}. */ get oninitialized(): - | ((params?: McpUiInitializedNotification["params"]) => void) + | ((params: McpUiInitializedNotification["params"]) => void) | undefined { return this.getEventHandler("initialized"); } set oninitialized( callback: - | ((params?: McpUiInitializedNotification["params"]) => void) + | ((params: McpUiInitializedNotification["params"]) => void) | undefined, ) { this.setEventHandler("initialized", callback); @@ -833,14 +653,10 @@ export class AppBridge extends Protocol { this.warnIfRequestHandlerReplaced("onmessage", this._onmessage, callback); this._onmessage = callback; this.replaceRequestHandler( - "ui/message", - { - params: McpUiMessageRequestSchema.shape.params, - result: McpUiMessageResultSchema, - }, - async (params, extra) => { + McpUiMessageRequestSchema, + async (request, extra) => { if (!this._onmessage) throw new Error("No onmessage handler set"); - return this._onmessage(params, extra); + return this._onmessage(request.params, extra); }, ); } @@ -906,14 +722,10 @@ export class AppBridge extends Protocol { this.warnIfRequestHandlerReplaced("onopenlink", this._onopenlink, callback); this._onopenlink = callback; this.replaceRequestHandler( - "ui/open-link", - { - params: McpUiOpenLinkRequestSchema.shape.params, - result: McpUiOpenLinkResultSchema, - }, - async (params, extra) => { + McpUiOpenLinkRequestSchema, + async (request, extra) => { if (!this._onopenlink) throw new Error("No onopenlink handler set"); - return this._onopenlink(params, extra); + return this._onopenlink(request.params, extra); }, ); } @@ -982,15 +794,11 @@ export class AppBridge extends Protocol { ); this._ondownloadfile = callback; this.replaceRequestHandler( - "ui/download-file", - { - params: McpUiDownloadFileRequestSchema.shape.params, - result: McpUiDownloadFileResultSchema, - }, - async (params, extra) => { + McpUiDownloadFileRequestSchema, + async (request, extra) => { if (!this._ondownloadfile) throw new Error("No ondownloadfile handler set"); - return this._ondownloadfile(params, extra); + return this._ondownloadfile(request.params, extra); }, ); } @@ -1086,15 +894,11 @@ export class AppBridge extends Protocol { ); this._onrequestdisplaymode = callback; this.replaceRequestHandler( - "ui/request-display-mode", - { - params: McpUiRequestDisplayModeRequestSchema.shape.params, - result: McpUiRequestDisplayModeResultSchema, - }, - async (params, extra) => { + McpUiRequestDisplayModeRequestSchema, + async (request, extra) => { if (!this._onrequestdisplaymode) throw new Error("No onrequestdisplaymode handler set"); - return this._onrequestdisplaymode(params, extra); + return this._onrequestdisplaymode(request.params, extra); }, ); } @@ -1187,15 +991,11 @@ export class AppBridge extends Protocol { ); this._onupdatemodelcontext = callback; this.replaceRequestHandler( - "ui/update-model-context", - { - params: McpUiUpdateModelContextRequestSchema.shape.params, - result: EmptyResultSchema, - }, - async (params, extra) => { + McpUiUpdateModelContextRequestSchema, + async (request, extra) => { if (!this._onupdatemodelcontext) throw new Error("No onupdatemodelcontext handler set"); - return this._onupdatemodelcontext(params, extra); + return this._onupdatemodelcontext(request.params, extra); }, ); } @@ -1217,13 +1017,14 @@ export class AppBridge extends Protocol { * bridge.oncalltool = async (params, extra) => { * return mcpClient.request( * { method: "tools/call", params }, - * { signal: extra.mcpReq.signal }, + * CallToolResultSchema, + * { signal: extra.signal }, * ); * }; * ``` * - * @see `CallToolRequest` from @modelcontextprotocol/client for the request type - * @see `CallToolResult` from @modelcontextprotocol/client for the result type + * @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type + * @see `CallToolResult` from @modelcontextprotocol/sdk for the result type */ private _oncalltool?: ( params: CallToolRequest["params"], @@ -1242,10 +1043,13 @@ export class AppBridge extends Protocol { ) { this.warnIfRequestHandlerReplaced("oncalltool", this._oncalltool, callback); this._oncalltool = callback; - this.replaceRequestHandler("tools/call", async (request, extra) => { - if (!this._oncalltool) throw new Error("No oncalltool handler set"); - return this._oncalltool(request.params, extra); - }); + this.replaceRequestHandler( + CallToolRequestSchema, + async (request, extra) => { + if (!this._oncalltool) throw new Error("No oncalltool handler set"); + return this._oncalltool(request.params, extra); + }, + ); } /** @@ -1270,43 +1074,23 @@ export class AppBridge extends Protocol { * ```ts source="./app-bridge.examples.ts#AppBridge_oncreatesamplingmessage_forwardToLlm" * bridge.oncreatesamplingmessage = async (params, extra) => { * // Apply rate limiting, user approval, cost controls here - * return await myLlmProvider.complete(params, { - * signal: extra.mcpReq.signal, - * }); + * return await myLlmProvider.complete(params, { signal: extra.signal }); * }; * ``` * - * @see `CreateMessageRequest` from @modelcontextprotocol/client for the request type - * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/client for result types + * @see `CreateMessageRequest` from @modelcontextprotocol/sdk for the request type + * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/sdk for result types */ - private _oncreatesamplingmessage?: ( - params: CreateMessageRequest["params"], - extra: RequestHandlerExtra, - ) => Promise; - get oncreatesamplingmessage() { - return this._oncreatesamplingmessage; - } set oncreatesamplingmessage( - callback: - | (( - params: CreateMessageRequest["params"], - extra: RequestHandlerExtra, - ) => Promise) - | undefined, + callback: ( + params: CreateMessageRequest["params"], + extra: RequestHandlerExtra, + ) => Promise, ) { - this.warnIfRequestHandlerReplaced( - "oncreatesamplingmessage", - this._oncreatesamplingmessage, - callback, - ); - this._oncreatesamplingmessage = callback; - this.replaceRequestHandler( - "sampling/createMessage", + this.setRequestHandler( + CreateMessageRequestSchema, async (request, extra) => { - if (!this._oncreatesamplingmessage) { - throw new Error("No oncreatesamplingmessage handler set"); - } - return this._oncreatesamplingmessage(request.params, extra); + return callback(request.params, extra); }, ); } @@ -1323,12 +1107,12 @@ export class AppBridge extends Protocol { * @example * ```typescript * // In your MCP client notification handler: - * mcpClient.setNotificationHandler("notifications/tools/list_changed", () => { + * mcpClient.setNotificationHandler(ToolListChangedNotificationSchema, () => { * bridge.sendToolListChanged(); * }); * ``` * - * @see `ToolListChangedNotification` from @modelcontextprotocol/client for the notification type + * @see `ToolListChangedNotification` from @modelcontextprotocol/sdk for the notification type */ sendToolListChanged(params: ToolListChangedNotification["params"] = {}) { return this.notification({ @@ -1354,13 +1138,14 @@ export class AppBridge extends Protocol { * bridge.onlistresources = async (params, extra) => { * return mcpClient.request( * { method: "resources/list", params }, - * { signal: extra.mcpReq.signal }, + * ListResourcesResultSchema, + * { signal: extra.signal }, * ); * }; * ``` * - * @see `ListResourcesRequest` from @modelcontextprotocol/client for the request type - * @see `ListResourcesResult` from @modelcontextprotocol/client for the result type + * @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type + * @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type */ private _onlistresources?: ( params: ListResourcesRequest["params"], @@ -1383,11 +1168,14 @@ export class AppBridge extends Protocol { callback, ); this._onlistresources = callback; - this.replaceRequestHandler("resources/list", async (request, extra) => { - if (!this._onlistresources) - throw new Error("No onlistresources handler set"); - return this._onlistresources(request.params, extra); - }); + this.replaceRequestHandler( + ListResourcesRequestSchema, + async (request, extra) => { + if (!this._onlistresources) + throw new Error("No onlistresources handler set"); + return this._onlistresources(request.params, extra); + }, + ); } /** @@ -1407,13 +1195,14 @@ export class AppBridge extends Protocol { * bridge.onlistresourcetemplates = async (params, extra) => { * return mcpClient.request( * { method: "resources/templates/list", params }, - * { signal: extra.mcpReq.signal } + * ListResourceTemplatesResultSchema, + * { signal: extra.signal } * ); * }; * ``` * - * @see `ListResourceTemplatesRequest` from @modelcontextprotocol/client for the request type - * @see `ListResourceTemplatesResult` from @modelcontextprotocol/client for the result type + * @see `ListResourceTemplatesRequest` from @modelcontextprotocol/sdk for the request type + * @see `ListResourceTemplatesResult` from @modelcontextprotocol/sdk for the result type */ private _onlistresourcetemplates?: ( params: ListResourceTemplatesRequest["params"], @@ -1437,7 +1226,7 @@ export class AppBridge extends Protocol { ); this._onlistresourcetemplates = callback; this.replaceRequestHandler( - "resources/templates/list", + ListResourceTemplatesRequestSchema, async (request, extra) => { if (!this._onlistresourcetemplates) throw new Error("No onlistresourcetemplates handler set"); @@ -1463,13 +1252,14 @@ export class AppBridge extends Protocol { * bridge.onreadresource = async (params, extra) => { * return mcpClient.request( * { method: "resources/read", params }, - * { signal: extra.mcpReq.signal }, + * ReadResourceResultSchema, + * { signal: extra.signal }, * ); * }; * ``` * - * @see `ReadResourceRequest` from @modelcontextprotocol/client for the request type - * @see `ReadResourceResult` from @modelcontextprotocol/client for the result type + * @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type + * @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type */ private _onreadresource?: ( params: ReadResourceRequest["params"], @@ -1492,11 +1282,14 @@ export class AppBridge extends Protocol { callback, ); this._onreadresource = callback; - this.replaceRequestHandler("resources/read", async (request, extra) => { - if (!this._onreadresource) - throw new Error("No onreadresource handler set"); - return this._onreadresource(request.params, extra); - }); + this.replaceRequestHandler( + ReadResourceRequestSchema, + async (request, extra) => { + if (!this._onreadresource) + throw new Error("No onreadresource handler set"); + return this._onreadresource(request.params, extra); + }, + ); } /** @@ -1511,12 +1304,12 @@ export class AppBridge extends Protocol { * @example * ```typescript * // In your MCP client notification handler: - * mcpClient.setNotificationHandler("notifications/resources/list_changed", () => { + * mcpClient.setNotificationHandler(ResourceListChangedNotificationSchema, () => { * bridge.sendResourceListChanged(); * }); * ``` * - * @see `ResourceListChangedNotification` from @modelcontextprotocol/client for the notification type + * @see `ResourceListChangedNotification` from @modelcontextprotocol/sdk for the notification type */ sendResourceListChanged( params: ResourceListChangedNotification["params"] = {}, @@ -1544,13 +1337,14 @@ export class AppBridge extends Protocol { * bridge.onlistprompts = async (params, extra) => { * return mcpClient.request( * { method: "prompts/list", params }, - * { signal: extra.mcpReq.signal }, + * ListPromptsResultSchema, + * { signal: extra.signal }, * ); * }; * ``` * - * @see `ListPromptsRequest` from @modelcontextprotocol/client for the request type - * @see `ListPromptsResult` from @modelcontextprotocol/client for the result type + * @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type + * @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type */ private _onlistprompts?: ( params: ListPromptsRequest["params"], @@ -1573,10 +1367,14 @@ export class AppBridge extends Protocol { callback, ); this._onlistprompts = callback; - this.replaceRequestHandler("prompts/list", async (request, extra) => { - if (!this._onlistprompts) throw new Error("No onlistprompts handler set"); - return this._onlistprompts(request.params, extra); - }); + this.replaceRequestHandler( + ListPromptsRequestSchema, + async (request, extra) => { + if (!this._onlistprompts) + throw new Error("No onlistprompts handler set"); + return this._onlistprompts(request.params, extra); + }, + ); } /** @@ -1591,12 +1389,12 @@ export class AppBridge extends Protocol { * @example * ```typescript * // In your MCP client notification handler: - * mcpClient.setNotificationHandler("notifications/prompts/list_changed", () => { + * mcpClient.setNotificationHandler(PromptListChangedNotificationSchema, () => { * bridge.sendPromptListChanged(); * }); * ``` * - * @see `PromptListChangedNotification` from @modelcontextprotocol/client for the notification type + * @see `PromptListChangedNotification` from @modelcontextprotocol/sdk for the notification type */ sendPromptListChanged(params: PromptListChangedNotification["params"] = {}) { return this.notification({ @@ -1605,6 +1403,46 @@ export class AppBridge extends Protocol { }); } + /** + * Verify that the guest supports the capability required for the given request method. + * @internal + */ + assertCapabilityForMethod(method: AppRequest["method"]): void { + // TODO + } + + /** + * Verify that a request handler is registered and supported for the given method. + * @internal + */ + assertRequestHandlerCapability(method: AppRequest["method"]): void { + // TODO + } + + /** + * Verify that the host supports the capability required for the given notification method. + * @internal + */ + assertNotificationCapability(method: AppNotification["method"]): void { + // TODO + } + + /** + * Verify that task creation is supported for the given request method. + * @internal + */ + protected assertTaskCapability(_method: string): void { + throw new Error("Tasks are not supported in MCP Apps"); + } + + /** + * Verify that task handler is supported for the given method. + * @internal + */ + protected assertTaskHandlerCapability(_method: string): void { + throw new Error("Task handlers are not supported in MCP Apps"); + } + /** * Get the host capabilities passed to the constructor. * @@ -1613,17 +1451,17 @@ export class AppBridge extends Protocol { * @see {@link McpUiHostCapabilities `McpUiHostCapabilities`} for the capabilities structure */ getCapabilities(): McpUiHostCapabilities { - return this._hostCapabilities; + return this._capabilities; } /** * Handle the ui/initialize request from the guest. * @internal */ - private async _onAppsInitialize( - params: McpUiInitializeRequest["params"], + private async _oninitialize( + request: McpUiInitializeRequest, ): Promise { - const requestedVersion = params.protocolVersion; + const requestedVersion = request.params.protocolVersion; if (this._appInfo !== undefined) { console.warn( @@ -1634,8 +1472,8 @@ export class AppBridge extends Protocol { ); } - this._appCapabilities = params.appCapabilities; - this._appInfo = params.appInfo; + this._appCapabilities = request.params.appCapabilities; + this._appInfo = request.params.appInfo; const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes( requestedVersion, @@ -1798,10 +1636,10 @@ export class AppBridge extends Protocol { * * @example * ```ts source="./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution" - * const result = await mcpClient.request({ - * method: "tools/call", - * params: { name: "get_weather", arguments: args }, - * }); + * const result = await mcpClient.request( + * { method: "tools/call", params: { name: "get_weather", arguments: args } }, + * CallToolResultSchema, + * ); * bridge.sendToolResult(result); * ``` * @@ -1927,7 +1765,11 @@ export class AppBridge extends Protocol { * @returns Promise resolving to the tool call result */ callTool(params: CallToolRequest["params"], options?: RequestOptions) { - return this.request({ method: "tools/call", params }, options); + return this.request( + { method: "tools/call", params }, + CallToolResultSchema, + options, + ); } /** @@ -1940,7 +1782,11 @@ export class AppBridge extends Protocol { * @returns Promise resolving to the list of tools */ listTools(params: ListToolsRequest["params"], options?: RequestOptions) { - return this.request({ method: "tools/list", params }, options); + return this.request( + { method: "tools/list", params }, + ListToolsResultSchema, + options, + ); } /** @@ -2016,12 +1862,13 @@ export class AppBridge extends Protocol { this.oncalltool = async (params, extra) => { return this._client!.request( { method: "tools/call", params }, - { signal: extra.mcpReq.signal }, + CallToolResultSchema, + { signal: extra.signal }, ); }; if (serverCapabilities.tools.listChanged) { this._client.setNotificationHandler( - "notifications/tools/list_changed", + ToolListChangedNotificationSchema, (n) => this.sendToolListChanged(n.params), ); } @@ -2030,24 +1877,27 @@ export class AppBridge extends Protocol { this.onlistresources = async (params, extra) => { return this._client!.request( { method: "resources/list", params }, - { signal: extra.mcpReq.signal }, + ListResourcesResultSchema, + { signal: extra.signal }, ); }; this.onlistresourcetemplates = async (params, extra) => { return this._client!.request( { method: "resources/templates/list", params }, - { signal: extra.mcpReq.signal }, + ListResourceTemplatesResultSchema, + { signal: extra.signal }, ); }; this.onreadresource = async (params, extra) => { return this._client!.request( { method: "resources/read", params }, - { signal: extra.mcpReq.signal }, + ReadResourceResultSchema, + { signal: extra.signal }, ); }; if (serverCapabilities.resources.listChanged) { this._client.setNotificationHandler( - "notifications/resources/list_changed", + ResourceListChangedNotificationSchema, (n) => this.sendResourceListChanged(n.params), ); } @@ -2056,21 +1906,23 @@ export class AppBridge extends Protocol { this.onlistprompts = async (params, extra) => { return this._client!.request( { method: "prompts/list", params }, - { signal: extra.mcpReq.signal }, + ListPromptsResultSchema, + { signal: extra.signal }, ); }; if (serverCapabilities.prompts.listChanged) { this._client.setNotificationHandler( - "notifications/prompts/list_changed", + PromptListChangedNotificationSchema, (n) => this.sendPromptListChanged(n.params), ); } } } - // Protocol.connect only wires the transport — no MCP handshake happens on - // the iframe channel. The View drives ui/initialize; - // ui/notifications/initialized remains the public View-ready signal. + // MCP-UI specific handlers are registered by the host component + // after the proxy is created. The standard MCP initialization + // (via oninitialized callback set in constructor) handles the ready signal. + return super.connect(transport); } } diff --git a/src/app.examples.ts b/src/app.examples.ts index 7d475ef..81bdd44 100644 --- a/src/app.examples.ts +++ b/src/app.examples.ts @@ -7,8 +7,11 @@ * @module */ -import type { SamplingMessage, Tool } from "@modelcontextprotocol/client"; -import type { McpServer, ToolCallback } from "@modelcontextprotocol/server"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; +import type { + McpServer, + ToolCallback, +} from "@modelcontextprotocol/sdk/server/mcp.js"; import { App, PostMessageTransport, @@ -366,7 +369,7 @@ async function App_createSamplingMessage_simple(app: App) { */ async function App_createSamplingMessage_withTools( app: App, - messages: SamplingMessage[], + messages: import("@modelcontextprotocol/sdk/types.js").SamplingMessage[], ) { //#region App_createSamplingMessage_withTools if (!app.getHostCapabilities()?.sampling?.tools) return; diff --git a/src/app.test.ts b/src/app.test.ts deleted file mode 100644 index 8420e81..0000000 --- a/src/app.test.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { afterEach, describe, expect, it } from "bun:test"; -import { - InMemoryTransport, - Server, - type JSONRPCMessage, -} from "@modelcontextprotocol/server"; -import { z } from "zod/v4"; - -import { App } from "./app.js"; -import { - LATEST_PROTOCOL_VERSION, - McpUiInitializeRequestSchema, - McpUiInitializeResultSchema, - McpUiInitializedNotificationSchema, -} from "./types.js"; - -type ConnectedPair = { - app: App; - server: Server; - sentByApp: JSONRPCMessage[]; - appsInitialized: Promise; -}; - -async function connectPair( - app = new App( - { name: "view-app", version: "1.0.0" }, - { tools: { listChanged: true } }, - { autoResize: false }, - ), -): Promise { - const server = new Server( - { name: "inner-mcp-server", version: "0.0.0" }, - { - capabilities: { resources: {}, tools: {} }, - }, - ); - const [appTransport, serverTransport] = InMemoryTransport.createLinkedPair(); - const sentByApp: JSONRPCMessage[] = []; - const send = appTransport.send.bind(appTransport); - appTransport.send = async (message, options) => { - sentByApp.push(message); - await send(message, options); - }; - - let resolveAppsInitialized!: () => void; - const appsInitialized = new Promise((resolve) => { - resolveAppsInitialized = resolve; - }); - - server.setRequestHandler( - "ui/initialize", - { - params: McpUiInitializeRequestSchema.shape.params, - result: McpUiInitializeResultSchema, - }, - (params) => { - expect(params.protocolVersion).toBe(LATEST_PROTOCOL_VERSION); - return { - protocolVersion: LATEST_PROTOCOL_VERSION, - hostCapabilities: { openLinks: {} }, - hostInfo: { name: "apps-host", version: "2.0.0" }, - hostContext: { theme: "dark" as const, locale: "en-US" }, - }; - }, - ); - server.setNotificationHandler( - "ui/notifications/initialized", - { params: McpUiInitializedNotificationSchema.shape.params }, - () => resolveAppsInitialized(), - ); - server.setRequestHandler("tools/call", (request) => ({ - content: [{ type: "text", text: `called ${request.params.name}` }], - })); - server.setRequestHandler("resources/read", (request) => ({ - contents: [{ uri: request.params.uri, text: "resource body" }], - })); - server.setRequestHandler("resources/list", () => ({ - resources: [{ uri: "test://resource", name: "Test resource" }], - })); - server.setRequestHandler("sampling/createMessage", () => ({ - model: "test-model", - role: "assistant", - content: { type: "text", text: "sampled" }, - })); - - await server.connect(serverTransport); - await app.connect(appTransport); - await appsInitialized; - - return { app, server, sentByApp, appsInitialized }; -} - -const connected: Array> = []; - -afterEach(async () => { - await Promise.all( - connected - .splice(0) - .flatMap(({ app, server }) => [app.close(), server.close()]), - ); -}); - -describe("App base MCP SDK v2 Protocol migration", () => { - it("performs only the Apps handshake — no MCP initialize on the wire", async () => { - const pair = await connectPair(); - connected.push(pair); - - const lifecycleMethods = pair.sentByApp - .filter( - (message): message is JSONRPCMessage & { method: string } => - "method" in message, - ) - .map((message) => message.method) - .filter((method) => - [ - "initialize", - "notifications/initialized", - "ui/initialize", - "ui/notifications/initialized", - ].includes(method), - ); - - // The filter above includes the core MCP lifecycle methods, so this - // equality also proves no `initialize`/`notifications/initialized` was sent - // — the wire contract deployed v1 hosts depend on. - expect(lifecycleMethods).toEqual([ - "ui/initialize", - "ui/notifications/initialized", - ]); - }); - - it("uses ui/initialize as the authority for Apps host state", async () => { - const pair = await connectPair(); - connected.push(pair); - - expect(pair.app.getHostVersion()).toEqual({ - name: "apps-host", - version: "2.0.0", - }); - expect(pair.app.getHostCapabilities()).toEqual({ openLinks: {} }); - expect(pair.app.getHostContext()).toEqual({ - theme: "dark", - locale: "en-US", - }); - }); - - it("preserves registered App tools with the base MCP SDK v2 Client handler context", async () => { - const pair = await connectPair(); - connected.push(pair); - let receivedSignal: AbortSignal | undefined; - - pair.app.registerTool( - "greet", - { description: "Greets a person" }, - async (extra) => { - receivedSignal = extra.mcpReq.signal; - return { content: [{ type: "text", text: "hello" }] }; - }, - ); - - const listed = await pair.server.request({ - method: "tools/list", - params: {}, - }); - const result = await pair.server.request({ - method: "tools/call", - params: { name: "greet", arguments: {} }, - }); - - expect(listed.tools.map((tool) => tool.name)).toEqual(["greet"]); - expect(result.content).toEqual([{ type: "text", text: "hello" }]); - expect(receivedSignal).toBeInstanceOf(AbortSignal); - }); - - it("uses base MCP SDK v2 method-keyed sends for standard MCP requests", async () => { - const pair = await connectPair(); - connected.push(pair); - - const toolResult = await pair.app.callServerTool({ - name: "refresh", - arguments: {}, - }); - const resource = await pair.app.readServerResource({ - uri: "test://resource", - }); - const resources = await pair.app.listServerResources(); - const sample = await pair.app.createSamplingMessage({ - messages: [{ role: "user", content: { type: "text", text: "hello" } }], - maxTokens: 20, - }); - - expect(toolResult.content).toEqual([ - { type: "text", text: "called refresh" }, - ]); - expect(resource.contents[0]).toEqual({ - uri: "test://resource", - text: "resource body", - }); - expect(resources.resources.map(({ uri }) => uri)).toEqual([ - "test://resource", - ]); - expect(sample.content).toEqual({ type: "text", text: "sampled" }); - }); - - it("preserves reconnect behavior after close", async () => { - const first = await connectPair(); - await first.app.close(); - await first.server.close(); - - const second = await connectPair(first.app); - connected.push(second); - - expect(second.app.getHostVersion()).toEqual({ - name: "apps-host", - version: "2.0.0", - }); - }); - - it("preserves auto-resize setup after Apps initialization", async () => { - const app = new App( - { name: "view-app", version: "1.0.0" }, - {}, - { autoResize: true }, - ); - let setupCalls = 0; - app.setupSizeChangedNotifications = () => { - setupCalls += 1; - return () => {}; - }; - - const pair = await connectPair(app); - connected.push(pair); - - expect(setupCalls).toBe(1); - }); - - it("preserves strict late-handler timing errors", async () => { - const app = new App( - { name: "view-app", version: "1.0.0" }, - {}, - { autoResize: false, strict: true }, - ); - const pair = await connectPair(app); - connected.push(pair); - - expect(() => { - app.ontoolinput = () => {}; - }).toThrow(/handler registered after connect/); - }); - - it("direct setRequestHandler throws when a handler is already registered", async () => { - const pair = await connectPair(); - connected.push(pair); - const params = z.object({}); - let handledBy = 0; - - pair.app.setRequestHandler("test/method", { params }, () => { - handledBy = 1; - return {}; - }); - expect(() => { - pair.app.setRequestHandler("test/method", { params }, () => { - handledBy = 2; - return {}; - }); - }).toThrow(/already registered/); - - await pair.server.request( - { method: "test/method", params: {} }, - z.object({}), - ); - expect(handledBy).toBe(1); - }); - - it("direct setRequestHandler throws for methods owned by the App itself", async () => { - const pair = await connectPair(); - connected.push(pair); - expect(() => { - pair.app.setRequestHandler("ping", () => ({})); - }).toThrow(/already registered/); - }); - - it("removeRequestHandler releases the method for re-registration", async () => { - const pair = await connectPair(); - connected.push(pair); - const params = z.object({}); - let handledBy = 0; - - pair.app.setRequestHandler("test/method", { params }, () => { - handledBy = 1; - return {}; - }); - pair.app.removeRequestHandler("test/method"); - pair.app.setRequestHandler("test/method", { params }, () => { - handledBy = 2; - return {}; - }); - - await pair.server.request( - { method: "test/method", params: {} }, - z.object({}), - ); - expect(handledBy).toBe(2); - }); - - it("direct setNotificationHandler throws when a handler is already registered", async () => { - const pair = await connectPair(); - connected.push(pair); - const calls: number[] = []; - const params = z.object({}); - - pair.app.setNotificationHandler("test/notification", { params }, () => { - calls.push(1); - }); - expect(() => { - pair.app.setNotificationHandler("test/notification", { params }, () => { - calls.push(2); - }); - }).toThrow(/already registered/); - await pair.server.notification({ - method: "test/notification", - params: {}, - }); - - expect(calls).toEqual([1]); - }); - - it("direct setNotificationHandler throws for event-mapped methods (listener first)", () => { - const app = new App( - { name: "view-app", version: "1.0.0" }, - {}, - { autoResize: false }, - ); - app.addEventListener("toolinput", () => {}); - expect(() => { - app.setNotificationHandler( - "ui/notifications/tool-input", - { params: z.object({}) }, - () => {}, - ); - }).toThrow(/already registered/); - }); - - it("event registration throws when a direct handler already owns the method", () => { - const app = new App( - { name: "view-app", version: "1.0.0" }, - {}, - { autoResize: false }, - ); - app.setNotificationHandler( - "ui/notifications/tool-input", - { params: z.object({}) }, - () => {}, - ); - expect(() => { - app.ontoolinput = () => {}; - }).toThrow(/already registered/); - expect(() => { - app.addEventListener("toolinput", () => {}); - }).toThrow(/already registered/); - }); - - it("on* request setters keep replace semantics", async () => { - const pair = await connectPair(); - connected.push(pair); - let handledBy = 0; - pair.app.onteardown = async () => { - handledBy = 1; - return {}; - }; - pair.app.onteardown = async () => { - handledBy = 2; - return {}; - }; - await pair.server.request( - { method: "ui/resource-teardown", params: {} }, - z.object({}), - ); - expect(handledBy).toBe(2); - }); -}); diff --git a/src/app.ts b/src/app.ts index 89c6c35..adfad5c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,45 +1,40 @@ import { - Protocol, - type BaseContext, - type ProtocolOptions, type RequestOptions, - type CallToolRequest, - type CallToolResult, - type CreateMessageRequest, - type CreateMessageResult, - type CreateMessageResultWithTools, - type Implementation, - type ListResourcesRequest, - type ListResourcesResult, - type ListToolsRequest, - type ListToolsResult, - type LoggingMessageNotification, - type ReadResourceRequest, - type ReadResourceResult, - type Tool, - type ToolAnnotations, - type ToolListChangedNotification, - type Transport, -} from "@modelcontextprotocol/client"; -import { EmptyResultSchema } from "@modelcontextprotocol/core"; -export { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "./constants.js"; -import { EventDispatcher, MethodRegistry } from "./events.js"; -import { - toModernArgs, - 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"; + mergeCapabilities, + ProtocolOptions, +} from "@modelcontextprotocol/sdk/shared/protocol.js"; -import { PostMessageTransport } from "./message-transport.js"; +import { + CallToolRequest, + CallToolRequestSchema, + CallToolResult, + CallToolResultSchema, + CreateMessageRequest, + CreateMessageResult, + CreateMessageResultSchema, + CreateMessageResultWithTools, + CreateMessageResultWithToolsSchema, + EmptyResultSchema, + Implementation, + ListResourcesRequest, + ListResourcesResult, + ListResourcesResultSchema, + ListToolsRequest, + ListToolsRequestSchema, + ListToolsResult, + LoggingMessageNotification, + PingRequestSchema, + ReadResourceRequest, + ReadResourceResult, + ReadResourceResultSchema, + Tool, + ToolAnnotations, + ToolListChangedNotification, +} from "@modelcontextprotocol/sdk/types.js"; +import { AppNotification, AppRequest, AppResult } from "./types"; +import { ProtocolWithEvents } from "./events"; +export { ProtocolWithEvents }; +import { PostMessageTransport } from "./message-transport"; import { LATEST_PROTOCOL_VERSION, McpUiAppCapabilities, @@ -60,7 +55,6 @@ import { McpUiResourceTeardownRequest, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResult, - McpUiResourceTeardownResultSchema, McpUiRequestTeardownNotification, McpUiSizeChangedNotification, McpUiToolCancelledNotification, @@ -73,60 +67,104 @@ import { McpUiToolResultNotificationSchema, McpUiRequestDisplayModeRequest, McpUiRequestDisplayModeResultSchema, -} from "./types.js"; +} from "./types"; +import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { StandardSchemaV1, standardSchemaToJsonSchema, validateStandardSchema, -} from "./standard-schema.js"; -import { z, type ZodLiteral, type ZodObject, type ZodType } from "zod/v4"; - -type UntypedHandlerSetter = (this: unknown, ...args: unknown[]) => void; - -type MethodSchema = ZodObject<{ - method: ZodLiteral; - params: ZodType; -}>; - -function mergeAppCapabilities( - base: McpUiAppCapabilities, - additional: McpUiAppCapabilities, -): McpUiAppCapabilities { - const result = { ...base, ...additional }; - if (additional.experimental !== undefined) { - result.experimental = { - ...base.experimental, - ...additional.experimental, - }; - } - if (additional.tools !== undefined) { - result.tools = { ...base.tools, ...additional.tools }; - } - return result; -} +} from "./standard-schema"; +import { z } from "zod/v4"; export type { StandardSchemaV1, StandardSchemaWithJSON, -} from "./standard-schema.js"; +} from "./standard-schema"; -export { PostMessageTransport } from "./message-transport.js"; -export * from "./types.js"; +export { PostMessageTransport } from "./message-transport"; +export * from "./types"; export { applyHostStyleVariables, applyHostFonts, getDocumentTheme, applyDocumentTheme, -} from "./styles.js"; +} from "./styles"; + +/** + * Metadata key for associating a UI resource URI with a tool. + * + * MCP servers include this key in tool definition metadata (via `tools/list`) + * to indicate which UI resource should be displayed when the tool is called. + * When hosts see a tool with this metadata, they fetch and render the + * corresponding {@link App `App`}. + * + * **Note**: This constant is provided for reference and backwards compatibility. + * Server developers should use {@link server-helpers!registerAppTool `registerAppTool`} + * with the `_meta.ui.resourceUri` format instead. Host developers must check both + * formats for compatibility. + * + * @example Modern format (server-side, not in Apps) + * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_modernFormat" + * // Preferred: Use registerAppTool with nested ui.resourceUri + * registerAppTool( + * server, + * "weather", + * { + * description: "Get weather forecast", + * _meta: { + * ui: { resourceUri: "ui://weather/forecast" }, + * }, + * }, + * handler, + * ); + * ``` + * + * @example Legacy format (deprecated, for backwards compatibility) + * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_legacyFormat" + * // Deprecated: Direct use of RESOURCE_URI_META_KEY + * server.registerTool( + * "weather", + * { + * description: "Get weather forecast", + * _meta: { + * [RESOURCE_URI_META_KEY]: "ui://weather/forecast", + * }, + * }, + * handler, + * ); + * ``` + * + * @example How hosts check for this metadata (must support both formats) + * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_hostSide" + * // Hosts should check both modern and legacy formats + * const meta = tool._meta; + * const uiMeta = meta?.ui as McpUiToolMeta | undefined; + * const legacyUri = meta?.[RESOURCE_URI_META_KEY] as string | undefined; + * const uiUri = uiMeta?.resourceUri ?? legacyUri; + * if (typeof uiUri === "string" && uiUri.startsWith("ui://")) { + * // Fetch the resource and display the UI + * } + * ``` + */ +export const RESOURCE_URI_META_KEY = "ui/resourceUri"; + +/** + * MIME type for MCP UI resources. + * + * Identifies HTML content as an MCP App UI resource. + * + * Used by {@link server-helpers!registerAppResource `registerAppResource`} as the default MIME type for app resources. + */ +export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; /** * Options for configuring {@link App `App`} behavior. * - * Extends `ProtocolOptions` from the base MCP SDK with `App`-specific configuration. + * Extends `ProtocolOptions` from the MCP SDK with `App`-specific configuration. * - * @see `ProtocolOptions` from @modelcontextprotocol/client for inherited options + * @see `ProtocolOptions` from @modelcontextprotocol/sdk for inherited options */ -export type AppOptions = Omit & { +export type AppOptions = ProtocolOptions & { /** * Automatically report size changes to the host using `ResizeObserver`. * @@ -166,7 +204,9 @@ export type AppOptions = Omit & { allowUnsafeEval?: boolean; }; -type RequestHandlerExtra = BaseContext; +type RequestHandlerExtra = Parameters< + Parameters[1] +>[1]; /** * Result of an app-registered tool callback. When `Out` is provided, @@ -188,7 +228,7 @@ export type AppToolResult< * validated/parsed input; when `In` is `undefined`, the callback receives only * `extra`. When `Out` is provided, the return's `structuredContent` is typed. * - * Mirrors `ToolCallback` from `@modelcontextprotocol/server` but is + * Mirrors `ToolCallback` from `@modelcontextprotocol/sdk/server/mcp.js` but is * parameterized over {@link StandardSchemaV1} instead of zod, so any * Standard-Schema-compatible library (Zod, ArkType, Valibot, …) can be used. */ @@ -206,7 +246,7 @@ export type AppToolCallback< /** * Handle returned by {@link App.registerTool}. Mirrors `RegisteredTool` from - * `@modelcontextprotocol/server` but stores + * `@modelcontextprotocol/sdk/server/mcp.js` but stores * {@link StandardSchemaV1} schemas. */ export type RegisteredAppTool = { @@ -231,9 +271,9 @@ export type RegisteredAppTool = { /** * Maps DOM-style event names to their notification `params` types. * - * Used by {@link App `App`} and its composed {@link EventDispatcher} to - * provide type-safe `addEventListener` / `removeEventListener` and singular - * `on*` handler support. + * Used by {@link App `App`} (which extends {@link ProtocolWithEvents `ProtocolWithEvents`}) + * to provide type-safe `addEventListener` / `removeEventListener` and + * singular `on*` handler support. */ export type AppEventMap = { toolinput: McpUiToolInputNotification["params"]; @@ -247,9 +287,9 @@ export type AppEventMap = { * Main class for MCP Apps to communicate with their host. * * The `App` class provides a framework-agnostic way to build interactive MCP Apps - * that run inside host applications. It extends the base MCP SDK's public - * `Protocol` class, composes an {@link EventDispatcher}, and adds the Apps - * handshake, lifecycle, and high-level APIs. + * that run inside host applications. It extends the MCP SDK's `Protocol` class and + * handles the connection lifecycle, initialization handshake, and bidirectional + * communication with the host. * * ## Architecture * @@ -264,16 +304,16 @@ export type AppEventMap = { * 3. **Interactive**: Send requests, receive notifications, call tools * 4. **Teardown**: Host sends teardown request before unmounting * - * ## Protocol and event methods + * ## Inherited Methods * - * From the base MCP SDK's `Protocol`, `App` provides: + * As a subclass of {@link ProtocolWithEvents `ProtocolWithEvents`}, `App` inherits: * - `setRequestHandler()` - Register handlers for requests from host * - `setNotificationHandler()` - Register handlers for notifications from host - * - * From its composed {@link EventDispatcher}, `App` also provides: * - `addEventListener()` - Append a listener for a notification event (multi-listener) * - `removeEventListener()` - Remove a previously added listener * + * @see {@link ProtocolWithEvents `ProtocolWithEvents`} for the DOM-model event system + * * ## Notification Setters (DOM-model `on*` handlers) * * For common notifications, the `App` class provides getter/setter properties @@ -302,90 +342,17 @@ export type AppEventMap = { * await app.connect(); * ``` */ -export class App extends Protocol { +export class App extends ProtocolWithEvents< + AppRequest, + AppNotification, + AppResult, + AppEventMap +> { private _hostCapabilities?: McpUiHostCapabilities; private _hostInfo?: Implementation; private _hostContext?: McpUiHostContext; private _registeredTools: { [name: string]: RegisteredAppTool } = {}; private _initializedSent = false; - private readonly _registeredEvents = new Set(); - private readonly _events = new EventDispatcher(); - private readonly _methods = new MethodRegistry(); - - // ── Handler registration with double-set protection ───────────────── - // - // The base SDK `Protocol` silently replaces an existing handler. The four - // overrides below restore the v1 behaviour: a direct `setRequestHandler` / - // `setNotificationHandler` for a method that already has a handler throws, - // so a stray registration cannot silently disconnect `on*` handlers or - // `addEventListener` listeners. They are arrow-function class fields rather - // than prototype methods so that `Protocol`'s constructor — which registers - // its own ping/cancelled/progress handlers before our fields initialize — - // hits the base implementation and skips tracking. - - /** - * Registers a request handler. Throws if a handler for the same method has - * already been registered — use the `on*` setter for replace semantics. - * - * @throws {Error} if a handler for this method is already registered. - */ - 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( - this, - method, - ...rest, - ); - }; - - /** - * Registers a notification handler. Throws if a handler for the same method - * has already been registered — use the `on*` setter (replace semantics) or - * `addEventListener` (multi-listener) for mapped events. - * - * @throws {Error} if a handler for this method is already registered. - */ - 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( - this, - method, - ...rest, - ); - }; - - override removeRequestHandler: Protocol["removeRequestHandler"] = - (method: string) => { - this._methods.release(method); - super.removeRequestHandler(method); - }; - - override removeNotificationHandler: Protocol["removeNotificationHandler"] = - (method: string) => { - this._methods.release(method); - super.removeNotificationHandler(method); - }; - - /** - * Register a request handler with replace semantics, bypassing the - * double-set protection of {@link setRequestHandler `setRequestHandler`}. - * Used by the `on*` request-handler setters. - */ - protected replaceRequestHandler: Protocol["setRequestHandler"] = - (method: string, ...rest: unknown[]) => { - this._methods.replace(method); - (super.setRequestHandler as unknown as UntypedHandlerSetter).call( - this, - method, - ...rest, - ); - }; /** * Warn if a host-bound method is called before {@link connect `connect`} has @@ -411,9 +378,7 @@ export class App extends Protocol { console.warn(`${msg}. This will throw in a future release.`); } - private readonly eventSchemas: { - [K in keyof AppEventMap]: MethodSchema; - } = { + protected readonly eventSchemas = { toolinput: McpUiToolInputNotificationSchema, toolinputpartial: McpUiToolInputPartialNotificationSchema, toolresult: McpUiToolResultNotificationSchema, @@ -462,65 +427,28 @@ export class App extends Protocol { console.warn(msg); } - protected setEventHandler( + protected override setEventHandler( event: K, handler: ((params: AppEventMap[K]) => void) | undefined, ): void { if (handler) this._assertHandlerTiming(event); - this._ensureEventSlot(event); - this._events.setHandler(event, handler); + super.setEventHandler(event, handler); } - addEventListener( + override addEventListener( event: K, handler: (params: AppEventMap[K]) => void, ): void { this._assertHandlerTiming(event); - this._ensureEventSlot(event); - this._events.addEventListener(event, handler); + super.addEventListener(event, handler); } - removeEventListener( + protected override onEventDispatch( event: K, - handler: (params: AppEventMap[K]) => void, + params: AppEventMap[K], ): void { - this._events.removeEventListener(event, handler); - } - - private _ensureEventSlot(event: K): void { - if (this._registeredEvents.has(event)) return; - const schema = this.eventSchemas[event]; - if (!schema) throw new Error(`Unknown event: ${String(event)}`); - this.setNotificationHandler( - schema.shape.method.value, - { params: schema.shape.params }, - (params) => { - const eventParams = params as AppEventMap[K]; - if (event === "hostcontextchanged") { - this._hostContext = { ...this._hostContext, ...eventParams }; - } - this._events.dispatch(event, eventParams); - }, - ); - this._registeredEvents.add(event); - } - - protected getEventHandler( - event: K, - ): ((params: AppEventMap[K]) => void) | undefined { - return this._events.getHandler(event); - } - - protected warnIfRequestHandlerReplaced( - name: string, - previous: unknown, - next: unknown, - ): void { - if (previous && next) { - console.warn( - `[MCP Apps] ${name} handler replaced. ` + - `Previous handler will no longer be called.`, - ); + if (event === "hostcontextchanged") { + this._hostContext = { ...this._hostContext, ...params }; } } @@ -528,7 +456,7 @@ export class App extends Protocol { * Create a new MCP App instance. * * @param _appInfo - App identification (name and version) - * @param _appCapabilities - Features and capabilities this app provides + * @param _capabilities - Features and capabilities this app provides * @param options - Configuration options including `autoResize` behavior * * @example @@ -542,44 +470,33 @@ export class App extends Protocol { */ constructor( private _appInfo: Implementation, - private _appCapabilities: McpUiAppCapabilities = {}, + private _capabilities: McpUiAppCapabilities = {}, private options: AppOptions = { autoResize: true }, ) { super(options); - // Claim the handlers the base Protocol constructor installed so a direct - // setNotificationHandler cannot silently replace them either. - this._methods.replace("notifications/cancelled"); - this._methods.replace("notifications/progress"); if (!options.allowUnsafeEval) { z.config({ jitless: true }); } - this.replaceRequestHandler("ping", (request) => { + this.setRequestHandler(PingRequestSchema, (request) => { console.log("Received ping:", request.params); return {}; }); - // Eagerly register hostcontextchanged so the owner-side context merge runs - // even if the user never assigns a handler or adds a listener. + // Eagerly register the hostcontextchanged event slot so that + // onEventDispatch (which merges into _hostContext) fires even if the + // user never assigns onhostcontextchanged or calls addEventListener. this.setEventHandler("hostcontextchanged", undefined); } - /** @internal */ - protected buildContext(ctx: BaseContext): BaseContext { - return ctx; - } - - private registerAppCapabilities(capabilities: McpUiAppCapabilities): void { + private registerCapabilities(capabilities: McpUiAppCapabilities): void { if (this.transport) { throw new Error( "Cannot register capabilities after transport is established", ); } - this._appCapabilities = mergeAppCapabilities( - this._appCapabilities, - capabilities, - ); + this._capabilities = mergeCapabilities(this._capabilities, capabilities); } registerTool< @@ -602,7 +519,7 @@ export class App extends Protocol { } const app = this; const notify = () => { - if (app._initializedSent && app._appCapabilities.tools?.listChanged) { + if (app._initializedSent && app._capabilities.tools?.listChanged) { void app.sendToolListChanged(); } }; @@ -674,8 +591,8 @@ export class App extends Protocol { // to declare { tools: {} } in the constructor just to use registerTool. // Only do this pre-connect; post-connect the capability was already // advertised (or wasn't) and can't change. - if (!this._appCapabilities.tools && !this.transport) { - this.registerAppCapabilities({ tools: { listChanged: true } }); + if (!this._capabilities.tools && !this.transport) { + this.registerCapabilities({ tools: { listChanged: true } }); } this.ensureToolHandlersInitialized(); @@ -1007,9 +924,9 @@ export class App extends Protocol { * without replacing. * * Notification params are automatically merged into the internal host context - * before any handler or listener fires. This means - * {@link getHostContext `getHostContext`} will return the updated values even - * before your callback runs. + * via {@link onEventDispatch `onEventDispatch`} before any handler or listener + * fires. This means {@link getHostContext `getHostContext`} will return the + * updated values even before your callback runs. * * Register handlers before calling {@link connect `connect`} to avoid missing notifications. * @@ -1088,14 +1005,10 @@ export class App extends Protocol { this.warnIfRequestHandlerReplaced("onteardown", this._onteardown, callback); this._onteardown = callback; this.replaceRequestHandler( - "ui/resource-teardown", - { - params: McpUiResourceTeardownRequestSchema.shape.params, - result: McpUiResourceTeardownResultSchema, - }, - (params, extra) => { + McpUiResourceTeardownRequestSchema, + (request, extra) => { if (!this._onteardown) throw new Error("No onteardown handler set"); - return this._onteardown(params, extra); + return this._onteardown(request.params, extra); }, ); } @@ -1145,7 +1058,7 @@ export class App extends Protocol { ) { this.warnIfRequestHandlerReplaced("oncalltool", this._oncalltool, callback); this._oncalltool = callback; - this.replaceRequestHandler("tools/call", (request, extra) => { + this.replaceRequestHandler(CallToolRequestSchema, (request, extra) => { if (!this._oncalltool) throw new Error("No oncalltool handler set"); return this._oncalltool(request.params, extra); }); @@ -1216,7 +1129,7 @@ export class App extends Protocol { callback, ); this._onlisttools = callback; - this.replaceRequestHandler("tools/list", (request, extra) => { + this.replaceRequestHandler(ListToolsRequestSchema, (request, extra) => { if (!this._onlisttools) throw new Error("No onlisttools handler set"); return this._onlisttools(request.params, extra); }); @@ -1226,7 +1139,7 @@ export class App extends Protocol { * Verify that the host supports the capability required for the given request method. * @internal */ - protected override assertCapabilityForMethod(method: string): void { + assertCapabilityForMethod(method: AppRequest["method"]): void { switch (method) { case "sampling/createMessage": if (!this._hostCapabilities?.sampling) { @@ -1242,21 +1155,21 @@ export class App extends Protocol { * Verify that the app declared the capability required for the given request method. * @internal */ - protected override assertRequestHandlerCapability(method: string): void { + assertRequestHandlerCapability(method: AppRequest["method"]): void { switch (method) { case "tools/call": case "tools/list": - if (!this._appCapabilities.tools) { + if (!this._capabilities.tools) { throw new Error( `Client does not support tool capability (required for ${method})`, ); } return; - default: - // `ping`, `ui/*`, and custom (vendor-prefixed) methods need no - // declared capability. The base SDK explicitly supports custom - // request handlers via the `{ params, result }` form. + case "ping": + case "ui/resource-teardown": return; + default: + throw new Error(`No handler for method ${method} registered`); } } @@ -1264,10 +1177,26 @@ export class App extends Protocol { * Verify that the app supports the capability required for the given notification method. * @internal */ - protected override assertNotificationCapability(_method: string): void { + assertNotificationCapability(_method: AppNotification["method"]): void { // TODO } + /** + * Verify that task creation is supported for the given request method. + * @internal + */ + protected assertTaskCapability(_method: string): void { + throw new Error("Tasks are not supported in MCP Apps"); + } + + /** + * Verify that task handler is supported for the given method. + * @internal + */ + protected assertTaskHandlerCapability(_method: string): void { + throw new Error("Task handlers are not supported in MCP Apps"); + } + /** * Call a tool on the originating MCP server (proxied through the host). * @@ -1316,6 +1245,7 @@ export class App extends Protocol { } return await this.request( { method: "tools/call", params }, + CallToolResultSchema, { // Hosts may interpose long-running or user-interactive steps before the // tool result arrives. Opting in here lets a host heartbeat keep the @@ -1372,7 +1302,11 @@ export class App extends Protocol { options?: RequestOptions, ): Promise { this._assertInitialized("readServerResource"); - return await this.request({ method: "resources/read", params }, options); + return await this.request( + { method: "resources/read", params }, + ReadResourceResultSchema, + options, + ); } /** @@ -1416,7 +1350,11 @@ export class App extends Protocol { options?: RequestOptions, ): Promise { this._assertInitialized("listServerResources"); - return await this.request({ method: "resources/list", params }, options); + return await this.request( + { method: "resources/list", params }, + ListResourcesResultSchema, + options, + ); } /** @@ -1477,8 +1415,8 @@ export class App extends Protocol { * } * ``` * - * @see `CreateMessageRequest` from @modelcontextprotocol/client for the request type - * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/client for result types + * @see `CreateMessageRequest` from @modelcontextprotocol/sdk for the request type + * @see `CreateMessageResult` / `CreateMessageResultWithTools` from @modelcontextprotocol/sdk for result types */ async createSamplingMessage( params: CreateMessageRequest["params"] & { tools?: undefined }, @@ -1493,8 +1431,12 @@ export class App extends Protocol { options?: RequestOptions, ): Promise { this._assertInitialized("createSamplingMessage"); + const resultSchema = params.tools + ? CreateMessageResultWithToolsSchema + : CreateMessageResultSchema; return await this.request( { method: "sampling/createMessage", params }, + resultSchema, options, ); } @@ -2011,18 +1953,14 @@ export class App extends Protocol { ); } this._initializedSent = false; + await super.connect(transport); try { - // Protocol.connect only wires the transport — no MCP handshake. The - // ui/initialize request below is the sole negotiation on this channel, - // matching the v1 wire behavior deployed hosts expect. - await super.connect(transport); - const result = await this.request( { method: "ui/initialize", params: { - appCapabilities: this._appCapabilities, + appCapabilities: this._capabilities, appInfo: this._appInfo, protocolVersion: LATEST_PROTOCOL_VERSION, }, diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index c879a03..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Metadata key for associating a UI resource URI with a tool. - * - * MCP servers include this key in tool definition metadata (via `tools/list`) - * to indicate which UI resource should be displayed when the tool is called. - * When hosts see a tool with this metadata, they fetch and render the - * corresponding {@link app!App `App`}. - * - * **Note**: This constant is provided for reference and backwards compatibility. - * Server developers should use {@link server-helpers!registerAppTool `registerAppTool`} - * with the `_meta.ui.resourceUri` format instead. Host developers must check both - * formats for compatibility. - * - * @example Modern format (server-side, not in Apps) - * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_modernFormat" - * // Preferred: Use registerAppTool with nested ui.resourceUri - * registerAppTool( - * server, - * "weather", - * { - * description: "Get weather forecast", - * _meta: { - * ui: { resourceUri: "ui://weather/forecast" }, - * }, - * }, - * handler, - * ); - * ``` - * - * @example Legacy format (deprecated, for backwards compatibility) - * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_legacyFormat" - * // Deprecated: Direct use of RESOURCE_URI_META_KEY - * server.registerTool( - * "weather", - * { - * description: "Get weather forecast", - * _meta: { - * [RESOURCE_URI_META_KEY]: "ui://weather/forecast", - * }, - * }, - * handler, - * ); - * ``` - * - * @example How hosts check for this metadata (must support both formats) - * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_hostSide" - * // Hosts should check both modern and legacy formats - * const meta = tool._meta; - * const uiMeta = meta?.ui as McpUiToolMeta | undefined; - * const legacyUri = meta?.[RESOURCE_URI_META_KEY] as string | undefined; - * const uiUri = uiMeta?.resourceUri ?? legacyUri; - * if (typeof uiUri === "string" && uiUri.startsWith("ui://")) { - * // Fetch the resource and display the UI - * } - * ``` - */ -export const RESOURCE_URI_META_KEY = "ui/resourceUri"; - -/** - * MIME type for MCP UI resources. - * - * Identifies HTML content as an MCP App UI resource. - * - * Used by {@link server-helpers!registerAppResource `registerAppResource`} as the default MIME type for app resources. - */ -export const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; diff --git a/src/events.test.ts b/src/events.test.ts deleted file mode 100644 index 60e7c35..0000000 --- a/src/events.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { describe, expect, it, spyOn } from "bun:test"; - -import { EventDispatcher } from "./events.js"; - -type TestEventMap = { - change: { value: number }; -}; - -describe("EventDispatcher", () => { - it("replaces the singular handler", () => { - const dispatcher = new EventDispatcher(); - const calls: string[] = []; - const first = () => calls.push("first"); - const second = () => calls.push("second"); - const warn = spyOn(console, "warn").mockImplementation(() => {}); - - try { - dispatcher.setHandler("change", first); - dispatcher.setHandler("change", second); - - expect(dispatcher.getHandler("change")).toBe(second); - dispatcher.dispatch("change", { value: 1 }); - expect(calls).toEqual(["second"]); - } finally { - warn.mockRestore(); - } - }); - - it("dispatches the singular handler before listeners in insertion order", () => { - const dispatcher = new EventDispatcher(); - const calls: string[] = []; - - dispatcher.setHandler("change", () => calls.push("handler")); - dispatcher.addEventListener("change", () => calls.push("listener-1")); - dispatcher.addEventListener("change", () => calls.push("listener-2")); - - dispatcher.dispatch("change", { value: 1 }); - - expect(calls).toEqual(["handler", "listener-1", "listener-2"]); - }); - - it("uses a listener snapshot when a listener is removed during dispatch", () => { - const dispatcher = new EventDispatcher(); - const calls: string[] = []; - const second = () => calls.push("second"); - const first = () => { - calls.push("first"); - dispatcher.removeEventListener("change", second); - }; - - dispatcher.addEventListener("change", first); - dispatcher.addEventListener("change", second); - - dispatcher.dispatch("change", { value: 1 }); - dispatcher.dispatch("change", { value: 2 }); - - expect(calls).toEqual(["first", "second", "first"]); - }); - - it("warns only when one singular handler replaces another", () => { - const dispatcher = new EventDispatcher(); - const warn = spyOn(console, "warn").mockImplementation(() => {}); - const first = () => {}; - const second = () => {}; - - try { - dispatcher.setHandler("change", first); - dispatcher.setHandler("change", undefined); - dispatcher.setHandler("change", first); - expect(warn).not.toHaveBeenCalled(); - - dispatcher.setHandler("change", second); - - expect(warn).toHaveBeenCalledTimes(1); - expect(String(warn.mock.calls[0]?.[0])).toContain( - "onchange handler replaced", - ); - } finally { - warn.mockRestore(); - } - }); -}); diff --git a/src/events.ts b/src/events.ts index aa2e862..4d17ca4 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,6 +1,17 @@ +import { Protocol } from "@modelcontextprotocol/sdk/shared/protocol.js"; +import { + Request, + Notification, + Result, +} from "@modelcontextprotocol/sdk/types.js"; +import { ZodLiteral, ZodObject } from "zod/v4"; + +type MethodSchema = ZodObject<{ method: ZodLiteral }>; + /** - * Per-event state: a singular `on*` handler (replace semantics) plus an - * ordered listener array (`addEventListener` semantics). + * Per-event state: a singular `on*` handler (replace semantics) plus a + * listener array (`addEventListener` semantics), mirroring the DOM model + * where `el.onclick` and `el.addEventListener("click", …)` coexist. */ interface EventSlot { onHandler?: ((params: T) => void) | undefined; @@ -8,19 +19,118 @@ interface EventSlot { } /** - * Protocol-independent DOM-style event fan-out. + * Intermediate base class that adds DOM-style event support on top of the + * MCP SDK's `Protocol`. * - * Owners perform event-specific state updates before calling - * {@link dispatch}. Dispatch then invokes the singular handler followed by a - * snapshot of listeners in insertion order. + * The base `Protocol` class stores one handler per method: + * `setRequestHandler()` and `setNotificationHandler()` replace any existing + * handler for the same method silently. This class introduces a two-channel + * event model inspired by the DOM: * - * @typeParam EventMap - Maps event names to their listener parameter types. + * ### Singular `on*` handler (like `el.onclick`) + * + * Subclasses expose `get`/`set` pairs that delegate to + * {@link setEventHandler `setEventHandler`} / + * {@link getEventHandler `getEventHandler`}. Assigning replaces the previous + * handler; assigning `undefined` clears it. `addEventListener` listeners are + * unaffected. + * + * ### Multi-listener (`addEventListener` / `removeEventListener`) + * + * Append to a per-event listener array. Listeners fire in insertion order + * after the singular `on*` handler. + * + * ### Dispatch order + * + * When a notification arrives for a mapped event: + * 1. {@link onEventDispatch `onEventDispatch`} (subclass side-effects) + * 2. The singular `on*` handler (if set) + * 3. All `addEventListener` listeners in insertion order + * + * ### Double-set protection + * + * Direct calls to {@link setRequestHandler `setRequestHandler`} / + * {@link setNotificationHandler `setNotificationHandler`} throw if a handler + * for the same method has already been registered (through any path), so + * accidental overwrites surface as errors instead of silent bugs. + * + * @typeParam EventMap - Maps event names to the listener's `params` type. */ -export class EventDispatcher> { - private readonly _eventSlots = new Map(); +export abstract class ProtocolWithEvents< + SendRequestT extends Request, + SendNotificationT extends Notification, + SendResultT extends Result, + EventMap extends Record, +> extends Protocol { + private _registeredMethods = new Set(); + private _eventSlots = new Map(); - /** Set, replace, or clear the singular handler for an event. */ - setHandler( + /** + * Event name → notification schema. Subclasses populate this so that + * the event system can lazily register a dispatcher with the correct + * schema on first use. + */ + protected abstract readonly eventSchemas: { + [K in keyof EventMap]: MethodSchema; + }; + + /** + * Called once per incoming notification, before any handlers or listeners + * fire. Subclasses may override to perform side effects such as merging + * notification params into cached state. + */ + protected onEventDispatch( + _event: K, + _params: EventMap[K], + ): void {} + + // ── Event system (DOM model) ──────────────────────────────────────── + + /** + * Lazily create the event slot and register a single dispatcher with the + * base `Protocol`. The dispatcher fans out to the `on*` handler and all + * `addEventListener` listeners. + */ + private _ensureEventSlot( + event: K, + ): EventSlot { + let slot = this._eventSlots.get(event) as + | EventSlot + | undefined; + if (!slot) { + const schema = this.eventSchemas[event]; + if (!schema) { + throw new Error(`Unknown event: ${String(event)}`); + } + slot = { listeners: [] }; + this._eventSlots.set(event, slot as EventSlot); + + // Claim this method so direct setNotificationHandler calls throw. + const method = schema.shape.method.value; + this._registeredMethods.add(method); + + const s = slot; // stable reference for the closure + super.setNotificationHandler(schema, (n) => { + const params = (n as { params: EventMap[K] }).params; + this.onEventDispatch(event, params); + // 1. Singular on* handler + s.onHandler?.(params); + // 2. addEventListener listeners — snapshot to tolerate removal during + // dispatch (e.g., a listener that calls removeEventListener on itself) + for (const l of [...s.listeners]) l(params); + }); + } + return slot; + } + + /** + * Set or clear the singular `on*` handler for an event. + * + * Replace semantics — like the DOM's `el.onclick = fn`. Assigning + * `undefined` clears the handler without affecting `addEventListener` + * listeners. + */ + protected setEventHandler( event: K, handler: ((params: EventMap[K]) => void) | undefined, ): void { @@ -34,15 +144,31 @@ export class EventDispatcher> { slot.onHandler = handler; } - /** Get the singular handler for an event, if one is set. */ - getHandler( + /** + * Get the singular `on*` handler for an event, or `undefined` if none is + * set. `addEventListener` listeners are not reflected here. + */ + protected getEventHandler( event: K, ): ((params: EventMap[K]) => void) | undefined { return (this._eventSlots.get(event) as EventSlot | undefined) ?.onHandler; } - /** Add a listener. Listeners run in insertion order after the handler. */ + /** + * Add a listener for a notification event. + * + * Unlike the singular `on*` handler, calling this multiple times appends + * listeners rather than replacing them. All registered listeners fire in + * insertion order after the `on*` handler when the notification arrives. + * + * Registration is lazy: the first call (for a given event, from either + * this method or the `on*` setter) registers a dispatcher with the base + * `Protocol`. + * + * @param event - Event name (a key of the `EventMap` type parameter). + * @param handler - Listener invoked with the notification `params`. + */ addEventListener( event: K, handler: (params: EventMap[K]) => void, @@ -50,7 +176,11 @@ export class EventDispatcher> { this._ensureEventSlot(event).listeners.push(handler); } - /** Remove the first matching listener, if present. */ + /** + * Remove a previously registered event listener. The dispatcher stays + * registered even if the listener array becomes empty; future + * notifications simply have no listeners to call. + */ removeEventListener( event: K, handler: (params: EventMap[K]) => void, @@ -63,70 +193,87 @@ export class EventDispatcher> { if (idx !== -1) slot.listeners.splice(idx, 1); } + // ── Handler registration with double-set protection ───────────────── + + // The two overrides below are arrow-function class fields rather than + // prototype methods so that Protocol's constructor — which registers its + // own ping/cancelled/progress handlers via `this.setRequestHandler` + // before our fields initialize — hits the base implementation and skips + // tracking. Converting these to proper methods would crash with + // `_registeredMethods` undefined during super(). + /** - * Dispatch an event to its singular handler and a listener snapshot. - * Listener mutations during dispatch take effect on the next dispatch. + * Registers a request handler. Throws if a handler for the same method + * has already been registered — use the `on*` setter (replace semantics) + * or `addEventListener` (multi-listener) for notification events. + * + * @throws {Error} if a handler for this method is already registered. */ - dispatch(event: K, params: EventMap[K]): void { - const slot = this._eventSlots.get(event) as - | EventSlot - | undefined; - if (!slot) return; - slot.onHandler?.(params); - for (const l of [...slot.listeners]) l(params); - } + override setRequestHandler: Protocol< + SendRequestT, + SendNotificationT, + SendResultT + >["setRequestHandler"] = (schema, handler) => { + this._assertMethodNotRegistered(schema, "setRequestHandler"); + super.setRequestHandler(schema, handler); + }; - private _ensureEventSlot( - event: K, - ): EventSlot { - let slot = this._eventSlots.get(event) as - | EventSlot - | undefined; - if (!slot) { - slot = { listeners: [] }; - this._eventSlots.set(event, slot as EventSlot); + /** + * Registers a notification handler. Throws if a handler for the same + * method has already been registered — use the `on*` setter (replace + * semantics) or `addEventListener` (multi-listener) for mapped events. + * + * @throws {Error} if a handler for this method is already registered. + */ + override setNotificationHandler: Protocol< + SendRequestT, + SendNotificationT, + SendResultT + >["setNotificationHandler"] = (schema, handler) => { + this._assertMethodNotRegistered(schema, "setNotificationHandler"); + super.setNotificationHandler(schema, handler); + }; + + /** + * Warn if a request handler `on*` setter is replacing a previously-set + * handler. Call from each request setter before updating the backing field. + */ + protected warnIfRequestHandlerReplaced( + name: string, + previous: unknown, + next: unknown, + ): void { + if (previous && next) { + console.warn( + `[MCP Apps] ${name} handler replaced. ` + + `Previous handler will no longer be called.`, + ); } - return slot; } -} -/** - * Tracks which JSON-RPC methods already have a handler registered through the - * owning `App` / `AppBridge`, so a second direct `setRequestHandler` / - * `setNotificationHandler` call for the same method throws instead of - * silently replacing the first handler (the base SDK `Protocol` replaces). - * - * The `on*` setters use {@link replace} for DOM-style replace semantics, and - * `removeRequestHandler` / `removeNotificationHandler` use {@link release}. - * - * @internal - */ -export class MethodRegistry { - private readonly _methods = new Set(); + /** + * Replace a request handler, bypassing double-set protection. Used by + * `on*` request-handler setters that need replace semantics. + */ + protected replaceRequestHandler: Protocol< + SendRequestT, + SendNotificationT, + SendResultT + >["setRequestHandler"] = (schema, handler) => { + const method = (schema as MethodSchema).shape.method.value; + this._registeredMethods.add(method); + super.setRequestHandler(schema, handler); + }; - /** Claim `method`. Throws if a handler is already registered for it. */ - claim(method: string, via: string): void { - if (this._methods.has(method)) { + private _assertMethodNotRegistered(schema: unknown, via: string): void { + const method = (schema as MethodSchema).shape.method.value; + if (this._registeredMethods.has(method)) { throw new Error( `Handler for "${method}" already registered (via ${via}). ` + `Use addEventListener() to attach multiple listeners, ` + `or the on* setter for replace semantics.`, ); } - this._methods.add(method); - } - - /** Claim `method` with replace semantics (never throws). */ - replace(method: string): void { - this._methods.add(method); - } - - /** Release `method` so it can be claimed again. */ - release(method: string): void { - this._methods.delete(method); - } - - has(method: string): boolean { - return this._methods.has(method); + this._registeredMethods.add(method); } } diff --git a/src/generated/schema.json b/src/generated/schema.json index 246ed7d..80b4ac6 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -9,19 +9,10 @@ "type": "object", "properties": { "experimental": { - "description": "Experimental features keyed by identifier.", + "description": "Experimental features (structure TBD).", "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {}, - "description": "Experimental features keyed by identifier." - } + "properties": {}, + "additionalProperties": false }, "tools": { "description": "App exposes MCP-style tools that the host can call.", @@ -307,19 +298,10 @@ "type": "object", "properties": { "experimental": { - "description": "Experimental features keyed by identifier.", + "description": "Experimental features (structure TBD).", "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {}, - "description": "Experimental features keyed by identifier." - } + "properties": {}, + "additionalProperties": false }, "openLinks": { "description": "Host supports opening external URLs.", @@ -614,9 +596,7 @@ "propertyNames": { "type": "string" }, - "additionalProperties": { - "$ref": "#/$defs/__schema0" - } + "additionalProperties": {} }, "required": { "type": "array", @@ -631,10 +611,25 @@ "outputSchema": { "type": "object", "properties": { - "$schema": { - "type": "string" + "type": { + "type": "string", + "const": "object" + }, + "properties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "required": { + "type": "array", + "items": { + "type": "string" + } } }, + "required": ["type"], "additionalProperties": {} }, "annotations": { @@ -1291,40 +1286,7 @@ } }, "required": ["method", "params"], - "additionalProperties": false, - "$defs": { - "__schema0": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "null" - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/$defs/__schema0" - } - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/__schema0" - } - } - ] - } - } + "additionalProperties": false }, "McpUiHostContext": { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -1397,9 +1359,7 @@ "propertyNames": { "type": "string" }, - "additionalProperties": { - "$ref": "#/$defs/__schema0" - } + "additionalProperties": {} }, "required": { "type": "array", @@ -1414,10 +1374,25 @@ "outputSchema": { "type": "object", "properties": { - "$schema": { - "type": "string" + "type": { + "type": "string", + "const": "object" + }, + "properties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "required": { + "type": "array", + "items": { + "type": "string" + } } }, + "required": ["type"], "additionalProperties": {} }, "annotations": { @@ -2069,40 +2044,7 @@ "additionalProperties": false } }, - "additionalProperties": {}, - "$defs": { - "__schema0": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "null" - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/$defs/__schema0" - } - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/__schema0" - } - } - ] - } - } + "additionalProperties": {} }, "McpUiHostCss": { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -2595,19 +2537,10 @@ "type": "object", "properties": { "experimental": { - "description": "Experimental features keyed by identifier.", + "description": "Experimental features (structure TBD).", "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {}, - "description": "Experimental features keyed by identifier." - } + "properties": {}, + "additionalProperties": false }, "tools": { "description": "App exposes MCP-style tools that the host can call.", @@ -2718,19 +2651,10 @@ "type": "object", "properties": { "experimental": { - "description": "Experimental features keyed by identifier.", + "description": "Experimental features (structure TBD).", "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {}, - "description": "Experimental features keyed by identifier." - } + "properties": {}, + "additionalProperties": false }, "openLinks": { "description": "Host supports opening external URLs.", @@ -3018,9 +2942,7 @@ "propertyNames": { "type": "string" }, - "additionalProperties": { - "$ref": "#/$defs/__schema0" - } + "additionalProperties": {} }, "required": { "type": "array", @@ -3035,10 +2957,25 @@ "outputSchema": { "type": "object", "properties": { - "$schema": { - "type": "string" + "type": { + "type": "string", + "const": "object" + }, + "properties": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "required": { + "type": "array", + "items": { + "type": "string" + } } }, + "required": ["type"], "additionalProperties": {} }, "annotations": { @@ -3700,40 +3637,7 @@ "hostCapabilities", "hostContext" ], - "additionalProperties": {}, - "$defs": { - "__schema0": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "null" - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/$defs/__schema0" - } - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/__schema0" - } - } - ] - } - } + "additionalProperties": {} }, "McpUiInitializedNotification": { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -5420,52 +5324,26 @@ "_meta": { "type": "object", "properties": { - "io.modelcontextprotocol/serverInfo": { + "progressToken": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + ] + }, + "io.modelcontextprotocol/related-task": { "type": "object", "properties": { - "name": { - "type": "string" - }, - "title": { - "type": "string" - }, - "icons": { - "type": "array", - "items": { - "type": "object", - "properties": { - "src": { - "type": "string" - }, - "mimeType": { - "type": "string" - }, - "sizes": { - "type": "array", - "items": { - "type": "string" - } - }, - "theme": { - "type": "string", - "enum": ["light", "dark"] - } - }, - "required": ["src"], - "additionalProperties": false - } - }, - "version": { - "type": "string" - }, - "websiteUrl": { - "type": "string" - }, - "description": { + "taskId": { "type": "string" } }, - "required": ["name", "version"], + "required": ["taskId"], "additionalProperties": false } }, @@ -5791,7 +5669,13 @@ ] } }, - "structuredContent": {}, + "structuredContent": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, "isError": { "type": "boolean" } diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 214b227..4368737 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -10,7 +10,7 @@ import { RequestIdSchema, ResourceLinkSchema, ToolSchema, -} from "@modelcontextprotocol/core"; +} from "@modelcontextprotocol/sdk/types.js"; /** * @description Color theme preference for the host environment. @@ -496,16 +496,11 @@ export const McpUiRequestTeardownNotificationSchema = z.object({ * @see {@link McpUiInitializeResult `McpUiInitializeResult`} for the initialization result that includes these capabilities */ export const McpUiHostCapabilitiesSchema = z.object({ - /** @description Experimental features keyed by identifier. */ + /** @description Experimental features (structure TBD). */ experimental: z - .record( - z.string(), - z - .record(z.string(), z.any()) - .describe("Experimental features keyed by identifier."), - ) + .object({}) .optional() - .describe("Experimental features keyed by identifier."), + .describe("Experimental features (structure TBD)."), /** @description Host supports opening external URLs. */ openLinks: z .object({}) @@ -588,16 +583,11 @@ export const McpUiHostCapabilitiesSchema = z.object({ * @see {@link McpUiInitializeRequest `McpUiInitializeRequest`} for the initialization request that includes these capabilities */ export const McpUiAppCapabilitiesSchema = z.object({ - /** @description Experimental features keyed by identifier. */ + /** @description Experimental features (structure TBD). */ experimental: z - .record( - z.string(), - z - .record(z.string(), z.any()) - .describe("Experimental features keyed by identifier."), - ) + .object({}) .optional() - .describe("Experimental features keyed by identifier."), + .describe("Experimental features (structure TBD)."), /** @description App exposes MCP-style tools that the host can call. */ tools: z .object({ diff --git a/src/interop-v1.test.ts b/src/interop-v1.test.ts deleted file mode 100644 index 31dbfa1..0000000 --- a/src/interop-v1.test.ts +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Cross-version interop against the *published* 1.x package. - * - * `ext-apps-v1` is an npm alias for `@modelcontextprotocol/ext-apps@1.7.5` - * (built on `@modelcontextprotocol/sdk` 1.x). Each pairing wires a real - * `App` to a real `AppBridge` of the other major over an in-memory duplex, - * with the bridge fronting a real MCP `Client`+`McpServer` of its own SDK - * generation, and asserts the observable outcomes. Complements - * `wire-compat.test.ts`, which replays hand-shaped 1.x JSON instead. - */ -import { describe, it, expect, beforeAll, afterAll } from "bun:test"; -import { readFileSync } from "node:fs"; -import { - Client as ClientV2, - InMemoryTransport as InMemoryTransportV2, - ProtocolError, - type Transport, -} from "@modelcontextprotocol/client"; -import { EmptyResultSchema as EmptyResultSchemaV2 } from "@modelcontextprotocol/core"; -import { McpServer as McpServerV2 } from "@modelcontextprotocol/server"; -import { Client as ClientV1 } from "@modelcontextprotocol/sdk/client/index.js"; -import { InMemoryTransport as InMemoryTransportV1 } from "@modelcontextprotocol/sdk/inMemory.js"; -import { McpServer as McpServerV1 } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { Protocol as ProtocolV1 } from "@modelcontextprotocol/sdk/shared/protocol.js"; -import { - EmptyResultSchema as EmptyResultSchemaV1, - McpError, -} from "@modelcontextprotocol/sdk/types.js"; -import { App as AppV1 } from "ext-apps-v1"; -import { AppBridge as AppBridgeV1 } from "ext-apps-v1/app-bridge"; -import { z } from "zod/v4"; - -import { App as AppV2 } from "./app.js"; -import { AppBridge as AppBridgeV2 } from "./app-bridge.js"; - -const flush = () => new Promise((resolve) => setTimeout(resolve, 5)); - -/** In-memory duplex. Structurally a `Transport` for both SDK generations. */ -function createDuplex(): [Transport, Transport] { - const make = () => { - let closed = false; - const t: Transport & { peer?: typeof t } = { - async start() {}, - async send(message) { - // Serialize like a real transport so each side parses plain JSON. - t.peer?.onmessage?.(JSON.parse(JSON.stringify(message))); - }, - async close() { - if (closed) return; - closed = true; - t.onclose?.(); - await t.peer?.close(); - }, - }; - return t; - }; - const [a, b] = [make(), make()]; - a.peer = b; - b.peer = a; - return [a, b]; -} - -/** One SDK generation's MCP pieces, as used by the App side or the host side. */ -const sdkV1 = { - Client: ClientV1, - McpServer: McpServerV1, - InMemoryTransport: InMemoryTransportV1, - EmptyResultSchema: EmptyResultSchemaV1, - error: (code: number, message: string): Error => new McpError(code, message), -}; -const sdkV2 = { - Client: ClientV2, - McpServer: McpServerV2, - InMemoryTransport: InMemoryTransportV2, - EmptyResultSchema: EmptyResultSchemaV2, - error: (code: number, message: string): Error => - new ProtocolError(code, message), -}; - -const pairings = [ - { - name: "2.x App with 1.x AppBridge (sdk 1.x host)", - App: AppV2 as any, - AppBridge: AppBridgeV1 as any, - view: sdkV2, - host: sdkV1, - // sdk 1.x reports a Zod params failure as InternalError. - invalidParamsCode: -32603, - // sdk 1.x forwards a handler-thrown -32002 unchanged. - handlerThrown32002Code: -32002, - // McpServer 1.x answers an unknown tool with an isError result. - unknownToolRejects: false, - }, - { - name: "1.x App with 2.x AppBridge (sdk 2.x host)", - App: AppV1 as any, - AppBridge: AppBridgeV2 as any, - view: sdkV1, - host: sdkV2, - invalidParamsCode: -32602, - // SDK 2.x never emits -32002 on the wire (see wire-compat.test.ts). - handlerThrown32002Code: -32602, - // McpServer 2.x answers an unknown tool with a JSON-RPC error. - unknownToolRejects: true, - }, -]; - -const hostInfo = { name: "TestHost", version: "9.9.9" }; -const appInfo = { name: "TestView", version: "1.2.3" }; -const hostCapabilities = { - openLinks: {}, - serverTools: {}, - serverResources: {}, - logging: {}, - updateModelContext: {}, -}; -const hostContext = { theme: "dark", displayMode: "inline", locale: "en-US" }; - -it("pits 2.x against the published 1.x package, not against itself", () => { - const v1PackageJson = JSON.parse( - readFileSync( - new URL("../../package.json", import.meta.resolve("ext-apps-v1")), - "utf8", - ), - ); - expect(v1PackageJson).toMatchObject({ - name: "@modelcontextprotocol/ext-apps", - version: "1.7.5", - }); - // 1.x classes extend the sdk-1 Protocol; 2.x classes do not. - expect(AppV1.prototype).toBeInstanceOf(ProtocolV1); - expect(AppBridgeV1.prototype).toBeInstanceOf(ProtocolV1); - expect(AppV2.prototype).not.toBeInstanceOf(ProtocolV1); - expect(AppBridgeV2.prototype).not.toBeInstanceOf(ProtocolV1); -}); - -// The tests in each pairing share one connected App/AppBridge fixture and run -// in file order (bun does not randomize); later tests assume earlier ones -// left the pair connected. -describe.each(pairings)("$name", (pairing) => { - const { view, host } = pairing; - let server: any, client: any, bridge: any, app: any; - const events: string[] = []; - const record = (name: string) => (params?: unknown) => { - events.push(`${name}:${JSON.stringify(params ?? null)}`); - }; - const drain = () => events.splice(0); - // Captured inside oninitialized: the bridge must have processed - // ui/initialize before ui/notifications/initialized arrives. - let appVersionAtInitialized: unknown = "not-fired"; - let hostAbortReason: unknown; - - beforeAll(async () => { - server = new host.McpServer({ name: "TestServer", version: "1.0.0" }); - server.registerTool( - "echo", - { inputSchema: z.object({ a: z.number() }) }, - async ({ a }: { a: number }) => ({ - content: [{ type: "text", text: `echo ${a}` }], - }), - ); - server.registerTool("boom", { inputSchema: z.object({}) }, async () => { - throw new Error("tool exploded"); - }); - server.registerResource( - "res", - "test://res", - { mimeType: "text/plain" }, - async (uri: URL) => ({ - contents: [{ uri: uri.href, mimeType: "text/plain", text: "hello" }], - }), - ); - const [clientTransport, serverTransport] = - host.InMemoryTransport.createLinkedPair(); - await server.connect(serverTransport); - client = new host.Client({ name: "HostOuterClient", version: "1.0.0" }); - await client.connect(clientTransport); - - bridge = new pairing.AppBridge(client, hostInfo, hostCapabilities, { - hostContext, - }); - app = new pairing.App( - appInfo, - { tools: { listChanged: true } }, - { - autoResize: false, - }, - ); - bridge.oninitialized = () => { - appVersionAtInitialized = bridge.getAppVersion(); - }; - for (const name of ["onsizechange", "onloggingmessage"]) { - bridge[name] = record(`bridge.${name}`); - } - for (const name of ["onmessage", "onupdatemodelcontext"]) { - bridge[name] = async (params: unknown) => { - record(`bridge.${name}`)(params); - return {}; - }; - } - bridge.onopenlink = (params: { url: string }, extra: any) => { - record("bridge.onopenlink")(params); - if (params.url !== "https://slow") return {}; - // Hang until the view cancels; sdk 1.x passes `extra.signal`, 2.x `extra.mcpReq.signal`. - const signal: AbortSignal = extra.signal ?? extra.mcpReq.signal; - return new Promise((_resolve, reject) => { - signal.addEventListener("abort", () => { - hostAbortReason = signal.reason; - reject(new Error("aborted")); - }); - }); - }; - for (const name of [ - "ontoolinput", - "ontoolresult", - "ontoolcancelled", - "onhostcontextchanged", - ]) { - app[name] = record(`app.${name}`); - } - app.onteardown = async (params: unknown) => { - record("app.onteardown")(params); - return {}; - }; - }); - - afterAll(async () => { - await app?.close().catch(() => {}); - await bridge?.close().catch(() => {}); - await client?.close().catch(() => {}); - await server?.close().catch(() => {}); - }); - - it("completes ui/initialize and delivers initialized in order", async () => { - const [appTransport, bridgeTransport] = createDuplex(); - // Tap the raw ui/initialize result so an additive key on the 2.x side - // (which the getters below would not surface) still fails here. - let initializeResult: unknown; - const send = bridgeTransport.send.bind(bridgeTransport); - bridgeTransport.send = async (message, options) => { - if ("result" in message && "id" in message && message.id === 0) { - initializeResult = message.result; - } - await send(message, options); - }; - await bridge.connect(bridgeTransport); - await app.connect(appTransport); - await flush(); - - expect(app.getHostContext()).toEqual(hostContext); - expect(app.getHostCapabilities()).toEqual(hostCapabilities); - expect(app.getHostVersion()).toEqual(hostInfo); - expect(bridge.getAppCapabilities()).toEqual({ - tools: { listChanged: true }, - }); - expect(appVersionAtInitialized).toEqual(appInfo); - expect(initializeResult).toEqual({ - protocolVersion: "2026-01-26", - hostCapabilities, - hostInfo, - hostContext, - }); - }); - - it("delivers host-to-view notifications", async () => { - bridge.sendToolInput({ arguments: { location: "NYC" } }); - bridge.sendToolResult({ content: [{ type: "text", text: "sunny" }] }); - bridge.sendToolCancelled({ reason: "user" }); - bridge.setHostContext({ theme: "light" }); - await flush(); - - expect(drain()).toEqual([ - 'app.ontoolinput:{"arguments":{"location":"NYC"}}', - 'app.ontoolresult:{"content":[{"type":"text","text":"sunny"}]}', - 'app.ontoolcancelled:{"reason":"user"}', - 'app.onhostcontextchanged:{"theme":"light"}', - ]); - expect(app.getHostContext()).toEqual({ ...hostContext, theme: "light" }); - }); - - it("round-trips view-to-host requests and notifications", async () => { - const message = { role: "user", content: [{ type: "text", text: "hi" }] }; - expect(await app.sendMessage(message)).toEqual({}); - expect(await app.openLink({ url: "https://example.com" })).toEqual({}); - expect(await app.requestDisplayMode({ mode: "fullscreen" })).toEqual({ - mode: "inline", - }); - bridge.onrequestdisplaymode = async (params: { mode: string }) => params; - expect(await app.requestDisplayMode({ mode: "fullscreen" })).toEqual({ - mode: "fullscreen", - }); - const context = { content: [{ type: "text", text: "ctx" }] }; - expect(await app.updateModelContext(context)).toEqual({}); - await app.sendLog({ level: "info", data: { m: "hello" } }); - await app.sendSizeChanged({ width: 320, height: 240 }); - await flush(); - - expect(drain()).toEqual([ - `bridge.onmessage:${JSON.stringify(message)}`, - 'bridge.onopenlink:{"url":"https://example.com"}', - `bridge.onupdatemodelcontext:${JSON.stringify(context)}`, - 'bridge.onloggingmessage:{"level":"info","data":{"m":"hello"}}', - 'bridge.onsizechange:{"width":320,"height":240}', - ]); - }); - - it("proxies tools/call and resources/read to the real MCP server", async () => { - expect( - await app.callServerTool({ name: "echo", arguments: { a: 1 } }), - ).toEqual({ content: [{ type: "text", text: "echo 1" }] }); - expect( - await app.callServerTool({ name: "boom", arguments: {} }), - ).toMatchObject({ - isError: true, - content: [{ type: "text", text: "tool exploded" }], - }); - const unknown = app.callServerTool({ name: "nope", arguments: {} }); - if (pairing.unknownToolRejects) { - await expect(unknown).rejects.toMatchObject({ code: -32602 }); - } else { - expect(await unknown).toMatchObject({ isError: true }); - } - expect(await app.readServerResource({ uri: "test://res" })).toEqual({ - contents: [{ uri: "test://res", mimeType: "text/plain", text: "hello" }], - }); - }); - - it("propagates JSON-RPC errors with the expected codes", async () => { - const raw = (method: string, params: unknown) => - app.request({ method, params }, view.EmptyResultSchema); - await expect(raw("ui/does-not-exist", {})).rejects.toMatchObject({ - code: -32601, - }); - await expect(raw("ui/open-link", { url: 42 })).rejects.toMatchObject({ - code: pairing.invalidParamsCode, - }); - bridge.onreadresource = async () => { - throw host.error(-32002, "Resource not found (host-thrown -32002)"); - }; - const notFound = app.readServerResource({ uri: "test://missing" }); - await expect(notFound).rejects.toMatchObject({ - code: pairing.handlerThrown32002Code, - message: expect.stringContaining( - "Resource not found (host-thrown -32002)", - ), - }); - }); - - it("cancels an in-flight request via AbortSignal on both ends", async () => { - const controller = new AbortController(); - const pending = app.openLink( - { url: "https://slow" }, - { signal: controller.signal }, - ); - await flush(); - controller.abort("user-cancelled"); - await expect(pending).rejects.toMatchObject({ - message: expect.stringContaining("user-cancelled"), - }); - await flush(); - expect(String(hostAbortReason)).toContain("user-cancelled"); - drain(); - }); - - it("tears down, then closes both ends", async () => { - expect(await bridge.teardownResource({})).toEqual({}); - expect(drain()).toEqual(["app.onteardown:{}"]); - - const closed: string[] = []; - app.onclose = () => closed.push("app"); - bridge.onclose = () => closed.push("bridge"); - await app.close(); - await flush(); - expect(closed.sort()).toEqual(["app", "bridge"]); - await expect(app.openLink({ url: "https://after-close" })).rejects.toThrow( - "Not connected", - ); - }); -}); diff --git a/src/legacy-handlers.test.ts b/src/legacy-handlers.test.ts deleted file mode 100644 index 0ef36d6..0000000 --- a/src/legacy-handlers.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test"; -import { InMemoryTransport, Server } from "@modelcontextprotocol/server"; -import { z } from "zod/v4"; - -import { App } from "./app.js"; -import { AppBridge } from "./app-bridge.js"; -import { - McpUiInitializeRequestSchema, - McpUiInitializeResultSchema, - McpUiInitializedNotificationSchema, - McpUiResourceTeardownRequestSchema, - McpUiToolInputNotificationSchema, - McpUiSizeChangedNotificationSchema, - McpUiOpenLinkRequestSchema, -} from "./types.js"; - -const CustomRequestSchema = z.object({ - method: z.literal("test/echo"), - params: z.object({ text: z.string() }), -}); - -const CustomNotificationSchema = z.object({ - method: z.literal("test/ping"), - params: z.object({ n: z.number() }), -}); - -let warn: ReturnType; -beforeEach(() => { - warn = spyOn(console, "warn").mockImplementation(() => {}); -}); -afterEach(() => warn.mockRestore()); - -describe("1.x setRequestHandler(Schema, handler) on App", () => { - it("registers a custom request and hands the handler the whole message plus a 1.x extra", async () => { - const app = new App({ name: "a", version: "1" }, {}, { autoResize: false }); - const server = new Server( - { name: "s", version: "1" }, - { capabilities: {} }, - ); - server.setRequestHandler( - "ui/initialize", - { - params: McpUiInitializeRequestSchema.shape.params, - result: McpUiInitializeResultSchema, - }, - () => ({ - protocolVersion: "2026-01-26", - hostCapabilities: {}, - hostInfo: { name: "h", version: "1" }, - hostContext: {}, - }), - ); - server.setNotificationHandler( - "ui/notifications/initialized", - { params: McpUiInitializedNotificationSchema.shape.params }, - () => {}, - ); - - let seen: unknown; - let seenExtra: { signal: AbortSignal; requestId: unknown } | undefined; - app.setRequestHandler(CustomRequestSchema, (request, extra) => { - seen = request; - seenExtra = extra; - return { echoed: request.params.text }; - }); - - const [at, st] = InMemoryTransport.createLinkedPair(); - await server.connect(st); - await app.connect(at); - - const result = await server.request( - { method: "test/echo", params: { text: "hi" } }, - z.object({ echoed: z.string() }), - ); - - expect(result).toEqual({ echoed: "hi" }); - expect(seen).toEqual({ method: "test/echo", params: { text: "hi" } }); - expect(seenExtra?.signal).toBeInstanceOf(AbortSignal); - expect(seenExtra?.requestId).toBeDefined(); - expect(warn).toHaveBeenCalledTimes(1); - expect(String(warn.mock.calls[0][0])).toContain( - 'setRequestHandler("test/echo", { params: Schema.shape.params }, handler)', - ); - - await app.close(); - await server.close(); - }); - - it("still throws for a method an on* setter already owns", () => { - const app = new App({ name: "a", version: "1" }, {}, { autoResize: false }); - app.onteardown = async () => ({}); - expect(() => - app.setRequestHandler( - McpUiResourceTeardownRequestSchema, - async () => ({}), - ), - ).toThrow(/already registered/); - }); - - it("still throws for an event-mapped notification", () => { - const app = new App({ name: "a", version: "1" }, {}, { autoResize: false }); - app.ontoolinput = () => {}; - expect(() => - app.setNotificationHandler(McpUiToolInputNotificationSchema, () => {}), - ).toThrow(/already registered/); - }); -}); - -describe("1.x setNotificationHandler(Schema, handler) on AppBridge", () => { - it("registers a custom notification and hands the handler the whole message", async () => { - const bridge = new AppBridge(null, { name: "h", version: "1" }, {}); - // A bare Server stands in for the View: unlike Client, connect() sends no - // MCP initialize, matching what a real App does on this channel. - const view = new Server({ name: "v", version: "1" }, { capabilities: {} }); - let seen: unknown; - bridge.setNotificationHandler(CustomNotificationSchema, (n) => { - seen = n; - }); - - const [bt, vt] = InMemoryTransport.createLinkedPair(); - await bridge.connect(bt); - await view.connect(vt); - await view.notification({ method: "test/ping", params: { n: 3 } }); - await new Promise((r) => setTimeout(r, 10)); - - expect(seen).toEqual({ method: "test/ping", params: { n: 3 } }); - await view.close(); - await bridge.close(); - }); - - it("still throws for a method an on* setter already owns", () => { - const bridge = new AppBridge(null, { name: "h", version: "1" }, {}); - bridge.onsizechange = () => {}; - expect(() => - bridge.setNotificationHandler( - McpUiSizeChangedNotificationSchema, - () => {}, - ), - ).toThrow(/already registered/); - bridge.onopenlink = async () => ({}); - expect(() => - bridge.setRequestHandler(McpUiOpenLinkRequestSchema, async () => ({})), - ).toThrow(/already registered/); - }); -}); diff --git a/src/legacy-handlers.ts b/src/legacy-handlers.ts deleted file mode 100644 index 237b8eb..0000000 --- a/src/legacy-handlers.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { BaseContext, RequestId } from "@modelcontextprotocol/client"; -import type { ZodLiteral, ZodObject, ZodType } from "zod/v4"; - -/** - * A whole-message Zod schema (`method` literal plus `params`), as accepted by - * the 1.x `setRequestHandler(Schema, handler)` forms. - */ -export type LegacyMethodSchema = ZodObject<{ - method: ZodLiteral; - params: ZodType; -}>; - -/** - * The `extra` argument 1.x request handlers received. - * - * @deprecated Use the 2.x `BaseContext` (`extra.mcpReq.signal`, `extra.mcpReq.id`). - */ -export type LegacyRequestHandlerExtra = { - signal: AbortSignal; - requestId: RequestId; - sessionId?: string; - _meta?: BaseContext["mcpReq"]["_meta"]; - sendRequest: BaseContext["mcpReq"]["send"]; - sendNotification: BaseContext["mcpReq"]["notify"]; - authInfo?: NonNullable["authInfo"]; -}; - -/** @deprecated Use `setRequestHandler("method", { params, result }, (params, ctx) => …)`. */ -export type LegacyRequestHandler = ( - request: S["_output"], - extra: LegacyRequestHandlerExtra, -) => unknown; - -/** @deprecated Use `setNotificationHandler("method", { params }, (params) => …)`. */ -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. - */ -type ModernArgs = [ - method: string, - schemas: { params: ZodType }, - handler: (params: unknown, ctx: BaseContext) => unknown, -]; - -const warned = new Set(); - -/** - * Translate a 1.x `(Schema, handler)` registration into the 2.x - * `(method, { params }, handler)` form, or return `undefined` when the call - * is already in the 2.x form. The 1.x handler receives the reassembled - * `{ method, params }` message and, for requests, a 1.x-shaped `extra`. - */ -export function toModernArgs( - kind: "request" | "notification", - args: unknown[], -): ModernArgs | undefined { - const [first, handler] = args; - const shape = (first as Partial | undefined)?.shape; - if (typeof shape?.method?.value !== "string") return undefined; - const method = shape.method.value; - const setter = - kind === "request" ? "setRequestHandler" : "setNotificationHandler"; - if (!warned.has(setter)) { - warned.add(setter); - console.warn( - `[ext-apps] ${setter}(Schema, handler) is deprecated; use ` + - `${setter}("${method}", { params: Schema.shape.params }, handler).`, - ); - } - const call = handler as (...a: unknown[]) => unknown; - return [ - method, - { params: shape.params }, - kind === "request" - ? (params, ctx) => call({ method, params }, legacyExtra(ctx)) - : (params) => call({ method, params }), - ]; -} - -function legacyExtra(ctx: BaseContext): LegacyRequestHandlerExtra { - return { - signal: ctx.mcpReq.signal, - requestId: ctx.mcpReq.id, - sessionId: ctx.sessionId, - _meta: ctx.mcpReq._meta, - sendRequest: ctx.mcpReq.send, - sendNotification: ctx.mcpReq.notify, - authInfo: ctx.http?.authInfo, - }; -} diff --git a/src/message-transport.test.ts b/src/message-transport.test.ts index e2518cc..d5f5092 100644 --- a/src/message-transport.test.ts +++ b/src/message-transport.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"; -import type { JSONRPCMessage } from "@modelcontextprotocol/client"; +import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; -import { PostMessageTransport } from "./message-transport.js"; +import { PostMessageTransport } from "./message-transport"; /** * Minimal `window` stub for bun's DOM-less test environment. diff --git a/src/message-transport.ts b/src/message-transport.ts index afc85e9..9d19543 100644 --- a/src/message-transport.ts +++ b/src/message-transport.ts @@ -1,11 +1,13 @@ -import { JSONRPCMessageSchema } from "@modelcontextprotocol/core"; -import type { +import { JSONRPCMessage, + JSONRPCMessageSchema, MessageExtraInfo, +} from "@modelcontextprotocol/sdk/types.js"; +import { Transport, TransportSendOptions, -} from "@modelcontextprotocol/client"; -import { TOOL_INPUT_PARTIAL_METHOD } from "./spec.types.js"; +} from "@modelcontextprotocol/sdk/shared/transport.js"; +import { TOOL_INPUT_PARTIAL_METHOD } from "./spec.types"; /** * JSON-RPC transport using `window.postMessage` for iframe↔parent communication. diff --git a/src/react/index.tsx b/src/react/index.tsx index 9693fb1..e76e197 100644 --- a/src/react/index.tsx +++ b/src/react/index.tsx @@ -31,7 +31,7 @@ * } * ``` */ -export * from "./useApp.js"; -export * from "./useAutoResize.js"; -export * from "./useDocumentTheme.js"; -export * from "./useHostStyles.js"; +export * from "./useApp"; +export * from "./useAutoResize"; +export * from "./useDocumentTheme"; +export * from "./useHostStyles"; diff --git a/src/react/useApp.tsx b/src/react/useApp.tsx index a8e1a09..6189246 100644 --- a/src/react/useApp.tsx +++ b/src/react/useApp.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from "react"; -import type { Implementation } from "@modelcontextprotocol/client"; +import { Implementation } from "@modelcontextprotocol/sdk/types.js"; +import { Client } from "@modelcontextprotocol/sdk/client"; import { App, AppOptions, McpUiAppCapabilities, PostMessageTransport, -} from "../app.js"; -export * from "../app.js"; +} from "../app"; +export * from "../app"; /** * Options for configuring the {@link useApp `useApp`} hook. diff --git a/src/react/useAutoResize.ts b/src/react/useAutoResize.ts index 66af07e..78217d6 100644 --- a/src/react/useAutoResize.ts +++ b/src/react/useAutoResize.ts @@ -1,5 +1,5 @@ import { useEffect, RefObject } from "react"; -import { App } from "../app.js"; +import { App } from "../app"; /** * React hook that automatically reports UI size changes to the host. diff --git a/src/react/useDocumentTheme.ts b/src/react/useDocumentTheme.ts index 3dc851e..2fdfcc2 100644 --- a/src/react/useDocumentTheme.ts +++ b/src/react/useDocumentTheme.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { getDocumentTheme } from "../styles.js"; -import { McpUiTheme } from "../types.js"; +import { getDocumentTheme } from "../styles"; +import { McpUiTheme } from "../types"; /** * React hook that provides the current document theme reactively. diff --git a/src/react/useHostStyles.ts b/src/react/useHostStyles.ts index d8376a0..e380efa 100644 --- a/src/react/useHostStyles.ts +++ b/src/react/useHostStyles.ts @@ -1,11 +1,11 @@ import { useEffect, useRef } from "react"; -import { App } from "../app.js"; +import { App } from "../app"; import { applyDocumentTheme, applyHostFonts, applyHostStyleVariables, -} from "../styles.js"; -import { McpUiHostContext } from "../types.js"; +} from "../styles"; +import { McpUiHostContext } from "../types"; /** * React hook that applies host style variables and theme as CSS custom properties. diff --git a/src/server/index.examples.ts b/src/server/index.examples.ts index 9ceebce..d583ae8 100644 --- a/src/server/index.examples.ts +++ b/src/server/index.examples.ts @@ -9,14 +9,17 @@ import * as crypto from "node:crypto"; import * as fs from "node:fs/promises"; -import type { McpServer, ToolCallback } from "@modelcontextprotocol/server"; +import type { + McpServer, + ToolCallback, + ReadResourceCallback, +} from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { registerAppTool, registerAppResource, getUiCapability, RESOURCE_MIME_TYPE, - type ReadResourceCallback, } from "./index.js"; // Stubs for external functions used in examples @@ -71,7 +74,7 @@ function registerAppTool_basicUsage(server: McpServer) { { title: "Get Weather", description: "Get current weather for a location", - inputSchema: z.object({ location: z.string() }), + inputSchema: { location: z.string() }, _meta: { ui: { resourceUri: "ui://weather/view.html" }, }, @@ -119,7 +122,7 @@ function registerAppTool_appOnlyVisibility(server: McpServer) { "update-quantity", { description: "Update item quantity in cart", - inputSchema: z.object({ itemId: z.string(), quantity: z.number() }), + inputSchema: { itemId: z.string(), quantity: z.number() }, _meta: { ui: { resourceUri: "ui://shop/cart.html", diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 489d0b0..1853cc0 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -6,14 +6,8 @@ import { RESOURCE_MIME_TYPE, getUiCapability, EXTENSION_ID, -} from "./index.js"; -import { - InMemoryTransport, - McpServer as RealMcpServer, - type McpServer, -} from "@modelcontextprotocol/server"; -import { Client } from "@modelcontextprotocol/client"; -import { z } from "zod/v4"; +} from "./index"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; describe("registerAppTool", () => { it("should pass through config to server.registerTool", () => { @@ -203,119 +197,6 @@ describe("registerAppTool", () => { }); }); -describe("registerAppTool schema forms", () => { - /** Registers tools on a fresh server, then connects a client to it. */ - async function connect(register: (server: RealMcpServer) => void) { - const server = new RealMcpServer({ name: "test", version: "0.0.0" }); - register(server); - const client = new Client({ name: "client", version: "0.0.0" }); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - await server.connect(serverTransport); - await client.connect(clientTransport); - return client; - } - - it("accepts a z.object() input schema and validates arguments", async () => { - const client = await connect((server) => - registerAppTool( - server, - "greet", - { - inputSchema: z.object({ name: z.string() }), - _meta: { ui: { resourceUri: "ui://greet/view.html" } }, - }, - async ({ name }) => ({ - content: [{ type: "text", text: `hello ${name}` }], - }), - ), - ); - - const { tools } = await client.listTools(); - expect(tools).toHaveLength(1); - expect(tools[0].inputSchema).toMatchObject({ - type: "object", - properties: { name: { type: "string" } }, - }); - expect(tools[0]._meta).toMatchObject({ - ui: { resourceUri: "ui://greet/view.html" }, - [RESOURCE_URI_META_KEY]: "ui://greet/view.html", - }); - - const result = await client.callTool({ - name: "greet", - arguments: { name: "world" }, - }); - expect(result.content).toEqual([{ type: "text", text: "hello world" }]); - - const invalid = await client.callTool({ - name: "greet", - arguments: { name: 42 }, - }); - expect(invalid.isError).toBe(true); - }); - - it("accepts a raw zod shape (deprecated form) and wraps it with z.object()", async () => { - const client = await connect((server) => - registerAppTool( - server, - "greet", - { - inputSchema: { name: z.string() }, - outputSchema: { greeting: z.string() }, - _meta: { ui: { resourceUri: "ui://greet/view.html" } }, - }, - async ({ name }) => ({ - content: [{ type: "text", text: `hello ${name}` }], - structuredContent: { greeting: `hello ${name}` }, - }), - ), - ); - - const { tools } = await client.listTools(); - expect(tools[0].inputSchema).toMatchObject({ - type: "object", - properties: { name: { type: "string" } }, - }); - expect(tools[0].outputSchema).toMatchObject({ - type: "object", - properties: { greeting: { type: "string" } }, - }); - - const result = await client.callTool({ - name: "greet", - arguments: { name: "world" }, - }); - expect(result.structuredContent).toEqual({ greeting: "hello world" }); - - const invalid = await client.callTool({ - name: "greet", - arguments: { name: 42 }, - }); - expect(invalid.isError).toBe(true); - }); - - it("accepts the raw-shape form without an input schema", async () => { - const client = await connect((server) => - registerAppTool( - server, - "ping", - { - outputSchema: { ok: z.boolean() }, - _meta: { ui: { resourceUri: "ui://ping/view.html" } }, - }, - async () => ({ - content: [{ type: "text", text: "pong" }], - structuredContent: { ok: true }, - }), - ), - ); - - const result = await client.callTool({ name: "ping", arguments: {} }); - expect(result.structuredContent).toEqual({ ok: true }); - }); -}); - describe("registerAppResource", () => { it("should register a resource with default MIME type", () => { let capturedName: string | undefined; diff --git a/src/server/index.ts b/src/server/index.ts index 5fad055..c90514a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -32,76 +32,46 @@ */ import { + RESOURCE_URI_META_KEY, + RESOURCE_MIME_TYPE, McpUiResourceCsp, McpUiResourceMeta, McpUiToolMeta, McpUiClientCapabilities, -} from "../spec.types.js"; -import { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "../constants.js"; +} from "../app.js"; import type { - CallToolResult, - ClientCapabilities, - InputRequiredResult, + BaseToolCallback, McpServer, - ReadResourceResult, RegisteredTool, ResourceMetadata, - ServerContext, - StandardSchemaWithJSON, - ToolAnnotations, ToolCallback, ReadResourceCallback as _ReadResourceCallback, RegisteredResource, -} from "@modelcontextprotocol/server"; -import type { z } from "zod/v4"; +} from "@modelcontextprotocol/sdk/server/mcp.js"; +import type { + AnySchema, + ZodRawShapeCompat, +} from "@modelcontextprotocol/sdk/server/zod-compat.js"; +import type { StandardSchemaWithJSON } from "../standard-schema"; +import type { + ClientCapabilities, + ReadResourceResult, + ToolAnnotations, +} from "@modelcontextprotocol/sdk/types.js"; // Re-exports for convenience export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE }; export type { ResourceMetadata, ToolCallback }; -/** - * A plain `{ field: z.string() }` record accepted by the deprecated raw-shape - * form of {@link registerAppTool `registerAppTool`}; the SDK auto-wraps it with `z.object()`. - * - * @deprecated Wrap with `z.object({...})` instead. - */ -export type ZodRawShape = Record; - -/** - * {@link ToolCallback `ToolCallback`} variant used when `inputSchema` is a {@link ZodRawShape `ZodRawShape`}. - * Mirrors the callback type of the SDK's deprecated `registerTool` overload. - * - * @deprecated Wrap with `z.object({...})` instead and use {@link ToolCallback `ToolCallback`}. - */ -export type LegacyToolCallback = - Args extends ZodRawShape - ? ( - args: z.infer>, - ctx: ServerContext, - ) => - | CallToolResult - | InputRequiredResult - | Promise - : ( - ctx: ServerContext, - ) => - | CallToolResult - | InputRequiredResult - | Promise; - /** * Base tool configuration matching the standard MCP server tool options. * Extended by {@link McpUiAppToolConfig `McpUiAppToolConfig`} to add UI metadata requirements. - * - * `inputSchema`/`outputSchema` accept any Standard JSON Schema (zod v4, ArkType, - * Valibot, ...). A raw zod shape (`{ field: z.string() }`) is still accepted for - * backward compatibility but deprecated. */ export interface ToolConfig { title?: string; description?: string; - inputSchema?: ZodRawShape | StandardSchemaWithJSON; - outputSchema?: ZodRawShape | StandardSchemaWithJSON; + inputSchema?: ZodRawShapeCompat | StandardSchemaWithJSON; + outputSchema?: ZodRawShapeCompat | StandardSchemaWithJSON; annotations?: ToolAnnotations; _meta?: Record; } @@ -187,7 +157,7 @@ export interface McpUiAppResourceConfig extends ResourceMetadata { * { * title: "Get Weather", * description: "Get current weather for a location", - * inputSchema: z.object({ location: z.string() }), + * inputSchema: { location: z.string() }, * _meta: { * ui: { resourceUri: "ui://weather/view.html" }, * }, @@ -227,7 +197,7 @@ export interface McpUiAppResourceConfig extends ResourceMetadata { * "update-quantity", * { * description: "Update item quantity in cart", - * inputSchema: z.object({ itemId: z.string(), quantity: z.number() }), + * inputSchema: { itemId: z.string(), quantity: z.number() }, * _meta: { * ui: { * resourceUri: "ui://shop/cart.html", @@ -245,25 +215,8 @@ export interface McpUiAppResourceConfig extends ResourceMetadata { * @see {@link registerAppResource `registerAppResource`} to register the HTML resource referenced by the tool */ export function registerAppTool< - OutputArgs extends StandardSchemaWithJSON, - InputArgs extends StandardSchemaWithJSON | undefined = undefined, ->( - server: Pick, - name: string, - config: McpUiAppToolConfig & { - inputSchema?: InputArgs; - outputSchema?: OutputArgs; - }, - cb: ToolCallback, -): RegisteredTool; -/** - * @deprecated Wrap with `z.object({...})` instead. Raw-shape form: - * `inputSchema`/`outputSchema` may be a plain `{ field: z.string() }` record; - * the SDK auto-wraps it with `z.object()`. - */ -export function registerAppTool< - InputArgs extends ZodRawShape, - OutputArgs extends ZodRawShape | StandardSchemaWithJSON | undefined = + OutputArgs extends ZodRawShapeCompat | StandardSchemaWithJSON, + InputArgs extends undefined | ZodRawShapeCompat | StandardSchemaWithJSON = undefined, >( server: Pick, @@ -272,14 +225,15 @@ export function registerAppTool< inputSchema?: InputArgs; outputSchema?: OutputArgs; }, - cb: LegacyToolCallback, -): RegisteredTool; -export function registerAppTool( - server: Pick, - name: string, - config: McpUiAppToolConfig, - // Widest callback shape accepted by either overload above. - cb: (...args: never[]) => unknown, + // The widened constraint signals the v2 API shape, but NOTE: McpServer in + // sdk@1.x still calls zod internals at runtime, so non-zod schemas will fail + // here until we depend on sdk v2. Zod (which all current callers use) is + // unaffected. The cast below bridges the 1.x type signature. + cb: ToolCallback< + InputArgs extends undefined | ZodRawShapeCompat | AnySchema + ? InputArgs + : AnySchema + >, ): RegisteredTool { // Normalize metadata for backward compatibility: // - If _meta.ui.resourceUri is set, also set the legacy flat key @@ -297,15 +251,13 @@ export function registerAppTool( normalizedMeta = { ...meta, ui: { ...uiMeta, resourceUri: legacyUri } }; } - // The public overloads above guarantee `config`/`cb` match one of the SDK's - // own registerTool overloads; the union-typed implementation cannot select - // between them, so widen for the forwarding call. + // Cast bridges the widened StandardSchemaWithJSON constraint to the + // sdk@1.x zod-typed signature. Drops once we depend on sdk v2. + // eslint-disable-next-line @typescript-eslint/no-explicit-any return server.registerTool( name, - { ...config, _meta: normalizedMeta } as Parameters< - McpServer["registerTool"] - >[1], - cb as ToolCallback, + { ...config, _meta: normalizedMeta } as any, + cb as any, ); } @@ -466,6 +418,10 @@ export const EXTENSION_ID = "io.modelcontextprotocol/ui"; * This helper retrieves the capability object from the `extensions` field * where MCP Apps advertises its support. * + * Note: The `clientCapabilities` parameter extends the SDK's `ClientCapabilities` + * type with an `extensions` field (pending SEP-1724). Once `extensions` is added + * to the SDK, this can use `ClientCapabilities` directly. + * * @param clientCapabilities - The client capabilities from the initialize response * @returns The MCP Apps capability settings, or `undefined` if not supported * @@ -500,7 +456,10 @@ export const EXTENSION_ID = "io.modelcontextprotocol/ui"; * ``` */ export function getUiCapability( - clientCapabilities: ClientCapabilities | null | undefined, + clientCapabilities: + | (ClientCapabilities & { extensions?: Record }) + | null + | undefined, ): McpUiClientCapabilities | undefined { if (!clientCapabilities) { return undefined; diff --git a/src/spec.types.ts b/src/spec.types.ts index 0c45396..7a8b337 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -18,7 +18,7 @@ import type { RequestId, ResourceLink, Tool, -} from "@modelcontextprotocol/client"; +} from "@modelcontextprotocol/sdk/types.js"; /** * Current protocol version supported by this SDK. @@ -163,8 +163,8 @@ export interface McpUiOpenLinkResult { /** @description True if the host failed to open the URL (e.g., due to security policy). */ isError?: boolean; /** - * Additional result properties are preserved for protocol extensibility. - * The generated schema uses passthrough() to allow them. + * Index signature required for MCP SDK `Protocol` class compatibility. + * Note: The generated schema uses passthrough() to allow additional properties. */ [key: string]: unknown; } @@ -195,8 +195,8 @@ export interface McpUiDownloadFileResult { /** @description True if the download failed (e.g., user cancelled or host denied). */ isError?: boolean; /** - * Additional result properties are preserved for protocol extensibility. - * The generated schema uses passthrough() to allow them. + * Index signature required for MCP SDK `Protocol` class compatibility. + * Note: The generated schema uses passthrough() to allow additional properties. */ [key: string]: unknown; } @@ -223,8 +223,8 @@ export interface McpUiMessageResult { /** @description True if the host rejected or failed to deliver the message. */ isError?: boolean; /** - * Additional result properties are preserved for protocol extensibility. - * The generated schema uses passthrough() to allow them. + * Index signature required for MCP SDK `Protocol` class compatibility. + * Note: The generated schema uses passthrough() to allow additional properties. */ [key: string]: unknown; } @@ -454,7 +454,7 @@ export interface McpUiResourceTeardownRequest { */ export interface McpUiResourceTeardownResult { /** - * Additional result properties are preserved for protocol extensibility. + * Index signature required for MCP SDK `Protocol` class compatibility. */ [key: string]: unknown; } @@ -492,8 +492,8 @@ export interface McpUiRequestTeardownNotification { * @see {@link McpUiInitializeResult `McpUiInitializeResult`} for the initialization result that includes these capabilities */ export interface McpUiHostCapabilities { - /** @description Experimental features keyed by identifier. */ - experimental?: Record; + /** @description Experimental features (structure TBD). */ + experimental?: {}; /** @description Host supports opening external URLs. */ openLinks?: {}; /** @description Host supports file downloads via ui/download-file. */ @@ -536,8 +536,8 @@ export interface McpUiHostCapabilities { * @see {@link McpUiInitializeRequest `McpUiInitializeRequest`} for the initialization request that includes these capabilities */ export interface McpUiAppCapabilities { - /** @description Experimental features keyed by identifier. */ - experimental?: Record; + /** @description Experimental features (structure TBD). */ + experimental?: {}; /** @description App exposes MCP-style tools that the host can call. */ tools?: { /** @description App supports tools/list_changed notifications. */ @@ -577,8 +577,8 @@ export interface McpUiInitializeResult { /** @description Rich context about the host environment. */ hostContext: McpUiHostContext; /** - * Additional result properties are preserved for protocol extensibility. - * The generated schema uses passthrough() to allow them. + * Index signature required for MCP SDK `Protocol` class compatibility. + * Note: The generated schema uses passthrough() to allow additional properties. */ [key: string]: unknown; } @@ -752,7 +752,7 @@ export interface McpUiRequestDisplayModeResult { /** @description The display mode that was actually set. May differ from requested if not supported. */ mode: McpUiDisplayMode; /** - * Additional result properties are preserved for protocol extensibility. + * Index signature required for MCP SDK `Protocol` class compatibility. * Note: The generated schema uses passthrough() to allow additional properties. */ [key: string]: unknown; diff --git a/src/standard-schema.ts b/src/standard-schema.ts index a06864a..4bb85de 100644 --- a/src/standard-schema.ts +++ b/src/standard-schema.ts @@ -4,22 +4,36 @@ import type { StandardTypedV1, } from "@standard-schema/spec"; -import type { StandardSchemaWithJSON } from "@modelcontextprotocol/client"; +export type { StandardJSONSchemaV1, StandardSchemaV1, StandardTypedV1 }; + +// TODO(sdk-v2): once @modelcontextprotocol/core v2 is stable, import +// StandardSchemaWithJSON / standardSchemaToJsonSchema / validateStandardSchema +// from there and delete this file. At that point decide whether to tighten +// App.registerTool to StandardSchemaWithJSON (drops zod 3 from the peer range +// and the lazy z.toJSONSchema fallback below). /** * A schema that implements both Standard Schema (validation) and Standard JSON * Schema (serialization). Zod v4, ArkType, and Valibot (via - * `@valibot/to-json-schema`) all satisfy this. Re-exported from the SDK so - * View authors can import it alongside {@link app!App `App`}. + * `@valibot/to-json-schema`) all satisfy this. + * + * Mirrors the type of the same name in `@modelcontextprotocol/core` v2 so that + * bumping to that package later is a drop-in import swap. * * @see https://standardschema.dev/ + * @see https://github.com/modelcontextprotocol/typescript-sdk/pull/1689 */ -export type { - StandardJSONSchemaV1, - StandardSchemaV1, - StandardSchemaWithJSON, - StandardTypedV1, -}; +export interface StandardSchemaWithJSON { + readonly "~standard": StandardSchemaV1.Props & + StandardJSONSchemaV1.Props; +} + +export namespace StandardSchemaWithJSON { + export type InferInput = + StandardTypedV1.InferInput; + export type InferOutput = + StandardTypedV1.InferOutput; +} /** JSON-Schema target draft used for tool input/output schemas (matches core MCP). */ const TARGET = { target: "draft-2020-12" } as const; @@ -27,8 +41,11 @@ const TARGET = { target: "draft-2020-12" } as const; /** * Serialize a Standard Schema to JSON Schema for the given direction. * - * Requires `~standard.jsonSchema` (zod v4, ArkType, Valibot, …); schemas - * without it throw. + * Uses `~standard.jsonSchema` when present (zod v4, ArkType, Valibot, …). + * Falls back to a lazy `zod/v4` import for zod v3.25.x — which implements + * `~standard.validate` but not yet `~standard.jsonSchema` — so the existing + * `^3.25.0 || ^4.0.0` peer range keeps working. Non-zod schemas without + * `jsonSchema` throw. */ export async function standardSchemaToJsonSchema( schema: StandardSchemaV1, @@ -40,6 +57,11 @@ export async function standardSchemaToJsonSchema( if (std.jsonSchema) { return std.jsonSchema[io](TARGET); } + if (std.vendor === "zod") { + const { z } = await import("zod/v4"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridging StandardSchemaV1 → zod's $ZodType for the v3.25 fallback + return z.toJSONSchema(schema as any, { io }); + } throw new Error( `Schema (vendor: ${std.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). ` + `Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`, diff --git a/src/styles.ts b/src/styles.ts index 895073b..ad900e2 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -1,4 +1,4 @@ -import { McpUiStyles, McpUiTheme } from "./types.js"; +import { McpUiStyles, McpUiTheme } from "./types"; /** * Get the current document theme from the root HTML element. diff --git a/src/types.ts b/src/types.ts index e4ed629..7fc6b71 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,55 +9,6 @@ * @see `generated/schema.test.ts` for compile-time verification */ -import type { - CallToolRequest, - CallToolResult, - CreateMessageRequest, - CreateMessageResult, - CreateMessageResultWithTools, - EmptyResult, - ListPromptsRequest, - ListPromptsResult, - ListResourceTemplatesRequest, - ListResourceTemplatesResult, - ListResourcesRequest, - ListResourcesResult, - ListToolsRequest, - ListToolsResult, - LoggingMessageNotification, - PingRequest, - PromptListChangedNotification, - ReadResourceRequest, - ReadResourceResult, - ResourceListChangedNotification, - ToolListChangedNotification, -} from "@modelcontextprotocol/client"; -import type { - McpUiDownloadFileRequest, - McpUiDownloadFileResult, - McpUiHostContextChangedNotification, - McpUiInitializeRequest, - McpUiInitializeResult, - McpUiInitializedNotification, - McpUiMessageRequest, - McpUiMessageResult, - McpUiOpenLinkRequest, - McpUiOpenLinkResult, - McpUiRequestDisplayModeRequest, - McpUiRequestDisplayModeResult, - McpUiRequestTeardownNotification, - McpUiResourceTeardownRequest, - McpUiResourceTeardownResult, - McpUiSandboxProxyReadyNotification, - McpUiSandboxResourceReadyNotification, - McpUiSizeChangedNotification, - McpUiToolCancelledNotification, - McpUiToolInputNotification, - McpUiToolInputPartialNotification, - McpUiToolResultNotification, - McpUiUpdateModelContextRequest, -} from "./spec.types.js"; - // Re-export all types from spec.types.ts export { LATEST_PROTOCOL_VERSION, @@ -118,6 +69,33 @@ export { type McpUiClientCapabilities, } from "./spec.types.js"; +// Import types needed for protocol type unions (not re-exported, just used internally) +import type { + McpUiInitializeRequest, + McpUiOpenLinkRequest, + McpUiDownloadFileRequest, + McpUiMessageRequest, + McpUiUpdateModelContextRequest, + McpUiResourceTeardownRequest, + McpUiRequestDisplayModeRequest, + McpUiHostContextChangedNotification, + McpUiToolInputNotification, + McpUiToolInputPartialNotification, + McpUiToolResultNotification, + McpUiToolCancelledNotification, + McpUiSandboxResourceReadyNotification, + McpUiInitializedNotification, + McpUiSizeChangedNotification, + McpUiSandboxProxyReadyNotification, + McpUiRequestTeardownNotification, + McpUiInitializeResult, + McpUiOpenLinkResult, + McpUiDownloadFileResult, + McpUiMessageResult, + McpUiResourceTeardownResult, + McpUiRequestDisplayModeResult, +} from "./spec.types.js"; + // Re-export all schemas from generated/schema.ts (already PascalCase) export { McpUiThemeSchema, @@ -158,11 +136,40 @@ export { McpUiToolMetaSchema, } from "./generated/schema.js"; +// Re-export SDK types used in protocol type unions +import { + CallToolRequest, + CallToolResult, + CreateMessageRequest, + CreateMessageResult, + CreateMessageResultWithTools, + EmptyResult, + ListPromptsRequest, + ListPromptsResult, + ListResourcesRequest, + ListResourcesResult, + ListResourceTemplatesRequest, + ListResourceTemplatesResult, + ListToolsRequest, + ListToolsResult, + LoggingMessageNotification, + PingRequest, + PromptListChangedNotification, + ReadResourceRequest, + ReadResourceResult, + ResourceListChangedNotification, + ToolListChangedNotification, +} from "@modelcontextprotocol/sdk/types.js"; + /** - * Union of every request an {@link app!App `App`} may send or receive. + * All request types in the MCP Apps protocol. * - * @deprecated 1.x fed this to the SDK's `Protocol` generics; SDK 2.x derives - * request types from the method name, so nothing consumes it anymore. + * Includes: + * - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode) + * - MCP server requests forwarded from the app (tools/call, tools/list, resources/list, + * resources/templates/list, resources/read, prompts/list) + * - MCP client requests forwarded to the host (sampling/createMessage) + * - Protocol requests (ping) */ export type AppRequest = | McpUiInitializeRequest @@ -182,9 +189,17 @@ export type AppRequest = | PingRequest; /** - * Union of every notification an {@link app!App `App`} may send or receive. + * All notification types in the MCP Apps protocol. * - * @deprecated See {@link AppRequest}. + * Host to app: + * - Tool lifecycle (input, input-partial, result, cancelled) + * - Host context changes + * - MCP list changes (tools, resources, prompts) + * - Sandbox resource ready + * + * App to host: + * - Initialized, size-changed, sandbox-proxy-ready, request-teardown + * - Logging messages */ export type AppNotification = // Sent to app @@ -205,9 +220,7 @@ export type AppNotification = | LoggingMessageNotification; /** - * Union of every result an {@link app!App `App`} may send or receive. - * - * @deprecated See {@link AppRequest}. + * All result types in the MCP Apps protocol. */ export type AppResult = | McpUiInitializeResult diff --git a/src/wire-compat.test.ts b/src/wire-compat.test.ts deleted file mode 100644 index 762ac77..0000000 --- a/src/wire-compat.test.ts +++ /dev/null @@ -1,576 +0,0 @@ -/** - * Cross-version wire compatibility. - * - * Replays JSON-RPC messages captured from ext-apps 1.7.x (built on - * `@modelcontextprotocol/sdk` 1.x) through the 2.x `App` and `AppBridge` over - * a raw in-memory transport, and asserts what 2.x emits back. The MCP Apps - * wire protocol is meant to be unchanged across the major bump; the few - * host-side error deltas are pinned here so they stay deliberate. - */ -import { describe, it, expect, afterEach } from "bun:test"; -import { - Client, - InMemoryTransport, - ProtocolError, - type JSONRPCMessage, - type Transport, -} from "@modelcontextprotocol/client"; -import { McpServer } from "@modelcontextprotocol/server"; -import { z } from "zod/v4"; - -import { App } from "./app.js"; -import { - AppBridge, - type McpUiHostCapabilities, - type McpUiHostContext, -} from "./app-bridge.js"; -import { LATEST_PROTOCOL_VERSION } from "./types.js"; - -/** Wait for pending microtasks/timers to complete. */ -const flush = () => new Promise((resolve) => setTimeout(resolve, 5)); - -/** - * One end of a raw in-memory channel. The 2.x side gets `transport`; the test - * plays the role of the 1.x peer by calling `inject()` with captured JSON and - * reading `sent` to see exactly what 2.x put on the wire. - */ -function createRawChannel() { - const sent: JSONRPCMessage[] = []; - let closed = false; - const transport: Transport = { - async start() {}, - async send(message) { - // Serialize like a real transport would so we assert on plain JSON. - sent.push(JSON.parse(JSON.stringify(message))); - }, - async close() { - if (closed) return; - closed = true; - transport.onclose?.(); - }, - }; - return { - transport, - sent, - /** Deliver a raw message (as the 1.x peer would have sent it). */ - inject(message: unknown) { - transport.onmessage?.(message as JSONRPCMessage); - }, - /** Messages sent after the given index. */ - since(index: number) { - return sent.slice(index); - }, - }; -} - -const hostInfo = { name: "TestHost", version: "9.9.9" }; -const hostCapabilities: McpUiHostCapabilities = { - openLinks: {}, - serverTools: {}, - serverResources: {}, - logging: {}, - updateModelContext: {}, -}; -const hostContext: McpUiHostContext = { - theme: "dark", - displayMode: "inline", - locale: "en-US", -}; - -/** Shaped like ext-apps 1.7.x View traffic (sdk-1 key order, 0-based numeric ids); not a recorded capture. */ -const v1View = { - initialize: { - method: "ui/initialize", - params: { - appCapabilities: { tools: { listChanged: true } }, - appInfo: { name: "TestView", version: "1.2.3" }, - protocolVersion: "2026-01-26", - }, - jsonrpc: "2.0", - id: 0, - }, - initialized: { method: "ui/notifications/initialized", jsonrpc: "2.0" }, - callTool: { - method: "tools/call", - params: { name: "echo", arguments: { a: 1 }, _meta: { progressToken: 6 } }, - jsonrpc: "2.0", - id: 6, - }, - readResource: { - method: "resources/read", - params: { uri: "test://x" }, - jsonrpc: "2.0", - id: 12, - }, - toolsListResult: (id: number) => ({ - result: { - tools: [ - { - name: "viewtool", - description: "view tool", - inputSchema: { - type: "object", - properties: { x: { type: "number" } }, - }, - }, - ], - }, - jsonrpc: "2.0", - id, - }), -}; - -/** Shaped like ext-apps 1.7.x AppBridge traffic (sdk 1.x); not a recorded capture. */ -const v1Host = { - initializeResult: (id: number) => ({ - result: { - protocolVersion: "2026-01-26", - hostCapabilities, - hostInfo, - hostContext, - }, - jsonrpc: "2.0", - id, - }), - toolInput: { - method: "ui/notifications/tool-input", - params: { arguments: { location: "NYC" } }, - jsonrpc: "2.0", - }, - callToolResult: (id: number) => ({ - result: { content: [{ type: "text", text: "echo 1" }] }, - jsonrpc: "2.0", - id, - }), - /** McpServer 1.x reports an unknown tool as an isError result, not an error. */ - callToolUnknownResult: (id: number) => ({ - result: { - content: [ - { type: "text", text: "MCP error -32602: Tool nope not found" }, - ], - isError: true, - }, - jsonrpc: "2.0", - id, - }), - resourceNotFoundError: (id: number) => ({ - jsonrpc: "2.0", - id, - error: { - code: -32002, - message: "MCP error -32002: Resource not found (host-thrown -32002)", - }, - }), - toolsList: { method: "tools/list", jsonrpc: "2.0", id: 0 }, - callViewTool: { - method: "tools/call", - params: { name: "viewtool", arguments: { x: 7 } }, - jsonrpc: "2.0", - id: 1, - }, -}; - -describe("wire compatibility: 2.x AppBridge with a 1.x View", () => { - let bridge: AppBridge; - - afterEach(async () => { - await bridge?.close().catch(() => {}); - }); - - async function connectBridge() { - const channel = createRawChannel(); - bridge = new AppBridge(null, hostInfo, hostCapabilities, { hostContext }); - await bridge.connect(channel.transport); - return channel; - } - - async function handshake() { - const channel = await connectBridge(); - let initialized = 0; - bridge.oninitialized = () => { - initialized++; - }; - channel.inject(v1View.initialize); - await flush(); - channel.inject(v1View.initialized); - await flush(); - return { channel, initialized: () => initialized }; - } - - it("sends nothing on connect: no MCP initialize toward the View", async () => { - const channel = await connectBridge(); - await flush(); - expect(channel.sent).toEqual([]); - }); - - it("answers a 1.x ui/initialize with the 1.x result shape and same id", async () => { - const channel = await connectBridge(); - channel.inject(v1View.initialize); - await flush(); - - expect(channel.sent).toEqual([ - { - jsonrpc: "2.0", - id: 0, - result: { - protocolVersion: LATEST_PROTOCOL_VERSION, - hostCapabilities, - hostInfo, - hostContext, - }, - }, - ]); - expect(bridge.getAppCapabilities()).toEqual({ - tools: { listChanged: true }, - }); - expect(bridge.getAppVersion()).toEqual({ - name: "TestView", - version: "1.2.3", - }); - }); - - it("accepts ui/notifications/initialized without params", async () => { - const { channel, initialized } = await handshake(); - expect(initialized()).toBe(1); - // Only the initialize response went out; the notification gets no reply - // and the bridge never starts an MCP handshake of its own. - expect(channel.sent).toHaveLength(1); - expect(channel.sent.some((m) => "method" in m)).toBe(false); - }); - - it("answers a 1.x tools/call request through oncalltool", async () => { - const { channel } = await handshake(); - bridge.oncalltool = async (params) => ({ - content: [{ type: "text", text: `echo ${params.arguments?.a}` }], - }); - const mark = channel.sent.length; - channel.inject(v1View.callTool); - await flush(); - - expect(channel.since(mark)).toEqual([ - { - jsonrpc: "2.0", - id: 6, - result: { content: [{ type: "text", text: "echo 1" }] }, - }, - ]); - }); - - it("re-encodes a handler-thrown -32002 as -32602 without the MCP error prefix", async () => { - // Documented host-side delta: SDK 2.x never emits -32002 on the wire. - const { channel } = await handshake(); - bridge.onreadresource = async () => { - throw new ProtocolError( - -32002, - "Resource not found (host-thrown -32002)", - ); - }; - const mark = channel.sent.length; - channel.inject(v1View.readResource); - await flush(); - - const [reply] = channel.since(mark); - expect(reply).toMatchObject({ - jsonrpc: "2.0", - id: 12, - error: { code: -32602 }, - }); - const message = (reply as { error: { message: string } }).error.message; - expect(message).toBe("Resource not found (host-thrown -32002)"); - expect(message).not.toMatch(/^MCP error/); - }); - - it("rejects invalid params on a ui/* request with -32602", async () => { - const { channel } = await handshake(); - bridge.onopenlink = async () => ({}); - const mark = channel.sent.length; - channel.inject({ - method: "ui/open-link", - params: { url: 42 }, - jsonrpc: "2.0", - id: 14, - }); - await flush(); - - expect(channel.since(mark)).toHaveLength(1); - expect(channel.since(mark)[0]).toMatchObject({ - jsonrpc: "2.0", - id: 14, - error: { code: -32602 }, - }); - }); - - it("lists and calls View tools with 1.x-shaped responses", async () => { - const { channel } = await handshake(); - const mark = channel.sent.length; - const listPromise = bridge.listTools({}); - await flush(); - - const [listRequest] = channel.since(mark) as Array<{ - id: number; - method: string; - }>; - expect(listRequest).toMatchObject({ jsonrpc: "2.0", method: "tools/list" }); - channel.inject(v1View.toolsListResult(listRequest.id)); - const list = await listPromise; - expect(list.tools.map((t) => t.name)).toEqual(["viewtool"]); - - const callPromise = bridge.callTool({ - name: "viewtool", - arguments: { x: 7 }, - }); - await flush(); - const [callRequest] = channel.since(mark + 1) as Array<{ id: number }>; - expect(callRequest).toMatchObject({ - jsonrpc: "2.0", - method: "tools/call", - params: { name: "viewtool", arguments: { x: 7 } }, - }); - channel.inject({ - result: { content: [{ type: "text", text: "view got 7" }] }, - jsonrpc: "2.0", - id: callRequest.id, - }); - expect(await callPromise).toEqual({ - content: [{ type: "text", text: "view got 7" }], - }); - }); - - it("only ever emits jsonrpc 2.0 messages, never an initialize request", async () => { - const { channel } = await handshake(); - bridge.oncalltool = async () => ({ content: [] }); - channel.inject(v1View.callTool); - bridge.sendToolInput({ arguments: { q: "x" } }); - bridge.sendToolResult({ content: [] }); - await flush(); - - for (const message of channel.sent) { - expect(message.jsonrpc).toBe("2.0"); - } - const methods = channel.sent - .filter((m): m is JSONRPCMessage & { method: string } => "method" in m) - .map((m) => m.method); - expect(methods).toEqual([ - "ui/notifications/tool-input", - "ui/notifications/tool-result", - ]); - expect(methods).not.toContain("initialize"); - expect(methods).not.toContain("notifications/initialized"); - }); -}); - -describe("wire compatibility: 2.x App with a 1.x host", () => { - let app: App; - - afterEach(async () => { - await app?.close().catch(() => {}); - }); - - /** Connect the App and answer its ui/initialize the way a 1.x host did. */ - async function handshake( - capabilities: ConstructorParameters[1] = {}, - setup: (app: App) => void = () => {}, - ) { - const channel = createRawChannel(); - app = new App({ name: "TestView", version: "1.2.3" }, capabilities, { - autoResize: false, - }); - setup(app); - const connected = app.connect(channel.transport); - await flush(); - - const [initialize] = channel.sent as Array<{ id: number }>; - channel.inject(v1Host.initializeResult(initialize.id)); - await connected; - await flush(); - return { channel, initialize }; - } - - it("sends a 1.x-compatible ui/initialize and ui/notifications/initialized", async () => { - const { channel } = await handshake({ tools: { listChanged: true } }); - - expect(channel.sent).toEqual([ - { - jsonrpc: "2.0", - id: 0, - method: "ui/initialize", - params: { - protocolVersion: LATEST_PROTOCOL_VERSION, - appInfo: { name: "TestView", version: "1.2.3" }, - appCapabilities: { tools: { listChanged: true } }, - }, - }, - { jsonrpc: "2.0", method: "ui/notifications/initialized" }, - ]); - expect(app.getHostContext()).toEqual(hostContext); - expect(app.getHostCapabilities()).toEqual(hostCapabilities); - expect(app.getHostVersion()).toEqual(hostInfo); - }); - - it("delivers a 1.x notification with sdk-1 key order to ontoolinput", async () => { - const received: unknown[] = []; - const { channel } = await handshake({}, (app) => { - app.ontoolinput = (params) => { - received.push(params); - }; - }); - channel.inject(v1Host.toolInput); - await flush(); - expect(received).toEqual([{ arguments: { location: "NYC" } }]); - }); - - it("resolves callServerTool from a 1.x tools/call response", async () => { - const { channel } = await handshake(); - const mark = channel.sent.length; - const promise = app.callServerTool({ name: "echo", arguments: { a: 1 } }); - await flush(); - - const [request] = channel.since(mark) as Array<{ id: number }>; - expect(request).toMatchObject({ - jsonrpc: "2.0", - method: "tools/call", - params: { - name: "echo", - arguments: { a: 1 }, - _meta: { progressToken: request.id }, - }, - }); - channel.inject(v1Host.callToolResult(request.id)); - expect(await promise).toEqual({ - content: [{ type: "text", text: "echo 1" }], - }); - }); - - it("passes through a 1.x isError tool result unchanged", async () => { - const { channel } = await handshake(); - const mark = channel.sent.length; - const promise = app.callServerTool({ name: "nope", arguments: {} }); - await flush(); - const [request] = channel.since(mark) as Array<{ id: number }>; - channel.inject(v1Host.callToolUnknownResult(request.id)); - expect(await promise).toEqual({ - content: [ - { type: "text", text: "MCP error -32602: Tool nope not found" }, - ], - isError: true, - }); - }); - - it("surfaces a 1.x -32002 error as a ProtocolError with code -32002", async () => { - const { channel } = await handshake(); - const mark = channel.sent.length; - const promise = app.readServerResource({ uri: "test://x" }); - await flush(); - const [request] = channel.since(mark) as Array<{ id: number }>; - channel.inject(v1Host.resourceNotFoundError(request.id)); - - const error = await promise.then( - () => undefined, - (e: unknown) => e, - ); - expect(error).toBeInstanceOf(ProtocolError); - expect((error as ProtocolError).code).toBe(-32002); - expect((error as ProtocolError).message).toContain( - "Resource not found (host-thrown -32002)", - ); - }); - - it("answers 1.x tools/list (no params) and tools/call requests from the host", async () => { - const { channel } = await handshake({ tools: { listChanged: true } }); - app.registerTool( - "viewtool", - { description: "view tool", inputSchema: z.object({ x: z.number() }) }, - async ({ x }) => ({ - content: [{ type: "text", text: `view got ${x}` }], - }), - ); - await flush(); - const mark = channel.sent.length; - - channel.inject(v1Host.toolsList); - await flush(); - const [listReply] = channel.since(mark) as unknown as Array<{ - result: { tools: Array<{ name: string; inputSchema: unknown }> }; - }>; - expect(listReply).toMatchObject({ jsonrpc: "2.0", id: 0 }); - expect(listReply.result.tools).toHaveLength(1); - expect(listReply.result.tools[0]).toMatchObject({ - name: "viewtool", - description: "view tool", - inputSchema: { - type: "object", - properties: { x: { type: "number" } }, - required: ["x"], - }, - }); - - channel.inject(v1Host.callViewTool); - await flush(); - expect(channel.since(mark + 1)).toEqual([ - { - jsonrpc: "2.0", - id: 1, - result: { content: [{ type: "text", text: "view got 7" }] }, - }, - ]); - }); -}); - -describe("AppBridge proxy over a default (modern era) 2.x Client/Server", () => { - let client: Client; - let server: McpServer; - let app: App; - let bridge: AppBridge; - - afterEach(async () => { - await app?.close().catch(() => {}); - await bridge?.close().catch(() => {}); - await client?.close().catch(() => {}); - await server?.close().catch(() => {}); - }); - - it("forwards tools/call and tools/list_changed with default negotiation", async () => { - server = new McpServer({ name: "ActualServer", version: "1.0.0" }); - server.registerTool( - "echo", - { inputSchema: z.object({ a: z.number() }) }, - async ({ a }) => ({ content: [{ type: "text", text: `echo ${a}` }] }), - ); - const [clientTransport, serverTransport] = - InMemoryTransport.createLinkedPair(); - await server.connect(serverTransport); - - client = new Client({ name: "HostOuterClient", version: "1.0.0" }); - await client.connect(clientTransport); - expect(client.getServerCapabilities()?.tools).toEqual({ - listChanged: true, - }); - - bridge = new AppBridge(client, hostInfo, hostCapabilities, { - hostContext, - }); - app = new App( - { name: "TestView", version: "1.2.3" }, - {}, - { - autoResize: false, - }, - ); - const listChanged: string[] = []; - app.setNotificationHandler("notifications/tools/list_changed", () => { - listChanged.push("tools"); - }); - const [appTransport, bridgeTransport] = - InMemoryTransport.createLinkedPair(); - await bridge.connect(bridgeTransport); - await app.connect(appTransport); - - expect( - await app.callServerTool({ name: "echo", arguments: { a: 1 } }), - ).toMatchObject({ content: [{ type: "text", text: "echo 1" }] }); - - server.sendToolListChanged(); - await flush(); - expect(listChanged).toEqual(["tools"]); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 934b950..08ff110 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "types": ["node", "bun"], "target": "ES2020", "module": "ESNext", "lib": ["ES2020", "DOM"], diff --git a/typedoc.config.mjs b/typedoc.config.mjs index c9b9476..4c5baa3 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -1,7 +1,5 @@ import { OptionDefaults } from "typedoc"; -const BASE_SDK_DOCS = "https://ts.sdk.modelcontextprotocol.io/v2/"; - /** @type {Partial} */ const config = { name: "MCP Apps", @@ -19,7 +17,6 @@ const config = { "docs/authorization.md", "docs/csp-cors.md", "docs/migrate_from_openai_apps.md", - "docs/migrate-to-2.md", ], entryPoints: [ "src/server/index.ts", @@ -31,32 +28,7 @@ const config = { ], excludePrivate: true, excludeInternal: false, - intentionallyNotExported: ["RequestHandlerExtra"], - externalSymbolLinkMappings: { - "@modelcontextprotocol/client": { - ClientOptions: BASE_SDK_DOCS, - ConnectOptions: BASE_SDK_DOCS, - DiscoverResult: BASE_SDK_DOCS, - "MessageExtraInfo.classification": BASE_SDK_DOCS, - ProtocolError: BASE_SDK_DOCS, - "Protocol.close": BASE_SDK_DOCS, - "Protocol.notification": BASE_SDK_DOCS, - ResponseCacheStore: BASE_SDK_DOCS, - SdkError: BASE_SDK_DOCS, - "SdkErrorCode.MethodNotSupportedByProtocolVersion": BASE_SDK_DOCS, - StandardJSONSchemaV1: BASE_SDK_DOCS, - "SdkErrorCode.UnsupportedResultType": BASE_SDK_DOCS, - "__type.enforceStrictCapabilities": BASE_SDK_DOCS, - }, - "@modelcontextprotocol/server": { - LoggingMessageNotification: BASE_SDK_DOCS, - "McpServer.registerTool": BASE_SDK_DOCS, - "MessageExtraInfo.classification": BASE_SDK_DOCS, - "Protocol.close": BASE_SDK_DOCS, - "Protocol.notification": BASE_SDK_DOCS, - "SdkErrorCode.UnsupportedResultType": BASE_SDK_DOCS, - }, - }, + intentionallyNotExported: ["MethodSchema", "RequestHandlerExtra"], blockTags: [...OptionDefaults.blockTags, "@description"], jsDocCompatibility: { exampleTag: false, @@ -77,7 +49,6 @@ const config = { out: "docs/api", plugin: [ "typedoc-github-theme", - "./scripts/typedoc-plugin-base-sdk-links.mjs", "./scripts/typedoc-plugin-fix-mermaid-entities.mjs", "./scripts/typedoc-plugin-seo.mjs", "./scripts/typedoc-plugin-mcpstyle.mjs",