Compare commits
190 changed files with 2084 additions and 5440 deletions
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
|
@ -114,21 +114,6 @@ jobs:
|
|||
grep -q "Ready" /tmp/mcpb-smoke.log
|
||||
working-directory: examples/pdf-server
|
||||
|
||||
dependency-isolation:
|
||||
name: Dependency isolation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
|
||||
- run: npm ci
|
||||
|
||||
- run: npm run test:dependency-isolation
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
@ -147,10 +132,8 @@ jobs:
|
|||
|
||||
- run: npm ci
|
||||
|
||||
# Headless runs without a channel use chromium-headless-shell, so the
|
||||
# full Chromium build (about 165 MB) would only be downloaded, never run.
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps --only-shell chromium
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npx playwright test --reporter=list
|
||||
|
|
@ -182,14 +165,9 @@ jobs:
|
|||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# The checkout lives on the Windows drive, which WSL reaches over 9P;
|
||||
# writing node_modules there is what makes this job slow (npm ci alone
|
||||
# took about 3 minutes). Copy the tree onto the distro's ext4 filesystem
|
||||
# first, as Microsoft's WSL guidance recommends for anything I/O heavy.
|
||||
- name: Build and test in WSL
|
||||
shell: wsl-bash {0}
|
||||
run: |
|
||||
cp -r "$(pwd)" ~/ext-apps && cd ~/ext-apps
|
||||
npm ci
|
||||
npm run build
|
||||
npm run examples:build
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ rm -fR package-lock.json node_modules && \
|
|||
|
||||
### Key Source Files
|
||||
|
||||
- `src/app.ts` - `App` subclasses `Protocol` from `@modelcontextprotocol/client`, handles View initialization, tool calls, and messaging
|
||||
- `src/app-bridge.ts` - `AppBridge` subclasses `Protocol` from `@modelcontextprotocol/client` for the iframe channel and proxies through a separate outer `Client`
|
||||
- `src/app.ts` - `App` class extends MCP Protocol, handles guest initialization, tool calls, messaging
|
||||
- `src/app-bridge.ts` - `AppBridge` class for hosts, proxies MCP requests, sends tool input/results to guests
|
||||
- `src/server/index.ts` - Helpers for MCP servers to register tools/resources with UI metadata
|
||||
- `src/types.ts` - Protocol types re-exported from `spec.types.ts` and Zod schemas from `generated/schema.ts` (auto-generated during build)
|
||||
- `src/message-transport.ts` - `PostMessageTransport` for iframe communication
|
||||
|
|
@ -70,12 +70,12 @@ rm -fR package-lock.json node_modules && \
|
|||
### Protocol Flow
|
||||
|
||||
```
|
||||
View (App/Protocol) <--PostMessageTransport--> Host (AppBridge/Protocol) <--separate MCP Client--> MCP Server
|
||||
View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server
|
||||
```
|
||||
|
||||
1. Host creates iframe with view HTML
|
||||
2. View creates `App` instance and calls `connect()` with `PostMessageTransport`
|
||||
3. View sends `ui/initialize` (the only handshake on the iframe channel) and receives authoritative Apps capabilities and context
|
||||
3. View sends `ui/initialize` request, receives host capabilities and context
|
||||
4. Host sends `sendToolInput()` with tool arguments after initialization
|
||||
5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()`
|
||||
6. Host sends `sendToolResult()` when tool execution completes
|
||||
|
|
|
|||
|
|
@ -502,7 +502,7 @@ Alternatively, use the [workflow dispatch](https://github.com/modelcontextprotoc
|
|||
|
||||
## Code of Conduct
|
||||
|
||||
This project follows our [Code of Conduct](https://github.com/modelcontextprotocol/.github/blob/main/CODE_OF_CONDUCT.md). Please review it before contributing.
|
||||
This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
|
|
|
|||
36
README.md
36
README.md
|
|
@ -11,7 +11,7 @@
|
|||
<br /><br />
|
||||
<a href="#why-mcp-apps">Why</a>
|
||||
·
|
||||
<a href="https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html">Quickstart</a>
|
||||
<a href="https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html">Quickstart</a>
|
||||
·
|
||||
<a href="https://apps.extensions.modelcontextprotocol.io/api/">API Docs</a>
|
||||
·
|
||||
|
|
@ -89,8 +89,6 @@ it will guide you through the rest.
|
|||
<a href="https://block.github.io/goose/docs/tutorials/building-mcp-apps/"><img src="https://img.shields.io/badge/Goose-docs-000000?logo=goose&logoColor=white" alt="Goose"></a>
|
||||
<a href="https://learning.postman.com/docs/postman-ai/mcp-requests/interact"><img src="https://img.shields.io/badge/Postman-docs-FF6C37?logo=postman&logoColor=white" alt="Postman"></a>
|
||||
<a href="https://www.mcpjam.com/blog/mcp-apps-example"><img src="https://img.shields.io/badge/MCPJam-docs-8B5CF6" alt="MCPJam"></a>
|
||||
<a href="https://manufact.com/inspector"><img src="https://img.shields.io/badge/mcp--use-inspector-E5F3FD" alt="mcp-use"></a>
|
||||
<a href="https://docs.alpic.ai/distribution/playground"><img src="https://img.shields.io/badge/Alpic-playground-E90060?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAKlBMVEUKDhz%2F%2F%2F%2BjpaogJDFBRE%2Fw8PHHyMvh4eNmaHF1d4CUlpuFho2ztLhWWWP5YkbJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1klEQVR42u2Y0ZKDIAxFBQQF8f9%2FdyW7dhAu7U7Iy87mzvShGfXU3CTGLotKpVKpVCqV6h%2FI2VuxfF3tUwcdVEccA2LNS%2BWCq2mUykG%2BCtg5iNmv76mllOApCPHrFQgNxJf8HHIQs10XdFtDKcEKPQ0x4brg6htKWGr0PIR8jq0tj4oQgJiIzD8rtATErMAWMj%2FxIYcBPjuPgoENcW3Jks%2BdLeXS32jLmise%2BZxQ8GRD%2BkkCbSlByi0Pgm3p0P6nJ1meXJ%2F8K1tKo7qUOFM4wdxEMMS4iSp9sqFJQrZ4hOZBTEa5gbaQ%2BTwI%2FcCu8TOq482xIWZkSx8MfAi0xcOEJTZkULKohXjm29epGU2SIGK%2BfT9J%2Bjre%2BRBYsiUYBSHkaEI2B0EInRx%2BkbApiB89EaMgBNdx7u5vDoJH79ne3yQElqxvEzYLKY%2Bl81PCZiFUsvlDwqYhw80uSkJg4z8Ttsss3OltwgQgMGGPlpSAwITVM0wCgius6iARCGzJcn%2BHJCSMXraCIATPsPW2SggCh364a1sIgveUew%2BTgtBGsuE9zDspyLBZ3M5aVCOCUDFlqXUbrm8j7%2FnvJ2B9G3kf%2BZBlt0iu%2Fr%2BNxHqRU6lUKpVKpVL9UX0BOrAPfcbTY8UAAAAASUVORK5CYII%3D" alt="Alpic Playground"></a>
|
||||
</p>
|
||||
|
||||
> [!NOTE]
|
||||
|
|
@ -122,36 +120,12 @@ resources:
|
|||
|
||||
## Getting Started
|
||||
|
||||
Requires Node.js 20+. The base MCP SDK packages are `^2.0.0` peers of
|
||||
`ext-apps` (`@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it for you).
|
||||
|
||||
For a View or host:
|
||||
|
||||
```bash
|
||||
npm install -S @modelcontextprotocol/ext-apps \
|
||||
@modelcontextprotocol/client@^2.0.0 \
|
||||
zod@^4.2.0
|
||||
npm install -S @modelcontextprotocol/ext-apps
|
||||
```
|
||||
|
||||
For an MCP server, add the server package (and, for HTTP transports, the Node
|
||||
and Express adapters):
|
||||
|
||||
```bash
|
||||
npm install -S @modelcontextprotocol/ext-apps \
|
||||
@modelcontextprotocol/client@^2.0.0 \
|
||||
@modelcontextprotocol/server@^2.0.0 \
|
||||
@modelcontextprotocol/node@^2.0.0 \
|
||||
@modelcontextprotocol/express@^2.0.0 \
|
||||
zod@^4.2.0
|
||||
```
|
||||
|
||||
The wire protocol is unchanged between `ext-apps` 1.x and 2.x: a 2.x View works
|
||||
in a 1.x host and a 2.x host renders 1.x Views. See the
|
||||
[migration guide](https://apps.extensions.modelcontextprotocol.io/api/documents/migrate-to-v2.html)
|
||||
when upgrading from 1.x.
|
||||
|
||||
**New here?** Start with the
|
||||
[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html)
|
||||
[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html)
|
||||
to build your first MCP App.
|
||||
|
||||
## Using the SDK
|
||||
|
|
@ -165,7 +139,7 @@ UI metadata.
|
|||
| `@modelcontextprotocol/ext-apps` | Build interactive Views (App class, PostMessageTransport) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app.html) |
|
||||
| `@modelcontextprotocol/ext-apps/react` | React hooks for Views (useApp, useHostStyles, etc.) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/_modelcontextprotocol_ext-apps_react.html) |
|
||||
| `@modelcontextprotocol/ext-apps/app-bridge` | Embed and communicate with Views in your chat client | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app-bridge.html) |
|
||||
| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server-helpers.html) |
|
||||
| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server.html) |
|
||||
|
||||
There's no _supported_ host implementation in this repo (beyond the
|
||||
[examples/basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host)
|
||||
|
|
@ -272,7 +246,7 @@ point your client at a local build:
|
|||
|
||||
## Resources
|
||||
|
||||
- [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html)
|
||||
- [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html)
|
||||
- [API Documentation](https://apps.extensions.modelcontextprotocol.io/api/)
|
||||
- [Specification (2026-01-26)](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx)
|
||||
([Draft](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx))
|
||||
|
|
|
|||
17
build.bun.ts
17
build.bun.ts
|
|
@ -1,9 +1,6 @@
|
|||
#!/usr/bin/env bun
|
||||
import { $ } from "bun";
|
||||
import { cpSync, mkdirSync, rmSync } from "node:fs";
|
||||
|
||||
// Avoid publishing artifacts left behind by an earlier build or branch.
|
||||
rmSync("dist", { recursive: true, force: true });
|
||||
import { cpSync, mkdirSync } from "node:fs";
|
||||
|
||||
// Run TypeScript compiler for type declarations
|
||||
await $`tsc`;
|
||||
|
|
@ -34,15 +31,9 @@ function buildJs(
|
|||
});
|
||||
}
|
||||
|
||||
// Peer dependencies stay external in the standard entry points so consumers
|
||||
// share one base MCP SDK and Zod instance. The *-with-deps entry points keep
|
||||
// bundling these dependencies for standalone browser use.
|
||||
const PEER_EXTERNALS = [
|
||||
"@modelcontextprotocol/client",
|
||||
"@modelcontextprotocol/core",
|
||||
"@modelcontextprotocol/server",
|
||||
"zod",
|
||||
];
|
||||
// zod is a peerDependency — keep it external so consumers share a single
|
||||
// zod instance (instanceof ZodError / schema.extend() break with duplicate copies).
|
||||
const PEER_EXTERNALS = ["@modelcontextprotocol/sdk", "zod"];
|
||||
|
||||
await Promise.all([
|
||||
buildJs("src/app.ts", {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ registerAppTool(
|
|||
"get_account_balance",
|
||||
{
|
||||
description: "Get account balance",
|
||||
inputSchema: z.object({ accountId: z.string() }),
|
||||
inputSchema: { accountId: z.string() },
|
||||
},
|
||||
async ({ accountId }) => {
|
||||
if (!authInfo) {
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
---
|
||||
title: Migrate to v2
|
||||
group: Getting Started
|
||||
description: Upgrade from ext-apps 1.x to 2.x — new base MCP SDK peer packages, API changes for Views and hosts, and what stays wire-compatible.
|
||||
---
|
||||
|
||||
# Migrating from ext-apps 1.x to 2.x
|
||||
|
||||
ext-apps 2.x is built on the base MCP TypeScript SDK 2.x, which replaced the
|
||||
single `@modelcontextprotocol/sdk` package with `@modelcontextprotocol/client`,
|
||||
`server`, `core`, `node` and `express`. The MCP Apps wire protocol did not
|
||||
change; the breaking changes are in dependencies and in the TypeScript API.
|
||||
|
||||
## Host compatibility
|
||||
|
||||
The `ui/*` messages exchanged over the iframe channel are byte-identical to
|
||||
1.x. A 2.x View runs in a 1.x host and a 2.x host renders 1.x Views. The only
|
||||
host-side deltas are in error responses (see below).
|
||||
|
||||
## What stays the same
|
||||
|
||||
`App` / `AppBridge` constructors, the `on*` setters, `addEventListener`,
|
||||
`callServerTool`, `readServerResource`, `registerAppResource`, the React hooks
|
||||
and the `_meta.ui.resourceUri` handling are unchanged. Published `.d.ts` files
|
||||
now resolve under `moduleResolution: NodeNext` / `Node16` without workarounds
|
||||
(#705).
|
||||
|
||||
## Peer dependencies by role
|
||||
|
||||
| Role | Install |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| View author | `@modelcontextprotocol/ext-apps`, `@modelcontextprotocol/client@^2.0.0`, `zod@^4.2.0` (+ `react`/`react-dom` for `./react`) |
|
||||
| Host author | same as View author |
|
||||
| MCP server author | View author packages + `@modelcontextprotocol/server@^2.0.0`; for HTTP transports also `@modelcontextprotocol/node@^2.0.0` (`NodeStreamableHTTPServerTransport`) and `@modelcontextprotocol/express@^2.0.0` (`createMcpExpressApp`, which peers on `express`) |
|
||||
| CDN / `*-with-deps` | nothing extra: `./app-with-deps` and `./react-with-deps` bundle client, core and zod (about 25% larger than the 1.x bundles) |
|
||||
|
||||
`@modelcontextprotocol/client` is a required peer (`App` and `AppBridge` extend
|
||||
its `Protocol` class); `@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it without you listing it;
|
||||
`@modelcontextprotocol/server` stays optional and is only needed for the
|
||||
`./server` helpers. Node.js 20+ is required.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
- **Peer packages.** `@modelcontextprotocol/sdk@^1` is replaced by the split
|
||||
2.x packages above, all at `^2.0.0`. Remove the 1.x package from your
|
||||
project: the two SDKs share no classes or types (a 1.x `McpError` or
|
||||
`Client` cannot be passed to 2.x code), even though they speak the same
|
||||
wire protocol.
|
||||
- **zod 3 dropped.** The peer range is `zod@^4.2.0`. Tool schemas must
|
||||
implement Standard JSON Schema (`~standard.jsonSchema`): zod 4.2+, ArkType,
|
||||
Valibot. zod 4.0 and 4.1 do not expose `~standard.jsonSchema` and are
|
||||
rejected too. Raw zod shapes (`{ q: z.string() }`) still work with
|
||||
`registerAppTool` but are deprecated; wrap them with `z.object({...})`.
|
||||
- **`App` / `AppBridge` extend `Protocol` from `@modelcontextprotocol/client`.**
|
||||
`ProtocolWithEvents` is gone; use the SDK's `Protocol`. The `AppRequest`,
|
||||
`AppNotification` and `AppResult` unions are kept as deprecated type
|
||||
aliases (nothing in 2.x consumes them) and will be removed in 3.0.
|
||||
- **Handler context.** Custom request handlers, `registerAppTool` callbacks
|
||||
(server) and `app.registerTool` callbacks (View) receive the SDK 2.x
|
||||
context: `extra.signal` → `extra.mcpReq.signal`, `extra.requestId` →
|
||||
`extra.mcpReq.id`; `extra.sessionId` is unchanged and `extra.authInfo`
|
||||
moved to `extra.http?.authInfo`.
|
||||
- **`setRequestHandler` / `setNotificationHandler` take method names.**
|
||||
`app.setRequestHandler(SomeRequestSchema, handler)` becomes
|
||||
`app.setRequestHandler("some/method", { params: SomeParamsSchema }, (params, ctx) => …)`
|
||||
for custom methods (the handler receives the parsed params); the two-argument
|
||||
`setRequestHandler("tools/call", handler)` form exists only for spec-defined
|
||||
method names. The 1.x `(Schema, handler)` form still works on `App` and
|
||||
`AppBridge` as a deprecated overload: it logs a one-time warning, hands the
|
||||
handler the whole `{ method, params }` message as before, and gives request
|
||||
handlers a 1.x-shaped `extra` (`signal`, `requestId`, `sessionId`, `_meta`,
|
||||
`sendRequest`, `sendNotification`, `authInfo`; exported as
|
||||
`LegacyRequestHandlerExtra`). It will be removed in 3.0.
|
||||
- **Double-registration guard covers the SDK's own handlers.** Registering a
|
||||
handler for `notifications/progress` or `notifications/cancelled` (which
|
||||
the base `Protocol` already owns) now throws `already registered`, as
|
||||
`ping` and the `on*`-owned methods did in 1.x. Use `addEventListener` or
|
||||
the SDK's progress callbacks instead of replacing these.
|
||||
- **Errors.** Remote JSON-RPC errors are `ProtocolError` (numeric `code`);
|
||||
local conditions are `SdkError` with a string `code`: request timeout →
|
||||
`"REQUEST_TIMEOUT"`, connection closed → `"CONNECTION_CLOSED"`. Cancelling a
|
||||
request with an `AbortSignal` also rejects with `"REQUEST_TIMEOUT"` (the
|
||||
message is the abort reason). Messages no longer carry the `MCP error N:`
|
||||
prefix.
|
||||
A resource-not-found reply that carries `data.uri` arrives as
|
||||
`ResourceNotFoundError` (a `ProtocolError` subclass, `code` `-32602`, with a
|
||||
`.uri` getter).
|
||||
|
||||
## Host-side wire deltas
|
||||
|
||||
Observed when a 2.x `AppBridge` answers a View; a 1.x View sees these too.
|
||||
|
||||
| Situation | 1.x host | 2.x host |
|
||||
| --------------------------------------------------------- | ------------------------------ | --------------------------------------------------- |
|
||||
| A handler throws `-32002` (resource not found) | `error.code: -32002` | `error.code: -32602` (the SDK never emits `-32002`) |
|
||||
| Invalid params on a `ui/*` request | `-32603` with a zod issue dump | `-32602 Invalid params for <method>: …` |
|
||||
| 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`.
|
||||
|
|
@ -66,7 +66,7 @@ The server-side changes involve updating metadata structure and using helper fun
|
|||
#### Before (OpenAI)
|
||||
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
function createServer() {
|
||||
|
|
@ -78,7 +78,7 @@ function createServer() {
|
|||
{
|
||||
title: "Shopping Cart",
|
||||
description: "Display the user's shopping cart",
|
||||
inputSchema: z.object({ userId: z.string() }),
|
||||
inputSchema: { userId: z.string() },
|
||||
annotations: { readOnlyHint: true },
|
||||
_meta: {
|
||||
"openai/outputTemplate": "ui://view/cart.html",
|
||||
|
|
@ -126,7 +126,7 @@ function createServer() {
|
|||
#### After (MCP Apps)
|
||||
|
||||
```typescript
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import {
|
||||
registerAppTool,
|
||||
registerAppResource,
|
||||
|
|
@ -144,7 +144,7 @@ function createServer() {
|
|||
{
|
||||
title: "Shopping Cart",
|
||||
description: "Display the user's shopping cart",
|
||||
inputSchema: z.object({ userId: z.string() }),
|
||||
inputSchema: { userId: z.string() },
|
||||
annotations: { readOnlyHint: true },
|
||||
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -56,11 +56,7 @@ flowchart LR
|
|||
- **Host** — The chat client (e.g., Claude Desktop) that connects to servers, embeds Views in iframes, and proxies communication between them.
|
||||
- **View** — The UI running inside a sandboxed iframe. It receives tool data from the Host and can call server tools or send messages back to the chat.
|
||||
|
||||
The View's `App` and the Host's `AppBridge` both subclass the `Protocol` class
|
||||
exported by `@modelcontextprotocol/client` for the iframe channel. A separate outer `Client` connects
|
||||
the Host to the actual MCP Server. Keeping those two connections separate
|
||||
preserves the Apps-only iframe handshake and prevents iframe capabilities from
|
||||
leaking into the server connection.
|
||||
The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
|
|
@ -97,7 +93,7 @@ sequenceDiagram
|
|||
```
|
||||
|
||||
1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
|
||||
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` (the only handshake on the iframe channel) and receives host context (theme, capabilities, container dimensions). `ui/initialize` is authoritative for Apps capabilities and identity, and `ui/notifications/initialized` is the public View-ready signal.
|
||||
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). This handshake ensures the View is ready before receiving data.
|
||||
3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering). This separation lets servers provide rich data to the UI without bloating the model's context.
|
||||
4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context.
|
||||
5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ registerAppTool(
|
|||
"update-quantity",
|
||||
{
|
||||
description: "Update item quantity in cart",
|
||||
inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
|
||||
inputSchema: { itemId: z.string(), quantity: z.number() },
|
||||
_meta: {
|
||||
ui: {
|
||||
resourceUri: "ui://shop/cart.html",
|
||||
|
|
@ -127,14 +127,14 @@ registerAppTool(
|
|||
{
|
||||
title: "Read Data Bytes",
|
||||
description: "Load binary data in chunks",
|
||||
inputSchema: z.object({
|
||||
inputSchema: {
|
||||
id: z.string().describe("Resource identifier"),
|
||||
offset: z.number().min(0).default(0).describe("Byte offset"),
|
||||
byteCount: z
|
||||
.number()
|
||||
.default(MAX_CHUNK_BYTES)
|
||||
.describe("Bytes to read"),
|
||||
}),
|
||||
},
|
||||
outputSchema: DataChunkSchema,
|
||||
// Hidden from model - only callable by the App
|
||||
_meta: { ui: { visibility: ["app"] } },
|
||||
|
|
@ -281,7 +281,10 @@ server.registerResource(
|
|||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="./patterns.tsx#binaryBlobResourceClient"
|
||||
const result = await app.readServerResource({ uri: `video://${videoId}` });
|
||||
const result = await app.request(
|
||||
{ method: "resources/read", params: { uri: `video://${videoId}` } },
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
|
||||
const content = result.contents[0];
|
||||
if (!content || !("blob" in content)) {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,16 @@ import { randomUUID } from "node:crypto";
|
|||
import type {
|
||||
CallToolResult,
|
||||
ReadResourceResult,
|
||||
} from "@modelcontextprotocol/server";
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { McpUiHostContext } from "../src/types.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useApp } from "../src/react/index.js";
|
||||
import { registerAppTool } from "../src/server/index.js";
|
||||
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
|
||||
import {
|
||||
McpServer,
|
||||
ResourceTemplate,
|
||||
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
|
@ -113,14 +117,14 @@ function chunkedDataServer(server: McpServer) {
|
|||
{
|
||||
title: "Read Data Bytes",
|
||||
description: "Load binary data in chunks",
|
||||
inputSchema: z.object({
|
||||
inputSchema: {
|
||||
id: z.string().describe("Resource identifier"),
|
||||
offset: z.number().min(0).default(0).describe("Byte offset"),
|
||||
byteCount: z
|
||||
.number()
|
||||
.default(MAX_CHUNK_BYTES)
|
||||
.describe("Bytes to read"),
|
||||
}),
|
||||
},
|
||||
outputSchema: DataChunkSchema,
|
||||
// Hidden from model - only callable by the App
|
||||
_meta: { ui: { visibility: ["app"] } },
|
||||
|
|
@ -248,7 +252,10 @@ function binaryBlobResourceServer(
|
|||
*/
|
||||
async function binaryBlobResourceClient(app: App, videoId: string) {
|
||||
//#region binaryBlobResourceClient
|
||||
const result = await app.readServerResource({ uri: `video://${videoId}` });
|
||||
const result = await app.request(
|
||||
{ method: "resources/read", params: { uri: `video://${videoId}` } },
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
|
||||
const content = result.contents[0];
|
||||
if (!content || !("blob" in content)) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ This tutorial assumes you've built an MCP server before and are comfortable with
|
|||
|
||||
We'll use the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) to build the server.
|
||||
|
||||
You'll also need Node.js 20+.
|
||||
You'll also need Node.js 18+.
|
||||
|
||||
## 1. Set up the project
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ Install the dependencies you'll need:
|
|||
|
||||
```bash
|
||||
npm init -y
|
||||
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@^2.0.0 @modelcontextprotocol/server@^2.0.0 @modelcontextprotocol/node@^2.0.0 @modelcontextprotocol/express@^2.0.0 zod@^4.2.0 express cors
|
||||
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors
|
||||
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
|
||||
```
|
||||
|
||||
|
|
@ -59,7 +59,6 @@ npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development IN
|
|||
```json source="../examples/quickstart/tsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["node", "vite/client"],
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
|
|
@ -88,7 +87,6 @@ npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development IN
|
|||
```json source="../examples/quickstart/tsconfig.server.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "NodeNext",
|
||||
|
|
@ -180,10 +178,9 @@ import {
|
|||
registerAppTool,
|
||||
RESOURCE_MIME_TYPE,
|
||||
} from "@modelcontextprotocol/ext-apps/server";
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
|
||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||
|
||||
|
|
@ -208,7 +205,7 @@ export function createServer(): McpServer {
|
|||
{
|
||||
title: "Get Time",
|
||||
description: "Returns the current server time.",
|
||||
inputSchema: z.object({}),
|
||||
inputSchema: {},
|
||||
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
|
||||
},
|
||||
async () => {
|
||||
|
|
@ -243,10 +240,10 @@ export function createServer(): McpServer {
|
|||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="../examples/quickstart/main.ts"
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/express";
|
||||
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
||||
import type { McpServer } from "@modelcontextprotocol/server";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import cors from "cors";
|
||||
import type { Request, Response } from "express";
|
||||
import { createServer } from "./server.js";
|
||||
|
|
@ -266,7 +263,7 @@ export async function startStreamableHTTPServer(
|
|||
|
||||
app.all("/mcp", async (req: Request, res: Response) => {
|
||||
const server = createServer();
|
||||
const transport = new NodeStreamableHTTPServerTransport({
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,6 @@ The [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/ex
|
|||
cd examples/basic-host
|
||||
```
|
||||
|
||||
The root install runs the package build, including type-checking the
|
||||
documentation snippets against the pinned base MCP SDK 2.x packages. Do
|
||||
not bypass a failed build before testing the host.
|
||||
|
||||
2. Start basic-host, pointing it to your MCP server:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"homepage": "https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host",
|
||||
"name": "@modelcontextprotocol/ext-apps-basic-host",
|
||||
"version": "2.0.0",
|
||||
"version": "1.7.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit && concurrently \"cross-env INPUT=index.html vite build\" \"cross-env INPUT=sandbox.html vite build\"",
|
||||
|
|
@ -11,11 +11,11 @@
|
|||
"dev": "cross-env NODE_ENV=development concurrently \"npm run watch\" \"npm run serve\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/client": "2.0.0",
|
||||
"@modelcontextprotocol/ext-apps": "^2.0.0",
|
||||
"@modelcontextprotocol/ext-apps": "^1.7.0",
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"zod": "^4.2.0"
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions, buildAllowAttribute, type McpUiUpdateModelContextRequest, type McpUiMessageRequest } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
import {
|
||||
Client,
|
||||
SSEClientTransport,
|
||||
StreamableHTTPClientTransport,
|
||||
type CallToolResult,
|
||||
type Resource,
|
||||
type Tool,
|
||||
} from "@modelcontextprotocol/client";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import type { CallToolResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { getTheme, onThemeChange } from "./theme";
|
||||
import { HOST_STYLE_VARIABLES } from "./host-styles";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getToolUiResourceUri, McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
import type { Tool } from "@modelcontextprotocol/client";
|
||||
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo, type ModelContext, type AppMessage } from "./implementation";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["node", "vite/client"],
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
* Or: node dist/index.js [--stdio]
|
||||
*/
|
||||
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/express";
|
||||
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
||||
import type { McpServer } from "@modelcontextprotocol/server";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import cors from "cors";
|
||||
import type { Request, Response } from "express";
|
||||
import { createServer } from "./server.js";
|
||||
|
|
@ -27,7 +27,7 @@ export async function startStreamableHTTPServer(
|
|||
|
||||
app.all("/mcp", async (req: Request, res: Response) => {
|
||||
const server = createServer();
|
||||
const transport = new NodeStreamableHTTPServerTransport({
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/server-basic-preact",
|
||||
"version": "2.0.0",
|
||||
"version": "1.7.4",
|
||||
"type": "module",
|
||||
"description": "Basic MCP App Server example using Preact",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/modelcontextprotocol/ext-apps.git",
|
||||
"url": "https://github.com/modelcontextprotocol/ext-apps",
|
||||
"directory": "examples/basic-server-preact"
|
||||
},
|
||||
"license": "MIT",
|
||||
|
|
@ -24,15 +24,12 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/client": "2.0.0",
|
||||
"@modelcontextprotocol/express": "2.0.0",
|
||||
"@modelcontextprotocol/ext-apps": "^2.0.0",
|
||||
"@modelcontextprotocol/node": "2.0.0",
|
||||
"@modelcontextprotocol/server": "2.0.0",
|
||||
"@modelcontextprotocol/ext-apps": "^1.7.0",
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
"preact": "^10.0.0",
|
||||
"zod": "^4.2.0"
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.0.0",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
import {
|
||||
McpServer,
|
||||
type CallToolResult,
|
||||
type ReadResourceResult,
|
||||
} from "@modelcontextprotocol/server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
||||
import { z } from "zod";
|
||||
// Works both from source (server.ts) and compiled (dist/server.js)
|
||||
const DIST_DIR = import.meta.filename.endsWith(".ts")
|
||||
? path.join(import.meta.dirname, "dist")
|
||||
|
|
@ -32,7 +28,7 @@ export function createServer(): McpServer {
|
|||
{
|
||||
title: "Get Time",
|
||||
description: "Returns the current server time as an ISO 8601 string.",
|
||||
inputSchema: z.object({}),
|
||||
inputSchema: {},
|
||||
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
|
||||
},
|
||||
async (): Promise<CallToolResult> => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue