Compare commits
248 changed files with 3365 additions and 29200 deletions
55
.github/workflows/ci.yml
vendored
55
.github/workflows/ci.yml
vendored
|
|
@ -24,8 +24,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
|
||||||
|
|
@ -72,17 +71,12 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- run: npm ci
|
- run: npm install
|
||||||
|
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
|
||||||
- name: Validate TypeDoc links
|
|
||||||
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
|
|
||||||
run: npm exec typedoc -- --treatValidationWarningsAsErrors --emit none
|
|
||||||
|
|
||||||
- run: npm run examples:build
|
- run: npm run examples:build
|
||||||
|
|
||||||
- name: Verify generated schemas are up-to-date
|
- name: Verify generated schemas are up-to-date
|
||||||
|
|
@ -103,32 +97,9 @@ jobs:
|
||||||
|
|
||||||
- name: Build MCPB bundle (pdf-server)
|
- name: Build MCPB bundle (pdf-server)
|
||||||
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
|
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
|
||||||
run: node build-mcpb.mjs
|
run: npx -y @anthropic-ai/mcpb pack
|
||||||
working-directory: examples/pdf-server
|
working-directory: examples/pdf-server
|
||||||
|
|
||||||
- name: Smoke-test MCPB bundle starts
|
|
||||||
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
|
|
||||||
run: |
|
|
||||||
unzip -q pdf-server.mcpb -d .mcpb-smoke
|
|
||||||
node .mcpb-smoke/dist/index.js --stdio < /dev/null 2>&1 | tee /tmp/mcpb-smoke.log
|
|
||||||
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:
|
e2e:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -140,17 +111,14 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@v7
|
||||||
|
|
||||||
- run: npm ci
|
- 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
|
- name: Install Playwright browsers
|
||||||
run: npx playwright install --with-deps --only-shell chromium
|
run: npx playwright install --with-deps chromium
|
||||||
|
|
||||||
- name: Run E2E tests
|
- name: Run E2E tests
|
||||||
run: npx playwright test --reporter=list
|
run: npx playwright test --reporter=list
|
||||||
|
|
@ -179,18 +147,13 @@ jobs:
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||||
sudo apt-get install -y nodejs
|
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
|
- name: Build and test in WSL
|
||||||
shell: wsl-bash {0}
|
shell: wsl-bash {0}
|
||||||
run: |
|
run: |
|
||||||
cp -r "$(pwd)" ~/ext-apps && cd ~/ext-apps
|
npm install
|
||||||
npm ci
|
|
||||||
npm run build
|
npm run build
|
||||||
npm run examples:build
|
npm run examples:build
|
||||||
npm test
|
npm test
|
||||||
|
|
@ -219,7 +182,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
|
|
||||||
- name: Create test project and install from git
|
- name: Create test project and install from git
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
65
.github/workflows/docs-preview.yml
vendored
65
.github/workflows/docs-preview.yml
vendored
|
|
@ -1,65 +0,0 @@
|
||||||
name: Docs Preview
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
paths:
|
|
||||||
- "docs/**"
|
|
||||||
- "src/**"
|
|
||||||
- "README.md"
|
|
||||||
- "typedoc.config.mjs"
|
|
||||||
- "scripts/typedoc-*.mjs"
|
|
||||||
- ".github/workflows/docs-preview.yml"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: docs-preview-${{ github.event.pull_request.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-deploy:
|
|
||||||
# Refuse to run for fork PRs — forks do not have access to the Cloudflare
|
|
||||||
# secrets, so deploys would fail anyway.
|
|
||||||
if: >-
|
|
||||||
github.event.action != 'closed' &&
|
|
||||||
github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
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 build
|
|
||||||
|
|
||||||
- run: npm run docs
|
|
||||||
|
|
||||||
- name: Deploy preview
|
|
||||||
uses: modelcontextprotocol/actions/cloudflare-pages-preview/deploy@main
|
|
||||||
with:
|
|
||||||
directory: docs
|
|
||||||
project-name: mcp-ext-apps-docs-preview
|
|
||||||
api-token: ${{ secrets.CF_PAGES_PREVIEW_API_TOKEN }}
|
|
||||||
account-id: ${{ secrets.CF_PAGES_PREVIEW_ACCOUNT_ID }}
|
|
||||||
comment-title: "📖 Docs Preview Deployed"
|
|
||||||
comment-marker: "<!-- docs-preview-comment -->"
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
if: >-
|
|
||||||
github.event.action == 'closed' &&
|
|
||||||
github.event.pull_request.head.repo.full_name == github.repository
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: modelcontextprotocol/actions/cloudflare-pages-preview/cleanup@main
|
|
||||||
with:
|
|
||||||
project-name: mcp-ext-apps-docs-preview
|
|
||||||
api-token: ${{ secrets.CF_PAGES_PREVIEW_API_TOKEN }}
|
|
||||||
account-id: ${{ secrets.CF_PAGES_PREVIEW_ACCOUNT_ID }}
|
|
||||||
comment-marker: "<!-- docs-preview-comment -->"
|
|
||||||
5
.github/workflows/docs.yml
vendored
5
.github/workflows/docs.yml
vendored
|
|
@ -16,9 +16,8 @@ jobs:
|
||||||
- uses: oven-sh/setup-bun@v2
|
- uses: oven-sh/setup-bun@v2
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
cache: npm
|
- run: npm install
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm run docs
|
- run: npm run docs
|
||||||
- uses: peaceiris/actions-gh-pages@v4
|
- uses: peaceiris/actions-gh-pages@v4
|
||||||
|
|
|
||||||
29
.github/workflows/npm-publish.yml
vendored
29
.github/workflows/npm-publish.yml
vendored
|
|
@ -42,10 +42,6 @@ jobs:
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
# All publish jobs depend directly on [build, test] (not on each other) so
|
|
||||||
# they all enter "waiting for approval" together and can be approved in a
|
|
||||||
# single "Review deployments" click.
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
|
|
@ -92,7 +88,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
environment: Release
|
environment: Release
|
||||||
needs: [build, test]
|
needs: [publish]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -112,7 +108,6 @@ jobs:
|
||||||
- cohort-heatmap-server
|
- cohort-heatmap-server
|
||||||
- customer-segmentation-server
|
- customer-segmentation-server
|
||||||
- debug-server
|
- debug-server
|
||||||
- lazy-auth-server
|
|
||||||
- map-server
|
- map-server
|
||||||
- pdf-server
|
- pdf-server
|
||||||
- scenario-modeler-server
|
- scenario-modeler-server
|
||||||
|
|
@ -139,18 +134,8 @@ jobs:
|
||||||
- name: Build example
|
- name: Build example
|
||||||
run: npm run build --workspace examples/${{ matrix.example }}
|
run: npm run build --workspace examples/${{ matrix.example }}
|
||||||
|
|
||||||
- name: Determine npm tag
|
|
||||||
id: npm-tag
|
|
||||||
run: |
|
|
||||||
VERSION=$(node -p "require('./package.json').version")
|
|
||||||
if [[ "$VERSION" == *"-"* ]]; then
|
|
||||||
echo "tag=--tag beta" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "tag=" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Publish example
|
- name: Publish example
|
||||||
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public ${{ steps.npm-tag.outputs.tag }}
|
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
|
||||||
|
|
||||||
|
|
@ -158,7 +143,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
environment: Release
|
environment: Release
|
||||||
needs: [build, test]
|
needs: [publish-examples]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
@ -174,8 +159,12 @@ jobs:
|
||||||
cache: npm
|
cache: npm
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
|
||||||
- name: Build pdf-server + MCPB bundle
|
- name: Build pdf-server
|
||||||
run: npm run build:mcpb --workspace examples/pdf-server
|
run: npm run build --workspace examples/pdf-server
|
||||||
|
|
||||||
|
- name: Pack MCPB bundle
|
||||||
|
run: npx -y @anthropic-ai/mcpb pack
|
||||||
|
working-directory: examples/pdf-server
|
||||||
|
|
||||||
- name: Upload MCPB to release
|
- name: Upload MCPB to release
|
||||||
run: gh release upload "${{ github.event.release.tag_name }}" examples/pdf-server/*.mcpb --clobber
|
run: gh release upload "${{ github.event.release.tag_name }}" examples/pdf-server/*.mcpb --clobber
|
||||||
|
|
|
||||||
1
.github/workflows/publish.yml
vendored
1
.github/workflows/publish.yml
vendored
|
|
@ -34,7 +34,6 @@ jobs:
|
||||||
./examples/cohort-heatmap-server \
|
./examples/cohort-heatmap-server \
|
||||||
./examples/customer-segmentation-server \
|
./examples/customer-segmentation-server \
|
||||||
./examples/debug-server \
|
./examples/debug-server \
|
||||||
./examples/lazy-auth-server \
|
|
||||||
./examples/map-server \
|
./examples/map-server \
|
||||||
./examples/pdf-server \
|
./examples/pdf-server \
|
||||||
./examples/scenario-modeler-server \
|
./examples/scenario-modeler-server \
|
||||||
|
|
|
||||||
11
.github/workflows/update-snapshots.yml
vendored
11
.github/workflows/update-snapshots.yml
vendored
|
|
@ -16,14 +16,10 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-snapshots:
|
update-snapshots:
|
||||||
# Run on workflow_dispatch OR when a maintainer comments "/update-snapshots" on a PR.
|
# Run on workflow_dispatch OR when someone comments "/update-snapshots" on a PR
|
||||||
# author_association check prevents arbitrary commenters from triggering a job with
|
|
||||||
# contents:write that pushes a [skip ci] commit to the PR branch.
|
|
||||||
if: >
|
if: >
|
||||||
github.event_name == 'workflow_dispatch' ||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
(github.event.issue.pull_request &&
|
(github.event.issue.pull_request && contains(github.event.comment.body, '/update-snapshots'))
|
||||||
contains(github.event.comment.body, '/update-snapshots') &&
|
|
||||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Get PR branch
|
- name: Get PR branch
|
||||||
|
|
@ -63,8 +59,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "20"
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v7
|
- uses: astral-sh/setup-uv@v7
|
||||||
|
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -15,6 +15,4 @@ __pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
# MCPB bundles (built artifacts)
|
# MCPB bundles (built artifacts)
|
||||||
*.mcpb
|
*.mcpb
|
||||||
.mcpb-stage/
|
|
||||||
.mcpb-smoke/
|
|
||||||
|
|
@ -14,16 +14,8 @@ if grep -E '"resolved": "https?://' package-lock.json | grep -v registry.npmjs.o
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Capture staged files so we only re-stage what the user intended to commit
|
|
||||||
# (avoids sweeping unrelated WIP into the commit). --diff-filter=d skips
|
|
||||||
# deletions so the xargs git add below doesn't fail on removed paths.
|
|
||||||
STAGED=$(git diff --name-only --cached --diff-filter=d)
|
|
||||||
|
|
||||||
npm run build:all
|
npm run build:all
|
||||||
npm run prettier:fix
|
npm run prettier:fix
|
||||||
|
|
||||||
# Re-stage originally-staged files (prettier may have reformatted them)
|
# Stage any files reformatted by prettier (generated files, source, docs, etc.)
|
||||||
echo "$STAGED" | xargs -r git add
|
git diff --name-only --diff-filter=M | xargs -r git add
|
||||||
|
|
||||||
# Also stage generated files (regenerated by build:all from spec.types.ts)
|
|
||||||
git add src/generated/
|
|
||||||
|
|
|
||||||
1
.npmrc
1
.npmrc
|
|
@ -1 +0,0 @@
|
||||||
registry=https://registry.npmjs.org/
|
|
||||||
|
|
@ -5,5 +5,4 @@ examples/basic-server-*/**/*.tsx
|
||||||
examples/quickstart/**/*.ts
|
examples/quickstart/**/*.ts
|
||||||
**/vendor/**
|
**/vendor/**
|
||||||
**/.build/**
|
**/.build/**
|
||||||
**/.claude/**
|
|
||||||
SKILL.md
|
SKILL.md
|
||||||
|
|
|
||||||
13
AGENTS.md
13
AGENTS.md
|
|
@ -39,10 +39,7 @@ npm test
|
||||||
# Check JSDoc comment syntax and `{@link}` references
|
# Check JSDoc comment syntax and `{@link}` references
|
||||||
npm exec typedoc -- --treatValidationWarningsAsErrors --emit none
|
npm exec typedoc -- --treatValidationWarningsAsErrors --emit none
|
||||||
|
|
||||||
# Regenerate package-lock.json
|
# Regenerate package-lock.json (especially on setups w/ custom npm registry)
|
||||||
# Note: repo .npmrc pins registry to npmjs.org, so a plain `npm i` is safe even
|
|
||||||
# if your global npm config points elsewhere. The Docker step below is optional
|
|
||||||
# — it locks linux-amd64 optionalDependencies (sharp, rollup, bun) for CI.
|
|
||||||
rm -fR package-lock.json node_modules && \
|
rm -fR package-lock.json node_modules && \
|
||||||
docker run --rm -it --platform linux/amd64 -v $PWD:/src:rw -w /src node:latest npm i && \
|
docker run --rm -it --platform linux/amd64 -v $PWD:/src:rw -w /src node:latest npm i && \
|
||||||
rm -fR node_modules && \
|
rm -fR node_modules && \
|
||||||
|
|
@ -60,8 +57,8 @@ rm -fR package-lock.json node_modules && \
|
||||||
|
|
||||||
### Key Source Files
|
### Key Source Files
|
||||||
|
|
||||||
- `src/app.ts` - `App` subclasses `Protocol` from `@modelcontextprotocol/client`, handles View initialization, tool calls, and messaging
|
- `src/app.ts` - `App` class extends MCP Protocol, handles guest initialization, tool calls, messaging
|
||||||
- `src/app-bridge.ts` - `AppBridge` subclasses `Protocol` from `@modelcontextprotocol/client` for the iframe channel and proxies through a separate outer `Client`
|
- `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/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/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
|
- `src/message-transport.ts` - `PostMessageTransport` for iframe communication
|
||||||
|
|
@ -70,12 +67,12 @@ rm -fR package-lock.json node_modules && \
|
||||||
### Protocol Flow
|
### 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
|
1. Host creates iframe with view HTML
|
||||||
2. View creates `App` instance and calls `connect()` with `PostMessageTransport`
|
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
|
4. Host sends `sendToolInput()` with tool arguments after initialization
|
||||||
5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()`
|
5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()`
|
||||||
6. Host sends `sendToolResult()` when tool execution completes
|
6. Host sends `sendToolResult()` when tool execution completes
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,6 @@ We welcome contributions to the MCP Apps SDK! This document outlines the process
|
||||||
- Provide a clear description of changes
|
- Provide a clear description of changes
|
||||||
- **Keep "Allow edits by maintainers" checked** when opening your PR — this lets maintainers rebase your branch and lets the [`/update-snapshots` workflow](#updating-snapshots-in-ci) push updated screenshots to it
|
- **Keep "Allow edits by maintainers" checked** when opening your PR — this lets maintainers rebase your branch and lets the [`/update-snapshots` workflow](#updating-snapshots-in-ci) push updated screenshots to it
|
||||||
|
|
||||||
## Examples Policy
|
|
||||||
|
|
||||||
The examples in `examples/` are maintained by the core team. **We do not accept pull requests that add new example servers from outside contributors.** Each example carries an ongoing maintenance cost — keeping it building, tested, and up to date with SDK changes — so we are deliberate about what we add.
|
|
||||||
|
|
||||||
If you have an idea for an example that would provide significant educational value, please [open an issue](https://github.com/modelcontextprotocol/ext-apps/issues) describing what the example would demonstrate and why it would be useful, and maintainers will evaluate the request.
|
|
||||||
|
|
||||||
PRs that add new examples without a prior approved issue may be closed.
|
|
||||||
|
|
||||||
## Running Examples
|
## Running Examples
|
||||||
|
|
||||||
Start the development environment with hot reloading:
|
Start the development environment with hot reloading:
|
||||||
|
|
@ -502,7 +494,7 @@ Alternatively, use the [workflow dispatch](https://github.com/modelcontextprotoc
|
||||||
|
|
||||||
## Code of Conduct
|
## 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
|
## Reporting Issues
|
||||||
|
|
||||||
|
|
@ -539,17 +531,40 @@ Before publishing releases, ensure the following are configured:
|
||||||
|
|
||||||
### Publishing a Release
|
### Publishing a Release
|
||||||
|
|
||||||
1. **Bump the version** across the root and all workspace packages:
|
Releases are published automatically via GitHub Actions when a GitHub Release is created.
|
||||||
|
|
||||||
|
#### Steps to publish:
|
||||||
|
|
||||||
|
1. **Update the version** in `package.json`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run bump -- minor # or: patch | major | prerelease --preid=beta | 1.7.0
|
# For a regular release
|
||||||
|
npm version patch # or minor, or major
|
||||||
|
|
||||||
|
# For a beta release
|
||||||
|
npm version prerelease --preid=beta
|
||||||
```
|
```
|
||||||
|
|
||||||
Commit and open a PR with a grouped changelog in the body (see prior `chore: bump …` PRs for the format).
|
2. **Commit the version bump** (if not done by `npm version`):
|
||||||
|
|
||||||
2. **Merge the PR**, then [draft a GitHub Release](https://github.com/modelcontextprotocol/ext-apps/releases/new) — create a `vX.Y.Z` tag on `main`, paste the changelog, publish.
|
```bash
|
||||||
|
git add package.json
|
||||||
|
git commit -m "Bump version to X.Y.Z"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
3. **Approve the deployment** — publishing the Release triggers the [npm-publish workflow](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/npm-publish.yml); all publish jobs wait together for a single "Review deployments" approval.
|
3. **Create a GitHub Release**:
|
||||||
|
- Go to [Releases](https://github.com/modelcontextprotocol/ext-apps/releases)
|
||||||
|
- Click "Draft a new release"
|
||||||
|
- Create a new tag matching the version (e.g., `v0.1.0`)
|
||||||
|
- Set the target branch (usually `main`)
|
||||||
|
- Write release notes describing the changes
|
||||||
|
- Click "Publish release"
|
||||||
|
|
||||||
|
4. **Monitor the workflow**:
|
||||||
|
- The [npm-publish workflow](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/npm-publish.yml) will trigger automatically
|
||||||
|
- It runs build and test jobs before publishing
|
||||||
|
- On success, the package is published to npm with provenance
|
||||||
|
|
||||||
#### npm Tags
|
#### npm Tags
|
||||||
|
|
||||||
|
|
@ -573,7 +588,7 @@ To release a patch for an older version:
|
||||||
|
|
||||||
### Testing Pre-releases
|
### Testing Pre-releases
|
||||||
|
|
||||||
Every commit and PR automatically publishes a preview package via [pkg-pr-new](https://github.com/stackblitz-labs/pkg.pr.new). Check the PR comments or workflow logs for the install command.
|
Every commit and PR automatically publishes a preview package via [pkg-pr-new](https://github.com/pkg-pr-new/pkg-pr-new). Check the PR comments or workflow logs for the install command.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
43
README.md
43
README.md
|
|
@ -1,17 +1,14 @@
|
||||||
<!-- LOGO -->
|
<!-- LOGO -->
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<picture>
|
<img src="media/mcp.svg#gh-light-mode-only" alt="MCP Apps" width="128">
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="media/mcp-white.svg">
|
<img src="media/mcp-white.svg#gh-dark-mode-only" alt="MCP Apps" width="128">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="media/mcp.svg">
|
|
||||||
<img src="media/mcp.svg" alt="MCP Apps" width="128">
|
|
||||||
</picture>
|
|
||||||
<h1>MCP Apps</h1>
|
<h1>MCP Apps</h1>
|
||||||
<p>
|
<p>
|
||||||
Build interactive UIs for MCP tools — charts, forms, dashboards — that render inline in Claude, ChatGPT and any other compliant chat client.
|
Build interactive UIs for MCP tools — charts, forms, dashboards — that render inline in Claude, ChatGPT and any other compliant chat client.
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<a href="#why-mcp-apps">Why</a>
|
<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>
|
<a href="https://apps.extensions.modelcontextprotocol.io/api/">API Docs</a>
|
||||||
·
|
·
|
||||||
|
|
@ -89,8 +86,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://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://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://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>
|
</p>
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|
@ -122,36 +117,12 @@ resources:
|
||||||
|
|
||||||
## Getting Started
|
## 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
|
```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
|
**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.
|
to build your first MCP App.
|
||||||
|
|
||||||
## Using the SDK
|
## Using the SDK
|
||||||
|
|
@ -165,7 +136,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` | 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/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/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
|
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)
|
[examples/basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host)
|
||||||
|
|
@ -272,7 +243,7 @@ point your client at a local build:
|
||||||
|
|
||||||
## Resources
|
## 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/)
|
- [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)
|
- [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))
|
([Draft](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx))
|
||||||
|
|
|
||||||
29
build.bun.ts
29
build.bun.ts
|
|
@ -1,18 +1,9 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import { $ } from "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 });
|
|
||||||
|
|
||||||
// Run TypeScript compiler for type declarations
|
// Run TypeScript compiler for type declarations
|
||||||
await $`tsc`;
|
await $`tsc`;
|
||||||
|
|
||||||
// Copy schema.json (tsc is emitDeclarationOnly, Bun.build doesn't emit JSON assets).
|
|
||||||
// Needed for the "./schema.json" package export.
|
|
||||||
mkdirSync("dist/src/generated", { recursive: true });
|
|
||||||
cpSync("src/generated/schema.json", "dist/src/generated/schema.json");
|
|
||||||
|
|
||||||
const isDevelopment = Bun.env.NODE_ENV === "development";
|
const isDevelopment = Bun.env.NODE_ENV === "development";
|
||||||
|
|
||||||
// Build all JavaScript/TypeScript files
|
// Build all JavaScript/TypeScript files
|
||||||
|
|
@ -34,20 +25,10 @@ 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",
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
buildJs("src/app.ts", {
|
buildJs("src/app.ts", {
|
||||||
outdir: "dist/src",
|
outdir: "dist/src",
|
||||||
external: PEER_EXTERNALS,
|
external: ["@modelcontextprotocol/sdk"],
|
||||||
}),
|
}),
|
||||||
buildJs("src/app.ts", {
|
buildJs("src/app.ts", {
|
||||||
outdir: "dist/src",
|
outdir: "dist/src",
|
||||||
|
|
@ -55,19 +36,19 @@ await Promise.all([
|
||||||
}),
|
}),
|
||||||
buildJs("src/app-bridge.ts", {
|
buildJs("src/app-bridge.ts", {
|
||||||
outdir: "dist/src",
|
outdir: "dist/src",
|
||||||
external: PEER_EXTERNALS,
|
external: ["@modelcontextprotocol/sdk"],
|
||||||
}),
|
}),
|
||||||
buildJs("src/react/index.tsx", {
|
buildJs("src/react/index.tsx", {
|
||||||
outdir: "dist/src/react",
|
outdir: "dist/src/react",
|
||||||
external: ["react", "react-dom", ...PEER_EXTERNALS],
|
external: ["react", "react-dom", "@modelcontextprotocol/sdk"],
|
||||||
}),
|
}),
|
||||||
buildJs("src/react/index.tsx", {
|
buildJs("src/react/index.tsx", {
|
||||||
outdir: "dist/src/react",
|
outdir: "dist/src/react",
|
||||||
external: ["react", "react-dom"],
|
external: ["react", "react-dom", "@modelcontextprotocol/sdk"],
|
||||||
naming: { entry: "react-with-deps.js" },
|
naming: { entry: "react-with-deps.js" },
|
||||||
}),
|
}),
|
||||||
buildJs("src/server/index.ts", {
|
buildJs("src/server/index.ts", {
|
||||||
outdir: "dist/src/server",
|
outdir: "dist/src/server",
|
||||||
external: PEER_EXTERNALS,
|
external: ["@modelcontextprotocol/sdk"],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ registerAppTool(
|
||||||
"get_account_balance",
|
"get_account_balance",
|
||||||
{
|
{
|
||||||
description: "Get account balance",
|
description: "Get account balance",
|
||||||
inputSchema: z.object({ accountId: z.string() }),
|
inputSchema: { accountId: z.string() },
|
||||||
},
|
},
|
||||||
async ({ accountId }) => {
|
async ({ accountId }) => {
|
||||||
if (!authInfo) {
|
if (!authInfo) {
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,21 @@
|
||||||
* MCP Apps custom theme — aligns TypeDoc styling with modelcontextprotocol.io
|
* MCP Apps custom theme — aligns TypeDoc styling with modelcontextprotocol.io
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap");
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-family-text:
|
--font-family-text: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
--font-family-code: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
--font-family-code:
|
|
||||||
"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: var(--font-family-text);
|
font-family: var(--font-family-text);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -26,12 +24,12 @@ body {
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--light-color-background: #ffffff;
|
--light-color-background: #ffffff;
|
||||||
--light-color-background-secondary: #f9fafb;
|
--light-color-background-secondary: #f9fafb;
|
||||||
--light-color-background-navbar: #ffffff;
|
--light-color-background-navbar: #ffffff;
|
||||||
--light-color-accent: #e5e7eb;
|
--light-color-accent: #e5e7eb;
|
||||||
--light-color-text: #111827;
|
--light-color-text: #111827;
|
||||||
--light-color-text-aside: #6b7280;
|
--light-color-text-aside: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -39,12 +37,12 @@ body {
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--dark-color-background: #0f1117;
|
--dark-color-background: #0f1117;
|
||||||
--dark-color-background-secondary: #161b22;
|
--dark-color-background-secondary: #161b22;
|
||||||
--dark-color-background-navbar: #0f1117;
|
--dark-color-background-navbar: #0f1117;
|
||||||
--dark-color-accent: #30363d;
|
--dark-color-accent: #30363d;
|
||||||
--dark-color-text: #f0f6fc;
|
--dark-color-text: #f0f6fc;
|
||||||
--dark-color-text-aside: #8b949e;
|
--dark-color-text-aside: #8b949e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -52,55 +50,32 @@ body {
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--dim-toolbar-contents-height: 3.5rem;
|
--dim-toolbar-contents-height: 3.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-page-toolbar {
|
.tsd-page-toolbar {
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
.tsd-page-toolbar {
|
.tsd-page-toolbar {
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.tsd-page-toolbar {
|
.tsd-page-toolbar {
|
||||||
background-color: rgba(15, 17, 23, 0.75);
|
background-color: rgba(15, 17, 23, 0.75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="light"] .tsd-page-toolbar {
|
:root[data-theme='light'] .tsd-page-toolbar {
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="dark"] .tsd-page-toolbar {
|
:root[data-theme='dark'] .tsd-page-toolbar {
|
||||||
background-color: rgba(15, 17, 23, 0.75);
|
background-color: rgba(15, 17, 23, 0.75);
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Theme-aware logo (rewritten from README <picture> by mcpstyle plugin) */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
/* Explicit theme selection via TypeDoc switcher — takes precedence */
|
|
||||||
:root[data-theme="light"] .mcp-logo-dark,
|
|
||||||
:root[data-theme="dark"] .mcp-logo-light {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* "OS" mode (data-theme='os') follows prefers-color-scheme */
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root[data-theme="os"] .mcp-logo-dark {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root[data-theme="os"] .mcp-logo-light {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -108,68 +83,66 @@ body {
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
.tsd-navigation a {
|
.tsd-navigation a {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
color: var(--color-text-aside);
|
color: var(--color-text-aside);
|
||||||
transition:
|
transition: background-color 0.15s ease, color 0.15s ease;
|
||||||
background-color 0.15s ease,
|
|
||||||
color 0.15s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-navigation a:hover:not(.current) {
|
.tsd-navigation a:hover:not(.current) {
|
||||||
background-color: rgba(107, 114, 128, 0.05);
|
background-color: rgba(107, 114, 128, 0.05);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-navigation a.current {
|
.tsd-navigation a.current {
|
||||||
background-color: rgba(107, 114, 128, 0.15);
|
background-color: rgba(107, 114, 128, 0.15);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Group headers (Documents, Security, API Documentation) */
|
/* Group headers (Documents, Security, API Documentation) */
|
||||||
.tsd-accordion-summary > h3 {
|
.tsd-accordion-summary > h3 {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
letter-spacing: 0.01em;
|
letter-spacing: 0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-menu {
|
.site-menu {
|
||||||
padding: 1.25rem 0;
|
padding: 1.25rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.tsd-navigation a {
|
.tsd-navigation a {
|
||||||
color: var(--color-text-aside);
|
color: var(--color-text-aside);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-navigation a:hover:not(.current) {
|
.tsd-navigation a:hover:not(.current) {
|
||||||
background-color: rgba(229, 231, 235, 0.05);
|
background-color: rgba(229, 231, 235, 0.05);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-navigation a.current {
|
.tsd-navigation a.current {
|
||||||
background-color: rgba(229, 231, 235, 0.15);
|
background-color: rgba(229, 231, 235, 0.15);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="dark"] .tsd-navigation a {
|
:root[data-theme='dark'] .tsd-navigation a {
|
||||||
color: var(--color-text-aside);
|
color: var(--color-text-aside);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="dark"] .tsd-navigation a:hover:not(.current) {
|
:root[data-theme='dark'] .tsd-navigation a:hover:not(.current) {
|
||||||
background-color: rgba(229, 231, 235, 0.05);
|
background-color: rgba(229, 231, 235, 0.05);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="dark"] .tsd-navigation a.current {
|
:root[data-theme='dark'] .tsd-navigation a.current {
|
||||||
background-color: rgba(229, 231, 235, 0.15);
|
background-color: rgba(229, 231, 235, 0.15);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -177,21 +150,21 @@ body {
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
.tsd-typography {
|
.tsd-typography {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-panel.tsd-typography h1 {
|
.tsd-panel.tsd-typography h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-bottom: 0.4em;
|
padding-bottom: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-panel.tsd-typography h2 {
|
.tsd-panel.tsd-typography h2 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
padding-bottom: 0.3em;
|
padding-bottom: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-panel.tsd-typography h3 {
|
.tsd-panel.tsd-typography h3 {
|
||||||
margin-top: 1.75rem;
|
margin-top: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
@ -200,17 +173,17 @@ body {
|
||||||
|
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
font-family: var(--font-family-code);
|
font-family: var(--font-family-code);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tsd-typography pre {
|
.tsd-typography pre {
|
||||||
padding: 1rem 1.25rem;
|
padding: 1rem 1.25rem;
|
||||||
border: 1px solid var(--color-accent);
|
border: 1px solid var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inline code */
|
/* Inline code */
|
||||||
.tsd-typography code:not(pre code) {
|
.tsd-typography code:not(pre code) {
|
||||||
padding: 0.15em 0.35em;
|
padding: 0.15em 0.35em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
#### Before (OpenAI)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { McpServer } from "@modelcontextprotocol/server";
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
function createServer() {
|
function createServer() {
|
||||||
|
|
@ -78,7 +78,7 @@ function createServer() {
|
||||||
{
|
{
|
||||||
title: "Shopping Cart",
|
title: "Shopping Cart",
|
||||||
description: "Display the user's shopping cart",
|
description: "Display the user's shopping cart",
|
||||||
inputSchema: z.object({ userId: z.string() }),
|
inputSchema: { userId: z.string() },
|
||||||
annotations: { readOnlyHint: true },
|
annotations: { readOnlyHint: true },
|
||||||
_meta: {
|
_meta: {
|
||||||
"openai/outputTemplate": "ui://view/cart.html",
|
"openai/outputTemplate": "ui://view/cart.html",
|
||||||
|
|
@ -126,7 +126,7 @@ function createServer() {
|
||||||
#### After (MCP Apps)
|
#### After (MCP Apps)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { McpServer } from "@modelcontextprotocol/server";
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
import {
|
import {
|
||||||
registerAppTool,
|
registerAppTool,
|
||||||
registerAppResource,
|
registerAppResource,
|
||||||
|
|
@ -144,7 +144,7 @@ function createServer() {
|
||||||
{
|
{
|
||||||
title: "Shopping Cart",
|
title: "Shopping Cart",
|
||||||
description: "Display the user's shopping cart",
|
description: "Display the user's shopping cart",
|
||||||
inputSchema: z.object({ userId: z.string() }),
|
inputSchema: { userId: z.string() },
|
||||||
annotations: { readOnlyHint: true },
|
annotations: { readOnlyHint: true },
|
||||||
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
|
_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.
|
- **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.
|
- **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
|
The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server.
|
||||||
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
|
## Lifecycle
|
||||||
|
|
||||||
|
|
@ -97,7 +93,7 @@ sequenceDiagram
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
|
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.
|
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.
|
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.
|
5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ registerAppTool(
|
||||||
"update-quantity",
|
"update-quantity",
|
||||||
{
|
{
|
||||||
description: "Update item quantity in cart",
|
description: "Update item quantity in cart",
|
||||||
inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
|
inputSchema: { itemId: z.string(), quantity: z.number() },
|
||||||
_meta: {
|
_meta: {
|
||||||
ui: {
|
ui: {
|
||||||
resourceUri: "ui://shop/cart.html",
|
resourceUri: "ui://shop/cart.html",
|
||||||
|
|
@ -127,14 +127,14 @@ registerAppTool(
|
||||||
{
|
{
|
||||||
title: "Read Data Bytes",
|
title: "Read Data Bytes",
|
||||||
description: "Load binary data in chunks",
|
description: "Load binary data in chunks",
|
||||||
inputSchema: z.object({
|
inputSchema: {
|
||||||
id: z.string().describe("Resource identifier"),
|
id: z.string().describe("Resource identifier"),
|
||||||
offset: z.number().min(0).default(0).describe("Byte offset"),
|
offset: z.number().min(0).default(0).describe("Byte offset"),
|
||||||
byteCount: z
|
byteCount: z
|
||||||
.number()
|
.number()
|
||||||
.default(MAX_CHUNK_BYTES)
|
.default(MAX_CHUNK_BYTES)
|
||||||
.describe("Bytes to read"),
|
.describe("Bytes to read"),
|
||||||
}),
|
},
|
||||||
outputSchema: DataChunkSchema,
|
outputSchema: DataChunkSchema,
|
||||||
// Hidden from model - only callable by the App
|
// Hidden from model - only callable by the App
|
||||||
_meta: { ui: { visibility: ["app"] } },
|
_meta: { ui: { visibility: ["app"] } },
|
||||||
|
|
@ -281,7 +281,10 @@ server.registerResource(
|
||||||
|
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
```ts source="./patterns.tsx#binaryBlobResourceClient"
|
```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];
|
const content = result.contents[0];
|
||||||
if (!content || !("blob" in content)) {
|
if (!content || !("blob" in content)) {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue