Compare commits

...
Author SHA1 Message Date
Olivier Chafik
6d9bdc7bab
Merge pull request #773 from modelcontextprotocol/repo-url-fix
Some checks failed
CI / Auto-fix formatting (push) Has been cancelled
CI / Build (Linux ARM64) (push) Has been cancelled
CI / Build (Linux x64) (push) Has been cancelled
CI / Build (Windows ARM64) (push) Has been cancelled
CI / Build (Windows x64) (push) Has been cancelled
CI / Build (macOS ARM64) (push) Has been cancelled
CI / Dependency isolation (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / Build (Windows WSL) (push) Has been cancelled
CI / Test git install (Linux ARM64) (push) Has been cancelled
CI / Test git install (Linux x64) (push) Has been cancelled
CI / Test git install (Windows ARM64) (push) Has been cancelled
CI / Test git install (Windows x64) (push) Has been cancelled
CI / Test git install (macOS ARM64) (push) Has been cancelled
Publish Any Commit / pkg-publish (push) Has been cancelled
Use the canonical git+https form for repository.url
2026-09-09 10:39:35 +01:00
Olivier Chafik
2be172defb Use the canonical git+https form for repository.url
npm normalizes the bare https URL to git+https://….git at publish time
and warns about it on every package. Storing the canonical form removes
the warning and matches what npm's trusted-publisher check compares
against the repository.
2026-09-08 19:37:47 +01:00
Olivier Chafik
352f6ced4d
CI: build the WSL job on ext4, skip the unused Chromium download, list tsconfig types (#772)
WSL job copies the checkout onto ext4 before building (9P was the cost: 605 s to 348 s); Playwright installs only the headless shell; every tsconfig lists its @types explicitly, a verified no-op on 5.9 and the prerequisite for TypeScript 7.
2026-09-08 19:18:07 +01:00
Olivier Chafik
ae4f7e0ad0 Sync the quickstart tsconfig snippets 2026-09-08 18:39:50 +01:00
Olivier Chafik
a0dcf16843 CI: build the WSL job on ext4, skip the unused Chromium download, list tsconfig types
The Windows WSL job is the critical path of every CI run (589 to 752 s).
Its checkout sits on the Windows drive, which WSL reaches over 9P, and
writing node_modules there is what makes npm ci take about three minutes
where the Linux job takes 23 s. The step now copies the tree onto the
distro's ext4 filesystem before installing and building, as Microsoft's
WSL guidance recommends for I/O-heavy work.

The e2e job downloaded the full Chromium build (about 165 MB) on every
run, but a headless run with no channel launches chromium-headless-shell
and never opens it. `--only-shell` skips that download.

Every tsconfig now lists the @types packages it uses (node, bun,
vite/client, dom-speech-recognition) instead of relying on TypeScript
auto-including everything under node_modules/@types. On TypeScript 5.9
this is a no-op: identical diagnostics across all 48 projects and
byte-identical emitted .d.ts files. It is a prerequisite for TypeScript 7,
which drops the auto-inclusion; with these lists the Go compiler passes
all 48 projects, so that upgrade becomes a version bump once TypeDoc
supports it.

Two cache-based approaches were evaluated and rejected: an npm cache for
the WSL job (npm ci is slow there because of 9P, not downloads, and a
prefix-restored cache grows without bound) and a Playwright browser cache
(Playwright's docs advise against it; the download is under 10 s).
2026-09-08 18:38:58 +01:00
Olivier Chafik
ae0fe55b84
Migration guide fixes and editor-visible deprecation for the 1.x handler form (#771)
Guide fact-checked claim by claim against the built package: three wording fixes, seven coverage gaps (including the double-registration guard now covering the base Protocol's progress/cancelled handlers), and the legacy setRequestHandler overload's @deprecated moved onto a documented call signature so editors report it.
2026-09-08 18:15:07 +01:00
Olivier Chafik
853c9f749c Migration guide fixes and editor-visible deprecation for the 1.x handler form
Guide: name @modelcontextprotocol/express in the server-author row, say
the two SDKs share no types rather than "do not interoperate" (they speak
the same wire protocol), note zod 4.0/4.1 lack ~standard.jsonSchema, list
the full 1.x-shaped extra, cover tool callbacks in the handler-context
bullet, document that the double-registration guard now covers the base
Protocol's notifications/progress and notifications/cancelled handlers,
mention ResourceNotFoundError, add the Express row to the import map, and
add a "What stays the same" section.

The @deprecated tag on the legacy setRequestHandler / setNotificationHandler
overload sat on the parameter inside an intersection type, which TypeScript
does not report. The overload now comes from LegacyRequestHandlerSetter /
LegacyNotificationHandlerSetter, named types with a documented call
signature, so editors flag the 1.x call and not the 2.x forms.

README links to the quickstart use the lowercase slug typedoc emits.
2026-09-08 18:01:18 +01:00
Olivier Chafik
12aa50b623
Add cross-version interop test against the published ext-apps 1.7.5 (#770)
Runs the published 1.7.5 App and AppBridge against the 2.x build over an in-memory transport in both pairings, via an npm alias devDependency and sdk 1.30. Complements wire-compat.test.ts: this one runs the real 1.x code and is the only test that catches a coordinated 2.x wire rename.
2026-09-08 16:17:27 +01:00
Olivier Chafik
218aef167e
Keep the 1.x handler registration forms as deprecated overloads (#769)
setRequestHandler(Schema, handler) and setNotificationHandler(Schema, handler) work on App and AppBridge as deprecated overloads forwarding to the 2.x method-keyed form; AppRequest, AppNotification and AppResult return as deprecated type aliases. Both removed in 3.0.
2026-09-08 16:17:02 +01:00
Olivier Chafik
5f6346ef86 Add cross-version interop test against published ext-apps 1.7.5
Install @modelcontextprotocol/ext-apps@1.7.5 under the npm alias ext-apps-v1
(plus @modelcontextprotocol/sdk@1.30.0, its peer) as devDependencies and run
the 2.x App against the 1.x AppBridge and vice versa over an in-memory
duplex, with each bridge fronting a real Client+McpServer of its own SDK
generation. Covers initialize, notifications, view-to-host requests,
tool/resource proxying, error codes, AbortSignal cancellation, teardown and
close.
2026-09-08 15:32:20 +01:00
Olivier Chafik
4eab52e372 Export the legacy handler types from the root entry
They appear in App's public setRequestHandler / setNotificationHandler
signatures, so TypeDoc's link validation needs them reachable from the
entry point.
2026-09-08 15:31:03 +01:00
Olivier Chafik
728b0bf38e Keep the 1.x handler registration forms as deprecated overloads
Consumers on 1.x wrote setRequestHandler(Schema, handler) and
setNotificationHandler(Schema, handler) with a whole-message Zod schema.
SDK 2.0 keys handlers by method name, so App and AppBridge now accept
that form as a third, deprecated overload: a small helper detects the
schema, extracts the method literal, forwards to the 2.x
(method, { params }, handler) form, and hands the legacy handler the
reassembled { method, params } message plus a 1.x-shaped extra
(signal, requestId, sessionId, _meta). A one-time console warning
points at the new form. The double-registration guard applies to both
forms.

The AppRequest, AppNotification and AppResult unions that 1.x fed to
the SDK's Protocol generics come back as deprecated type aliases, since
they leaked through the root entry and cost nothing to keep.

Both go away in 3.0.
2026-09-08 14:24:25 +01:00
Olivier Chafik
4cd4273947
SDK 2.0 fixups: lockfile, peers, handler guards, docs, wire-compat tests (#768)
Follow-up to #720 and #705: regenerated lockfile, ^2.0.0 peers with client required, types from @modelcontextprotocol/client, restored double-registration guard, deprecated raw-shape registerAppTool overload, migration guide, wire-compat tests, MCPB build from the local package.
2026-09-08 14:20:31 +01:00
Olivier Chafik
6d083cd69f Address review: MCPB build, guard the SDK's own handlers, doc fixes
- pdf-server build-mcpb.mjs packs the ext-apps build from this checkout
  into the staging dir instead of asking the registry for ^2.0.0, which
  is not published yet; CI's "Build MCPB bundle" step failed on ETARGET.
- Claim notifications/cancelled and notifications/progress in the
  MethodRegistry right after super() so a direct setNotificationHandler
  cannot silently replace the base Protocol's own handlers.
- Migration guide and add-app-to-server skill: custom methods use the
  three-argument setRequestHandler("m", { params }, handler) form (the
  two-argument form is only for spec methods); zod schemas live in
  @modelcontextprotocol/core, not client/server; it is
  toolInfo.tool.outputSchema that loosened in schema.json, and the
  recursive JSON-value def is nested under three definitions; core is a
  required peer that client already depends on.
- app-bridge.ts: fix "forthe"/"forresult" typos in @see links, update
  three JSDoc examples to the method-string setNotificationHandler form,
  move the UntypedHandlerSetter type below the import block (same in
  app.ts).
- wire-compat.test.ts: say the fixtures are 1.x-shaped, not captures.
2026-09-08 13:59:09 +01:00
Olivier Chafik
f722753c8f Finish .js import extensions and drop the zod 3 fallback test
Add explicit .js extensions to the relative imports in app.ts,
app-bridge.ts and their tests so the emitted declarations resolve under
NodeNext (#704). Replace the zod 3 JSON Schema fallback test, whose
code path was removed, with the remaining non-Standard-JSON-Schema
rejection case as its own test.
2026-09-08 13:59:09 +01:00
Olivier Chafik
cfdc0c725f fix: restore double-registration guard on App and AppBridge handlers
The base SDK Protocol silently replaces an existing request or
notification handler. v1's ProtocolWithEvents threw instead, so a stray
setRequestHandler / setNotificationHandler could not disconnect on*
handlers or addEventListener listeners. Restore that guard on both App
and AppBridge with a small MethodRegistry, keep replace semantics for the
on* setters via a protected replaceRequestHandler, and release methods
on removeRequestHandler / removeNotificationHandler.

Also:
- give oncreatesamplingmessage a getter, a replace warning and the same
  clearable shape as the other request setters
- let App register handlers for custom (non-spec) methods; the default
  branch of assertRequestHandlerCapability threw a misleading
  "No handler for method X registered" error
- move the RESOURCE_URI_META_KEY / RESOURCE_MIME_TYPE docs to
  src/constants.ts where the constants now live
- fix stale "inherited from Server" wording and point @see links at
  @modelcontextprotocol/client, which is what hosts install
2026-09-08 13:59:09 +01:00
Olivier Chafik
ea6b89c9e2 Add wire compatibility tests against captured 1.x traffic
Replay JSON-RPC messages recorded from ext-apps 1.7.x (sdk 1.x key
order and ids) through the 2.x App and AppBridge over a raw in-memory
transport and assert exactly what 2.x emits: the ui/initialize
handshake, params-less ui/notifications/initialized, tools/call in both
directions, and the -32002 to -32602 re-encoding on the host side. Also
check that AppBridge never sends an MCP initialize toward the View and
that the outer-client proxy works with a default (non-legacy) 2.x
Client/Server pair.
2026-09-08 13:58:37 +01:00
Olivier Chafik
c5b2543115 Document the 2.x install matrix and add a migration guide
Install instructions now list the split base MCP SDK packages at ^2.0.0
by role (core comes in transitively, server only for server authors),
require Node 20+, and state that the wire protocol is unchanged so 2.x
Views and 1.x hosts interoperate. The agent skills lose the exact
prerelease wording and add-app-to-server gains a v1 to v2 import
mapping. docs/migrate-to-2.md lists the breaking changes, the peer
matrix and the host-side error deltas.
2026-09-08 13:58:37 +01:00
Olivier Chafik
09bf2aae3c Drop the zod 3 JSON Schema fallback
The zod peer range is ^4.2.0, whose schemas implement
~standard.jsonSchema, so the lazy zod/v4 toJSONSchema fallback for
zod 3.25 is dead code. Re-export StandardSchemaWithJSON from the client
package instead of keeping a structurally identical local copy.
2026-09-08 13:58:37 +01:00
Olivier Chafik
0a477bb25d Use explicit .js extensions in relative imports
Consumers using moduleResolution NodeNext need explicit extensions in
the emitted declaration files. Covers every source file except app.ts,
app-bridge.ts and events.ts (and their tests), which are being reworked
separately.
2026-09-08 13:58:36 +01:00
Olivier Chafik
7f686ff2b4 Update dependency isolation check for the required client peer
The app-only consumer now also exercises the ./react entry. The
server-only consumer no longer asserts that client is absent from
node_modules (npm auto-installs required peers) but still checks that
the server entry does not bundle it. Surface npm and esbuild errors
instead of swallowing them.
2026-09-08 13:58:36 +01:00
Olivier Chafik
d6ebcca9a0 Accept raw zod shapes in registerAppTool as a deprecated overload
SDK v2 registerTool takes Standard JSON Schema objects (z.object(...))
and keeps a deprecated overload for the v1 raw-shape form. Mirror that
here so existing servers passing { field: z.string() } keep compiling;
the call is forwarded to the SDK, which auto-wraps the shape. Tests
exercise both forms end to end through a real McpServer and Client.
2026-09-08 13:58:36 +01:00
Olivier Chafik
040942b3af Import shared wire types from @modelcontextprotocol/client
spec.types.ts previously inferred CallToolResult, ContentBlock and
friends from core's Zod schemas via a local core-types module so that
the type surface stayed free of a client package edge. Now that client
is a required peer, take the types from it directly and delete the
indirection. Generated schemas are unchanged.
2026-09-08 13:58:22 +01:00
Olivier Chafik
50eb30ec8d Bump to 2.0.0 and widen SDK peer ranges to ^2.0.0
The client, core and server packages become ^2.0.0 peers and client is
now a required peer (App and AppBridge extend its Protocol class);
server stays optional for View-only consumers. Examples move to
zod ^4.2.0 to match the library peer range.

The root package is not yet published at 2.0.0, so the lockfile now
links the examples' @modelcontextprotocol/ext-apps dependency to the
repository root instead of a registry copy. This drops the stale 1.x
tarball (and its sdk 1.x peer) from the lockfile and makes
scripts/link-self.mjs a no-op.
2026-09-08 13:58:22 +01:00
Olivier Chafik
f28f5ddb3c Harden dependency isolation check and run it in CI
npm pack runs the prepare script for directory specs even with
--ignore-scripts and its output can pollute stdout, so locate the packed
tarball on disk instead of parsing --json output. Run npm with a minimal
environment so caller npm config cannot leak into the synthetic
consumers, and require exact versions for the consumers' dependencies.
Add a CI job that runs the check on Node 22.
2026-09-08 13:58:21 +01:00
Olivier Chafik
90123733bb test: regenerate lockfile (re-add nested @types/node entries) 2026-09-08 13:58:21 +01:00
Olivier Chafik
f9c671c323
Add explicit .js extensions to relative imports for NodeNext consumers (#705)
Emitted declarations used extensionless relative imports, so types failed to resolve under moduleResolution NodeNext/Node16 (#704).

Closes #704
2026-09-08 13:57:46 +01:00
Ken Jo
264d9cae44 fix: add explicit .js extensions to relative imports so declarations resolve under NodeNext/Node16
The emitted .d.ts copy relative specifiers verbatim, and the extensionless form
(e.g. `from "./events"`) doesn't resolve under Node16/NodeNext, hiding inherited
members such as `addEventListener` (TS2339). Appending `.js` matches the existing
SDK-import style and works with both bundler-mode type-checking and Bun.build.
Types-only; runtime is unaffected.

Fixes #704
2026-09-08 13:40:58 +01:00
Olivier Chafik
1a713173cc
Migrate to MCP SDK 2.0 with official Protocol and isolated role peers (#720)
App and AppBridge extend the public Protocol from @modelcontextprotocol/client; the iframe wire protocol (ui/initialize only) is unchanged. Peers move to the split SDK 2.0 packages with client and server as optional role peers. Fixups (lockfile, peer ranges, restored handler guards, docs, wire-compat tests) follow in a separate PR.

Co-authored-by: Andrew Khadder <andrew@manufact.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
Closes #702
2026-09-08 12:44:49 +01:00
Quentin Churet
10195ad918
docs: add Alpic Playground to supported clients (#729)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-12 18:04:54 +03:00
Enrico
4e4886c5ff chore: update MCP SDK packages to stable 2.0.0 2026-07-28 15:02:48 -07:00
Olivier Chafik
92f46a5745
chore: bump ext-apps to 1.7.5 (#724) 2026-07-23 12:25:07 +01:00
Enrico
c847bc0887 docs: align Protocol and role-specific setup 2026-07-22 15:37:22 -07:00
Enrico
e7ffe7c738 chore: update MCP SDK packages to beta.5 2026-07-22 15:24:49 -07:00
Olivier Chafik
7d498079e8
chore: clear npm audit findings (15 -> 3) (#725)
Non-breaking transitive updates via `npm audit fix`, plus a `sharp` major
bump. Takes `npm audit` from 15 findings (2 critical, 7 high, 4 moderate,
2 low) to 3 moderate.

Critical / high:

- shell-quote 1.8.3 -> 1.9.0 (GHSA-w7jw-789q-3m8p, GHSA-395f-4hp3-45gv)
- @babel/core 7.28.6 -> 7.29.7 (GHSA-4x5r-pxfx-6jf8)
- sharp ^0.34.5 -> ^0.35.3 (GHSA-f88m-g3jw-g9cj; libvips CVE-2026-33327,
  CVE-2026-33328, CVE-2026-35590, CVE-2026-35591)
- brace-expansion 1.1.16 / 2.1.2 (GHSA-3jxr-9vmj-r5cp)
- fast-uri 3.1.4 (GHSA-v2hh-gcrm-f6hx, GHSA-4c8g-83qw-93j6)
- hono 4.12.31 (8 advisories, incl. GHSA-88fw-hqm2-52qc, GHSA-hvrm-45r6-mjfj)
- linkify-it 5.0.2 (GHSA-22p9-wv53-3rq4, GHSA-v245-v573-v5vm)

Plus markdown-it 14.3.0, body-parser 2.3.0, type-is 2.1.0, undici 7.28.0
and vite 6.4.3.

sharp is a devDependency used only by tests/e2e/generate-grid-screenshots,
which is testIgnore'd unless GENERATE_SCREENSHOTS=1. The only breaking
change in 0.35 is the Node floor moving to >=20.9.0, and CI runs Node 22;
the API used (png / resize / toFile) is unchanged.

The 3 remaining findings are all the same advisory (GHSA-frvp-7c67-39w9,
path traversal in @hono/node-server's serve-static on Windows), reported
once each for @hono/node-server, @modelcontextprotocol/sdk and
@modelcontextprotocol/ext-apps. The fix is in @hono/node-server 2.0.5 but
sdk 1.29.0 pins ^1.19.9, so the range cannot reach it; that needs an
upstream bump. It is not reachable here either way — the SDK imports only
getRequestListener and serve from that package, never serve-static.
2026-07-22 19:34:30 +01:00
victor-openai
c55a3a231f
fix: preserve experimental capability settings (#721) 2026-07-22 19:28:57 +03:00
Enrico
7664bb1010 test: isolate optional SDK role dependencies 2026-07-20 11:29:43 -07:00
Felix Weinberger
43eed45404 Point SDK packages at the pkg.pr.new preview of typescript-sdk#2501
The Protocol-based migration needs the Protocol class exported from the
package roots, which lands in modelcontextprotocol/typescript-sdk#2501.
Pin the preview builds so the branch is installable; swap to the next
published beta once that PR is released.
2026-07-20 11:14:56 -07:00
Felix Weinberger
ccae093d1a Port App and AppBridge to the public v2 Protocol base class
Replaces the Client/Server subclassing from the v2 migration with direct
Protocol extension, now that the SDK exports Protocol from the client
package root. This restores the v1 wire contract on the iframe channel:
ui/initialize is the first and only handshake, so deployed hosts keep
working unchanged.

Deleted with the double handshake: the inner MCP initialize round-trip
and its pinned protocol version, the contentless inner capabilities and
their retro-registration plumbing, the oninitialized defineProperty
shadow, and the getCapabilities/getHostCapabilities split (v1
getCapabilities restored). Tests pin the wire order and assert no MCP
initialize is sent in either direction.

Note: package.json overrides point the SDK packages at locally built
tarballs carrying the Protocol export; replace with published versions
before any upstream PR.
2026-07-20 11:14:56 -07:00
Dominic Farolino
2ca6a59d2f
spec: sync object-src with default-src (#715) 2026-07-17 03:15:10 +03:00
Mel Ludowise
9778cf97a2
Make lazy-auth-server usable on mobile (#714)
The consent page shipped without a viewport meta tag, so mobile browsers
laid it out at a desktop width. Its Approve/Deny buttons were 37px tall
and nested a <button> inside an <a>, which is invalid HTML — interactive
content is not permitted inside a link.

- Add a viewport meta tag to the consent page
- Replace the <a><button> nesting with anchors styled as buttons
  (role="button" plus a Space-key handler to keep button keyboard
  semantics)
- Lay the actions out as a wrapping flex row with equal 56px tap targets
- Add touch-action: manipulation so a tap resolves to a click immediately
  rather than waiting on the double-tap-to-zoom gesture
- Suppress long-press text selection and the iOS callout menu on the
  buttons, scoped so the card's client/redirect details stay copyable
- Give buttons an :active style since removing the native tap highlight
  otherwise leaves no press feedback
- Apply the same 56px minimum and touch handling to the in-app view's
  buttons (the fullscreen toggle was ~33x33)
2026-07-16 17:00:17 +01:00
Andrew Khadder
f50ec6ebfb
fix: regenerate lockfile for npm 10 2026-07-14 17:16:27 -07:00
Andrew Khadder
f94cb38b0b
refactor: finish MCP SDK v2 migration 2026-07-14 17:07:30 -07:00
Andrew Khadder
a76e3a7691
refactor: migrate shared SDK surfaces to v2 2026-07-14 15:46:56 -07:00
Andrew Khadder
d19a1fb83a
refactor: migrate AppBridge to SDK v2 server 2026-07-14 15:34:33 -07:00
Andrew Khadder
59e6ffc6d8
refactor: migrate App to SDK v2 client 2026-07-14 15:02:25 -07:00
Andrew Khadder
209cfc6241
refactor: compose protocol-neutral event dispatch 2026-07-14 13:49:10 -07:00
Andrew Khadder
b383847bf2
chore: prepare SDK v2 dependencies 2026-07-14 13:35:51 -07:00
Copilot
cf87f2a2c2
ci: fix README prettier build failure (#698)
* Initial plan

* fix: apply Prettier formatting to README table row

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-07 21:29:16 +03:00
Andrew Khadder
cd969dbc31
docs: add mcp-use inspector badge to supported clients (#650) 2026-07-07 21:05:17 +03:00
Cameron Yick
f0d9582eaa
spec: add missing HostCapabilities fields (#653) 2026-07-07 21:02:07 +03:00
Ensar Gök
ea8775dcb5
docs: API Docs link for ext-apps/server (#697)
Updated API documentation link for ext-apps/server.
The link `/server.html` does not exists.
2026-07-07 20:43:37 +03:00
Aqil Aziz
fa12744908
docs: fix map-server readme and code of conduct links (#657) 2026-06-17 18:51:57 +03:00
Olivier Chafik
ca1d29894f
chore: bump ext-apps to 1.7.4 (#685)
* chore: bump ext-apps to 1.7.4

* fix: resolve all npm audit vulnerabilities

- Remove unused vitest devDependency from basic-host (critical advisory,
  package was never referenced by any test or config)
- systeminformation ^5.31.5 -> ^5.31.6 (command injection, GHSA-hvx9-hwr7-wjj9)
- Lockfile-only bumps: devalue 5.8.1, fast-uri 3.1.2, hono 4.12.23,
  ip-address 10.2.0, express-rate-limit 8.5.2, postcss 8.5.15, qs 6.15.2,
  svelte 5.56.1

npm audit now reports 0 vulnerabilities.
2026-06-04 22:10:06 +01:00
Olivier Chafik
0ffe16781e
lazy-auth-server: support mounting under a base path (#683)
* lazy-auth-server: support mounting under a base path

createApp() previously built every advertised URL (AS metadata
endpoints, PRM resource, WWW-Authenticate resource_metadata, consent
approve link, elicitation callbacks) from the bare origin, so mounting
the app inside another Express app — app.use("/lazy-auth",
createApp()) — advertised URLs that pointed at the host's root and
collided with the host's own OAuth endpoints.

Derive the public base URL from Express's req.baseUrl instead (empty
when standalone, so standalone behavior is unchanged), and treat
PUBLIC_URL as including any mount path. Document the mounting pattern,
including the root well-known rewrite hosts must add for RFC 8414/9728
path-insertion discovery.

* lazy-auth-server: derive DIST_DIR from import.meta.url

import.meta.filename and import.meta.dirname are undefined in some
module-VM contexts (e.g. importing this package from jest), which made
createApp() throw at import time. import.meta.url is defined
everywhere; derive the dist directory from it instead.
2026-06-04 19:47:57 +01:00
Mel Ludowise
a990780293
Require PKCE and bind redirect_uri in lazy-auth-server token exchange (#681)
* Require PKCE and bind redirect_uri in lazy-auth-server token exchange

Hardens the example's mock authorization server:

- /authorize now requires a PKCE S256 code_challenge (the MCP auth
  spec mandates PKCE for clients; previously a code issued without a
  challenge skipped verification at the token endpoint)
- /token rejects authorization-code exchanges where a provided
  redirect_uri does not match the authorization request (RFC 6749
  §4.1.3); OAuth 2.1 clients that omit it still work, relying on the
  now-mandatory PKCE binding

* Enforce single-use authorization codes

Track redeemed code IDs (jti) in memory until the code's own 5-minute
expiry; replaying a code at the token endpoint now fails with
invalid_grant (RFC 6749 §4.1.2). Also documents why wildcard CORS is
intentional for this demo (browser-based hosts must read
WWW-Authenticate; no ambient credentials exist to protect).
2026-06-02 13:30:49 +01:00
Olivier Chafik
7d4434e991
chore: bump ext-apps to 1.7.3 (#680)
No SDK API changes since 1.7.2.

Examples:
- Add lazy-auth-server example (#679)
- Publish lazy-auth-server to npm (added to publish workflows)

Also syncs example dependency ranges to ^1.7.0 (output of npm run bump).
2026-06-01 12:10:47 +01:00
Mel Ludowise
fcc4b03bde
Add lazy-auth-server example (#679)
Adds an example MCP App server demonstrating lazy (on-demand) OAuth:
the server connects and lists tools without authentication, and only
prompts for auth when a protected tool is called, by responding 401
with a WWW-Authenticate header pointing at protected-resource metadata.

- Public `show_auth_button` app calls the protected `get_secret` tool
  via callServerTool; the host runs the OAuth flow on 401 and retries
- Embedded mock authorization server (authorization-code + PKCE,
  short-lived HS256 tokens, refresh + session revocation) so the whole
  flow runs from a single process
- TTL-scoped endpoint paths (/ttl/<seconds>/mcp) let slow or automated
  clients request longer-lived tokens per connection, threaded through
  OAuth via RFC 8707 resource indicators
- `elicit_url` / `elicit_by_error` tools demonstrating URL elicitation
2026-06-01 11:19:53 +01:00
Olivier Chafik
9a37ad7182
chore: bump ext-apps to 1.7.2 (#660)
No SDK API changes since 1.7.1.

Examples:
- pdf-server: switch pdf-lib to maintained @cantoo/pdf-lib fork (#651)
- bump example transitives to latest patches (#658)
2026-05-15 19:01:25 +01:00
Olivier Chafik
1d091e2132
chore: bump example transitives to latest patches (#658)
- vite-plugin-solid 2.11.10 -> 2.11.12 (basic-server-solid)
- babel-preset-solid 1.9.10 -> 1.9.12
- babel-plugin-jsx-dom-expressions 0.40.3 -> 0.40.6
- vite-prerender-plugin 0.5.12 -> 0.5.13 (basic-server-preact)
- @modelcontextprotocol/ext-apps 1.0.0 -> 1.7.1 (lockfile had a stale
  registry pin of the repo's own published package that was never bumped
  alongside package.json's version field)

Tighten the basic-server-solid range to ^2.11.12 to track the patch
series with current Solid HMR fixes.
2026-05-08 15:55:06 +01:00
Olivier Chafik
3cf5b588a1
chore(pdf-server): switch pdf-lib to maintained @cantoo/pdf-lib fork (#651)
The original pdf-lib package has been unmaintained since Nov 2021.
@cantoo/pdf-lib is an actively maintained, API-compatible fork with
ongoing releases and accumulated bugfixes.

Also updates test assertions from the deprecated PDFNumber.value() to
PDFNumber.asNumber() per the fork's API guidance.
2026-05-08 13:01:54 +01:00
Olivier Chafik
0008d3b78f
chore: bump ext-apps to 1.7.1 (#643) 2026-04-27 16:16:33 +01:00
Olivier Chafik
ba31be1f91
perf(pdf-server): lazy form extraction via range transport + incremental viewer scans (#639)
* perf(pdf-server): lazy form extraction via range transport + incremental viewer scans

Server: display_pdf now opens the document via PDFDataRangeTransport
(disableAutoFetch) and only runs the per-page form/annotation walk when
getFieldObjects() is non-empty, so form-free PDFs are probed with ~10-25%
of bytes instead of a full download. The unused viewFieldInfo Map is
removed.

Viewer: getDocument sets disableAutoFetch/disableStream; baseline
annotation scan and field-name mapping run lazily per rendered page
instead of walking every page after load, so first paint no longer
schedules a full-file pull.

E2E: new range-counting HTTPS fixture (W-9 for forms, generated
text+image PDF for no-forms) with stallAfterBytes control, and four
regression tests asserting form fields are returned, <30% served on
no-forms display_pdf, first page renders while later ranges are stalled,
and overlap stays bounded.

* fix(pdf-server): handle separated field/widget trees in extractFormSchema

pdfjs getFieldObjects() returns the full field-tree array. For PDFs with
a separated structure (pdf-lib, some authoring tools) the typed widget
sits at fields[1+] behind a typeless container at fields[0]; the previous
code only inspected fields[0] and skipped them all. Pick the first entry
with a non-empty type instead.

Makes the e2e forms.pdf fixture fully generated (no checked-in third-party
asset on the hot path); fw9.pdf stays as a unit-test fixture for the
hierarchical/XFA case.

* fix(pdf-server): address range-transport hangs and lazy-scan tombstone loss

PdfCacheRangeTransport:
- abort() is a no-op stub on PDFDataRangeTransport (it's the hook pdfjs
  calls *on* the transport, not an upstream error channel). Expose a
  .failed promise that rejects on the first fetch error and race every
  pdfjs await against it in display_pdf, so transient network errors
  surface into the existing catch instead of hanging the tool call.
- pdfjs coalesces adjacent missing chunks into one unbounded
  requestDataRange; readPdfRange clamps each call to MAX_CHUNK_BYTES.
  Loop and deliver in slices so every requested chunk is marked loaded.

Viewer (mcp-app.ts):
- The lazy per-page baseline scan left pdfBaselineAnnotations incomplete,
  so persistAnnotations and getAnnotatedPdfBytes silently dropped
  restoredRemovedIds tombstones for unvisited pages. Union those ids into
  the computed diff and removedRefs.

Test fixture: release stalled handlers before resetStats/close so a
failing stalled test doesn't hang afterAll; fail fast if started with
NODE_ENV=production. NODE_TLS_REJECT_UNAUTHORIZED scope documented (full
per-process scoping needs a validateUrl localhost allow, tracked
separately).

* test(pdf-server): bridge coverage gaps and switch fixture to gated loopback HTTP

PdfCacheRangeTransport.deliver(): pdf.js's reader is keyed by the
original begin and removed after one delivery, so accumulate slices and
call onDataRange once with the full buffer (the previous multi-call
approach threw inside pdfjs). Covered by a new integration test that
drives getDocument()/getPage(1) on a >1MB PDF through a clamping
in-memory readPdfRange.

validateUrl: allow http://127.0.0.1|localhost|[::1] only when
PDF_SERVER_ALLOW_LOOPBACK_HTTP is set, so a remote deploy can't be made
to probe its own ports. Covered by env-on/off unit tests.

Fixture switched to plain HTTP (no openssl, no
NODE_TLS_REJECT_UNAUTHORIZED). Adds /error.pdf (500s after 50KB) and two
e2e tests: page 2 renders after stall release (>512KB object path), and
display_pdf returns gracefully on mid-load 500. Existing <30% test now
samples stats before the iframe loads.

New unit tests: display_pdf returns (not hangs) on mid-load fetch
failure via in-memory MCP client; computeDiff/serializeDiff contract
tests pinning the restoredRemovedIds tombstone-preservation behaviour.

* test(pdf-server): e2e for restoredRemovedIds tombstone preservation through viewer

Adds /with-native-annot.pdf fixture (2 pages, native /Text annot on
page 2) and a Playwright test that drives delete on page 2 → iframe
reload → interact add_annotations on page 1 (persist with page 2
unscanned) → assert localStorage diff still contains the removed id →
page 2 still shows it gone. Covers the mcp-app.ts glue the unit tests
can't reach.

* refactor(pdf-server): simplify per round-2 review and fix false-pass test

server.ts: extract probeFormFields() so display_pdf calls one helper
instead of inlining 40 lines of transport/orFail plumbing; make
extractFormSchema's fieldObjects param required (the optional branch
only existed for a test helper).

server.test.ts: the >1MB integration test now forces the image XObject
fetch via getOperatorList() and asserts max requestDataRange span >
MAX_CHUNK_BYTES, so it can't go vacuous. Dedupe makeRandomJpeg by
importing from the fixture.

mcp-app.ts: restore per-annotation try/catch isolation in the lazy
baseline scan (a throw was skipping AnnotationLayer.render); only
carry forward restoredRemovedIds while the baseline scan is incomplete
so a stale id can't pin dirty=true once every page is scanned.

tests: bump fixture JPEG to 1.1MB; consolidate 7 e2e tests to 4 (merge
overlap into the byte-budget test, merge stall+page-2, drop the
error-e2e duplicate of the unit integration test); delete
run-fixture.mjs; drop /error.pdf and ERROR_AFTER_BYTES; rename
tombstone test's describe and clarify page-2 covers the viewer
transport.

* fix(pdf-server): preserve partial probeFormFields results when extractFormFieldInfo throws

* test(pdf-server): fix tombstone e2e — deleted native annotations become a cleared card, not removed from DOM

* test(pdf-server): mark tombstone e2e as fixme — needs basic-host iframe-reload replay support

* test(pdf-server): link tombstone fixme to tracking issue #642
2026-04-27 15:32:53 +01:00
Olivier Chafik
30a78b60b4
perf(pdf-server): share cache across server instances and dedupe form parsing (#637)
* perf(pdf-server): share cache across server instances and dedupe form parsing

In stateless HTTP deployments createServer() is called per request, so
the per-instance pdfCache and the 4 MB viewer HTML were discarded after
every call. Hoist both to module scope.

Also refactor extractFormSchema/extractFormFieldInfo to accept an
already-parsed PDFDocumentProxy so display_pdf downloads and parses the
PDF once instead of twice.

* perf(pdf-server): cap total cache bytes with LRU eviction

The module-level sharedPdfCache could grow unbounded within the 60s
lifetime window under a burst of distinct URLs. Track running total
bytes and evict least-recently-used entries on insert when it would
exceed CACHE_MAX_TOTAL_BYTES (256MB). getCacheEntry now bumps the
accessed entry to the end of insertion order so eviction targets the
LRU entry rather than the oldest insert.

createPdfCache takes an optional maxTotalBytes for testability.
2026-04-24 15:57:58 +01:00
Cameron Yick
7344d4c966
fix(docs): update contributing guide on package preview (#601) 2026-04-24 11:36:43 +01:00
Suriya Prakash
0f6d653b4a
test: add unit tests for buildAllowAttribute (#541) 2026-04-24 11:36:00 +01:00
Maurizio Albari
0b83679909
fix(qr-server): pass host/port to FastMCP for Docker compatibility (#372)
When FastMCP is created with the default host=127.0.0.1, MCP SDK 1.26+
automatically enables DNS rebinding protection that only allows requests
from localhost. This breaks Docker where requests come from
host.docker.internal, causing 421 Invalid Host header errors.

By passing host=HOST (0.0.0.0 for Docker), DNS rebinding protection
is not auto-enabled, allowing requests from any host.
2026-04-24 11:35:13 +01:00
Olivier Chafik
77b8daa1e8
test: add unit coverage for PostMessageTransport source validation (#536)
* test: add unit coverage for PostMessageTransport source validation

PostMessageTransport is the iframe↔host communication layer and contains
the primary security boundary (event.source validation). Previously it
had zero unit coverage — only exercised indirectly via E2E tests, where
the cross-app injection test is a no-op due to cross-origin contentDocument
restrictions.

Covers 18 test cases across:
- Source validation: trusted source delivers, untrusted/null sources
  dropped silently (no onerror DoS vector), transport survives rejection
- Message format: valid requests/notifications delivered; ambient
  non-JSON-RPC traffic (devtools, extensions) silently ignored;
  malformed jsonrpc:'2.0' payloads surface via onerror
- send(): posts to configured target with '*' targetOrigin
- Lifecycle: start() registers listener, close() removes it and fires
  onclose, post-close messages ignored, multi-transport isolation

Uses a minimal in-memory window stub rather than jsdom/happy-dom —
consistent with the existing bun test setup which has no DOM configured.

Note: the transport validates event.source (window identity), NOT
event.origin. send() posts with '*'. Origin is intentionally not part
of this transport's trust model.

* fix: resolve TS diagnostics in message-transport tests

- TS2352: cast `{ id: string }` through `unknown` before
  `MessageEventSource` (lines 366, 370) — no structural overlap
- TS80007: drop ineffective `await` on bun's `.resolves` matcher
  (line 355) — it returns void, not Promise<void>

* docs: clarify origin validation lives in sandbox proxy, not transport
2026-04-24 11:27:32 +01:00
Olivier Chafik
64b4fa16de
chore: bump ext-apps to 1.7.0 (#628) 2026-04-21 16:19:22 +01:00
Olivier Chafik
3e9950062b
build: add npm run bump; parallel npm-publish approval (#568)
* ci: automate release process with workflow-driven version bumps and PR flow

Adds a Release workflow that bumps all package versions, generates release
notes, and opens a PR. Merging the PR auto-tags and creates the GitHub
Release, which triggers npm-publish. Also adds `npm run bump` for local use
and a release.yml config for categorized auto-generated notes.

* ci: simplify release workflow and flatten npm-publish approval chain

- Drop auto-generated RELEASES.md prepending (notes are written manually in PR)
- Flatten npm-publish job dependencies so all publish jobs wait for approval
  together instead of sequentially, reducing 3 approval clicks to 1
- Remove Release environment from publish-mcpb (only needs GH_TOKEN)
- Use --generate-notes for GitHub Release creation

* fix: address review findings in release automation

- release.yml: use RELEASE_TOKEN PAT for gh release create so the
  release:published event triggers npm-publish (GITHUB_TOKEN events
  don't cascade to other workflows)
- release.yml: checkout merge_commit_sha in tag job instead of ref:main
  to avoid tagging later commits
- release.yml: checkout ref:main in prepare job so dispatching from a
  feature branch doesn't pollute the release PR
- release.yml: constrain preid to type:choice[beta] to match
  npm-publish.yml's tag detection
- release.yml: drop -f on push so re-runs fail loudly instead of
  clobbering manual RELEASES.md edits; drop pr-edit fallback
- release.yml: ensure release label exists before gh pr create
- release.yml: make tag push idempotent for re-runs after partial
  failure
- bump-version.mjs: update workspace dependency ranges so major bumps
  don't leave examples pointing at the old major
- bump-version.mjs: run npm install --package-lock-only so npm ci
  on the release PR doesn't fail on lockfile mismatch
- npm-publish.yml: add npm-tag detection to publish-examples so beta
  example releases don't overwrite latest
- .github/release.yml: use [bot] suffix in author excludes so
  dependabot PRs are actually filtered from release notes
- CONTRIBUTING.md: document RELEASE_TOKEN setup; fix npm run -- syntax

* style: auto-fix prettier formatting

* build: add npm run bump; parallel npm-publish approval

- scripts/bump-version.mjs (+ npm run bump): bumps root and all workspace
  package versions, updates examples' ^X.Y.0 dependency range on the root
  package (matters on major bumps), and refreshes package-lock so npm ci
  accepts the release PR.
- npm-publish.yml: all publish jobs now depend directly on [build, test]
  instead of chaining, so they enter "waiting for approval" together and a
  single Review-deployments click approves the lot. Prerelease versions
  (any X.Y.Z-…) publish under --tag beta so they don't take latest.
- CONTRIBUTING.md: release steps now use npm run bump.

Dropped from the original change: the workflow_dispatch release.yml
(required a long-lived PAT to cascade events), label-based
.github/release.yml auto-notes (PRs aren't labeled today), and the
release-X.Y npm-tag for non-main branches (no backport branches yet).

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-21 16:03:27 +01:00
Olivier Chafik
cc02e17e21
fix(react): StrictMode cleanup + relax late-handler guard for re-reg (#631)
* fix(react): close App on useApp unmount; relax late-handler guard for re-registration

useApp's effect cleanup now closes the App (and its transport) instead
of only flipping a `mounted` flag. Under React StrictMode dev
double-invoke, the abandoned first instance's PostMessageTransport
otherwise keeps a window `message` listener and receives every host
postMessage alongside the second instance.

The late-handler guard from #629 now tracks `_everHadListener` per
event and only flags the *first* registration of a one-shot event
post-handshake. Re-registration (React useEffect cleanup + re-add on
dep change) of an event that had a pre-connect handler is silent,
including under `strict: true`.

* feat(app-bridge): warn on second ui/initialize

If a View double-mounts (e.g. React StrictMode in dev) without closing
the previous App, AppBridge now logs a console.warn when it receives a
second ui/initialize. Behavior is unchanged — it still responds and the
latest appInfo/appCapabilities replace the previous values — this is
purely a diagnostic for host implementers.
2026-04-21 15:44:57 +01:00
Olivier Chafik
9658678eaa
refactor(app): unify pre-handshake guard to console.warn (#630)
_assertInitialized used console.error while _assertHandlerTiming and the
AppBridge override used console.warn. Align all three on console.warn for
the non-strict path; strict still throws.

Tests: hoist the console.warn spy to beforeEach/afterEach in the
pre-handshake describe to avoid bun:test spy stacking now that all three
guards target the same console method.
2026-04-21 15:06:49 +01:00
Olivier Chafik
6fa5d4e6ed
feat(app): warn when one-shot handlers are registered after connect() (#629)
Mirrors the outbound-side guard from #623: registering a toolinput /
toolinputpartial / toolresult / toolcancelled handler after the
ui/initialize handshake has completed now logs a console.warn (or
throws under { strict: true }), since the host may have already fired
the notification by then. hostcontextchanged is exempt — late
listeners for repeating events are legitimate.

Covers both the on* setters and addEventListener.
2026-04-21 14:50:17 +01:00
Olivier Chafik
9d68315720
feat(app): default Zod to jitless; add allowUnsafeEval opt-out (#618)
Zod v4's object parser compiles a fast path with `new Function()`, which
violates CSP without `unsafe-eval`. Hosts that enforce the spec's default
CSP (e.g. VS Code) cannot load any View that parses an incoming message —
the SDK's generated schemas trip this on the very first ui/initialize
roundtrip.

The App constructor now sets `z.config({ jitless: true })` by default so
Views work out of the box under strict CSP. New `AppOptions.allowUnsafeEval`
(default false) skips this for Views that know their host permits
`unsafe-eval` and want Zod's faster JIT path.

Addresses #374. Also relevant to #199 (threejs-server has a separate
`new Function()` use in its own shader compiler that this does not fix).
2026-04-21 14:49:09 +01:00
Olivier Chafik
5dfc88f113
docs(types): drop stale resourceUri JSDoc referencing deprecated flat key (#626) 2026-04-21 14:48:52 +01:00
Jonathan Hefner
b170150a82
Add examples policy to CONTRIBUTING.md (#550)
New `examples/` are costly to review and maintain across SDK changes.
Add a policy section stating that example PRs from outside contributors
are not accepted, and redirect contributors to open an issue instead.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 13:53:58 +01:00
Olivier Chafik
989ddc7568
chore: bump vite, hono, @hono/node-server to patched versions (#616)
Lockfile-only bumps to address npm audit findings:
- vite 6.4.1 -> 6.4.2 (high)
- hono 4.12.9 -> 4.12.14 (moderate)
- @hono/node-server 1.19.11 -> 1.19.14 (moderate)

All within existing semver ranges; no package.json changes needed.
2026-04-21 13:53:20 +01:00
Olivier Chafik
9a75b891b2
chore: drop GitHub issue URLs from pre-handshake guard log messages (#625)
The runtime console.error/warn strings in App._assertInitialized and
AppBridge.replaceRequestHandler linked to a tracking issue. Issue URLs
in runtime logs are brittle and send developers to a discussion thread
rather than actionable docs; the message itself already says what to do.
JSDoc @see links retained.
2026-04-21 13:50:10 +01:00
Olivier Chafik
4995746759
fix(types): forbid csp/permissions on McpUiToolMeta via never (#624)
* fix(types): forbid csp/permissions on McpUiToolMeta via never

Hosts read _meta.ui.csp and _meta.ui.permissions from the resource
(resources/read content item, with resources/list entry as fallback),
never from the tool. Typing them as `never` on McpUiToolMeta gives
TypeScript users a compile-time error pointing at McpUiResourceMeta
instead of silently accepting and ignoring them.

Companion to anthropics/apps#38705 (host-side console.warn).
Refs anthropics/claude-ai-mcp#40.

* drop misleading #40 link from McpUiToolMeta.csp JSDoc
2026-04-21 13:49:07 +01:00
Olivier Chafik
3fb521b71f
spec: Add tool registration for Apps, to be called by Host (WebMCP-style!) (#72)
* feat: Add tool registration and bidirectional tool support

This PR adds comprehensive tool support for MCP Apps, enabling apps
to register their own tools and handle tool calls from the host.

- Add `registerTool()` method for registering tools with input/output schemas
- Add `oncalltool` setter for handling tool call requests from host
- Add `onlisttools` setter for handling tool list requests from host
- Add `sendToolListChanged()` for notifying host of tool updates
- Registered tools support enable/disable/update/remove operations

- Add `sendCallTool()` method for calling tools on the app
- Add `sendListTools()` method for listing available app tools
- Fix: Use correct ListToolsResultSchema (was ListToolsRequestSchema)

- Add comprehensive tests for tool registration lifecycle
- Add tests for input/output schema validation
- Add tests for bidirectional tool call communication
- Add tests for tool list change notifications
- All 27 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* nits

* Update apps.mdx

* feat: Add automatic request handlers for app tool registration

Implement automatic `oncalltool` and `onlisttools` handlers that are
initialized when apps register tools. This removes the need for manual
handler setup and ensures tools work seamlessly out of the box.

- Add automatic `oncalltool` handler that routes calls to registered tools
- Add automatic `onlisttools` handler that returns full Tool objects with JSON schemas
- Convert Zod schemas to MCP-compliant JSON Schema using `zod-to-json-schema`
- Add 27 comprehensive tests covering automatic handlers and tool lifecycle
- Test coverage includes error handling, schema validation, and multi-app isolation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* type updates

* fix: Ensure tools/list returns valid JSON Schema for all tools

- Always return inputSchema as object (never undefined)
- Keep filter for enabled tools only in list
- Update test to match behavior (only enabled tools in list)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* type updates

* rm zod-to-json-schema

* fix: Update RegisteredTool to use 'handler' instead of 'callback' (SDK API change)

* refactor: Rename sendCallTool/sendListTools to callTool/listTools

Avoid double-verb naming pattern for consistency with existing API.

* feat: Add screenshot and click support to SDK

- Add McpUiScreenshotRequest/Result and McpUiClickRequest/Result types
- Add onscreenshot and onclick handlers to App class
- Add screenshot() and click() methods to AppBridge class
- Generate updated Zod schemas

* feat(map-server): Enable registerTool with navigate-to and get-current-view

- Uncomment and fix the navigate-to tool for animated navigation
- Add get-current-view tool to query camera position and bounding box
- Add flyToBoundingBox function for smooth camera animation
- Add setLabel function for displaying location labels

* feat(pdf-server): Add widget interaction tools

- get-document-info: Get title, current page, total pages, zoom level
- go-to-page: Navigate to a specific page
- get-page-text: Extract text from a page
- search-text: Search for text across the document
- set-zoom: Adjust zoom level

* feat(shadertoy-server): Add widget interaction tools

* feat(examples): Add widget interaction tools to budget-allocator, shadertoy, wiki-explorer, and threejs

budget-allocator:
- get-allocations: Get current budget allocations
- set-allocation: Set allocation for a category
- set-total-budget: Adjust total budget
- set-company-stage: Change stage for benchmarks
- get-benchmark-comparison: Compare against benchmarks

shadertoy:
- set-shader-source: Update shader source code
- get-shader-info: Get shader source and compilation status
- Sends errors via updateModelContext

wiki-explorer:
- search-article: Search for Wikipedia articles
- get-current-article: Get current article info
- highlight-node: Highlight a graph node
- get-visible-nodes: List visible nodes

threejs:
- set-scene-source: Update the Three.js scene source code
- get-scene-info: Get current scene state and any errors
- Sends syntax errors to model via updateModelContext

* feat: Add expand-node tool to wiki-explorer and update tool descriptions

Wiki Explorer:
- Add expand-node tool - the critical missing tool for graph exploration
- Claude can now programmatically expand nodes to discover linked articles

Server descriptions updated to mention widget tools:
- map-server: navigate-to, get-current-view
- pdf-server: go-to-page, get-page-text, search-text, set-zoom, get-document-info
- budget-allocator: get-allocations, set-allocation, set-total-budget, etc.
- shadertoy: set-shader-source, get-shader-info
- wiki-explorer: expand-node, search-article, highlight-node, etc.

All descriptions now mention 'Use list_widget_tools to discover available actions.'

* refactor: Simplify tool descriptions - remove client implementation details

The server tool descriptions now just mention that widgets are interactive
and can be controlled, without teaching the model about list_widget_tools
(which is the client's responsibility to teach).

Before: 'The widget exposes tools: X, Y, Z. Use list_widget_tools to discover...'
After:  'The widget is interactive and exposes tools for X and Y.'

* Remove ui/screenshot and ui/click protocol methods

These will be proposed separately from the app tool registration changes.

* fix(app): auto-register tools capability in registerTool

When registerTool is called before connect() on an App created without
explicit tools capability, setRequestHandler's capability assertion
would throw, breaking app initialization at module load.

Auto-register { tools: { listChanged: true } } on first registerTool
call (pre-connect only), mirroring McpServer.registerTool behavior.

Fixes pdf-annotations e2e failures where the PDF canvas never rendered
because registerTool threw at module scope.

* pdf-server: replace app-tools with 1:1 interact command mapping

Converts the 5 placeholder app-registered tools into 12 tools that map
directly to the server's interact commands (navigate, search, find,
search_navigate, zoom, add_annotations, update_annotations,
remove_annotations, highlight_text, fill_form, get_text, get_screenshot)
plus the existing get-document-info.

Implementation stays DRY by dispatching through the existing
processCommands() handler — each tool callback constructs a PdfCommand
and runs it via a small runCommand() wrapper. For get_text and
get_screenshot, the page-data collection is extracted from
handleGetPages into a shared collectPageData() helper so results can
be returned directly instead of round-tripping through the server.

Tool names and zod schemas mirror the interact command parameter shapes
from server.ts so the model sees the same surface whether it goes
through interact or app-tools. The server-side interact tool is
unchanged and remains available for hosts without app-tool support.

* chore: drop unused test vars and stale Request/Result imports

* docs(spec): address review feedback on app tool registration

- Add outputSchema to Tool struct (matches SDK + core MCP)
- Reword "black box" bullets: drop stale screenshots reference, clarify no DOM access
- Clarify push vs pull contrast with explicit setWidgetState + ui/update-model-context refs
- Add Task-reference future-work bullet for tools outliving render lifecycle

* docs(spec): clarify app DOM is not host-introspectable; frame tools as pull complementing update-model-context push

* fix(app): emit title and omit absent outputSchema in tools/list

* feat(app): accept Standard Schema in registerTool/registerAppTool

App.registerTool and registerAppTool now accept any schema implementing
the Standard Schema spec (Zod ≥3.25, ArkType, Valibot, …) instead of
zod-only. Uses ~standard.validate / ~standard.jsonSchema directly, so
app.ts no longer imports zod at runtime — zod is now an optional peer.

Mirrors the StandardSchemaWithJSON type from
modelcontextprotocol/typescript-sdk#1689 so bumping to SDK v2 is a
drop-in import swap. Adds @standard-schema/spec (types-only) and a
hand-rolled non-zod test.

* docs(spec): reference core MCP Tasks for long-running app tools

App tools/call MAY be task-augmented per the core MCP Tasks utility
(spec 2025-11-25). Adds a Long-Running App Tools subsection: hosts proxy
tasks/* while the app is mounted; apps SHOULD delegate long-lived work
to the server so the task survives iframe teardown. Removes the stale
'future extension' bullet — Tasks is shipped.

* docs(spec): note Schema Validation accepts any Standard Schema library

* chore: clean unused-var and no-op-await diagnostics in app.ts and tests

* refactor(app): fold error prefix into validateStandardSchema; clarify registerAppTool runtime caveat

* chore(typedoc): mark RequestHandlerExtra as intentionally not exported

AppToolCallback (introduced with the Standard Schema migration) references
the local RequestHandlerExtra alias in its parameter type. The alias is a
private Parameters<> derivation and isn't part of the public API surface,
so add it to intentionallyNotExported alongside AppOptions/MethodSchema.

* fix(app): restore zod v3.25 compat via lazy z.toJSONSchema fallback

zod v3.25.x implements ~standard.validate but not ~standard.jsonSchema,
so constraining to StandardSchemaWithJSON broke the ^3.25.0 peer range.
Widen registerTool to StandardSchemaV1 and fall back to a lazy zod/v4
import for serialization when ~standard.jsonSchema is absent and
vendor==='zod'. Non-zod schemas without jsonSchema get a clear error.

* docs(spec): slim Long-Running App Tools to a pointer into core MCP Tasks

* chore: add TODO(sdk-v2) marker in standard-schema.ts

* fix(app): align registerTool runtime with declared types

- Handler reads registeredTool.{input,output}Schema so update() takes effect
- Call cb(extra) when no inputSchema, matching AppToolCallback<undefined>
- Throw on duplicate name; guard list_changed pre-connect; notify on register
- Drop misleading zod optional peer (still required by generated/schema.ts)
- examples: fix pdf search/find stale match count; threejs set-scene-source re-render
- spec: Zod → Standard Schema; clarify tools availability via tools/list after init

* feat(app): type structuredContent from outputSchema in registerTool

AppToolCallback gains an Out param: when outputSchema is provided,
structuredContent is required and typed via StandardSchemaV1.InferOutput,
with an `isError: true` escape hatch. Without outputSchema, return type
is unchanged (CallToolResult). Uses intersection rather than Omit since
CallToolResult's index signature swallows Omit's known keys.

Also drops the `as any` casts on z.object(...) in tests — zod 4 satisfies
StandardSchemaV1 directly.

* fix(app): registerTool edge cases + example tool correctness

App.registerTool:
- Skip outputSchema validation when result.isError (matches AppToolResult type)
- Pin callback arity to original config.inputSchema; only the validation schema is mutable via update()
- Default missing tools/call arguments to {}
- remove() is a no-op if the handle is stale (re-registered or already removed)
- Gate list_changed notifications on the listChanged capability

Examples:
- threejs: propagate executeThreeCode failures to onSceneError/onSceneRendering; catch updateModelContext rejection
- wiki-explorer: search-article preserves graph on no-match
- budget-allocator: fix category/stage values in tool descriptions

* feat(app,app-bridge): guard requests sent before handshake completes (#620)

* feat(app,app-bridge): guard requests sent before handshake completes

Calling host-bound methods before the ui/initialize →
ui/notifications/initialized handshake completes can race the handshake
on strict hosts and leave the iframe permanently hidden.

App (View side):
- All 8 host-bound methods (callServerTool, readServerResource,
  listServerResources, sendMessage, updateModelContext, openLink,
  downloadFile, requestDisplayMode) now check that connect() has sent
  the initialized notification.
- New AppOptions.strict (default false): console.error when false,
  throw when true. AppOptions is now exported.
- useApp() forwards strict to the App constructor.
- Flag resets on reconnect.

AppBridge (Host side):
- replaceRequestHandler is overridden to wrap every host-bound handler
  with a console.warn if the request arrives before
  ui/notifications/initialized. Never throws. ui/initialize and ping
  use setRequestHandler directly and are exempt; notifications are
  unaffected.
- Catches Views that hand-roll postMessage without the SDK.
- Flag resets on reconnect.

Refs anthropics/claude-ai-mcp#61, anthropics/claude-ai-mcp#149.

* fix(docs): drop AppOptions from intentionallyNotExported now that it is exported

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-21 13:46:13 +01:00
Olivier Chafik
926dcb4fe9
feat: MCP app sampling support via stock SDK types (alt to #477) (#530)
* feat: MCP app sampling support via stock SDK types

Alternative to #477. Instead of forking a stripped-down sampling schema,
this reuses the stock `CreateMessageRequest` / `CreateMessageResult` /
`CreateMessageResultWithTools` types from `@modelcontextprotocol/sdk`
directly — same pattern already used for `tools/call`.

- Spec: `sampling/createMessage` listed under `### Standard MCP Messages`,
  `sampling?: { tools?: {} }` added to `HostCapabilities` (mirrors MCP
  `ClientCapabilities.sampling` shape for easy pass-through).
- SDK: `App.createSamplingMessage()` with overloads that narrow the return
  type based on whether `params.tools` is set; `AppBridge.oncreatesamplingmessage`
  setter; `CreateMessageRequest`/`CreateMessageResult*` added to the
  `AppRequest`/`AppResult` unions.
- Picks up SEP-1577 tool-calling support (`tools`, `toolChoice`, `tool_use`
  content blocks, `stopReason: "toolUse"`, array content) for free — unblocks
  the "nested agents" use case motivated by the original PR.

https://claude.ai/code/session_01ENGWTtsfcyP4S6fTWtUMAh

* feat(app): guard createSamplingMessage before handshake completes

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-21 13:29:09 +01:00
Olivier Chafik
4f89a6f8ea
feat(react): forward autoResize from useApp to App (#622)
useApp() previously hardcoded autoResize: true. Expose it as a hook
option (default unchanged) so callers can disable the built-in
ResizeObserver and drive sizing themselves.
2026-04-21 11:51:04 +01:00
Olivier Chafik
94c94216d4
feat(app,app-bridge): guard requests sent before handshake completes (#620) (#623)
* feat(app,app-bridge): guard requests sent before handshake completes

Calling host-bound methods before the ui/initialize →
ui/notifications/initialized handshake completes can race the handshake
on strict hosts and leave the iframe permanently hidden.

App (View side):
- All 8 host-bound methods (callServerTool, readServerResource,
  listServerResources, sendMessage, updateModelContext, openLink,
  downloadFile, requestDisplayMode) now check that connect() has sent
  the initialized notification.
- New AppOptions.strict (default false): console.error when false,
  throw when true. AppOptions is now exported.
- useApp() forwards strict to the App constructor.
- Flag resets on reconnect.

AppBridge (Host side):
- replaceRequestHandler is overridden to wrap every host-bound handler
  with a console.warn if the request arrives before
  ui/notifications/initialized. Never throws. ui/initialize and ping
  use setRequestHandler directly and are exempt; notifications are
  unaffected.
- Catches Views that hand-roll postMessage without the SDK.
- Flag resets on reconnect.

Refs anthropics/claude-ai-mcp#61, anthropics/claude-ai-mcp#149.

* fix(docs): drop AppOptions from intentionallyNotExported now that it is exported
2026-04-21 11:45:14 +01:00
Olivier Chafik
739c0e049d
build: fix pre-commit hook for symlinked self-dep and staged deletions (#621)
- link-self.mjs: skip when node_modules/@modelcontextprotocol/ext-apps is
  a symlink (e.g. to the repo root). cpSync("dist", target/dist) was
  failing with ERR_FS_CP_EINVAL because src and dest resolved to the
  same path.
- .husky/pre-commit: capture staged files with --diff-filter=d so the
  re-stage step doesn't fail with "pathspec did not match" on commits
  (typically merges) that include deletions.
2026-04-21 11:28:46 +01:00
214 changed files with 13881 additions and 2999 deletions

View file

@ -114,6 +114,21 @@ jobs:
grep -q "Ready" /tmp/mcpb-smoke.log
working-directory: examples/pdf-server
dependency-isolation:
name: Dependency isolation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run test:dependency-isolation
e2e:
runs-on: ubuntu-latest
steps:
@ -132,8 +147,10 @@ jobs:
- run: npm ci
# Headless runs without a channel use chromium-headless-shell, so the
# full Chromium build (about 165 MB) would only be downloaded, never run.
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
run: npx playwright install --with-deps --only-shell chromium
- name: Run E2E tests
run: npx playwright test --reporter=list
@ -165,9 +182,14 @@ jobs:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
# The checkout lives on the Windows drive, which WSL reaches over 9P;
# writing node_modules there is what makes this job slow (npm ci alone
# took about 3 minutes). Copy the tree onto the distro's ext4 filesystem
# first, as Microsoft's WSL guidance recommends for anything I/O heavy.
- name: Build and test in WSL
shell: wsl-bash {0}
run: |
cp -r "$(pwd)" ~/ext-apps && cd ~/ext-apps
npm ci
npm run build
npm run examples:build

View file

@ -42,6 +42,10 @@ jobs:
- run: npm ci
- 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:
runs-on: ubuntu-latest
if: github.event_name == 'release'
@ -88,7 +92,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [publish]
needs: [build, test]
permissions:
contents: read
@ -108,6 +112,7 @@ jobs:
- cohort-heatmap-server
- customer-segmentation-server
- debug-server
- lazy-auth-server
- map-server
- pdf-server
- scenario-modeler-server
@ -134,8 +139,18 @@ jobs:
- name: Build 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
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public ${{ steps.npm-tag.outputs.tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
@ -143,7 +158,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [publish-examples]
needs: [build, test]
permissions:
contents: write

View file

@ -34,6 +34,7 @@ jobs:
./examples/cohort-heatmap-server \
./examples/customer-segmentation-server \
./examples/debug-server \
./examples/lazy-auth-server \
./examples/map-server \
./examples/pdf-server \
./examples/scenario-modeler-server \

View file

@ -15,8 +15,9 @@ if grep -E '"resolved": "https?://' package-lock.json | grep -v registry.npmjs.o
fi
# Capture staged files so we only re-stage what the user intended to commit
# (avoids sweeping unrelated WIP into the commit)
STAGED=$(git diff --name-only --cached)
# (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 prettier:fix

View file

@ -5,4 +5,5 @@ examples/basic-server-*/**/*.tsx
examples/quickstart/**/*.ts
**/vendor/**
**/.build/**
**/.claude/**
SKILL.md

View file

@ -60,8 +60,8 @@ rm -fR package-lock.json node_modules && \
### Key Source Files
- `src/app.ts` - `App` class extends MCP Protocol, handles guest initialization, tool calls, messaging
- `src/app-bridge.ts` - `AppBridge` class for hosts, proxies MCP requests, sends tool input/results to guests
- `src/app.ts` - `App` subclasses `Protocol` from `@modelcontextprotocol/client`, handles View initialization, tool calls, and messaging
- `src/app-bridge.ts` - `AppBridge` subclasses `Protocol` from `@modelcontextprotocol/client` for the iframe channel and proxies through a separate outer `Client`
- `src/server/index.ts` - Helpers for MCP servers to register tools/resources with UI metadata
- `src/types.ts` - Protocol types re-exported from `spec.types.ts` and Zod schemas from `generated/schema.ts` (auto-generated during build)
- `src/message-transport.ts` - `PostMessageTransport` for iframe communication
@ -70,12 +70,12 @@ rm -fR package-lock.json node_modules && \
### Protocol Flow
```
View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server
View (App/Protocol) <--PostMessageTransport--> Host (AppBridge/Protocol) <--separate MCP Client--> MCP Server
```
1. Host creates iframe with view HTML
2. View creates `App` instance and calls `connect()` with `PostMessageTransport`
3. View sends `ui/initialize` request, receives host capabilities and context
3. View sends `ui/initialize` (the only handshake on the iframe channel) and receives authoritative Apps capabilities and context
4. Host sends `sendToolInput()` with tool arguments after initialization
5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()`
6. Host sends `sendToolResult()` when tool execution completes

View file

@ -27,6 +27,14 @@ We welcome contributions to the MCP Apps SDK! This document outlines the process
- 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
## 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
Start the development environment with hot reloading:
@ -494,7 +502,7 @@ Alternatively, use the [workflow dispatch](https://github.com/modelcontextprotoc
## Code of Conduct
This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing.
This project follows our [Code of Conduct](https://github.com/modelcontextprotocol/.github/blob/main/CODE_OF_CONDUCT.md). Please review it before contributing.
## Reporting Issues
@ -531,40 +539,17 @@ Before publishing releases, ensure the following are configured:
### Publishing a Release
Releases are published automatically via GitHub Actions when a GitHub Release is created.
#### Steps to publish:
1. **Update the version** in `package.json`:
1. **Bump the version** across the root and all workspace packages:
```bash
# For a regular release
npm version patch # or minor, or major
# For a beta release
npm version prerelease --preid=beta
npm run bump -- minor # or: patch | major | prerelease --preid=beta | 1.7.0
```
2. **Commit the version bump** (if not done by `npm version`):
Commit and open a PR with a grouped changelog in the body (see prior `chore: bump …` PRs for the format).
```bash
git add package.json
git commit -m "Bump version to X.Y.Z"
git push origin main
```
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.
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
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.
#### npm Tags
@ -588,7 +573,7 @@ To release a patch for an older version:
### Testing Pre-releases
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.
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.
---

View file

@ -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,6 +89,8 @@ 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]
@ -120,12 +122,36 @@ resources:
## Getting Started
Requires Node.js 20+. The base MCP SDK packages are `^2.0.0` peers of
`ext-apps` (`@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it for you).
For a View or host:
```bash
npm install -S @modelcontextprotocol/ext-apps
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@^2.0.0 \
zod@^4.2.0
```
For an MCP server, add the server package (and, for HTTP transports, the Node
and Express adapters):
```bash
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@^2.0.0 \
@modelcontextprotocol/server@^2.0.0 \
@modelcontextprotocol/node@^2.0.0 \
@modelcontextprotocol/express@^2.0.0 \
zod@^4.2.0
```
The wire protocol is unchanged between `ext-apps` 1.x and 2.x: a 2.x View works
in a 1.x host and a 2.x host renders 1.x Views. See the
[migration guide](https://apps.extensions.modelcontextprotocol.io/api/documents/migrate-to-v2.html)
when upgrading from 1.x.
**New here?** Start with the
[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html)
[Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html)
to build your first MCP App.
## Using the SDK
@ -139,7 +165,7 @@ UI metadata.
| `@modelcontextprotocol/ext-apps` | Build interactive Views (App class, PostMessageTransport) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app.html) |
| `@modelcontextprotocol/ext-apps/react` | React hooks for Views (useApp, useHostStyles, etc.) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/_modelcontextprotocol_ext-apps_react.html) |
| `@modelcontextprotocol/ext-apps/app-bridge` | Embed and communicate with Views in your chat client | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app-bridge.html) |
| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server.html) |
| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server-helpers.html) |
There's no _supported_ host implementation in this repo (beyond the
[examples/basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host)
@ -246,7 +272,7 @@ point your client at a local build:
## Resources
- [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html)
- [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html)
- [API Documentation](https://apps.extensions.modelcontextprotocol.io/api/)
- [Specification (2026-01-26)](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx)
([Draft](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx))

View file

@ -1,6 +1,9 @@
#!/usr/bin/env bun
import { $ } from "bun";
import { cpSync, mkdirSync } from "node:fs";
import { cpSync, mkdirSync, rmSync } from "node:fs";
// Avoid publishing artifacts left behind by an earlier build or branch.
rmSync("dist", { recursive: true, force: true });
// Run TypeScript compiler for type declarations
await $`tsc`;
@ -31,9 +34,15 @@ function buildJs(
});
}
// zod is a peerDependency — keep it external so consumers share a single
// zod instance (instanceof ZodError / schema.extend() break with duplicate copies).
const PEER_EXTERNALS = ["@modelcontextprotocol/sdk", "zod"];
// Peer dependencies stay external in the standard entry points so consumers
// share one base MCP SDK and Zod instance. The *-with-deps entry points keep
// bundling these dependencies for standalone browser use.
const PEER_EXTERNALS = [
"@modelcontextprotocol/client",
"@modelcontextprotocol/core",
"@modelcontextprotocol/server",
"zod",
];
await Promise.all([
buildJs("src/app.ts", {

View file

@ -150,7 +150,7 @@ registerAppTool(
"get_account_balance",
{
description: "Get account balance",
inputSchema: { accountId: z.string() },
inputSchema: z.object({ accountId: z.string() }),
},
async ({ accountId }) => {
if (!authInfo) {

130
docs/migrate-to-2.md Normal file
View file

@ -0,0 +1,130 @@
---
title: Migrate to v2
group: Getting Started
description: Upgrade from ext-apps 1.x to 2.x — new base MCP SDK peer packages, API changes for Views and hosts, and what stays wire-compatible.
---
# Migrating from ext-apps 1.x to 2.x
ext-apps 2.x is built on the base MCP TypeScript SDK 2.x, which replaced the
single `@modelcontextprotocol/sdk` package with `@modelcontextprotocol/client`,
`server`, `core`, `node` and `express`. The MCP Apps wire protocol did not
change; the breaking changes are in dependencies and in the TypeScript API.
## Host compatibility
The `ui/*` messages exchanged over the iframe channel are byte-identical to
1.x. A 2.x View runs in a 1.x host and a 2.x host renders 1.x Views. The only
host-side deltas are in error responses (see below).
## What stays the same
`App` / `AppBridge` constructors, the `on*` setters, `addEventListener`,
`callServerTool`, `readServerResource`, `registerAppResource`, the React hooks
and the `_meta.ui.resourceUri` handling are unchanged. Published `.d.ts` files
now resolve under `moduleResolution: NodeNext` / `Node16` without workarounds
(#705).
## Peer dependencies by role
| Role | Install |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| View author | `@modelcontextprotocol/ext-apps`, `@modelcontextprotocol/client@^2.0.0`, `zod@^4.2.0` (+ `react`/`react-dom` for `./react`) |
| Host author | same as View author |
| MCP server author | View author packages + `@modelcontextprotocol/server@^2.0.0`; for HTTP transports also `@modelcontextprotocol/node@^2.0.0` (`NodeStreamableHTTPServerTransport`) and `@modelcontextprotocol/express@^2.0.0` (`createMcpExpressApp`, which peers on `express`) |
| CDN / `*-with-deps` | nothing extra: `./app-with-deps` and `./react-with-deps` bundle client, core and zod (about 25% larger than the 1.x bundles) |
`@modelcontextprotocol/client` is a required peer (`App` and `AppBridge` extend
its `Protocol` class); `@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it without you listing it;
`@modelcontextprotocol/server` stays optional and is only needed for the
`./server` helpers. Node.js 20+ is required.
## Breaking changes
- **Peer packages.** `@modelcontextprotocol/sdk@^1` is replaced by the split
2.x packages above, all at `^2.0.0`. Remove the 1.x package from your
project: the two SDKs share no classes or types (a 1.x `McpError` or
`Client` cannot be passed to 2.x code), even though they speak the same
wire protocol.
- **zod 3 dropped.** The peer range is `zod@^4.2.0`. Tool schemas must
implement Standard JSON Schema (`~standard.jsonSchema`): zod 4.2+, ArkType,
Valibot. zod 4.0 and 4.1 do not expose `~standard.jsonSchema` and are
rejected too. Raw zod shapes (`{ q: z.string() }`) still work with
`registerAppTool` but are deprecated; wrap them with `z.object({...})`.
- **`App` / `AppBridge` extend `Protocol` from `@modelcontextprotocol/client`.**
`ProtocolWithEvents` is gone; use the SDK's `Protocol`. The `AppRequest`,
`AppNotification` and `AppResult` unions are kept as deprecated type
aliases (nothing in 2.x consumes them) and will be removed in 3.0.
- **Handler context.** Custom request handlers, `registerAppTool` callbacks
(server) and `app.registerTool` callbacks (View) receive the SDK 2.x
context: `extra.signal` → `extra.mcpReq.signal`, `extra.requestId` →
`extra.mcpReq.id`; `extra.sessionId` is unchanged and `extra.authInfo`
moved to `extra.http?.authInfo`.
- **`setRequestHandler` / `setNotificationHandler` take method names.**
`app.setRequestHandler(SomeRequestSchema, handler)` becomes
`app.setRequestHandler("some/method", { params: SomeParamsSchema }, (params, ctx) => …)`
for custom methods (the handler receives the parsed params); the two-argument
`setRequestHandler("tools/call", handler)` form exists only for spec-defined
method names. The 1.x `(Schema, handler)` form still works on `App` and
`AppBridge` as a deprecated overload: it logs a one-time warning, hands the
handler the whole `{ method, params }` message as before, and gives request
handlers a 1.x-shaped `extra` (`signal`, `requestId`, `sessionId`, `_meta`,
`sendRequest`, `sendNotification`, `authInfo`; exported as
`LegacyRequestHandlerExtra`). It will be removed in 3.0.
- **Double-registration guard covers the SDK's own handlers.** Registering a
handler for `notifications/progress` or `notifications/cancelled` (which
the base `Protocol` already owns) now throws `already registered`, as
`ping` and the `on*`-owned methods did in 1.x. Use `addEventListener` or
the SDK's progress callbacks instead of replacing these.
- **Errors.** Remote JSON-RPC errors are `ProtocolError` (numeric `code`);
local conditions are `SdkError` with a string `code`: request timeout →
`"REQUEST_TIMEOUT"`, connection closed → `"CONNECTION_CLOSED"`. Cancelling a
request with an `AbortSignal` also rejects with `"REQUEST_TIMEOUT"` (the
message is the abort reason). Messages no longer carry the `MCP error N:`
prefix.
A resource-not-found reply that carries `data.uri` arrives as
`ResourceNotFoundError` (a `ProtocolError` subclass, `code` `-32602`, with a
`.uri` getter).
## Host-side wire deltas
Observed when a 2.x `AppBridge` answers a View; a 1.x View sees these too.
| Situation | 1.x host | 2.x host |
| --------------------------------------------------------- | ------------------------------ | --------------------------------------------------- |
| A handler throws `-32002` (resource not found) | `error.code: -32002` | `error.code: -32602` (the SDK never emits `-32002`) |
| Invalid params on a `ui/*` request | `-32603` with a zod issue dump | `-32602 Invalid params for <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`.

View file

@ -66,7 +66,7 @@ The server-side changes involve updating metadata structure and using helper fun
#### Before (OpenAI)
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
@ -78,7 +78,7 @@ function createServer() {
{
title: "Shopping Cart",
description: "Display the user's shopping cart",
inputSchema: { userId: z.string() },
inputSchema: z.object({ userId: z.string() }),
annotations: { readOnlyHint: true },
_meta: {
"openai/outputTemplate": "ui://view/cart.html",
@ -126,7 +126,7 @@ function createServer() {
#### After (MCP Apps)
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpServer } from "@modelcontextprotocol/server";
import {
registerAppTool,
registerAppResource,
@ -144,7 +144,7 @@ function createServer() {
{
title: "Shopping Cart",
description: "Display the user's shopping cart",
inputSchema: { userId: z.string() },
inputSchema: z.object({ userId: z.string() }),
annotations: { readOnlyHint: true },
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
},

View file

@ -56,7 +56,11 @@ flowchart LR
- **Host** — The chat client (e.g., Claude Desktop) that connects to servers, embeds Views in iframes, and proxies communication between them.
- **View** — The UI running inside a sandboxed iframe. It receives tool data from the Host and can call server tools or send messages back to the chat.
The View acts as an MCP client, the Host acts as a proxy, and the Server is a standard MCP server.
The View's `App` and the Host's `AppBridge` both subclass the `Protocol` class
exported by `@modelcontextprotocol/client` for the iframe channel. A separate outer `Client` connects
the Host to the actual MCP Server. Keeping those two connections separate
preserves the Apps-only iframe handshake and prevents iframe capabilities from
leaking into the server connection.
## Lifecycle
@ -93,7 +97,7 @@ sequenceDiagram
```
1. **Discovery** — The Host learns about tools and their UI resources when connecting to the server.
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` and receives host context (theme, capabilities, container dimensions). This handshake ensures the View is ready before receiving data.
2. **Initialization** — When a UI tool is called, the Host renders the iframe. The View sends `ui/initialize` (the only handshake on the iframe channel) and receives host context (theme, capabilities, container dimensions). `ui/initialize` is authoritative for Apps capabilities and identity, and `ui/notifications/initialized` is the public View-ready signal.
3. **Data delivery** — The Host sends tool arguments and, once available, tool results to the View. Results include both `content` (text for the model's context) and optionally `structuredContent` (data optimized for UI rendering). This separation lets servers provide rich data to the UI without bloating the model's context.
4. **Interactive phase** — The user interacts with the View. The View can call tools, send messages, or update context.
5. **Teardown** — Before unmounting, the Host notifies the View so it can save state or release resources.

View file

@ -19,7 +19,7 @@ registerAppTool(
"update-quantity",
{
description: "Update item quantity in cart",
inputSchema: { itemId: z.string(), quantity: z.number() },
inputSchema: z.object({ itemId: z.string(), quantity: z.number() }),
_meta: {
ui: {
resourceUri: "ui://shop/cart.html",
@ -127,14 +127,14 @@ registerAppTool(
{
title: "Read Data Bytes",
description: "Load binary data in chunks",
inputSchema: {
inputSchema: z.object({
id: z.string().describe("Resource identifier"),
offset: z.number().min(0).default(0).describe("Byte offset"),
byteCount: z
.number()
.default(MAX_CHUNK_BYTES)
.describe("Bytes to read"),
},
}),
outputSchema: DataChunkSchema,
// Hidden from model - only callable by the App
_meta: { ui: { visibility: ["app"] } },
@ -281,10 +281,7 @@ server.registerResource(
<!-- prettier-ignore -->
```ts source="./patterns.tsx#binaryBlobResourceClient"
const result = await app.request(
{ method: "resources/read", params: { uri: `video://${videoId}` } },
ReadResourceResultSchema,
);
const result = await app.readServerResource({ uri: `video://${videoId}` });
const content = result.contents[0];
if (!content || !("blob" in content)) {

View file

@ -17,16 +17,12 @@ import { randomUUID } from "node:crypto";
import type {
CallToolResult,
ReadResourceResult,
} from "@modelcontextprotocol/sdk/types.js";
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
} from "@modelcontextprotocol/server";
import type { McpUiHostContext } from "../src/types.js";
import { useEffect, useState } from "react";
import { useApp } from "../src/react/index.js";
import { registerAppTool } from "../src/server/index.js";
import {
McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
import { z } from "zod";
/**
@ -117,14 +113,14 @@ function chunkedDataServer(server: McpServer) {
{
title: "Read Data Bytes",
description: "Load binary data in chunks",
inputSchema: {
inputSchema: z.object({
id: z.string().describe("Resource identifier"),
offset: z.number().min(0).default(0).describe("Byte offset"),
byteCount: z
.number()
.default(MAX_CHUNK_BYTES)
.describe("Bytes to read"),
},
}),
outputSchema: DataChunkSchema,
// Hidden from model - only callable by the App
_meta: { ui: { visibility: ["app"] } },
@ -252,10 +248,7 @@ function binaryBlobResourceServer(
*/
async function binaryBlobResourceClient(app: App, videoId: string) {
//#region binaryBlobResourceClient
const result = await app.request(
{ method: "resources/read", params: { uri: `video://${videoId}` } },
ReadResourceResultSchema,
);
const result = await app.readServerResource({ uri: `video://${videoId}` });
const content = result.contents[0];
if (!content || !("blob" in content)) {

View file

@ -21,7 +21,7 @@ This tutorial assumes you've built an MCP server before and are comfortable with
We'll use the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) to build the server.
You'll also need Node.js 18+.
You'll also need Node.js 20+.
## 1. Set up the project
@ -37,7 +37,7 @@ Install the dependencies you'll need:
```bash
npm init -y
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/client@^2.0.0 @modelcontextprotocol/server@^2.0.0 @modelcontextprotocol/node@^2.0.0 @modelcontextprotocol/express@^2.0.0 zod@^4.2.0 express cors
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
```
@ -59,6 +59,7 @@ npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development IN
```json source="../examples/quickstart/tsconfig.json"
{
"compilerOptions": {
"types": ["node", "vite/client"],
"target": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
@ -87,6 +88,7 @@ npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development IN
```json source="../examples/quickstart/tsconfig.server.json"
{
"compilerOptions": {
"types": ["node"],
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
@ -178,9 +180,10 @@ import {
registerAppTool,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpServer } from "@modelcontextprotocol/server";
import fs from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
const DIST_DIR = path.join(import.meta.dirname, "dist");
@ -205,7 +208,7 @@ export function createServer(): McpServer {
{
title: "Get Time",
description: "Returns the current server time.",
inputSchema: {},
inputSchema: z.object({}),
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
},
async () => {
@ -240,10 +243,10 @@ export function createServer(): McpServer {
<!-- prettier-ignore -->
```ts source="../examples/quickstart/main.ts"
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createMcpExpressApp } from "@modelcontextprotocol/express";
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
import type { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
@ -263,7 +266,7 @@ export async function startStreamableHTTPServer(
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
const transport = new StreamableHTTPServerTransport({
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});

View file

@ -28,6 +28,10 @@ The [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/ex
cd examples/basic-host
```
The root install runs the package build, including type-checking the
documentation snippets against the pinned base MCP SDK 2.x packages. Do
not bypass a failed build before testing the host.
2. Start basic-host, pointing it to your MCP server:
```bash

View file

@ -1,7 +1,7 @@
{
"homepage": "https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host",
"name": "@modelcontextprotocol/ext-apps-basic-host",
"version": "1.6.0",
"version": "2.0.0",
"type": "module",
"scripts": {
"build": "tsc --noEmit && concurrently \"cross-env INPUT=index.html vite build\" \"cross-env INPUT=sandbox.html vite build\"",
@ -11,11 +11,11 @@
"dev": "cross-env NODE_ENV=development concurrently \"npm run watch\" \"npm run serve\""
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^1.0.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/ext-apps": "^2.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"zod": "^4.1.13"
"zod": "^4.2.0"
},
"devDependencies": {
"@types/express": "^5.0.0",
@ -29,7 +29,6 @@
"prettier": "^3.6.2",
"typescript": "^5.9.3",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.3.0",
"vitest": "^3.2.4"
"vite-plugin-singlefile": "^2.3.0"
}
}

View file

@ -1,8 +1,12 @@
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions, buildAllowAttribute, type McpUiUpdateModelContextRequest, type McpUiMessageRequest } from "@modelcontextprotocol/ext-apps/app-bridge";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import type { CallToolResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
import {
Client,
SSEClientTransport,
StreamableHTTPClientTransport,
type CallToolResult,
type Resource,
type Tool,
} from "@modelcontextprotocol/client";
import { getTheme, onThemeChange } from "./theme";
import { HOST_STYLE_VARIABLES } from "./host-styles";

View file

@ -1,5 +1,5 @@
import { getToolUiResourceUri, McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import type { Tool } from "@modelcontextprotocol/client";
import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo, type ModelContext, type AppMessage } from "./implementation";

Some files were not shown because too many files have changed in this diff Show more