diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fb41938..6848e4c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -114,6 +114,21 @@ jobs:
grep -q "Ready" /tmp/mcpb-smoke.log
working-directory: examples/pdf-server
+ dependency-isolation:
+ name: Dependency isolation
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: "22"
+ cache: npm
+
+ - run: npm ci
+
+ - run: npm run test:dependency-isolation
+
e2e:
runs-on: ubuntu-latest
steps:
@@ -132,8 +147,10 @@ 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 chromium
+ run: npx playwright install --with-deps --only-shell chromium
- name: Run E2E tests
run: npx playwright test --reporter=list
@@ -165,9 +182,14 @@ 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 6bfc8cd..219b947 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` 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/app.ts` - `App` subclasses `Protocol` from `@modelcontextprotocol/client`, handles View initialization, tool calls, and messaging
+- `src/app-bridge.ts` - `AppBridge` subclasses `Protocol` from `@modelcontextprotocol/client` for the iframe channel and proxies through a separate outer `Client`
- `src/server/index.ts` - Helpers for MCP servers to register tools/resources with UI metadata
- `src/types.ts` - Protocol types re-exported from `spec.types.ts` and Zod schemas from `generated/schema.ts` (auto-generated during build)
- `src/message-transport.ts` - `PostMessageTransport` for iframe communication
@@ -70,12 +70,12 @@ rm -fR package-lock.json node_modules && \
### Protocol Flow
```
-View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server
+View (App/Protocol) <--PostMessageTransport--> Host (AppBridge/Protocol) <--separate 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` request, receives host capabilities and context
+3. View sends `ui/initialize` (the only handshake on the iframe channel) and receives authoritative Apps capabilities and context
4. Host sends `sendToolInput()` with tool arguments after initialization
5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()`
6. Host sends `sendToolResult()` when tool execution completes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6592106..e28dcd1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -502,7 +502,7 @@ Alternatively, use the [workflow dispatch](https://github.com/modelcontextprotoc
## Code of Conduct
-This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing.
+This project follows our [Code of Conduct](https://github.com/modelcontextprotocol/.github/blob/main/CODE_OF_CONDUCT.md). Please review it before contributing.
## Reporting Issues
diff --git a/README.md b/README.md
index 7348e15..ab4004d 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
Why
·
- Quickstart
+ Quickstart
·
API Docs
·
@@ -89,6 +89,8 @@ it will guide you through the rest.
+
+
> [!NOTE]
@@ -120,12 +122,36 @@ 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
+npm install -S @modelcontextprotocol/ext-apps \
+ @modelcontextprotocol/client@^2.0.0 \
+ zod@^4.2.0
```
+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
@@ -139,7 +165,7 @@ UI metadata.
| `@modelcontextprotocol/ext-apps` | Build interactive Views (App class, PostMessageTransport) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app.html) |
| `@modelcontextprotocol/ext-apps/react` | React hooks for Views (useApp, useHostStyles, etc.) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/_modelcontextprotocol_ext-apps_react.html) |
| `@modelcontextprotocol/ext-apps/app-bridge` | Embed and communicate with Views in your chat client | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app-bridge.html) |
-| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server.html) |
+| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server-helpers.html) |
There's no _supported_ host implementation in this repo (beyond the
[examples/basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host)
@@ -246,7 +272,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 a129d5d..61361e6 100644
--- a/build.bun.ts
+++ b/build.bun.ts
@@ -1,6 +1,9 @@
#!/usr/bin/env bun
import { $ } from "bun";
-import { cpSync, mkdirSync } from "node:fs";
+import { cpSync, mkdirSync, rmSync } from "node:fs";
+
+// Avoid publishing artifacts left behind by an earlier build or branch.
+rmSync("dist", { recursive: true, force: true });
// Run TypeScript compiler for type declarations
await $`tsc`;
@@ -31,9 +34,15 @@ function buildJs(
});
}
-// 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"];
+// 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",
+];
await Promise.all([
buildJs("src/app.ts", {
diff --git a/docs/authorization.md b/docs/authorization.md
index 59fc26c..df7adc6 100644
--- a/docs/authorization.md
+++ b/docs/authorization.md
@@ -150,7 +150,7 @@ registerAppTool(
"get_account_balance",
{
description: "Get account balance",
- inputSchema: { accountId: z.string() },
+ inputSchema: z.object({ accountId: z.string() }),
},
async ({ accountId }) => {
if (!authInfo) {
diff --git a/docs/migrate-to-2.md b/docs/migrate-to-2.md
new file mode 100644
index 0000000..f9341d0
--- /dev/null
+++ b/docs/migrate-to-2.md
@@ -0,0 +1,130 @@
+---
+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 d91281b..22d5d9f 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/sdk/server/mcp.js";
+import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
@@ -78,7 +78,7 @@ function createServer() {
{
title: "Shopping Cart",
description: "Display the user's shopping cart",
- inputSchema: { userId: z.string() },
+ inputSchema: z.object({ userId: z.string() }),
annotations: { readOnlyHint: true },
_meta: {
"openai/outputTemplate": "ui://view/cart.html",
@@ -126,7 +126,7 @@ function createServer() {
#### After (MCP Apps)
```typescript
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import { McpServer } from "@modelcontextprotocol/server";
import {
registerAppTool,
registerAppResource,
@@ -144,7 +144,7 @@ function createServer() {
{
title: "Shopping Cart",
description: "Display the user's shopping cart",
- inputSchema: { userId: z.string() },
+ inputSchema: z.object({ userId: z.string() }),
annotations: { readOnlyHint: true },
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
},
diff --git a/docs/overview.md b/docs/overview.md
index 28f5b23..8eda95d 100644
--- a/docs/overview.md
+++ b/docs/overview.md
@@ -56,7 +56,11 @@ flowchart LR
- **Host** — The chat client (e.g., Claude Desktop) that connects to servers, embeds Views in iframes, and proxies communication between them.
- **View** — The UI running inside a sandboxed iframe. It receives tool data from the Host and can call server tools or send messages back to the chat.
-The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server.
+The View's `App` 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.
## Lifecycle
@@ -93,7 +97,7 @@ sequenceDiagram
```
1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
-2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). This handshake ensures the View is ready before receiving data.
+2. **Initialization** — When a UI tool is called, the Host renders the iframe. The 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.
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 87a6d82..884de26 100644
--- a/docs/patterns.md
+++ b/docs/patterns.md
@@ -19,7 +19,7 @@ registerAppTool(
"update-quantity",
{
description: "Update item quantity in cart",
- inputSchema: { itemId: z.string(), quantity: z.number() },
+ inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
_meta: {
ui: {
resourceUri: "ui://shop/cart.html",
@@ -127,14 +127,14 @@ registerAppTool(
{
title: "Read Data Bytes",
description: "Load binary data in chunks",
- inputSchema: {
+ inputSchema: z.object({
id: z.string().describe("Resource identifier"),
offset: z.number().min(0).default(0).describe("Byte offset"),
byteCount: z
.number()
.default(MAX_CHUNK_BYTES)
.describe("Bytes to read"),
- },
+ }),
outputSchema: DataChunkSchema,
// Hidden from model - only callable by the App
_meta: { ui: { visibility: ["app"] } },
@@ -281,10 +281,7 @@ server.registerResource(
```ts source="./patterns.tsx#binaryBlobResourceClient"
-const result = await app.request(
- { method: "resources/read", params: { uri: `video://${videoId}` } },
- ReadResourceResultSchema,
-);
+const result = await app.readServerResource({ uri: `video://${videoId}` });
const content = result.contents[0];
if (!content || !("blob" in content)) {
diff --git a/docs/patterns.tsx b/docs/patterns.tsx
index 6b87692..bb9eaa4 100644
--- a/docs/patterns.tsx
+++ b/docs/patterns.tsx
@@ -17,16 +17,12 @@ import { randomUUID } from "node:crypto";
import type {
CallToolResult,
ReadResourceResult,
-} from "@modelcontextprotocol/sdk/types.js";
-import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
+} from "@modelcontextprotocol/server";
import type { McpUiHostContext } from "../src/types.js";
import { useEffect, useState } from "react";
import { useApp } from "../src/react/index.js";
import { registerAppTool } from "../src/server/index.js";
-import {
- McpServer,
- ResourceTemplate,
-} from "@modelcontextprotocol/sdk/server/mcp.js";
+import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
import { z } from "zod";
/**
@@ -117,14 +113,14 @@ function chunkedDataServer(server: McpServer) {
{
title: "Read Data Bytes",
description: "Load binary data in chunks",
- inputSchema: {
+ inputSchema: z.object({
id: z.string().describe("Resource identifier"),
offset: z.number().min(0).default(0).describe("Byte offset"),
byteCount: z
.number()
.default(MAX_CHUNK_BYTES)
.describe("Bytes to read"),
- },
+ }),
outputSchema: DataChunkSchema,
// Hidden from model - only callable by the App
_meta: { ui: { visibility: ["app"] } },
@@ -252,10 +248,7 @@ function binaryBlobResourceServer(
*/
async function binaryBlobResourceClient(app: App, videoId: string) {
//#region binaryBlobResourceClient
- const result = await app.request(
- { method: "resources/read", params: { uri: `video://${videoId}` } },
- ReadResourceResultSchema,
- );
+ const result = await app.readServerResource({ uri: `video://${videoId}` });
const content = result.contents[0];
if (!content || !("blob" in content)) {
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 3e00796..a021912 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 18+.
+You'll also need Node.js 20+.
## 1. Set up the project
@@ -37,7 +37,7 @@ Install the dependencies you'll need:
```bash
npm init -y
-npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors
+npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@^2.0.0 @modelcontextprotocol/server@^2.0.0 @modelcontextprotocol/node@^2.0.0 @modelcontextprotocol/express@^2.0.0 zod@^4.2.0 express cors
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
```
@@ -59,6 +59,7 @@ 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",
@@ -87,6 +88,7 @@ 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",
@@ -178,9 +180,10 @@ import {
registerAppTool,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import { McpServer } from "@modelcontextprotocol/server";
import fs from "node:fs/promises";
import path from "node:path";
+import { z } from "zod";
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -205,7 +208,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time.",
- inputSchema: {},
+ inputSchema: z.object({}),
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
},
async () => {
@@ -240,10 +243,10 @@ export function createServer(): McpServer {
```ts source="../examples/quickstart/main.ts"
-import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
-import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
-import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+import { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -263,7 +266,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/docs/testing-mcp-apps.md b/docs/testing-mcp-apps.md
index df1396c..a8eb333 100644
--- a/docs/testing-mcp-apps.md
+++ b/docs/testing-mcp-apps.md
@@ -28,6 +28,10 @@ The [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/ex
cd examples/basic-host
```
+ The root install runs the package build, including type-checking the
+ documentation snippets against the 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 117beca..cefe9d5 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"scripts": {
"build": "tsc --noEmit && concurrently \"cross-env INPUT=index.html vite build\" \"cross-env INPUT=sandbox.html vite build\"",
@@ -11,11 +11,11 @@
"dev": "cross-env NODE_ENV=development concurrently \"npm run watch\" \"npm run serve\""
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@modelcontextprotocol/client": "2.0.0",
+ "@modelcontextprotocol/ext-apps": "^2.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/express": "^5.0.0",
@@ -29,7 +29,6 @@
"prettier": "^3.6.2",
"typescript": "^5.9.3",
"vite": "^6.0.0",
- "vite-plugin-singlefile": "^2.3.0",
- "vitest": "^3.2.4"
+ "vite-plugin-singlefile": "^2.3.0"
}
}
diff --git a/examples/basic-host/src/implementation.ts b/examples/basic-host/src/implementation.ts
index 31e3698..5f680ff 100644
--- a/examples/basic-host/src/implementation.ts
+++ b/examples/basic-host/src/implementation.ts
@@ -1,8 +1,12 @@
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 } 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 {
+ Client,
+ SSEClientTransport,
+ StreamableHTTPClientTransport,
+ type CallToolResult,
+ type Resource,
+ type Tool,
+} from "@modelcontextprotocol/client";
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 3d488a7..00c0d52 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/sdk/types.js";
+import type { Tool } from "@modelcontextprotocol/client";
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 9d7c3bc..3c143ca 100644
--- a/examples/basic-host/tsconfig.json
+++ b/examples/basic-host/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 7642632..ffdff52 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 { 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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/basic-server-preact/package.json b/examples/basic-server-preact/package.json
index 353d5f0..60aa40d 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Preact",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/basic-server-preact"
},
"license": "MIT",
@@ -24,12 +24,15 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
"preact": "^10.0.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@preact/preset-vite": "^2.0.0",
diff --git a/examples/basic-server-preact/server.ts b/examples/basic-server-preact/server.ts
index bf14ea5..dfec94a 100644
--- a/examples/basic-server-preact/server.ts
+++ b/examples/basic-server-preact/server.ts
@@ -1,8 +1,12 @@
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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")
@@ -28,7 +32,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
- inputSchema: {},
+ inputSchema: z.object({}),
_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 a8bdc68..351a8ec 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
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 32182d9..9bf9389 100644
--- a/examples/basic-server-preact/tsconfig.json
+++ b/examples/basic-server-preact/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/basic-server-preact/tsconfig.server.json
+++ b/examples/basic-server-preact/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 ec187b6..21c5e18 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/sdk/server/express.js";
-import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
-import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
+import { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/basic-server-react/package.json b/examples/basic-server-react/package.json
index a8bdfa8..ea51a8a 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using React",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/basic-server-react"
},
"license": "MIT",
@@ -34,13 +34,16 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts
index 23f6dda..d77c908 100644
--- a/examples/basic-server-react/server.ts
+++ b/examples/basic-server-react/server.ts
@@ -1,8 +1,12 @@
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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")
@@ -29,7 +33,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
- inputSchema: {},
+ inputSchema: z.object({}),
_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 6bb5540..2b5fbef 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
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 fc3c210..3ecbdad 100644
--- a/examples/basic-server-react/tsconfig.json
+++ b/examples/basic-server-react/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/basic-server-react/tsconfig.server.json
+++ b/examples/basic-server-react/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 c8d9de2..095ef8d 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/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 { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
+import { createMcpExpressApp } from "@modelcontextprotocol/express";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
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 StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/basic-server-solid/package.json b/examples/basic-server-solid/package.json
index 1925ae1..b3ca909 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Solid",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/basic-server-solid"
},
"license": "MIT",
@@ -24,12 +24,15 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
"solid-js": "1.9.10",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/basic-server-solid/server.ts b/examples/basic-server-solid/server.ts
index 2ed2d93..addb522 100644
--- a/examples/basic-server-solid/server.ts
+++ b/examples/basic-server-solid/server.ts
@@ -1,8 +1,12 @@
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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")
@@ -28,7 +32,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
- inputSchema: {},
+ inputSchema: z.object({}),
_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 66d47f3..2d78b7b 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
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 84f0efe..bb13a01 100644
--- a/examples/basic-server-solid/tsconfig.json
+++ b/examples/basic-server-solid/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/basic-server-solid/tsconfig.server.json
+++ b/examples/basic-server-solid/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 6c50a25..734ec0c 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 { 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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/basic-server-svelte/package.json b/examples/basic-server-svelte/package.json
index c7fef6b..19fe4c9 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Svelte",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/basic-server-svelte"
},
"license": "MIT",
@@ -24,12 +24,15 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
"svelte": "^5.0.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.0",
diff --git a/examples/basic-server-svelte/server.ts b/examples/basic-server-svelte/server.ts
index da603ca..9414285 100644
--- a/examples/basic-server-svelte/server.ts
+++ b/examples/basic-server-svelte/server.ts
@@ -1,8 +1,12 @@
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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")
@@ -28,7 +32,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
- inputSchema: {},
+ inputSchema: z.object({}),
_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 fde128d..cabcc31 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
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 5edb66a..4fd3cd7 100644
--- a/examples/basic-server-svelte/tsconfig.json
+++ b/examples/basic-server-svelte/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/basic-server-svelte/tsconfig.server.json
+++ b/examples/basic-server-svelte/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 286fa34..4de7331 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/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 { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
+import { createMcpExpressApp } from "@modelcontextprotocol/express";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
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 StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/basic-server-vanillajs/package.json b/examples/basic-server-vanillajs/package.json
index 72b6ed2..ebfebbb 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using vanilla JavaScript",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/basic-server-vanillajs"
},
"license": "MIT",
@@ -24,11 +24,14 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts
index 5b2daf7..693d861 100644
--- a/examples/basic-server-vanillajs/server.ts
+++ b/examples/basic-server-vanillajs/server.ts
@@ -1,6 +1,9 @@
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
@@ -29,7 +32,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
- inputSchema: {},
+ inputSchema: z.object({}),
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 c3168c2..6b5084a 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
import "./global.css";
import "./mcp-app.css";
diff --git a/examples/basic-server-vanillajs/tsconfig.json b/examples/basic-server-vanillajs/tsconfig.json
index 535267b..b028ff5 100644
--- a/examples/basic-server-vanillajs/tsconfig.json
+++ b/examples/basic-server-vanillajs/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/basic-server-vanillajs/tsconfig.server.json
+++ b/examples/basic-server-vanillajs/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 669a071..d774269 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 { 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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/basic-server-vue/package.json b/examples/basic-server-vue/package.json
index cb924b4..858dd89 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Basic MCP App Server example using Vue",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/basic-server-vue"
},
"license": "MIT",
@@ -24,12 +24,15 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
"vue": "^3.5.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/basic-server-vue/server.ts b/examples/basic-server-vue/server.ts
index 5aa72de..9adb911 100644
--- a/examples/basic-server-vue/server.ts
+++ b/examples/basic-server-vue/server.ts
@@ -1,8 +1,12 @@
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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")
@@ -28,7 +32,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time as an ISO 8601 string.",
- inputSchema: {},
+ inputSchema: z.object({}),
_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 196ad64..a45c262 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
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 5edb66a..4fd3cd7 100644
--- a/examples/basic-server-vue/tsconfig.json
+++ b/examples/basic-server-vue/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/basic-server-vue/tsconfig.server.json
+++ b/examples/basic-server-vue/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 cc488f0..02c03cb 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 { 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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/budget-allocator-server/package.json b/examples/budget-allocator-server/package.json
index ded5814..6e41622 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Budget allocator MCP App Server with interactive visualization",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/budget-allocator-server"
},
"license": "MIT",
@@ -26,12 +26,14 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@modelcontextprotocol/express": "2.0.0",
+ "@modelcontextprotocol/ext-apps": "^2.0.0",
+ "@modelcontextprotocol/node": "2.0.0",
+ "@modelcontextprotocol/server": "2.0.0",
"chart.js": "^4.4.0",
"cors": "^2.8.5",
"express": "^5.1.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/budget-allocator-server/server.ts b/examples/budget-allocator-server/server.ts
index 04fba11..01f5dbf 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 } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type {
- CallToolResult,
- ReadResourceResult,
-} from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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: {},
+ inputSchema: z.object({}),
outputSchema: BudgetDataResponseSchema,
_meta: { ui: { resourceUri } },
},
diff --git a/examples/budget-allocator-server/tsconfig.json b/examples/budget-allocator-server/tsconfig.json
index 535267b..b028ff5 100644
--- a/examples/budget-allocator-server/tsconfig.json
+++ b/examples/budget-allocator-server/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/budget-allocator-server/tsconfig.server.json
+++ b/examples/budget-allocator-server/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 d59903b..8b33603 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 { 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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/cohort-heatmap-server/package.json b/examples/cohort-heatmap-server/package.json
index 9f82c64..7e2d2e2 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Cohort heatmap MCP App Server for retention analysis",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/cohort-heatmap-server"
},
"license": "MIT",
@@ -26,13 +26,15 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@modelcontextprotocol/express": "2.0.0",
+ "@modelcontextprotocol/ext-apps": "^2.0.0",
+ "@modelcontextprotocol/node": "2.0.0",
+ "@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/cohort-heatmap-server/server.ts b/examples/cohort-heatmap-server/server.ts
index 6d07144..129e2dc 100644
--- a/examples/cohort-heatmap-server/server.ts
+++ b/examples/cohort-heatmap-server/server.ts
@@ -1,5 +1,7 @@
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
@@ -178,8 +180,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.shape,
- outputSchema: CohortDataSchema.shape,
+ inputSchema: GetCohortDataInputSchema,
+ outputSchema: CohortDataSchema,
_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 fc3c210..3ecbdad 100644
--- a/examples/cohort-heatmap-server/tsconfig.json
+++ b/examples/cohort-heatmap-server/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/cohort-heatmap-server/tsconfig.server.json
+++ b/examples/cohort-heatmap-server/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"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 130076c..c788757 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 { 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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/customer-segmentation-server/package.json b/examples/customer-segmentation-server/package.json
index aa52b35..bac3b89 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Customer segmentation MCP App Server with filtering",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/customer-segmentation-server"
},
"license": "MIT",
@@ -26,12 +26,14 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@modelcontextprotocol/express": "2.0.0",
+ "@modelcontextprotocol/ext-apps": "^2.0.0",
+ "@modelcontextprotocol/node": "2.0.0",
+ "@modelcontextprotocol/server": "2.0.0",
"chart.js": "^4.4.0",
"cors": "^2.8.5",
"express": "^5.1.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/customer-segmentation-server/server.ts b/examples/customer-segmentation-server/server.ts
index 244415d..7cc1c1f 100644
--- a/examples/customer-segmentation-server/server.ts
+++ b/examples/customer-segmentation-server/server.ts
@@ -1,8 +1,8 @@
-import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type {
- CallToolResult,
- ReadResourceResult,
-} from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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.shape,
- outputSchema: GetCustomerDataOutputSchema.shape,
+ inputSchema: GetCustomerDataInputSchema,
+ outputSchema: GetCustomerDataOutputSchema,
_meta: { ui: { resourceUri } },
},
async ({ segment }): Promise => {
diff --git a/examples/customer-segmentation-server/tsconfig.json b/examples/customer-segmentation-server/tsconfig.json
index 535267b..b028ff5 100644
--- a/examples/customer-segmentation-server/tsconfig.json
+++ b/examples/customer-segmentation-server/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/customer-segmentation-server/tsconfig.server.json
+++ b/examples/customer-segmentation-server/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "types": ["node"],
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
diff --git a/examples/debug-server/main.ts b/examples/debug-server/main.ts
index 3c89a97..9fb0825 100644
--- a/examples/debug-server/main.ts
+++ b/examples/debug-server/main.ts
@@ -4,10 +4,10 @@
* Or: node dist/index.js [--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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/debug-server/package.json b/examples/debug-server/package.json
index 1c505e2..e28f4d3 100644
--- a/examples/debug-server/package.json
+++ b/examples/debug-server/package.json
@@ -1,11 +1,11 @@
{
"name": "@modelcontextprotocol/server-debug",
- "version": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "Debug MCP App Server for testing all SDK capabilities",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/debug-server"
},
"license": "MIT",
@@ -34,9 +34,11 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
- "zod": "^4.1.13"
+ "@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"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/debug-server/server.ts b/examples/debug-server/server.ts
index fd3452a..068f94d 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 } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type {
- CallToolResult,
- ReadResourceResult,
-} from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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 535267b..b028ff5 100644
--- a/examples/debug-server/tsconfig.json
+++ b/examples/debug-server/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/debug-server/tsconfig.server.json
+++ b/examples/debug-server/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "types": ["node"],
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
diff --git a/examples/integration-server/main.ts b/examples/integration-server/main.ts
index c45787e..9a42842 100644
--- a/examples/integration-server/main.ts
+++ b/examples/integration-server/main.ts
@@ -4,10 +4,10 @@
* Or: node dist/index.js [--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 { createMcpExpressApp } from "@modelcontextprotocol/express";
+import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import type { McpServer } from "@modelcontextprotocol/server";
+import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
- const transport = new StreamableHTTPServerTransport({
+ const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
diff --git a/examples/integration-server/package.json b/examples/integration-server/package.json
index 2104382..7e04f4a 100644
--- a/examples/integration-server/package.json
+++ b/examples/integration-server/package.json
@@ -1,6 +1,6 @@
{
"name": "integration-server",
- "version": "1.7.3",
+ "version": "2.0.0",
"private": true,
"type": "module",
"scripts": {
@@ -15,13 +15,16 @@
"serve": "bun --watch main.ts"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@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",
"cors": "^2.8.5",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "zod": "^4.1.13"
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/integration-server/server.ts b/examples/integration-server/server.ts
index e44689d..346f4f7 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 } from "@modelcontextprotocol/sdk/server/mcp.js";
-import type {
- CallToolResult,
- ReadResourceResult,
-} from "@modelcontextprotocol/sdk/types.js";
+import {
+ McpServer,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
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: {},
+ inputSchema: z.object({}),
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.resource(
+ server.registerResource(
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 7f0b18c..281a356 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/sdk/types.js";
+import type { CallToolResult } from "@modelcontextprotocol/client";
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 fc3c210..3ecbdad 100644
--- a/examples/integration-server/tsconfig.json
+++ b/examples/integration-server/tsconfig.json
@@ -1,5 +1,6 @@
{
"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 05ddd8e..27eb28f 100644
--- a/examples/integration-server/tsconfig.server.json
+++ b/examples/integration-server/tsconfig.server.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "types": ["node"],
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
diff --git a/examples/lazy-auth-server/README.md b/examples/lazy-auth-server/README.md
index 769ac18..f2e7545 100644
--- a/examples/lazy-auth-server/README.md
+++ b/examples/lazy-auth-server/README.md
@@ -51,6 +51,33 @@ https:///ttl/3600/mcp ← tokens for this connection live 1 hour
This works through [RFC 8707 resource indicators](https://www.rfc-editor.org/rfc/rfc8707): MCP hosts send the MCP server URL as the `resource` parameter in OAuth authorization and token requests, and this server issues tokens for that grant with the lifetime encoded in the path (refresh tokens are extended to at least match). The TTL is a _path_ segment rather than a query param because hosts canonicalize resource indicators and strip query strings. Each TTL endpoint also enforces its value as a maximum token age, so connecting to a path with a _lower_ TTL than a token's issued lifetime forces the refresh flow. To exercise the full **re-auth** flow, call the `revoke_auth_token` tool.
+## Mounting under a base path
+
+`createApp()` can also be mounted inside another Express app, so an existing server can host this example at a sub-path of its own origin:
+
+```ts
+import { createApp } from "@modelcontextprotocol/server-lazy-auth";
+
+hostApp.use("/lazy-auth", createApp());
+// → MCP endpoint at https:///lazy-auth/mcp
+```
+
+All advertised URLs (OAuth metadata, `WWW-Authenticate` `resource_metadata`, PRM `resource`, elicitation callbacks) include the mount path automatically, derived from Express's `req.baseUrl`. When `PUBLIC_URL` is set, it must include the mount path (e.g. `https://example.com/lazy-auth`).
+
+One thing the mounted app cannot do for itself: [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414#section-3) / [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) put well-known discovery documents at the _root_ of the origin with the path inserted after the well-known prefix (`/.well-known/oauth-authorization-server/lazy-auth`), and MCP SDK clients only try that insertion form. The host app must rewrite those root paths into the mount before its other routes:
+
+```ts
+hostApp.use((req, _res, next) => {
+ const m = req.url.match(
+ /^\/\.well-known\/(oauth-authorization-server|oauth-protected-resource)\/lazy-auth(\/.*)?$/,
+ );
+ if (m) req.url = `/lazy-auth/.well-known/${m[1]}${m[2] ?? ""}`;
+ next();
+});
+```
+
+Rewriting into the mount (rather than calling the sub-app directly) keeps `req.baseUrl` — and therefore every advertised URL — consistent.
+
## How It Works
1. **Connect without auth** — `initialize`, `tools/list`, and public tool calls succeed with no `Authorization` header.
diff --git a/examples/lazy-auth-server/package.json b/examples/lazy-auth-server/package.json
index 8b7cb3f..dbd4601 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": "1.7.3",
+ "version": "2.0.0",
"type": "module",
"description": "MCP App example demonstrating lazy (on-demand) OAuth: public tools work unauthenticated, protected tools return 401 + WWW-Authenticate so the host runs the OAuth flow only when needed",
"repository": {
"type": "git",
- "url": "https://github.com/modelcontextprotocol/ext-apps",
+ "url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
"directory": "examples/lazy-auth-server"
},
"license": "MIT",
@@ -22,11 +22,13 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@modelcontextprotocol/ext-apps": "^1.7.0",
- "@modelcontextprotocol/sdk": "^1.29.0",
+ "@modelcontextprotocol/ext-apps": "^2.0.0",
+ "@modelcontextprotocol/node": "2.0.0",
+ "@modelcontextprotocol/server": "2.0.0",
"cors": "^2.8.5",
"express": "^5.1.0",
- "jose": "^6.0.0"
+ "jose": "^6.0.0",
+ "zod": "^4.2.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
diff --git a/examples/lazy-auth-server/server.ts b/examples/lazy-auth-server/server.ts
index fa46d67..5240008 100644
--- a/examples/lazy-auth-server/server.ts
+++ b/examples/lazy-auth-server/server.ts
@@ -21,24 +21,30 @@ import {
registerAppTool,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/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 { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
+import {
+ McpServer,
+ UrlElicitationRequiredError,
+ type CallToolResult,
+ type ReadResourceResult,
+} from "@modelcontextprotocol/server";
import cors from "cors";
import express, { type Express, type Request, type Response } from "express";
import { SignJWT, jwtVerify } from "jose";
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)
-const DIST_DIR = import.meta.filename.endsWith(".ts")
- ? path.join(import.meta.dirname, "dist")
- : import.meta.dirname;
+// Works both from source (server.ts) and compiled (dist/server.js). Derived
+// from import.meta.url rather than import.meta.filename/dirname, which are
+// undefined in some module-VM contexts (e.g. importing this package from
+// jest).
+const SERVER_FILE = fileURLToPath(import.meta.url);
+const DIST_DIR = SERVER_FILE.endsWith(".ts")
+ ? path.join(path.dirname(SERVER_FILE), "dist")
+ : path.dirname(SERVER_FILE);
// ─── Config ──────────────────────────────────────────────────────────────────
@@ -88,25 +94,35 @@ function isLoopbackHostname(hostname: string): boolean {
* explicitly.
*/
function resolvePublicUrl(req?: Request): URL {
+ // Mount path when this app is mounted inside another Express app
+ // (e.g. app.use("/lazy-auth", createApp())). Empty when standalone.
+ // PUBLIC_URL, when set, must already include any mount path.
+ const basePath = req?.baseUrl ?? "";
const envUrl = process.env.PUBLIC_URL;
if (envUrl) return new URL(envUrl.endsWith("/") ? envUrl : envUrl + "/");
const host = req?.headers.host;
if (host) {
try {
- const url = new URL(`http://${host}/`);
+ const url = new URL(`http://${host}${basePath}/`);
if (isLoopbackHostname(url.hostname)) return url;
} catch {
// Malformed Host header → fall through to the localhost default.
}
}
- return new URL(`http://localhost:${PORT}/`);
+ return new URL(`http://localhost:${PORT}${basePath}/`);
+}
+
+/** Public base URL as a string with no trailing slash (may include a base path). */
+function publicBaseHref(req?: Request): string {
+ const href = resolvePublicUrl(req).href;
+ return href.endsWith("/") ? href.slice(0, -1) : href;
}
/** OAuth issuer. In REACTIVE_AUTH_ONLY mode, uses a /auth subpath so root well-known 404s.
- * Otherwise uses the root origin (standard). */
+ * Otherwise uses the public base URL (origin + any mount path). */
const ISSUER_SUFFIX = REACTIVE_AUTH_ONLY ? "/auth" : "";
function resolveIssuer(req?: Request): string {
- return resolvePublicUrl(req).origin + ISSUER_SUFFIX;
+ return publicBaseHref(req) + ISSUER_SUFFIX;
}
// ─── Mock OAuth (HS256, stateless codes) ─────────────────────────────────────
@@ -273,18 +289,28 @@ async function signAuthCode(
return new SignJWT({ ...payload, typ: "code" })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
+ .setJti(crypto.randomUUID())
.setIssuer(issuer)
.setExpirationTime("5m")
.sign(JWT_SECRET);
}
+// Authorization codes are single-use (RFC 6749 §4.1.2): remember redeemed code
+// IDs until the code's own 5-minute expiry makes replay impossible anyway.
+const redeemedCodeJtis = new Map(); // jti → unix GC time
+
async function verifyAuthCode(
code: string,
issuer: string,
): Promise {
try {
const { payload } = await jwtVerify(code, JWT_SECRET, { issuer });
- if (payload.typ !== "code") return undefined;
+ if (payload.typ !== "code" || !payload.jti) return undefined;
+ const now = Math.floor(Date.now() / 1000);
+ for (const [k, gc] of redeemedCodeJtis)
+ if (gc < now) redeemedCodeJtis.delete(k);
+ if (redeemedCodeJtis.has(payload.jti)) return undefined; // already redeemed
+ redeemedCodeJtis.set(payload.jti, now + 5 * 60 + 10);
return payload as unknown as CodePayload;
} catch {
return undefined;
@@ -366,11 +392,21 @@ async function handleAuthorize(req: Request, res: Response) {
});
return;
}
+ // PKCE is mandatory (the MCP auth spec requires it of clients, and this AS
+ // only advertises S256). Rejecting up front keeps stolen-code attacks out of
+ // the demo even though it has no real data to protect.
+ if (!code_challenge || code_challenge_method !== "S256") {
+ res.status(400).json({
+ error: "invalid_request",
+ error_description: "PKCE with S256 code_challenge is required",
+ });
+ return;
+ }
const issuer = resolveIssuer(req);
if (approved !== "1") {
// Show consent page. Keeps the OAuth popup visible so users can see the flow.
- const approveUrl = new URL(resolvePublicUrl(req).origin + "/authorize");
+ const approveUrl = new URL(publicBaseHref(req) + "/authorize");
for (const [k, v] of Object.entries(req.query))
if (v) approveUrl.searchParams.set(k, String(v));
approveUrl.searchParams.set("approved", "1");
@@ -379,20 +415,36 @@ async function handleAuthorize(req: Request, res: Response) {
if (state) denyUrl.searchParams.set("state", state);
res.type("text/html").send(/*html*/ `
Authorize
+
+.actions{display:flex;flex-wrap:wrap;gap:12px;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
+.btn{box-sizing:border-box;flex:1;min-height:56px;min-width:56px;display:flex;align-items:center;justify-content:center;
+padding:12px 20px;font-size:16px;font-weight:bold;border:none;border-radius:8px;cursor:pointer;
+text-decoration:none;color:#fff;touch-action:manipulation;-webkit-tap-highlight-color:transparent}
+.btn:active{filter:brightness(0.8)}
+.approve{background:#1a7a3e}.deny{background:#b91c1c}
🔑 Mock Authorization
An application is requesting access:
- Client
- ${escapeHtml(client_id ?? "(none)")}
- Scope
- ${escapeHtml(scope ?? "(default)")}
- Redirect
- ${escapeHtml(redirect_uri)}
-
-
-