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/README.md b/README.md
index ab4004d..2fd89ff 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
Why
·
- Quickstart
+ Quickstart
·
API Docs
·
@@ -90,7 +90,6 @@ it will guide you through the rest.
-
> [!NOTE]
@@ -122,36 +121,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
@@ -272,7 +247,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..f54b237 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.5",
"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..81ed0de 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.5",
"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..5ae783e 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.5",
"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..c2950d9 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.5",
"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..8238feb 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.5",
"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..8ca330e 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.5",
"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..9c0eb7b 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.5",
"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..0cd94d0 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.5",
"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..9988679 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.5",
"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..2fc85f9 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.5",
"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..16d2f4c 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.5",
"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..a8b5421 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.5",
"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..8e96906 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.5",
"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..6670b3e 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
@@ -622,7 +621,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 +654,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 +698,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 +736,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 +751,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 +783,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 +1047,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/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/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..5de1754 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.5",
"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..8942a90 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.5",
"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..8152866 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.5",
"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..e1dcf2d 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.5",
"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..d51dabd 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.5",
"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..716c7b5 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.5",
"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..0505f47 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.5",
"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..e9e39cc 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.5",
"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..9506c61 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.5",
"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..2121460 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.5",
"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..8e9d495 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.5",
"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..8d8b8ec 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.5",
"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..af02eae 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.5",
"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..9dc76e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@modelcontextprotocol/ext-apps",
- "version": "2.0.0",
+ "version": "1.7.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@modelcontextprotocol/ext-apps",
- "version": "2.0.0",
+ "version": "1.7.5",
"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",
@@ -48,23 +44,18 @@
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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.5",
"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"
@@ -2679,93 +2613,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 +2690,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
@@ -4419,10 +4297,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 +4316,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"
@@ -6016,13 +5892,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 +5909,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.4",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
+ "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
"funding": [
{
"type": "github",
@@ -6552,10 +6393,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 +6592,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": {
@@ -7758,7 +7596,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"
@@ -9904,13 +9741,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..15ef5d7 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.5",
"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",
@@ -111,20 +105,15 @@
"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/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..d327c95 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,18 +34,11 @@ 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: () => {},
};
}
@@ -85,53 +88,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,7 +97,6 @@ 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 () => {
@@ -667,30 +622,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 +691,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 +809,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 +1485,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 +1532,6 @@ describe("App <-> AppBridge integration", () => {
},
async () => ({ content: [] }),
);
- await app.connect(appTransport);
-
expect(bridge.listTools({})).rejects.toThrow(
/does not implement Standard JSON Schema/,
);
@@ -2228,10 +2191,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 +2235,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 +2286,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 +2309,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 +2321,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 +2336,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 +2351,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 +2366,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 +2721,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 +2766,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..6b66482 100644
--- a/src/generated/schema.json
+++ b/src/generated/schema.json
@@ -614,9 +614,7 @@
"propertyNames": {
"type": "string"
},
- "additionalProperties": {
- "$ref": "#/$defs/__schema0"
- }
+ "additionalProperties": {}
},
"required": {
"type": "array",
@@ -631,10 +629,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 +1304,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 +1377,7 @@
"propertyNames": {
"type": "string"
},
- "additionalProperties": {
- "$ref": "#/$defs/__schema0"
- }
+ "additionalProperties": {}
},
"required": {
"type": "array",
@@ -1414,10 +1392,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 +2062,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",
@@ -3018,9 +2978,7 @@
"propertyNames": {
"type": "string"
},
- "additionalProperties": {
- "$ref": "#/$defs/__schema0"
- }
+ "additionalProperties": {}
},
"required": {
"type": "array",
@@ -3035,10 +2993,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 +3673,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 +5360,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 +5705,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..a8bc1b6 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.
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..bedc1f5 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;
}
@@ -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",