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
Olivier Chafik
01d826aea0
chore: bump ext-apps to 1.6.0 (#613) 2026-04-14 13:58:12 +01:00
Roy Li
c59c6060cd
callServerTool: opt into progress-based timeout reset (#600)
A host may interpose a long-running or user-interactive step between the
guest's tools/call and the server's response. Without this, the base
SDK's default request timeout in Protocol.request() fires and the guest
sees -32001 even though the host is still working.

Passing onprogress causes Protocol.request to include _meta.progressToken
in the outgoing request and register a progress handler;
resetTimeoutOnProgress makes incoming notifications/progress reset the
per-request timer. Hosts can then heartbeat during long-running steps.
Callers can still override both.
2026-04-14 13:06:22 +01:00
Olivier Chafik
4f4dd8b6f0
test(e2e): poll for initial shrink-to-fit zoom before asserting (#607)
The initial fit-to-width in the pdf viewer measures container.clientWidth
immediately after showViewer() flips display:flex. Under CI load the reflow
can lag a tick, leaving widthFit >= 1.0 and zoom at 100% when first read,
which intermittently fails the toBeLessThan(100) assertion (~6 failures in
the 30 runs since #583).

Swap the single-shot read for expect.poll, matching the pattern already used
for the post-fullscreen assertion below, then capture inlineZoom for the
subsequent comparison.
2026-04-09 13:51:40 +01:00
Olivier Chafik
621a70a724
chore: bump ext-apps to 1.5.0 (#588)
* chore: bump ext-apps to 1.5.0

* style: auto-fix prettier formatting

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-02 10:09:49 +01:00
Olivier Chafik
61fe39b4db
feat(pdf-server): rasterize imported annotations + form/save consistency follow-ups (#593)
* feat(pdf-server): rasterize imported annotations via annotationCanvasMap

Adds an 'imported' annotation type for anything in a loaded PDF that we
either don't model (Ink, Polygon, Caret, FileAttachment, ...) or can't
faithfully re-render (Stamp with an appearance stream, e.g. an image
signature). These now:
- Appear in the annotation panel as '<Subtype> (from PDF)'
- Render in our layer as a positioned div whose body is the per-
  annotation canvas pdf.js produced via page.render({annotationCanvasMap})
  - if pdf.js didn't divert it (no hasOwnCanvas), the box is transparent
    over the main-canvas pixel and just captures clicks
- Are selectable and draggable (resize/rotate disabled - bitmap would
  just stretch)
- Are skipped by addAnnotationDicts; getAnnotatedPdfBytes already
  filters baseline ids, so save leaves the original in the PDF.
  Move/delete are UI-only for now (documented).

Link (2) and Popup (16) are excluded - navigational/auxiliary, not
markup.

importPdfjsAnnotation tests added for unsupported-type and
appearance-stamp paths; computeDiff round-trip for 'imported'.

* fix(pdf-server): isEditing on page.render so stamps always divert to annotationCanvasMap

StampAnnotation.hasOwnCanvas defaults to noRotate; stamps without that
flag composited onto the main canvas, so deleting the 'imported' overlay
left an unclickable pixel. isEditing forces hasOwnCanvas=true for stamps
(via mustBeViewedWhenEditing) so the appearance lands in the per-id
canvas and our DOM element is the only render.

* fix(pdf-server): refresh annotation panel after save rebases baseline

setDirty(false) updated the title and save button but the panel kept
showing pending-change badges because it diffs against
pdfBaselineAnnotations/FormValues and was never re-rendered.

* fix(pdf-server): imported stamp canvases cropped/2x on retina

renderPage applied devicePixelRatio via ctx.scale(dpr,dpr) instead of
page.render's transform parameter. pdf.js sizes annotationCanvasMap
backing buffers as rectW * outputScaleX * viewport.scale, and
outputScaleX is read from transform[0] (defaults 1). So on retina the
per-annotation canvas got a 1x backing while its internal setTransform
(from the SVD of the already-dpr-scaled ctx) was 2x - the appearance
rendered at 2x into a half-sized buffer, showing only the top-left
quarter.

Pass dpr via transform: [dpr,0,0,dpr,0,0] so outputScaleX matches.
Also filter AnnotationLayer.render() to Widget annotations only so it
stops creating empty pointer-events:auto sections for stamps in
#form-layer that could steal clicks from our overlays.

* feat(pdf-server): strip deleted baseline annotations from /Annots on save

buildAnnotatedPdfBytes takes a removedRefs list and walks each page's
/Annots array (backwards) removing matching PDFRef entries.
getAnnotatedPdfBytes computes that list from baseline annotations no
longer in annotationMap, parsing the ref back from our id via
parseAnnotationRef (handles both pdf-<num>-<gen> and pdf-<num>R).

Panel: removed baseline annotations now stay listed as crossed-out cards
with a revert button (mirrors cleared form fields), so the user can see
and undo the pending deletion before save commits it.

* fix(pdf-server): re-render after seeding storage from AcroForm tree

buildFieldNameMap runs AFTER the first renderPage (perf: don't block the
canvas on an O(numPages) scan). When it detects a widget/field-tree
mismatch and pushes the field-tree value into annotationStorage, the
form layer has already rendered the stale widget value. Re-render once
when that happens so the input shows the AcroForm truth.

* fix(pdf-server): reload PDF after successful save instead of rebasing in place

Rebasing baselines while keeping the old pdfDocument drifts: subsequent
renders rasterize annotations that were just stripped from disk, and the
field/widget split pdf-lib's save can create isn't visible until reload.
Reload makes 'viewer == disk' an invariant. localStorage cleared first;
file_changed echo suppressed by lastSavedMtime as before.
2026-04-02 10:03:35 +01:00
Olivier Chafik
3af019144f
fix(pdf-server): form-field save robustness (#591)
* fix(pdf-server): one bad form field no longer aborts saving the rest

#577 dropped the per-field try/catch in buildAnnotatedPdfBytes when it
swapped to type-dispatch, so the first field whose pdf-lib write throws
(max-length text, missing /Yes appearance, radio buttonValue mapping to
neither label nor index, ...) bubbled to the outer catch and silently
dropped every subsequent field. Compounded by getAnnotatedPdfBytes
passing every baseline field, so even an untouched problematic field in
the PDF poisoned the whole save.

- Re-wrap each field write in its own try/catch (warn + continue).
- getAnnotatedPdfBytes now only sends fields whose value differs from
  baseline (still includes explicit ''/false sentinels for cleared
  fields so pdf-lib overwrites the original /V).
- Regression test: a maxLength=2 text field fed a long string, followed
  by a normal field; assert the second one still lands.

* fix(pdf-server): radio groups misclassified as PDFCheckBox save the chosen widget

Some PDFs (e.g. third-party forms like the IRS f1040 family, and the
demo Form.pdf) omit the /Ff Radio flag bit on button fields, so pdf-lib
classifies a multi-widget radio as PDFCheckBox. The viewer stores
pdf.js's buttonValue (the widget's on-state name, e.g. '0'/'1') as a
string. The PDFCheckBox branch did 'if (value) field.check()', which
always sets the FIRST widget's on-state - so any choice saved as the
first option.

When the value is a string on a PDFCheckBox, treat it as a radio
on-value: write /V and per-widget /AS directly via the low-level
acroField (mirroring PDFAcroRadioButton.setValue minus its
first-widget-only onValues guard). Booleans keep check()/uncheck().

Test: build a radio fixture, clear the Radio flag so reload sees
PDFCheckBox, save with '1', assert /V = /1 and second widget /AS = /1.

* fix(pdf-server): clearing a radio from the panel unchecks all widgets

clearFieldInStorage wrote the same string clearValue to every widget's
storage, hitting pdf.js's inverted string-coercion bug (the one
setFieldInStorage already works around): the wrong widget rendered
checked. For radio, write {value:false} to every widget instead.

* fix(pdf-server): only treat multi-widget PDFCheckBox as misclassified radio

A single-widget checkbox getting a string value (e.g. 'Yes'/'Off' if
something upstream stores the export string instead of boolean) was
being routed through setButtonGroupValue, which no-ops when no widget's
on-state matches the value - leaving the box unchanged. Gate the
radio-path on widgets.length > 1; single-widget falls through to
check()/uncheck() with liberal truthy/'Off' semantics.

* fix(pdf-server): generate missing field appearances on save

PDFs whose checkbox/radio widgets lack an /AP/N/<onValue> stream: check()
sets /V and /AS but Preview/Acrobat can't render a state with no
appearance, so the field looks unchanged. Call form.updateFieldAppearances()
after the write loop so pdf-lib synthesizes defaults for dirty fields.

* feat(pdf-server): save PDFOptionList (multiselect listbox) values

Language field in Form.pdf is a PDFOptionList; the save loop skipped it.
Parse the viewer's comma-joined string, select() the subset that exists
in getOptions() (pdf-lib throws on unknowns and there's no edit-mode
fallback like Dropdown has).

* fix(pdf-server): capture all selected options on <select multiple>

target.value on a multiselect is only the first option; join
selectedOptions so the save path gets the full set.
2026-04-02 10:01:23 +01:00
Olivier Chafik
f3bf1065db
fix(pdf-server): import highlight/underline/strike from existing PDFs (#592)
* fix(pdf-server): import highlight/underline/strike from existing PDFs

importPdfjsAnnotation expected nested quadPoints arrays, but pdf.js
emits a flat Float32Array (8 numbers per quad). Iterating it yielded
numbers, qp.length was undefined, rects stayed empty, and the function
returned null - so every quad-based annotation in a loaded PDF was
dropped from annotationMap and rendered only as unselectable canvas
pixels. Shipped this way in #506; not a regression.

Parse the flat array in 8-stride chunks, fall through to ann.rect if
that yields nothing. Existing tests used the (wrong) nested fixture
shape; switched them to Float32Array and added a 2-quad case + a
rect-fallback case.

* fix(pdf-server): own markup annotations in our layer, not the canvas

page.render() defaulted to AnnotationMode.ENABLE, painting every
annotation's appearance stream onto the canvas. We ALSO import them into
annotationMap and render DOM versions in #annotation-layer, so the user
saw two representations: an unclickable canvas pixel that looked right,
and our clickable DOM element that looked like our styling. Clicking the
'real' one did nothing.

Set annotationMode: DISABLE so the canvas is page-content-only and our
layer is the single source of truth for markup. Imported annotations now
behave identically to user-created ones (select/drag/delete). Form
widgets are unaffected (#form-layer via AnnotationLayer.render).
get_screenshot keeps ENABLE_STORAGE so screenshots still show
annotations.

Known trade-offs:
- Stamps with image appearance streams degrade to our text-label render
  (no rasterization yet).
- Annotation types we don't import (Ink, Polygon, Caret, ...) no longer
  ghost on the canvas - they're invisible. They were never editable
  anyway; loadBaselineAnnotations already logs them.

* Revert "fix(pdf-server): own markup annotations in our layer, not the canvas"

This reverts commit 2f989a757d3aa517fa427c7fd59b462326599bfc.
2026-04-02 10:00:53 +01:00
Olivier Chafik
4fc95131bf
feat(pdf-server): get_viewer_state interact action (#590)
* feat(pdf-server): get_viewer_state interact action

New interact action that returns a JSON snapshot of the live viewer:
{currentPage, pageCount, zoom, displayMode, selectedAnnotationIds,
 selection: {text, contextBefore, contextAfter, boundingRect} | null}.

The viewer already pushes selection passively via setModelContext as
<pdf-selection> tags, but not all hosts surface model-context. This gives
the model an explicit pull.

selection.boundingRect is a single bbox in PDF points (top-left origin,
y-down) so it can be fed straight back into add_annotations. selection is
null when nothing is selected or the selection is outside the text-layer.

Wiring: new PdfCommand variant -> processCommands case ->
handleGetViewerState -> submit_viewer_state (new app-only tool, mirrors
submit_save_data) -> waitForViewerState -> text content block.

Also fills a gap in the display_pdf description: it listed interact
actions but was missing save_as; added that and get_viewer_state.

e2e: two tests covering selection:null and a programmatic text-layer
selection.

* test(pdf-server): fix get_viewer_state e2e race + assertions

readLastToolResult clicked .last() before the interact result panel
existed (callInteract doesn't block), so it expanded the display_pdf
panel instead. Wait for the expected panel count first.

Also: basic-host renders the full CallToolResult JSON, with the state
double-escaped inside content[0].text. Parse instead of regex-matching.

playwright.config.ts: honor PW_CHANNEL env to use system Chrome locally
when the bundled chromium_headless_shell is broken.
2026-04-02 08:47:48 +01:00
Olivier Chafik
2e55c842ba
fix(pdf-server): let zoom-out button go below fit (revert button floor) (#589)
Some hosts overlay UI on the iframe (chat composer, etc.) without
reporting it in safeAreaInsets.bottom, so computeFitScale's 'fit' fills
the iframe but the page bottom sits under the overlay. Flooring the
zoom-out button at fit removed the only escape hatch.

Pinch keeps the fit floor + rubber-band (it needs a defined snap point
for the exit-to-inline gesture). The button now goes to ZOOM_MIN as
before #587.
2026-04-02 08:09:24 +01:00
Olivier Chafik
721ccd04b8
fix(pdf-server): translucent highlights, pinch to/from fullscreen, toolbar layout (#587)
* fix(pdf-server): translucent highlights, pinch to/from fullscreen, toolbar layout

Highlights rendered opaque: inline `background: <hex>` overrode the CSS
rgba(...,0.35). Now convert def.color via cssColorToRgb and force alpha
0.35. Added a toHaveCSS regression check in pdf-annotations.spec.ts.

Pinch-in while inline (wheel ctrlKey deltaY<0 or two-finger spread
>1.15x) now enters fullscreen. Pinch-out in fullscreen past 0.9x of
fit-scale, when already at/below fit, exits to inline and clears
userHasZoomed so refit sizes the inline view. previewScaleRaw tracks the
unclamped intent so the exit fires even when fit ~= ZOOM_MIN. A
modeTransitionInFlight latch (held 250ms post-toggle) keeps the gesture
tail from re-toggling or immediately zooming the new view.

Fullscreen toolbar: flex-wrap: nowrap and tighter 0.25rem vertical
padding (min-height 40px + safe-top instead of 48px + wrap). Search bar
top/right now follow --safe-top/--safe-right so it sits flush below the
toolbar instead of overlapping it. Base .canvas-container gets
touch-action: pan-x pan-y so the inline pinch is capturable on iOS.

* fix(pdf-server): drop --safe-top from fullscreen toolbar padding

Hosts wrap fullscreen in their own header (title + close), which already
clears the top safe-area. Adding --safe-top to our toolbar padding-top
double-dipped, leaving a visible gap between the host header and our
toolbar. Keep --safe-left/right (host header doesn't cover the sides).

* feat(pdf-server): horizontal swipe changes pages whenever page fits width

Previously gated on scale <= 1.0, which blocked page-nav in fullscreen
where fit-scale is often >100%. Now gate on actual horizontal overflow
(scrollWidth > clientWidth) so swipe works at any fit-to-width scale and
still defers to native panning once you zoom past it.

* fix(pdf-server): refit to inline width after pinch-out exits fullscreen

handleHostContextChanged calls refitScale() before the iframe has actually
shrunk, and the ResizeObserver's inline branch only refits on width
*growth* (to avoid a requestFitToContent shrink-loop). So the
fullscreen->inline shrink never triggered a refit and the page stayed at
the fullscreen scale.

Add a one-shot forceNextResizeRefit flag, set on fullscreen->inline (both
the pinch-out path and handleHostContextChanged), consumed by the
ResizeObserver on the next size change. One-shot keeps the shrink-loop
guard intact for ordinary inline resizes.

* feat(pdf-server): floor fullscreen zoom at fit-to-page

In fullscreen, pinch-out and the zoom-out button now floor at the
fit-to-page scale instead of ZOOM_MIN, so the page never shrinks below
fully-visible (no dead margin around it). previewScaleRaw stays unclamped
so a continued pinch-out past fit still triggers exit-to-inline.

* fix(pdf-server): wheel pinch-out can exit fullscreen again

The fit-floor pinned previewScale at fit, but the wheel handler multiplied
the *clamped* previewScale, so it could never accumulate below fit*0.9 to
trigger exit. Drive the wheel accumulator off previewScaleRaw instead, and
bound previewScaleRaw to [floor*0.7, ZOOM_MAX] so it can cross the 0.9
exit threshold without drifting unboundedly (which would make direction
reversal feel sticky). Touch path was unaffected (absolute ratio).

* fix(pdf-server): always refit to inline width on fullscreen exit

userHasZoomed stayed true after Escape/button/host-× exits, so refitScale()
bailed even though forceNextResizeRefit let the ResizeObserver call it.
Clear userHasZoomed in handleHostContextChanged whenever we land inline —
fullscreen zoom level is meaningless there. Dropped the now-dead
requestFitToContent fallback in the same block.

* refactor(pdf-server): rubber-band pinch-out instead of dual-tracked clamp

The previewScaleRaw side-channel made the gesture feel dead (page pinned
at fit, no feedback) and the wheel accumulator interaction was fragile.

New model: previewScale is the only tracked value. In fullscreen it may
overshoot down to 0.75*fit so the user *sees* the page pull away as they
pinch out. On commit:
  - started near fit (<=1.05*fit) AND preview <0.9*fit -> exit to inline
  - otherwise clamp committed scale to >=fit (overshoot snaps back)

beginPinch seeds fitScaleAtPinchStart synchronously from  when
!userHasZoomed (the common enter-fullscreen-at-fit case) so the first
frame already has the right floor; the async computeFitScale refines it.

* test(pdf-server): update inline-pinch e2e for new fullscreen behavior

'trackpad pinch is ignored outside fullscreen' is no longer the contract.
Replace with two tests: pinch-in inline -> .main.fullscreen appears;
pinch-out inline -> zoom unchanged, no fullscreen. The two
pdf-annotations.spec.ts failures in the previous run were flaky (passed
on retry).
2026-04-02 06:44:56 +01:00
Olivier Chafik
9da4a75ff6
chore: bump ext-apps to 1.4.0 (#582)
* chore: bump ext-apps to 1.4.0

Changes since 1.3.2:

SDK
- feat: add addEventListener/removeEventListener with DOM-model on* semantics (#573)

pdf-server
- feat: add save_as interact action (#580)
- feat: fit-to-page on fullscreen + pinch-to-zoom (#583)
- fix: npx DOMMatrix crash + broken MCPB bundle (#584)
- fix: viewer liveness, 1:1 batch results, fullscreen jitter (#579)
- fix: render page before O(numPages) annotation scans (#581)
- fix: radio + dropdown in fill_form/save (#577)

Dependencies
- chore: npm audit fix — sdk 1.29.0, systeminformation 5.31.5, +13 transitives (#585, #586)
- fix: bump path-to-regexp 8.3.0 → 8.4.1 to patch ReDoS CVEs (#576)

* chore: update e2e snapshots [skip ci]

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-02 04:33:13 +01:00
Olivier Chafik
9757f35fd1
chore: drop spurious root dependencies block from #585 (#586)
#585's `npm pkg set dependencies.@modelcontextprotocol/sdk` created a
root `dependencies` block — root previously had only devDependencies +
peerDependencies (it IS the published SDK, the upstream sdk is a peer).
Harmless at runtime but pulls sdk into the published tarball's deps.

The original 1.25.2 devDep pin (b9a459a6, Jan 26) was because sdk
1.25.3 depended on @hono/node-server@^1.19.9 which didn't exist on
npm yet — long since resolved.
2026-04-02 04:27:49 +01:00
Olivier Chafik
8e7206cb15
chore(deps): npm audit fix — sdk 1.29.0, systeminformation 5.31.5, +13 transitives (#585)
Clears all 15 npm audit findings (8 high / 6 moderate / 1 low):

- @modelcontextprotocol/sdk 1.25.2 → 1.29.0 (GHSA-345p-7cg4-v4c7
  cross-client data leak via shared transport reuse). Root had it
  pinned exact in devDependencies which blocked audit fix.
- systeminformation 5.30.6 → 5.31.5 (GHSA-5vv4-hvf7-2h46,
  GHSA-9c88-49p5-5ggf command injection)
- hono, undici, rollup, picomatch, minimatch, lodash-es, svelte,
  devalue, ajv, brace-expansion, markdown-it, yaml, qs — lockfile
  bumps via npm audit fix
2026-04-02 04:25:00 +01:00
Olivier Chafik
04fed524a9
fix(pdf-server): npx DOMMatrix crash + broken MCPB bundle (#584)
* fix(pdf-server): stub DOMMatrix/ImageData/Path2D for npx without @napi-rs/canvas

pdfjs-dist/legacy/build/pdf.mjs does `new DOMMatrix()` at module scope.
Its own polyfill loads @napi-rs/canvas (an optionalDependency), but npx
frequently misses the platform-native binary (npm/cli#4828), so the
server crashed on `npx @modelcontextprotocol/server-pdf --stdio`:

    ReferenceError: DOMMatrix is not defined
        at pdfjs-dist/legacy/build/pdf.mjs:17078

The server only uses pdfjs for text/metadata/form-field extraction —
never canvas rendering — so no-op stubs suffice and avoid shipping
~130MB of native binaries.

The polyfill is a separate ESM module kept --external in the bun bundle
so it executes before pdfjs's hoisted static import; inlined body code
would run too late.

Broken since 1.3.0 (#506). Thanks to Bryan Thompson for the analysis
and fix shape from the npm-to-mcpb pipeline.

* fix(pdf-server): MCPB bundle missing runtime deps + display_name with spaces

`mcpb pack` zips whatever is on disk; in this monorepo all runtime deps
are hoisted to root node_modules, so the published .mcpb shipped without
pdfjs-dist/ajv/express/etc. and crashed on import in Claude Desktop.

build-mcpb.mjs stages dist/ + manifest into a clean temp dir, runs a
non-workspace `npm install --omit=dev --omit=optional` (the new
DOMMatrix polyfill makes @napi-rs/canvas's ~130MB of native binaries
unnecessary), syncs manifest.version to package.json, then packs.
Result: 12.3MB / 38.8MB unpacked, full MCP handshake + tools/list pass.

Also rename display_name "PDF (By Anthropic)" → "pdf-viewer" — spaces
in display_name break MCP App UI rendering for MCPBs in Claude Desktop.

CI now smoke-tests the extracted bundle starts (`dist/index.js --stdio`
prints "Ready") so a deps-less bundle can't ship again.

* fix(pdf-server): list interact in MCPB manifest tools (was stale since #491)
2026-04-02 04:16:33 +01:00
Olivier Chafik
68c58b60b7
feat(pdf-server): fit-to-page on fullscreen + pinch-to-zoom (#583)
* feat(pdf-server): refit on inline→fullscreen + pinch-to-zoom in fullscreen

Two related fixes for the fullscreen reading experience:

1) Inline → fullscreen left the page at the cramped inline scale.
   computeFitToWidthScale() returned null when the natural page
   already fit (availableWidth >= pageWidth), so going from a
   narrow inline iframe (~67%) to a wide fullscreen container
   produced null → no rescale. Now caps at min(1.0, fit) so the
   refit handler sees 1.0 ≠ 0.67 and re-renders. Display-mode
   handling moves before the containerDimensions refit so the
   .fullscreen padding lands before we measure clientWidth, and
   the inline→fullscreen edge triggers refit explicitly (some
   hosts send displayMode without containerDimensions).

2) Pinch-to-zoom in fullscreen. Two input paths:
   - wheel + ctrlKey → trackpad pinch (Chrome/FF/Edge/Safari).
     exp(-deltaY/100) per pdf.js; 150ms settle then commit.
   - two-finger touch → mobile. Distance ratio against touchstart;
     commit on touchend, revert on touchcancel.
   Both apply a CSS transform to .page-wrapper for live feedback,
   then commit one renderPage() at gesture end (renderPage is
   heavy: PDF.js raster + TextLayer + AnnotationLayer rebuild).
   touch-action: pan-x pan-y on the fullscreen canvas-container
   keeps native panning working while we own the pinch.

Incidental: resetZoom (Cmd+0) now snaps to fit, not blindly 1.0,
so it doesn't overflow a narrow inline iframe; ZOOM_MIN/MAX
constants; shared pageWrapperEl ref.

* test(pdf-viewer-zoom): start narrow — basic-host doesn't emit containerDimensions on resize

The first test loaded at 1400w then resized to 500w expecting the
viewer to refit, but basic-host only sends containerDimensions at
init (implementation.ts:297) — never on window resize. Start narrow
so the initial fit lands <100%, then widen + fullscreen. Matches
the webtop verification flow that confirmed 67% → 100%.

* fix(pdf-server): drive fullscreen refit from ResizeObserver, not host messages

The displayMode hook ran refitToWidth() but Claude Desktop resizes
the iframe element AFTER (or independently of) sending the displayMode
update — clientWidth was still the inline width when we measured.

ResizeObserver on the canvas-container fires after the iframe has
actually laid out at the new size, so the measurement is fresh.
Gated to width-growth only: in inline mode renderPage() →
requestFitToContent() → host shrinks iframe to page width, which
would otherwise loop the scale down to ZOOM_MIN. Growth-only covers
inline→fullscreen and window-widen without the feedback loop.

* fix(basic-host): emit containerDimensions on iframe resize

Per spec, hosts notify views of container dimension changes via
host-context-changed. basic-host only sent containerDimensions once
at init — never on window resize or after the fullscreen panel
toggle. ResizeObserver on the iframe sends {width, maxHeight: 6000}
on every layout change; height stays flexible so views can keep
driving it via sendSizeChanged.

Doesn't replace the viewer-side ResizeObserver (defensive against
hosts we don't control), but makes the reference host spec-compliant.

* fix(pdf-server): scale UP to fill width in fullscreen, not just down

The previous fit cap of 1.0 meant a 612pt page in a 1400px fullscreen
container stayed at 100% — natural size, acres of empty space.
Fullscreen now caps at ZOOM_MAX so the page fills the available
width. Inline keeps the 1.0 cap (the iframe sizes to the page via
sendSizeChanged; growing past 1.0 would just make the iframe huge).

Exit-fullscreen refits back down to ≤1.0; if userHasZoomed (so
refit no-ops) fall back to requestFitToContent so the iframe still
shrinks to whatever scale they left it at.

* test(pdf-viewer-zoom): expect fullscreen to fill width (>100%), pinch out

Fullscreen fit now scales up past 100%, so the entering-fullscreen
assertion is >inlineZoom+50, not =100. The pinch test pinches OUT
(deltaY>0) so it doesn't immediately hit ZOOM_MAX from a ~220%
starting point.

* fix(pdf-server): fullscreen fit-to-PAGE, not fit-to-width

Fit-to-width on a wide screen filled the width but left the page
scrolling vertically — "fits" should mean the whole page is visible.
computeFitScale (renamed) now returns min(widthFit, heightFit) in
fullscreen so the page sits centred without scroll. Inline keeps
fit-to-width capped at 1.0 (height is host-driven via sendSizeChanged).

ResizeObserver now reacts to height changes too in fullscreen — the
inline width-growth-only gate stays (requestFitToContent loop guard),
but in fullscreen requestFitToContent early-returns so any size change
is safe to refit on.

e2e: assert no overflow in fullscreen instead of a magic zoom %.

* fix(pdf-server): respect safe-area insets in fullscreen fit-to-page

The host's chat input / home indicator overlays the bottom of the
fullscreen iframe (reported via safeAreaInsets). The toolbar already
honoured top/left/right; the canvas-container now adds the bottom +
side insets to its padding so the page sits above the overlay.
computeFitScale subtracts padding, so fit-to-page lands clear.
ResizeObserver watches the content box, so inset changes re-trigger
the fit.

* fix(pdf-server): clear pinch transform in same frame as canvas resize

commitPinch cleared the CSS transform BEFORE renderPage's first
await — for one frame the page snapped back to the old rendered
size, then jumped forward when the canvas resized. renderPage now
clears the transform immediately after setting canvas.style.width/
height, so the size handoff is atomic.

Also bump the wheel settle timer 150→200ms; slow trackpad pinches
can leave >150ms gaps and would commit mid-gesture.

* fix(pdf-server): no blank flash on pinch commit; clamp Ctrl+mousewheel; basic-host observer cleanup

Three review follow-ups:

1) Snap on pinch release: setting canvasEl.width clears the backing
   store, so between the resize and page.render() completing the
   page was blank. Snapshot the old bitmap first and drawImage it
   scaled as a placeholder; the real render paints over it. Gated
   on a transform being present so page-nav renders aren't affected.

2) Ctrl + physical mouse wheel sent deltaY≈±100 → exp(±1)≈2.7× per
   notch, slamming straight to ZOOM_MIN/MAX. Clamp per-event delta
   to ±25 (≈±28% per notch); trackpad pinch deltas are ~±1-10 so
   the clamp is a no-op there.

3) basic-host's iframe ResizeObserver had no disposal path. Chained
   disconnect() into appBridge.onclose (Protocol base class hook).

* simplify(pdf-server): pinch snap was transform-origin mismatch, not bitmap

The snap on pinch release was the page jumping vertically:
transform-origin: center center scales around the wrapper's middle,
but the flex container is align-items: flex-start so on commit the
canvas grows from the TOP. Preview top edge moved up; committed top
edge stays put → jump down. transform-origin: 50% 0 matches the
layout anchor, no jump.

Drop the canvas-snapshot placeholder (over-engineered, wrong fix),
the dead transition='' lines (.page-wrapper has no CSS transition),
and the containerDimensions→refit hook (ResizeObserver covers actual
size changes; the host message isn't load-bearing).
2026-04-02 03:43:47 +01:00
Olivier Chafik
800f6d08fd
fix(pdf-server): viewer liveness, 1:1 batch results, fullscreen jitter (#579)
* fix(pdf-server): fail fast on never-polled viewer + surface batch errors

interact's get_screenshot/get_text waited the full 45s for a viewer that
never mounted (host went idle before iframe reached startPolling), then
timed out with a generic message. Now tracks viewsPolled and fails in ~8s
with a recovery hint. Batch errors were also ordered success-first —
hosts that flatten isError to content[0] showed "Queued: Filled N fields"
and dropped the actual failure entirely. Viewer side: poll even when PDF
load throws, and catch connect() rejection instead of silently blanking.

* fix(pdf-server): squash batch errors into one block, warn on phantom annotation updates

Three issues from a hung interact in production:

1. Batch error swallowed by host flattening. The earlier fix unshifted the
   error before successes, but the prefix went on top of THAT — so
   content[0] was just "Batch failed at step 2/2 (get_screenshot):" with
   the actual timeout text in content[1], which the host dropped. Now all
   text joins into one block; images survive after.

2. update_annotations silent no-op. The viewer's processCommands does
   `if (!existing) continue` for unknown ids. If a prior add_annotations
   failed mid-batch the model gets a happy "Queued" while nothing renders.
   Track ids server-side; warn (not error — user might have drawn the
   annotation manually in the iframe).

3. Per-def isolation in the viewer. A throw in addAnnotation killed the
   whole processCommands batch including any get_pages later in the queue,
   so submit_page_data never fired and the server waited the full 45s.
   Now each def gets its own try/catch.

Also: stderr logging on interact failure (only — not chatty per-call) so
the next hang shows step timing in mcp-server-*.log instead of nowhere.

* fix(pdf-server): images first on success too, not just on error

The host's content[0] flattening drops images on successful batches just
the same: [add_annotations, get_screenshot] → content = ["Queued: ...",
<image>] → model sees only the text. Observed in audit logs where the
exact same iframe + viewUUID returned an image when the batch was
[get_screenshot, get_screenshot] but only text when add_annotations
came first.

Stable-sort images to the front. The Queued lines still arrive, just
after what the model actually asked to see.

* Revert "fix(pdf-server): images first on success too, not just on error"

This reverts commit 129a2dcd7a04e5bd116e59c4d6fc211db4203e2e.

* refactor(pdf-server): 1:1 batch results, drop isError when there's something to lose

LocalAgentMode SDK collapses isError:true results to a bare string of
content[0].text. The squash-everything-into-one-block defense from
0dbb8ed3 worked but lost the positional contract — the model couldn't
tell which command produced which result.

Cleaner: content[i] is the result of commands[i]. If a step fails the
batch stops there with that slot's text starting with "ERROR", and
content.length tells the model how far it got. No isError on multi-step
failures — there's no way to set it without the SDK eating the prior
images. Single-command failures keep isError:true since there's nothing
before content[0] to lose; the SDK's flatten-to-content[0].text is
exactly the ERROR text we want shown.

Tool description documents the contract.

* fix(pdf-server): isError only for single-command failures, not failedAt===0

failedAt===0 in a multi-step batch still has a positional contract to
honor — content.length=1 vs commandList.length=3 is a signal the model
loses if the SDK flattens the array to a string. The ERROR text encodes
"step 1/3" but the simpler rule ("batches never set isError") is
easier to document and reason about.

* fix(pdf-viewer): read live displayMode in requestFitToContent, guard tiny measurements

currentDisplayMode defaults to "inline" and handleHostContextChanged
only writes it `if (ctx.displayMode)`. If the host's initial context
omits the field — or the update lands one tick after a renderPage() —
requestFitToContent measures a near-empty pageWrapper and sends ~85px
(toolbar + padding only), shrinking a fullscreen iframe to a sliver.
Workaround was exit + re-enter fullscreen to force a context update.

Read app.getHostContext().displayMode directly (always fresh), and
separately refuse to send when pageWrapper measures smaller than the
toolbar — that's never a real layout, just an early render.

* revert(pdf-server): drop viewAnnotationIds — wrong about source of truth

The viewer's annotationMap is baseline (loadBaselineAnnotations from the
PDF) + diff restored from localStorage + manual edits + model adds.
viewAnnotationIds only tracks the last category, so the warning fires
for any update to an annotation the model didn't itself add — including
the entire restored state.

That's not "best-effort" miss rate, it's structurally wrong about
where the data lives. Model would learn to ignore the warning. The
viewer-side log.error on the silent skip (mcp-app.ts:4251) is the right
layer for this.
2026-04-02 00:49:29 +01:00
Olivier Chafik
9d55050a00
fix(pdf-server): render page before O(numPages) annotation scans (#581)
PR #506 added loadBaselineAnnotations() and buildFieldNameMap() between
showViewer() and renderPage(). Both iterate every page calling
getPage()+getAnnotations(), so the viewer sat with an unsized empty
canvas for ~400ms (15-page doc, scales linearly) before painting.

Neither scan blocks the canvas — page.render() only needs canvasContext
+ viewport. renderAnnotationsForPage() reads annotationMap (graceful
empty), AnnotationLayer.render() takes cachedFieldObjects (graceful
null). So: render first, scan after, re-render to overlay annotations
+ form values. Same fix at the reload path.
2026-04-01 22:45:45 +01:00
Olivier Chafik
4b3f6252a4
pdf-server: add save_as interact action (#580)
* pdf-server: add save_as interact action

Adds a model-callable save_as action to the interact tool that writes the
viewer's current annotated state (annotations + form values) to a new file.

Reuses the get_pages request/reply pattern: model's interact call blocks
while the viewer builds bytes via getAnnotatedPdfBytes() and posts them
back through a new submit_save_data app-only tool.

Validation runs synchronously before bothering the viewer:
- path must be absolute or file://
- isWritablePath gate (same as save_pdf)
- existence check; fails with a descriptive error unless overwrite: true

The viewer can report build errors back via submit_save_data({error}),
so the model gets a real message instead of the 45s timeout.

* pdf-server: warn model off app-only tools in descriptions

Hosts that don't filter visibility:["app"] tools from the model's tool
list will still surface these. The save_pdf one in particular is a
near-miss for the new save_as interact action — point the model at the
right one.

* pdf-server: save_as without path overwrites original

When path is omitted, save_as falls back to the displayed PDF's local
path (tracked per-viewUUID by display_pdf). Same writability gate as
the viewer's save button: isWritablePath + OS-level W_OK probe, so it
refuses on read-only mounts before the viewer roundtrip.

Requires explicit overwrite: true since the original always exists —
the model can't accidentally clobber it.

Remote PDFs have no source path stored; the error tells the model to
pass an explicit path.
2026-04-01 22:45:00 +01:00
Olivier Chafik
38700569e7
fix(pdf-server): radio + dropdown in fill_form/save (#577)
* fix(pdf-server): fail fast on never-polled viewer + surface batch errors

interact's get_screenshot/get_text waited the full 45s for a viewer that
never mounted (host went idle before iframe reached startPolling), then
timed out with a generic message. Now tracks viewsPolled and fails in ~8s
with a recovery hint. Batch errors were also ordered success-first —
hosts that flatten isError to content[0] showed "Queued: Filled N fields"
and dropped the actual failure entirely. Viewer side: poll even when PDF
load throws, and catch connect() rejection instead of silently blanking.

* fix(pdf-server): radio button + dropdown handling in fill_form and save

Three related bugs around non-text form fields:

Viewer fill_form (mcp-app.ts): writing a string to annotationStorage on
every radio widget trips pdf.js's inverted string coercion
(`value !== buttonValue` → true) — the first-rendered widget checks
itself and clears the rest, so whatever you ask for, you get the first
option. Fix: extract a setFieldInStorage() helper that writes
{value: true} only on the widget whose buttonValue matches, {value: false}
on siblings, matching pdf.js's own change handler. Also covers the
syncFormValuesToStorage() path (localStorage restore).

Save (pdf-annotations.ts): buildAnnotatedPdfBytes only knew about
getTextField/getCheckBox — dropdowns and radios threw in the try block
and fell through the catch, so they were silently dropped on save.
Fix: instanceof dispatch on getFieldMaybe(). For radios, accept either
the option label or a numeric buttonValue (indexed into getOptions()),
since the viewer stores the latter for PDFs with /Opt.

display_pdf output (server.ts): radio groups showed as N identical
`name [Btn]` lines with no way to tell them apart; dropdowns didn't
list their options. Fix: surface buttonValue as exportValue and the
options array from the annotation data already in hand.
2026-04-01 22:28:28 +01:00
Olivier Chafik
f987bc43b2
fix(deps): bump path-to-regexp 8.3.0 → 8.4.1 to patch ReDoS CVEs (#576)
Transitive dep via express → router. Fixes:
- GHSA-27v5-c462-wpq7 (ReDoS via multiple wildcards)
- GHSA-j3q9-mxjg-w52f (DoS via sequential optional groups)

router@2.2.0 accepts ^8.0.0, so this is a clean lockfile-only bump.
2026-04-01 18:04:00 +01:00
Olivier Chafik
0266171b92
Add addEventListener/removeEventListener with DOM-model on* semantics (#573)
* feat(events): DOM-model on* semantics with addEventListener/removeEventListener

Fixes #551 (useHostStyles broken: fonts clobber theme/CSS variables)
Fixes #225 (user onhostcontextchanged and SDK hooks mutually exclusive)

The on* setters now follow the DOM event model:
- Replace semantics (like el.onclick) with getters
- Coexist with addEventListener/removeEventListener listeners
- Both channels fire on dispatch: on* handler then listeners

Introduces ProtocolWithEvents base class between Protocol and
App/AppBridge. Each notification event gets a slot with two independent
channels (singular on* handler + addEventListener listener array).

useHostStyles hooks now use addEventListener with proper cleanup,
so they compose correctly with user on* handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix typedoc validation warnings

- Export AppEventMap, AppBridgeEventMap, ProtocolWithEvents so they
  appear in generated docs (they're part of public addEventListener
  signatures)
- Move AppBridgeEventMap above the AppBridge class JSDoc — it had been
  inserted between the doc block and the class, orphaning the AppBridge
  docs onto the wrong type
- Drop @param callback tags from on* getter/setter pairs — typedoc
  attaches the doc block to the getter signature which has no params
- Add MethodSchema to intentionallyNotExported (internal zod helper
  referenced by the now-documented eventSchemas field)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 18:48:33 +01:00
Olivier Chafik
14969c5da2
chore: bump ext-apps to 1.3.2 (#569)
Changes since 1.3.1:
- Revert fullscreen size-changed skip (#570)
- Fix auto-resize destroying horizontal scroll positions (#567)
- Remove examples/pdf-server/plugin/ — canonical copy is upcoming in knowledge-work-plugins (#565)
2026-03-27 17:45:09 +00:00
Mel Ludowise
fd1186cc2d
Revert fullscreen size-changed skip (#570)
Reverts the fullscreen displayMode guard added in #567. Skipping
size-changed measurement during fullscreen caused apps to not receive
an initial size report after the transition, breaking layout for some
apps.
2026-03-26 16:46:48 +00:00
Mel Ludowise
a7273bbf42
Fix auto-resize destroying horizontal scroll positions (#567)
* Fix auto-resize destroying horizontal scroll positions

The setupSizeChangedNotifications method temporarily sets
html.style.width to 'fit-content' to measure intrinsic content width.
For responsive apps that derive width from their container (width: 100%,
flex: 1, etc.), fit-content resolves to 0px. The synchronous reflow at
0px causes browsers to clamp scrollLeft on all horizontal scroll
containers to 0, permanently destroying their scroll positions.

Replace the fit-content width measurement with window.innerWidth. The
height measurement (max-content) is preserved since hosts rely on it.
Neither the iOS nor web host uses the width value from size-changed
notifications, so this change has no functional impact on hosts while
fixing scroll position clamping for all MCP apps with horizontal scroll
views.

* Skip size-changed measurement during fullscreen

Hosts determine the container size in fullscreen mode, so size-changed
notifications are not needed. The temporary max-content height override
used for measurement causes visible vertical jitter during animated
fullscreen transitions by briefly reflowing the document on every frame.
2026-03-26 10:08:18 +00:00
Olivier Chafik
8961b03163
Remove examples/pdf-server/plugin/ — canonical copy is upcoming in knowledge-work-plugins (#565)
This directory duplicated the Claude Code plugin that's being added to
the knowledge-work plugin marketplace (anthropics/knowledge-work-plugins#72).
Keeping a copy here meant two sources of truth drifting apart — this one
is already stale vs. the server's interact/annotation capabilities from #506.

The MCPB packaging (manifest.json) remains — that's the distributable
server artifact. The Claude Code plugin wrapper belongs in the plugin
marketplace repo where it can be versioned and discovered alongside
other plugins.

Note: committed with --no-verify because the pre-commit hook's
'xargs git add $STAGED' step fails on deletion-only commits (the paths
no longer exist). Hook fix tracked separately.
2026-03-25 15:40:19 +00:00
Olivier Chafik
e8ca859429
chore: bump ext-apps to 1.3.1 (#562)
Changes since 1.3.0:
- fix(pdf-server): detached-buffer on range re-request + stale e2e assertion (#561)
2026-03-23 20:19:06 +00:00
Olivier Chafik
e8a4ea5f89
fix(pdf-server): detached-buffer on range re-request + stale e2e assertion (#561)
* fix(pdf-server): slice bytes before onDataRange to avoid detached buffer

PDF.js transfers the ArrayBuffer to its worker via postMessage, detaching
it in the main thread. When the same range is re-requested (common on
iOS/WKWebView under memory pressure during scroll/zoom/navigate), the
cached Uint8Array now wraps a detached buffer and onDataRange throws
'Buffer is already detached'.

Fix: pass result.bytes.slice() so the rangeCache entry stays valid.

* test(e2e): update pdf-annotations assertion for new display_pdf prompt text

The 91880a6d prompt rewrite changed the interact-enabled result text from
'Displaying PDF (viewUUID: ...)' to 'PDF opened. viewUUID: ...'. The e2e
test was still asserting the old string.
2026-03-23 20:17:25 +00:00
Olivier Chafik
6723c8ff10
chore: bump ext-apps to 1.3.0 (#560)
Changes since 1.2.2:
- feat: add ui/notifications/request-teardown for view-initiated termination (#215)
- feat(pdf-server): annotations, interact tool, page extraction & prompt engineering (#506)
- fix(examples): improve mobile support for map, pdf, and threejs examples (#555)
2026-03-23 20:09:27 +00:00
Frédéric Barthelet
c026b398a1
Add ui/notifications/request-teardown notification for view-initiated termination (#215)
* Add ui/close-resource request for UI to initiate termination

* Add missing METHOD export

* Fix typo in remaining ui/request-close

* Align spec terminology to teardown/termination instead of close/clearnup

* Implement final comments

* Removing view behavior on initiation of request termination
2026-03-19 22:01:59 +02:00
Olivier Chafik
296b5921fb
pdf-server: annotations, interact tool, page extraction & prompt engineering (#506)
* pdf-server: add interact tool to allow model to modify existing view

* pdf-server: add annotations, download, form fill, and highlight_text

Add PDF annotation system with 7 annotation types (highlight, underline,
strikethrough, note, rectangle, freetext, stamp), text-based highlighting,
form filling, and annotated PDF download using pdf-lib.

- Server: annotation Zod schemas, extended interact tool with add/update/remove
  annotations, highlight_text, and fill_form actions
- Client: annotation layer rendering with PDF coordinate conversion, persistence
  via localStorage (using toolInfo.id key), pdf-lib-based download with embedded
  annotations and form fills, uses app.downloadFile() SDK with <a> fallback
- Model context includes annotation summary

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

* pdf-server: add get_pages tool for batch text/screenshot extraction

New tool `get_pages` lets the model get text and/or screenshots from
arbitrary page ranges without navigating the visible viewer.

- Server: `get_pages` tool with interval-based page ranges (optional
  start/end, open ranges supported), `getText`/`getScreenshots` flags,
  request-response bridge via `submit_page_data` app-only tool
- Client: offscreen rendering (hidden canvas, no visual interference),
  text from cache or on-demand extraction, screenshots scaled to 768px
  max dimension, results submitted back to server
- Max 20 pages per request, 60s timeout

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

* pdf-server: move get_pages into interact tool as an action

Fold get_pages into the interact tool to minimize tools requiring
approval. Now accessed via `interact(action: "get_pages", ...)`.

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

* pdf-server: improve interact tool annotation discoverability

- Add concrete per-type schema docs with field names in tool description
- Add JSON example showing add_annotations with highlight + stamp
- Replace opaque z.record(z.string(), z.unknown()) with typed union
  of all annotation schemas (full + partial forms) so the model sees
  exact field names and types
- Remove redundant manual safeParse since Zod inputSchema validates

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

* pdf-server: improve annotation discoverability + add E2E tests

- display_pdf result text now explicitly lists annotation capabilities
  (highlights, stamps, notes, etc.) instead of vague "navigate, search, zoom, etc."
- Restructured interact tool description: annotations promoted to top,
  with clear type reference, JSON example, and bold section headers
- Added pdf-annotations.spec.ts with 6 E2E tests covering:
  - Result text mentions annotation capabilities
  - interact tool available in dropdown
  - add_annotations renders highlight
  - Multiple annotation types render (highlight, note, stamp, freetext, rectangle)
  - remove_annotations removes from DOM
  - highlight_text finds and highlights text

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

* pdf-server: add Claude API prompt discovery tests for annotations

Tests that Claude can discover and use PDF annotation capabilities
by calling the Anthropic Messages API with the tool schemas and
simulated display_pdf result.

Disabled by default — skipped unless ANTHROPIC_API_KEY is set:
  ANTHROPIC_API_KEY=sk-... npx playwright test tests/e2e/pdf-annotations-api.spec.ts

3 scenarios tested:
- Model uses highlight_text when asked to highlight the title
- Model discovers annotation capabilities when asked "can you annotate?"
- Model uses interact (add_annotations or get_pages) when asked to add notes

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

* pdf-server: add example prompts, testing docs, and updated tools table to README

- Example prompts for annotations, navigation, page extraction, stamps, forms
- Documents how to run E2E tests and API prompt discovery tests
- Updated tools table to include interact tool
- Updated key patterns table with annotations, command queue, file download
- Added pdf-lib to dependencies list

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

* pdf-server: simplify interact annotations schema (7802 → 2239 chars)

The typed Zod union (14 anyOf variants: 7 full + 7 partial annotation
types) produced a 5,817-char JSON schema for the annotations field alone.
This bloated the interact tool schema to 7,802 chars, which may cause
the model to struggle with or skip the tool.

Replace with z.record(z.string(), z.any()) — annotation types are
already fully documented in the tool description. Schema drops to
2,239 chars (71% reduction), annotations field to 254 chars (96% reduction).

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

* pdf-server: enumerate all interact actions in display_pdf result

The display_pdf result text now lists every action by name (navigate,
search, find, search_navigate, zoom, add_annotations, update_annotations,
remove_annotations, highlight_text, fill_form, get_pages) so the model
knows exactly what commands are available without needing to inspect the
interact tool schema.

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

* pdf-server: validate viewUUID in interact, clarify in description

The model was passing "pdf-viewer" instead of the actual UUID, causing
get_pages to timeout (commands queued under wrong key, client never
picks them up).

- Add activeViewUUIDs set tracking UUIDs issued by display_pdf
- Validate viewUUID at the top of interact handler with clear error
- Add "IMPORTANT: viewUUID must be the exact UUID returned by display_pdf"
  to the interact tool description

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

* pdf-server: fix annotation clickability and replace emoji icon with SVG

- Raise annotation-layer z-index above text-layer so note annotations
  receive hover/click events (was z-index: 1, now 3; text-layer is 2)
- Replace memo emoji (data-icon attr) with CSS mask SVG document icon
  that respects currentColor for consistent cross-platform rendering

* pdf-server: add annotation side panel with bidirectional linking

Right-side panel (250px) shows all annotations grouped by page with
expand/collapse cards. Clicking a card navigates to the page and pulses
the annotation; clicking a note icon on the PDF highlights its card in
the panel. Panel auto-shows on first annotation, remembers user toggle
preference via localStorage, and shows a badge count when collapsed.

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

* pdf-server: replace 300ms interval polling with long-polling

Server now holds poll_pdf_commands requests open (up to 30s) until
commands arrive, waking waiters via enqueueCommand. Client loops
sequentially instead of using setInterval, with 2s backoff on errors.
Reduces idle RPC traffic from ~3 calls/sec to ~2 calls/min.

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

* pdf-server: add interactive form filling via PDF.js AnnotationLayer

Render PDF.js AnnotationLayer with renderForms:true so form fields
appear as interactive HTML inputs. Build a fieldName→annotationID map
via getFieldObjects() to bridge fill_form (which uses field names) with
annotationStorage (which uses annotation IDs). Sync user input back to
formFieldValues for persistence and PDF download.

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

* pdf-server: add elicit_form_inputs to display_pdf for upfront form filling

When enabled and the client supports elicitation, extracts form fields
from the PDF via pdf-lib and prompts the user to fill them before the
viewer loads. Elicited values are returned in content/structuredContent
and enqueued as a fill_form command for the viewer.

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

* pdf-server: replace pdf-lib with pdfjs-dist for server-side form field extraction

Use pdfjs-dist's getDocument() + getFieldObjects() instead of pdf-lib's
PDFDocument.load() + getForm().getFields() in extractFormSchema(). This
removes the pdf-lib import from the server bundle (pdf-lib is still used
client-side for PDF modification in downloadAnnotatedPdf). Uses the legacy
pdfjs-dist build to avoid DOMMatrix dependency in Node.js.

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

* pdf-server: fix form field font size and programmatic fill_form

Set --scale-factor/--total-scale-factor CSS variables on the form layer
so AnnotationLayer font-size rules resolve correctly instead of falling
back to browser defaults. Also update live DOM elements directly in
fill_form handler so values appear immediately without waiting for a
full re-render.

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

* pdf-server: fix listbox font size overflow in AnnotationLayer

After rendering the annotation layer, shrink select[size] font to fit
within the PDF rect height. The default AnnotationLayer CSS uses a fixed
9px * scale-factor which overflows when many options share a small rect.

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

* pdf-server: add trash icon to annotation sidebar cards

Show a delete button on each annotation card that appears on hover.
Clicking it removes the annotation from the PDF and persists the change.

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

* pdf-server: fix space key captured in form fields

Skip keyboard navigation shortcuts (space, arrows, +/-) when any input,
textarea, or select element is focused, not just the search/page inputs.

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

* pdf-server: show form fields in annotation sidebar with clear buttons

Form field values now appear in the sidebar under a "Form Fields" group,
with trash icons to clear individual values. The badge count and auto-show
logic include form fields. The panel open/close now calls
requestFitToContent with width to avoid overflow in inline layout mode.

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

* pdf-server: clarify Y coordinate system in interact tool description

Explicitly state that Y=0 is the bottom edge and Y=792 is the top for
US Letter, with concrete guidance on typical values for top/bottom
placement to prevent models from using top-down screen coordinates.

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

* pdf-server: validate fill_form field names and report valid fields

Extract form field names during display_pdf and cache per viewer UUID.
fill_form now soft-fails on unknown field names (applies valid ones,
reports skipped ones). Both display_pdf and fill_form results include
the list of valid field names so the model can self-correct.

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

* pdf-server: add compact annotation strip for inline mode

In inline mode, replace the 250px side panel with a slim bottom strip
that shows one annotation/form-field at a time with prev/next navigation.
The side panel is still used in fullscreen mode.

- Strip shows item swatch, label, preview + counter (N of M · Page P)
- Click item to navigate to its page; delete single or clear all
- requestFitToContent includes strip height in size calculation
- fieldNameToPage map built during init for form field page context
- Display mode toggle switches between strip and panel automatically

* pdf-server: improve display_pdf description for form/annotation use cases

Add explicit guidance on when to use the tool: filling out forms
(tax forms, applications), annotating PDFs, and interactive review.
This helps models route user requests like 'help me fill out this form'
to the display_pdf tool.

* pdf-server: only show download button when host supports downloadFile

* pdf-server: resizable sidebar + human-readable form field labels

- Sidebar panel is now resizable by dragging its left edge (150px min,
  50vw max). Width is persisted in localStorage.
- Form fields show human-readable labels from PDF's TU (alternativeText)
  field when available. Mechanical names like ').F1_01[0]' are replaced
  with the generic label 'Field' when no TU text exists.
- Empty values are no longer shown in the sidebar.

* pdf-server: fix field label collection to use getAnnotations() API

getFieldObjects() doesn't include alternativeText. Instead, iterate
per-page annotations (only pages with known form fields) to collect
the TU/alternativeText labels that PDF authors set as tooltips.

* pdf-server: move strip below toolbar + follow form field focus

- Strip is now positioned below the toolbar (above search bar) instead
  of at the bottom, for better visibility and proximity to the toolbar.
- Form field focus changes automatically sync the strip to show the
  focused field's item.
- Search bar top offset adjusts dynamically when strip is shown/hidden.

* pdf-server: add Clear all to fullscreen sidebar + steer model to reuse views

- Add 'Clear all' button in the fullscreen sidebar panel header
- Update display_pdf description to instruct models to use the interact
  tool with existing viewUUID rather than calling display_pdf again for
  the same PDF (e.g. when user asks to annotate an already-open doc)

* pdf-server: add page dimensions to model context + improve coordinate docs

- Model context now includes 'Page size: 612x792pt (coordinates: origin
  at bottom-left, Y increases upward)' so the model knows actual page
  dimensions when placing annotations.
- Interact tool description expanded with clearer coordinate guidance,
  margin hints, and a tip to use highlight_text for text annotations.
- Download button temporarily shown unconditionally (capability check
  commented out with TODO for re-enabling).

* pdf-server: auto-close panel when empty + stronger display_pdf reuse guidance

- Sidebar/strip auto-closes when all items are cleared (via delete or
  Clear all), and the annotations toolbar button hides.
- display_pdf description now uses CRITICAL warning to prevent models
  from re-calling it to add annotations to an already-open PDF.

* pdf-server: skip elicitation when form fields have mechanical names

Only use elicitation if all editable fields have human-readable names.
Fields with brackets, dots, or ALL_CAPS_UNDERSCORE patterns (and no
alternativeText label from the PDF) are considered mechanical and
would be confusing in an elicitation form. Also use alternativeText
as the elicitation field title when available.

* pdf-server: paint annotations on screenshots + list coords in model context

Screenshots sent to the model (both updateModelContext and get_pages)
now include annotations drawn on top of the PDF canvas, so the model
can visually verify placement. The text context also lists each
annotation on the current page with its coordinates.

* pdf-server: style multiselect/listbox form fields to match viewer theme

* pdf-server: add command batching to interact tool

Extract command processing into a helper function and accept an optional
`commands` array for sequential batch execution. This lets the model
send multiple actions (e.g. add annotations + get screenshot) in a single
tool call, reducing round trips. Stops on first error in a batch.

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

* pdf-server: fix multiselect rendering by removing font-size override

The custom font-size: inherit on #form-layer select was conflicting with
PDF.js AnnotationLayer's inline font-size, causing doubled/overlapping text
in select options. Remove the override and keep only the checked-option
highlight style.

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

* pdf-server: fix form reset button by passing fieldObjects to AnnotationLayer

PDF.js AnnotationLayer needs the fieldObjects parameter to bind reset
form button actions. Cache the result of doc.getFieldObjects() and pass
it through to annotationLayer.render().

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

* pdf-server: improve annotation labels in sidebar and strip

Use human-readable labels instead of raw type names (e.g. "Rectangle"
instead of "rectangle", "Stamp: APPROVED" instead of "stamp"). Remove
redundant preview text for types that have no additional content (rectangle,
underline, strikethrough, stamp) to avoid "rectangle Rectangle" duplication.

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

* pdf-server: split get_pages into get_text and get_screenshot commands

Replace the combined get_pages action (with getText/getScreenshots flags)
with two focused commands:
- get_text: extract text from pages (single page or intervals)
- get_screenshot: capture a single page as PNG image

This makes the API cleaner and returns each command's result as its own
content block in batch mode. Also fix download function syntax error
from earlier capability check commenting.

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

* pdf-server: fix download by using pdfDocument.getData() instead of re-fetching

The download was failing with "No PDF header found" because it re-fetched
the PDF via read_pdf_bytes tool calls, but stateless MCP transport creates
fresh server instances per request with empty caches. Using getData() from
the already-loaded pdf.js document avoids the round-trip entirely.

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

* pdf-server: increase express body limit to fix get_screenshot hang

The submit_page_data tool sends base64 PNG screenshots that exceed the
default 100KB express.json() body limit from createMcpExpressApp. This
caused the request to silently fail, making waitForPageData timeout and
the get_screenshot command hang. Replaced createMcpExpressApp with manual
express setup using a 50MB body limit.

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

* pdf-server: use JPEG for screenshots to fix get_screenshot hang

Switch from PNG to JPEG (quality 0.85) for both offscreen screenshots
(get_screenshot) and model context images. JPEG is much smaller than PNG
for document pages, keeping the submit_page_data payload well within the
MCP SDK's default 100KB express body limit. This also reverts the
temporary workaround of replacing createMcpExpressApp with a custom
express setup.

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

* pdf-server: add ' - edited' suffix to download filename when modified

Renames the download from '_annotated' to ' - edited' and also triggers
the suffix when form fields have been filled, not just when annotations
are present.

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

* pdf-server: include viewUUID in display_pdf structuredContent

The viewUUID was only in _meta but not in structuredContent, so the model
couldn't retrieve it from the tool result to pass to interact.

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

* pdf-server: move interact action list from display_pdf content to description

The content response was bloated with static instructions about interact
actions. Moved that to the tool description where it belongs, keeping
only the dynamic PDF URL and viewUUID in the content.

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

* pdf-server: fix form reset setting dropdowns to first item

PDF.js resetform handler deselects all options but doesn't re-add the
hidden empty placeholder, so the browser falls back to showing the first
real option. Work around this by listening for resetform on combo selects
and re-inserting a hidden blank option when nothing is selected.

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

* pdf-server: sort form fields in sidebar/strip by intrinsic PDF order

Fields are now sorted by page number then top-to-bottom Y position on
the page, matching the visual reading order of the PDF form rather than
the arbitrary insertion order of formFieldValues.

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

* pdf-server: add viewUUID to display_pdf outputSchema

The viewUUID was added to structuredContent but not to the outputSchema,
causing MCP validation error -32602.

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

* pdf-server: allow horizontal scrolling in inline mode

When the PDF is zoomed or wider than the container, users can now scroll
horizontally in inline mode instead of the content being clipped.

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

* pdf-server: add viewUUID to model context, remove activeViewUUIDs validation

Include viewUUID in the updateModelContext header so the model can
reference it when calling interact. Remove the activeViewUUIDs set
validation since it breaks after server restarts — command queue TTL
pruning is sufficient cleanup. Also prune empty queues with no active
pollers.

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

* pdf-server: fix delete button alignment in sidebar annotation cards

Annotations without preview text had the delete button hugging the type
label instead of right-aligning. Add margin-left: auto so the button is
consistently pushed to the right edge of the row.

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

* pdf-server: add interactive annotations (select, drag, resize, rotate, undo/redo)

Enable pointer-events on all annotation types so they can be clicked to
select. Draggable types (rectangle, freetext, stamp, note) support smooth
drag-to-move. Rectangles show corner resize handles, stamps show a rotate
handle. Delete/Backspace removes the selected annotation, Ctrl+Z/Shift+Z
provides unlimited undo/redo, and Escape deselects. Sidebar cards sync
with on-canvas selection state.

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

* pdf-server: clicking sidebar card focuses form field input and scrolls into view

Sidebar annotation cards now scroll the annotation into view, and form
field cards navigate to the correct page and focus the input element.

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

* pdf-server: fix form widget double-text by making inputs opaque

PDF.js AnnotationLayer renders interactive form inputs with a
semi-transparent background, letting the canvas's static form appearance
bleed through. Since the widget font (9px * scale, sans-serif) doesn't
match the PDF's actual font metrics, this creates a "double text" misalignment
at most zoom levels. Make text/choice widgets opaque so only the interactive
layer's text is visible.

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

* pdf-server: add selection/focus to model context, double-click to send messages, toggle resize/rotate

- Model context now includes selected annotation ID and focused form
  field name so the model knows what the user is interacting with.
- Double-clicking a sidebar annotation card sends a message prompting
  the model to change that annotation.
- Double-clicking a sidebar form field card sends a message prompting
  the model to fill that field.
- Double-clicking an annotation in the renderer toggles between resize
  and rotate handles (for rectangles and stamps).
- Rectangles now support rotation via the rotate handle.

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

* pdf-server: replace inline-mode strip with floating accordion sidebar

Replace the compact horizontal annotation strip with a floating panel
at top-right in inline mode. Uses accordion-style collapsible sections
(per-page annotations + form fields) with at most one section open at
a time. Refactors renderAnnotationPanel into reusable helpers
(appendAccordionSection, createAnnotationCard, createFormFieldCard).

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

* pdf-server: remove dead annotation strip code

The compact horizontal annotation strip is no longer used since inline
mode now uses a floating accordion sidebar. Remove all strip-related
HTML, CSS, JS (DOM refs, StripItem type, buildStripItems, renderStrip,
navigateToStripItem, deleteStripItem, event listeners, stripHeight
calculations).

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

* pdf-server: multi-select annotations, natural prompts, form reset sync

- Convert selection from single ID to Set for multi-select (Shift+click)
- Deselect annotations when focusing a form input field
- Fix rotation to always use center origin (stamp was using left-bottom)
- Simplify double-click sendMessage prompts (e.g. "update Notes: ")
- Call updateModelContext before sendMessage to guarantee context is sent
- Listen for PDF.js resetform events to clear formFieldValues, update
  sidebar badge/count, and hide icon when no items remain

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

* pdf-server: draggable floating panel with magnetic corner anchoring

- Reduce floating panel width (220→180px), add border-radius
- Panel header is draggable; on drop, snaps to nearest corner
- Persists corner preference to localStorage
- Auto-docks to opposite side when overlapping selected annotations
- Accordion chevron: ▶ when collapsed, ▼ when expanded
- Slightly reduce section header size for compactness

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

* pdf-server: allow all accordion sections to be collapsed

Only auto-open the first section on initial render. Once the user has
interacted with accordion headers, respect their choice to collapse all
sections without forcing one back open.

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

* pdf-server: polish interactions and UI details

- Remove auto-show annotation panel; wait for user to open it
- Badge color: blue (accent) instead of red
- Search bar: offset floating panel below it when anchored top-right
- Text selection deselects annotations and clears field focus
- Click on text layer / empty area clears field focus too
- Zoom keeps selected annotation visible (scrollIntoView after render)
- Fullscreen button: distinct exit icon + tooltip with Esc hint

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

* pdf-server: fix text deselect, stamp dblclick, rotation direction, export

- Text selection and text-layer mousedown now deselect annotations
- Add user-select: none on draggable annotations (prevents stamp text
  selection on double-click, allowing resize/rotate toggle to work)
- Fix rotation direction: remove CSS transform negation so clockwise
  drag = clockwise rotation on screen
- Fix stamp export: rotate around center (matching CSS transformOrigin)
  and negate rotation for PDF coordinate convention
- Add rotation support to rectangle PDF export
- Remove auto-show annotation panel; badge uses blue accent color

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

* pdf-server: allow any text as stamp label, not just predefined enum

The StampLabel was unnecessarily restricted to 6 predefined values
(APPROVED, DRAFT, etc). The PDF format supports arbitrary stamp text,
so open it up to z.string() while keeping common labels as examples
in the schema description.

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

* pdf-server: show resize + rotate handles simultaneously, dblclick sends message

Remove the interactionMode toggle — instead show both corner resize handles
and the rotate handle at the same time on selected annotations. Rectangle
gets resize corners + rotate handle; stamp gets rotate handle only.
Double-click on annotation elements now sends a message to modify the
annotation (matching sidebar card behavior) instead of toggling modes.

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

* pdf-server: SVG fullscreen icons, Y-coord docs, dirty flag

- Replace Unicode fullscreen chars (⛶/⊠) with proper SVG expand/collapse icons
- Add Cmd+Enter keyboard shortcut for fullscreen toggle
- Improve coordinate system docs: emphasize rectangle x,y is BOTTOM-LEFT corner, add worked examples
- Add isDirty state with * prefix on title for unsaved changes
- Track dirty state through persistAnnotations, suppress during restore

* pdf-server: diff-based annotation persistence, proper PDF annotation export, PDF annotation import

Extract annotation logic into pdf-annotations.ts module with comprehensive tests. Replace content-stream drawing (drawRectangle/drawText) with proper PDF annotation dicts (/Type /Annot) that are editable in Acrobat/Preview. Persistence now stores only a diff (additions/removals) relative to the PDF's native annotations, keeping localStorage small. On load, annotations are imported from the PDF via pdf.js and merged with the user's diff.

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

* pdf-server: enhance page shadow for better depth perception

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

* pdf-server: use floating panel in fullscreen, respect insets, direction-aware resize

Always use the floating annotations panel (both inline and fullscreen) instead
of the sidebar in fullscreen mode. Panel anchorage now respects safe area insets
(especially bottom). Resize handle repositions to the correct edge based on
which corner the panel is anchored to. Initial width reduced from 180px to 135px.

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

* pdf-server: add save_pdf tool and save button for local files

Add server-side save_pdf tool that writes annotated PDF bytes back to the
original local file. Client shows a save button (floppy disk icon) next to
the download button for file:// URLs. Ctrl/Cmd+S shortcut saves when dirty.
Saving clears the dirty flag and localStorage diff.

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

* pdf-server: add circle and line annotation types synced with PDF.js

Add circle (ellipse) and line annotation support across all layers:
server Zod schemas, pdf-annotations module (import/export), client
renderers, canvas painting for screenshots, CSS, drag/resize handling,
and tool description documentation.

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

* pdf-server: add searchability keywords to display_pdf tool description

Include viewer, render, renderer, display, show, annotate, edit, and
form keywords so the tool is easier to discover by models.

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

* pdf-server: switch model-facing annotation API to top-left origin coordinates

Convert Y coordinates at the command boundary (add/update_annotations) so the
model uses intuitive top-left origin while internal storage stays in PDF's
native bottom-left origin. Also converts coordinates back to model space in
context strings for consistency.

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

* pdf-server: fix stamp rotation in PDF export via appearance stream matrix

Stamp rotation was a no-op (empty if-block with a comment). Now computes
a proper rotation matrix around the bounding box center and applies it
to the appearance stream. Also adds verification tests for rectangle
stroke/fill colors and stamp color/rotation in exported PDF dicts.

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

* pdf-server: fix screenshots to include filled form field values

Screenshots sent to the model were missing form field values because
the canvas render didn't use ENABLE_STORAGE mode. Now both offscreen
renders (model context and get_pages) pass annotationStorage to PDF.js
so filled fields appear in screenshots. Also reuses renderPageOffscreen
for model context screenshots to deduplicate rendering logic.

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

* pdf-server: list form fields with page, bounding box, and label in display_pdf result

The model needs field locations to fill forms accurately, especially for
fields with mechanical names or no name at all. Extracts per-page bounding
boxes in model coordinates (top-left origin) and includes labels where
available from PDF alternativeText.

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

* pdf-server: include full field listing in fill_form result and update UI after fill

fill_form results now include all form fields with names, types, pages, and
bounding boxes so the model can self-correct on unknown field names. Also
updates the annotation panel badge and visibility after programmatic fills.

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

* pdf-server: add formFields to display_pdf structuredContent and output schema

Programmatic consumers need structured access to form field metadata
(names, types, pages, bounding boxes) without parsing text content.

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

* pdf-server: add image annotation type with URL/base64 support, drag-drop, and PDF export

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

* pdf-server: fix image annotation click/select, add Shift-resize for aspect ratio, add handle tooltips

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

* pdf-server: fix image annotation to stretch to fill its bounding box

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

* pdf-server: fix stamp PDF export to match on-screen appearance

Use non-standard /Name to prevent Preview.app from substituting built-in
stamp graphics, align padding with CSS (4pt/12pt), add ExtGState for 0.6
opacity, and fix text baseline positioning.

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

* pdf-server: improve annotation panel UX and image resize behavior

- Page accordion click navigates to that page
- Form fields appear in their page's accordion section (before annotations)
- Selecting annotation or focusing form field auto-expands page accordion
- Image resize preserves aspect ratio by default (Shift for free resize)
- Add aspect field ("preserve"|"ignore") to ImageAnnotation type
- Fix selection UI regression: remove overflow:hidden that clipped handles

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

* pdf-server: add signature/signing keywords and image example to tool descriptions

- Add signing/signature keywords to display_pdf description for discoverability
- Add image annotation example (signature placement) to interact tool description
- Clarify that imageUrl supports file paths and HTTP URLs with auto-fetch

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

* pdf-server: discourage base64 encoding in image annotation, clarify imageUrl accepts file paths

Models tend to encode images as base64 themselves instead of passing file
paths. Make the schema descriptions and tool docs explicit: prefer imageUrl
with a file path, no data: URIs, don't encode images yourself.

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

* pdf-server: remove imageData from image annotation API, require imageUrl

Models were encoding images as base64 themselves instead of passing file
paths. Remove imageData from the model-facing Zod schema entirely — only
imageUrl (file path or HTTPS URL) is accepted. The server fetches and
embeds the image automatically. imageData remains as an internal field
for client-side rendering and drag-drop. Also mention drag-and-drop as
an alternative in the tool description.

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

* pdf-server: add clipboard support (copy/cut/paste annotations and images)

- Request clipboardWrite permission in resource metadata
- Ctrl/Cmd+C: copy selected annotations as JSON to clipboard
- Ctrl/Cmd+X: cut (copy + delete) selected annotations
- Ctrl/Cmd+V / paste event: paste annotations from clipboard JSON,
  or paste images from clipboard (e.g. screenshots, copied images)
- Refactor drag-drop image creation into shared addImageFromFile()
- Pasted annotations get new IDs and slight offset to avoid overlap
- Pasted images are centered on the current page

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

* pdf-server: add disableInteract option for distributed deployments

Adds a `disableInteract` boolean to `createServer()` options that skips
registering the `interact`, `poll_pdf_commands`, and `submit_page_data`
tools (all relying on an in-memory command queue). Adjusts `display_pdf`
description and schema accordingly when disabled.

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

* pdf-server: flip interact to opt-in, enable only for --stdio

Rename disableInteract → enableInteract (default false) so existing
library consumers get read-only mode without code changes. The CLI
entry point passes enableInteract: true only when running with --stdio.

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

* pdf-server: log client-roots warning once at startup, not per request

* pdf-server: fix form widget transparency, poll spam, and pdf.js warnings

- Form widgets: PDF.js writes inline background-color (from the
  annotation /BC entry, typically 'transparent'), defeating our CSS.
  Force opaque with !important. Fixes double-text in listboxes and
  text inputs at any zoom.
- Poll spam: view was calling poll_pdf_commands in a tight loop when
  interact is disabled (HTTP mode) — server returns isError, which
  doesn't throw, so the loop never backed off. Server now signals
  interactEnabled via _meta; view only polls when true, and stops
  entirely on isError results.
- Silence Helvetica / inset-border warnings: set verbosity=ERRORS
  for server-side getDocument (only used for form introspection,
  no rendering, so font fallback warnings are noise).

* pdf-server: set standardFontDataUrl for server-side pdf.js

Resolve pdfjs-dist/standard_fonts/ at runtime via createRequire. Works
from both source (bun main.ts) and bundled dist/ since pdfjs-dist is
--external. NodeStandardFontDataFactory reads via fs.readFile, so a
filesystem path with trailing separator is what's expected.

Keep verbosity=ERRORS as backstop for unrelated warnings (e.g.
'Unimplemented border style: inset').

* pdf-server: use unpkg CDN for pdf.js standard fonts, add to CSP

- Single CDN URL pinned to the bundled pdfjs-dist version, used in both
  server (Node) and viewer (browser)
- Node: default NodeStandardFontDataFactory uses fs.readFile which can't
  fetch URLs — pass a minimal FetchStandardFontDataFactory
- Browser: DOMStandardFontDataFactory already fetches, just pass the URL
- Add https://unpkg.com to CSP connectDomains (pdf.js loads font
  binaries via fetch(), maps to connect-src)

* pdf-server: guard against missing totalBytes in viewer

Without this, an invalid totalBytes flows to PDFDataRangeTransport.length
→ worker ChunkedStream sized at 0 → opaque 'Bad end offset: N' error.
The guard turns that into an actionable message (rebuild hint).

* pdf-server: re-probe file size at load time, ignore stale totalBytes from history

The display_pdf result (including totalBytes) is baked into conversation
history. If the user saves the annotated PDF (grows the file) then
reloads the conversation, the host replays the stale size. pdf.js
initializes its ChunkedStream at that stale length, then the first live
read_pdf_bytes returns the current (larger) file — every chunk fails
the 'end === bytes.length' check with 'Bad end offset: N'.

Fix: drop the fileSizeBytes param and always probe via a live 1-byte
read_pdf_bytes call before setting up the range transport.

* pdf-server: file watching, save confirmation, disk-change reload

Server:
- fs.watch on local files when interact is enabled; pushes file_changed
  via the poll_pdf_commands channel. Re-attaches on rename to survive
  atomic writes (vim/vscode). Debounced 150ms. Skips unchanged mtime.
- save_pdf returns {filePath, mtimeMs} so the saving viewer can
  recognise its own write's echo. Other viewers on the same file still
  get notified (their content is genuinely stale).
- stopFileWatch in pruneStaleQueues TTL sweep.

Viewer:
- In-app confirm dialog (no window.confirm — would block the iframe).
- Save button: hidden until first edit, enabled while dirty, disabled
  (but still visible) after save, re-enabled on next edit.
- savePdf() asks for confirmation, records lastSavedMtime.
- loadGeneration counter invalidates stale fetchChunk results and
  aborts the preloader when the PDF is reloaded underneath them.
- reloadPdf() clears all per-document state (byte cache, annotations,
  form fields, undo/redo, text caches, field maps, localStorage diff)
  and loads fresh.
- file_changed handler: suppresses own-save echo via saveInProgress +
  mtime match; auto-reloads when clean; prompts Keep/Discard when dirty.

* pdf-server: add file watching tests

Covers: external write → file_changed enqueued via poll_pdf_commands;
debounced rapid writes; stopFileWatch prevents further events; save_pdf
returns mtimeMs; watcher survives atomic rename (vim/vscode pattern).

* pdf-server: fix confirm-btn-primary hover, show filename only in save prompt

- .confirm-btn:hover's background cascaded to primary buttons too
  (same specificity, primary has both classes) — turning the blue
  button light grey on hover. Re-assert background in primary:hover.
- Save confirm: extract just the filename from pdfUrl (strip file://
  prefix, take basename).

* pdf-server: match confirm dialog to host's native style

Use host-provided CSS variables (--color-*, --font-*, --border-radius-*,
--shadow-*) from applyHostStyleVariables, with local fallbacks.

- Larger border-radius (--border-radius-xl), softer shadow (--shadow-lg)
- Larger bold title (--font-heading-md-size, --font-weight-bold)
- Primary button uses inverse colors (dark bg + light text) like the
  host's downloadFile dialog, not blue
- New .confirm-detail box: monospace bordered rect for filename
- Button order: Cancel first, primary last (native convention)
- Escape resolves to first non-primary button

* pdf-server: derive dirty from diff — undoing all changes clears save button

persistAnnotations() was unconditionally setDirty(true). It already
computes the diff vs baseline — use isDiffEmpty(diff) to decide instead.
Now undoing all the way back to the original state marks the viewer
clean again (save button disables, title loses asterisk).

* pdf-server: import saved form field values from PDF as baseline

getFieldObjects() returns the PDF's stored form values but we were only
reading field IDs/pages from it. After saving a filled form and reopening,
the panel showed nothing and there was no way to see what was filled.

- New pdfBaselineFormValues map, populated in buildFieldNameMap() from
  each field's .value (skipping empty/Off/button values). Seeds
  formFieldValues so the panel shows PDF-stored values on open.
- computeDiff takes an optional baselineFormFields param and only
  includes values that differ — opening a filled PDF doesn't mark dirty,
  editing a field does, reverting to the PDF's value marks clean again.
- importFieldValue() normalises radio-group value lookup (parent entry
  has value=undefined, children have the real export value), checkbox→
  true, listbox array→joined string.

* pdf-server: fix form Reset button; split panel into Reset vs Clear all

Reset-button regression: buildFieldNameMap() now seeds formFieldValues
from the PDF's stored values, but syncFormValuesToStorage() was pushing
them back into annotationStorage in our normalised repr (checkbox→true,
radio→export string). pdf.js's native repr may differ, and overwriting
it breaks the form's Reset button. Fix: skip syncing values that match
baseline — the PDF's own values are already in storage natively.

Panel buttons:
- Reset: revert to what's in the PDF file. Restores baseline annotations
  and form values, clears undo/redo. Empty diff → clean.
  Disabled when not dirty.
- Clear all: removes EVERYTHING including PDF-native items. Non-empty
  diff (baseline items are 'removed') → dirty; saving writes stripped PDF.
  Disabled when nothing to clear.

* pdf-server: fix Clear all to push defaultValue; gate save on writability

Clear all regression: annotationStorage.remove(id) only drops our
override — the widget reverts to the PDF's stored /V (the baseline
value). To actually clear, push each field's defaultValue (/DV) via
setValue instead, which is what the PDF's native Reset button does.

Save button visibility: server now checks fs.access(path, W_OK) and
reports via _meta.writable. Viewer gates save on that instead of just
'is this a local path'. Hides the button for read-only mounts and
Claude Desktop's uploads directory. Removed dead isLocalFileUrl().

* pdf-server: fix annotationStorage keys — use widget annotation IDs

Root cause: getFieldObjects() returns field-dictionary refs (the /T
tree, e.g. '86R'), but annotationStorage is keyed by WIDGET annotation
refs (what page.getAnnotations() returns, e.g. '9R'). These differ
when a PDF's field and its widget /Kids are separate objects.

fieldNameToIds was built from getFieldObjects().id, so EVERY
storage write was keyed wrong and silently no-op'd:
- syncFormValuesToStorage: wrote to dead keys
- fill_form: setValue + querySelector([data-element-id=...]) both missed
- individual field delete: remove() on wrong key
- clearAllItems: setValue to wrong keys

Fix: build fieldNameToIds from page.getAnnotations() which gives the
correct widget IDs. Keep cachedFieldObjects only for type/value/
defaultValue metadata (matched by name, not id) and for passing to
AnnotationLayer.render() where pdf.js uses it internally.

Also extract clearFieldInStorage() helper — pushes defaultValue for a
single field, used by both individual delete and Clear all.

* pdf-server: remap fieldObjects IDs to widget IDs for pdf.js Reset

pdf.js _bindResetFormAction iterates fieldObjects using each entry's .id
to (a) key annotationStorage and (b) querySelector([data-element-id=...]).
Both expect WIDGET annotation IDs. fieldObjects contains field-dict IDs.
Works only when field and widget share a PDF object — which pdf-lib's
save breaks (it splits merged objects on write).

This is a latent pdf.js bug, not a regression from our changes — it was
broken the moment the user first saved via save_pdf.

Fix: rebuild cachedFieldObjects with widget IDs (from fieldNameToIds)
before passing to AnnotationLayer.render({fieldObjects}). Skip parent
entries with no concrete id (radio group /T tree root). Preserve
type/defaultValue/exportValues which Reset needs.

* pdf-server: fix combo reset, scope writability, drop flaky CI assert

Combo reset: pdf.js's resetform handler sets all option.selected =
(option.value === defaultFieldValue); when defaultFieldValue is null
nothing matches. Chrome then synchronously normalises the non-multiple
<select> by auto-selecting option[0] — so by the time our fix-listener
ran, selectedIndex was 0 and the state-check failed. Now: capture
whether a real default exists before the event; if reset didn't land
on it, force-prepend a hidden blank and select it explicitly.
(Was latent — before the ID fix, resetform never dispatched on selects.)

Writability scope: only mark writable if the file is explicitly in
allowedLocalFiles (CLI arg = opt-in) OR strictly under an allowed
root directory (isAncestorDir already excludes the root itself via
rel !== ''), AND fs.access W_OK passes. A root passed by the client
is a boundary, not a target.

CI: drop the post-rename second-write assertion — fs.watch re-attach
semantics differ between kqueue and inotify, inherently racy in CI.
Only assert the rename itself is detected.

* pdf-server: fix Reset bug; show cleared baseline fields w/ revert

Reset-all bug: clearUserFormStorage skipped any field whose name was
in pdfBaselineFormValues — but if the user had EDITED a baseline field,
the edit sits in storage under that name. Skipping it left the widget
showing the stale edit while the panel showed the restored baseline.
Fix: remove ALL storage overrides on reset — every field reverts to the
PDF's /V, which IS baseline. Removed the now-dead helper.

Panel: cleared baseline fields now stay visible instead of vanishing.
State is derived per-field by comparing formFieldValues to
pdfBaselineFormValues (no new data structures — both maps already
exist, the comparison was just never rendered):

- unchanged: current === baseline → solid swatch, trash clears
- modified:  baseline exists, current differs → solid swatch, revert
- cleared:   baseline exists, current empty/absent → outlined cross
             swatch, struck-out label/value, revert restores
- added:     no baseline → solid swatch, trash removes

Panel iterates union(formFieldValues, pdfBaselineFormValues) so cleared
items don't disappear. sidebarItemCount uses the same union so the
toolbar button stays visible as long as there are baseline items OR
edits. Per-item revert: formFieldValues.set(name, baseline) +
storage.remove(id) → widget reverts to /V.

* pdf-server: import baseline from widget fieldValue, not field-dict

page.getAnnotations().fieldValue is what AnnotationLayer actually
renders. getFieldObjects().value reads the field-dict /V — which can
be out of sync in an internally-inconsistent PDF (e.g. after a pdf-lib
setText silently failed on a comb field, leaving the field-dict stale
while the widget still shows the older value from the raw bytes).

Your Form.pdf: ID widget shows 'eeeee', field-dict says 'eew2e'.
Panel was importing 'eew2e' → mismatch with what's on screen.

Fix: capture a.fieldValue during the page.getAnnotations scan (we're
already iterating there for widget IDs) and prefer it over the
field-dict value. Fall back to field-dict if the widget doesn't expose
one. normaliseFieldValue() handles the format differences (choice
widgets give arrays; field-dict gives strings).

* pdf-server: fix radio input → 'on'; use export value for consistency

pdf.js creates <input type=radio> without setting .value (pdf.mjs:
18138-18144), so target.value defaults to the HTML spec's 'on'. Our
input listener read that → panel showed 'on' after clicking a radio,
but baseline import correctly used the export value ('0') — so
clicking then reverting showed two different strings for the same
selection.

Fix: store each radio widget's buttonValue during the annotation scan
(we're already there for widget IDs), read it in the input listener
via data-element-id. Now baseline, click, revert, and save all agree
on the export value.

(Form.pdf happens to use '0'/'1' as export values instead of
'Male'/'Female' — that's the form's design; the labels are just PDF
text next to the widgets, not form data.)

* pdf-server: panel Reset/Clear-all as icon buttons, keep tooltips

* pdf-server: file roots from MCP client are read-only

allowedLocalFiles conflated two sources: CLI args (user explicitly
named the file when starting the server — overwriting is intentional)
and MCP file roots (client-uploaded copies in ad-hoc hidden folders
that the client doesn't expect to change).

New cliLocalFiles set tracks only the CLI-sourced files. Writable now
requires: file is in cliLocalFiles OR strictly under a directory root.
Directory roots are mounted folders where saving is expected.

save_pdf enforces the same scope server-side — the viewer hides the
button based on _meta.writable, but we must not trust the client.

* pdf-server: file root stays read-only even under a directory root

Extracted isWritablePath() with the full policy in one place (was
duplicated between display_pdf and save_pdf):

- CLI file → writable (user explicitly named it)
- MCP file root → read-only, ALWAYS. Even when the path happens to
  fall inside a mounted directory root — the client sending it as a
  file root is the stronger signal ('here's an upload')
- under a directory root at any depth → writable (mounted folder)
- the directory root itself → not writable (rel === '' excluded)
- no roots and no CLI files → nothing writable

8 unit tests cover each case.

* pdf-server: fix e2e (interact for HTTP test harness), skip rename test on Linux

E2E: tests/e2e/pdf-annotations.spec.ts expects 'interact' in the tool
dropdown and calls it for annotation tests. Since 3f2c209c interact is
stdio-only; e2e runs HTTP basic-host → tool absent → all tests fail
(broken since Mar 4). Add --enable-interact CLI flag for HTTP mode and
pass it in 'npm run serve'. The in-memory command queue works fine for
the e2e setup (one long-lived process); the stdio-only restriction is
about stateless multi-instance deployments.

Rename test: inotify watches inodes. When the directory entry is
atomically swapped to a NEW inode, the watcher on the OLD inode gets
no event. kqueue (macOS) watches paths and fires 'rename'. Gate with
skipIf(platform !== 'darwin').

* pdf-server: list_pdfs walks directory roots for *.pdf files

Recurse into allowedLocalDirs (depth ≤ 8, ≤ 500 files) and enumerate
actual .pdf files as file:// URLs alongside the explicitly-registered
ones. Skip dotfiles/dirs and node_modules. Dedup (a file may be both
explicitly registered and under a walked directory). Report truncation.

* pdf-server: fix stale e2e assertion after 2d47f14b

Commit 2d47f14b moved the interact action list from display_pdf's
content to the tool description, but the e2e test still asserted the
result text contained 'add_annotations', 'highlight_text', etc.
Now checks for viewUUID / interactEnabled / 'Displaying PDF' which
are the actual result contents.

* pdf-server: add unpkg to resourceDomains (font-src) for pdf.js FontFace

pdf.js in the browser doesn't just fetch() the .ttf bytes — it also
creates FontFace('name', 'url(https://unpkg.com/...)') which goes
through CSP font-src, not connect-src. Nest's CSP blocked it
('font-src self assets.claude.ai'). pdf.js fell back to system fonts
so nothing broke visually, but the CDN URL was doing nothing.

(Separate finding re: the 'bricked UI' report — logs show fill_form
processed cleanly at 16:27:18, then Nest's router navigated away 13s
later. Poll id=17 returned to an already-unmounted iframe. Normal
teardown, not a brick.)

* pdf-server: validate imageUrl scheme before <img src> assignment

Fixes CodeQL js/xss + js/client-side-unvalidated-url-redirection on
line 1994 (renderImageAnnotation) and the canvas-paint fallback.
The server resolves imageUrl to imageData before enqueueing, so
def.imageUrl only survives to the client if the server-side fetch
fails. safeImageSrc whitelists https/http/data/blob and returns
undefined for javascript: et al.

* pdf-server: fix get_pages timeout race; trim fill_form response

get_pages round-trip (interact → enqueue → poll → render → submit):

- GET_PAGES_TIMEOUT_MS 60s → 45s. The MCP SDK also defaults to 60s, so
  if the round-trip got close, the client timed out first and our error
  went nowhere. Server rejects first now.
- waitForPageData wires extra.signal. If the client cancels interact
  (timeout/user abort), we clean up the pendingPageRequests entry
  immediately instead of leaking until our own timeout. Also simplified
  to a single settle callback instead of {resolve, reject, timer}.
- handleGetPages .catch() → submit empty pages. Was fire-and-forget
  with no error path; any rejection before the inner try/catch meant
  no submit_page_data → server waited the full timeout.

fill_form response: dropped the full field listing that was appended
on every call. display_pdf already returns it; echoing it back after
every successful fill is noise. Only list valid field names when the
model got one wrong.

* pdf-server: return URL.href from safeImageSrc so CodeQL sees sanitizer

The previous attempt returned the raw input string after a scheme
check, which CodeQL's taint tracker doesn't recognise as a sanitiser
(it sees tainted in → tainted out). Now parse with the URL constructor
and return the .href property — this is a canonical sanitiser pattern
CodeQL models as a barrier for js/xss and unvalidated-url-redirection.

Also folded the data: URI branch into safeImageSrc for a single call
site responsible for producing the <img src> string.

* feat(pdf-server): treat uploads-root dirs as read-only by default

Dir roots whose basename is 'uploads' (e.g. Claude Desktop's attachment
drop folder) are now treated as read-only unless --writeable-uploads-root
is passed. This prevents the save button from appearing for attached PDFs
that the client doesn't expect to be overwritten.

Also gates the _debug diagnostic block on --debug and adds save/restore
of writeFlags.allowUploadsRoot in test beforeEach/afterEach.

* feat(pdf-server): add --debug flag, fix interact timeout, skip persist for read-only cmds

- Wire --debug CLI flag through to createServer; _debug diagnostic block
  in display_pdf _meta is now only emitted when --debug is set.
- Viewer: showDebugBubble renders _debug payload as a fixed overlay.
- Fix interact get_pages timeout: await handleGetPages instead of
  fire-and-forget, so submit_page_data doesn't queue behind the next
  30s long-poll on serialized host connections.
- Skip persistAnnotations for read-only commands (get_pages, file_changed).

* fix(pdf-server): rebase annotations baseline after save

After a successful save_pdf, update pdfBaselineAnnotations and
pdfBaselineFormValues to reflect what was written to disk. Without
this, removing all annotations after a save produced an empty diff
(compared to the stale pre-save baseline), incorrectly disabling
the save button even though the file on disk still had annotations.

* fix(pdf-server): sync annotation panel on canvas click, fix tooltip z-index

Clicking an annotation on the canvas now re-renders the panel to
expand the correct page section and scrolls the card into view.
Previously only the selection class toggled without updating the
accordion.

Raise tooltip z-index to 100 within the annotation layer and
promote the annotation layer above the form layer on note hover
(:has selector) so tooltips aren't clipped by the form overlay.

* debug(pdf-server): add __pdfDebug() helper + log skipped baseline annotations

Adds window.__pdfDebug() that dumps annotationMap, baseline,
layer children, and localStorage diff to diagnose ghost annotations
(visible on canvas but not in panel, not selectable).

Also logs when importPdfjsAnnotation returns null for non-widget
annotations, and surfaces any thrown errors during baseline import
instead of silently swallowing them.

* debug(pdf-server): expose pdfDocument.annotationStorage + window.__pdf internals

__pdfDebug() now also dumps PDF.js's annotationStorage contents
(editor stamps live there, invisible to our tracking) and all
localStorage keys matching pdf-annot pattern.

After running __pdfDebug(), internals are exposed as
window.__pdf.{pdfDocument, annotationMap, annotationLayerEl, formLayerEl}
for interactive console poking.

* fix(pdf-server): hide viewUUID when interact disabled, widen annotations panel

- display_pdf result text no longer leaks viewUUID when the interact
  tool is not registered (model has no use for it).
- Annotations panel default width: 250px -> 500px (max-width:50vw
  still caps on narrow viewports).

* refactor(pdf-server): extract src/commands.ts, dedupe PdfCommand type

PdfCommand was defined independently in server.ts and mcp-app.ts with
no compiler check to keep them in sync. Extract to src/commands.ts as
the single source of truth; both sides import the type.

Uses the TS interfaces from pdf-annotations.ts (not Zod-inferred types)
since those already describe the post-resolveImageAnnotation wire shape
(imageData present, x/y/width/height non-optional).

Also deletes ~150 lines of dead Zod schemas (PdfAnnotationDef and the
10 variant schemas that built it) -- they were never used as runtime
validators, only for z.infer<typeof> type inference. Input remains
z.record(z.any()) for model-API forgiveness.

* fix(pdf-server): correctness cleanup bundle

- findTextRectsFromCache: return [] instead of a hardcoded placeholder
  rect when text-layer DOM isn't rendered. The placeholder was persisted
  as real coordinates; caller already guards on empty.
- onteardown: bump loadGeneration to stop the preloader (reuses the
  reload-abort check) and clear searchDebounceTimer.
- startPreloading: re-check loadGeneration inside the pause-wait loop
  so teardown mid-pause exits instead of spinning.
- app.sendMessage calls: wrap in .catch(log.error) so host-unsupported
  doesn't throw an unhandled rejection.
- Remove dead ternary (Highlight:Highlight), stale TODO and commented
  downloadBtn line.

* docs(pdf-server): fix action names in example prompts, document all flags

- Example prompts referenced get_pages with getText/getScreenshots params;
  actual model-facing actions are get_text (intervals) and get_screenshot
  (single page). Fixed 3 prompts.
- Document --debug, --enable-interact, --writeable-uploads-root flags.
- Add image annotation type to the Annotation Types table.
- Note that interact is disabled in HTTP mode unless --enable-interact.
- Add Deployment section (stdio vs HTTP single-instance vs stateless).

* test(pdf-server): add interact tool unit tests

New describe("interact tool") block with 7 tests covering:
- enqueue -> poll_pdf_commands roundtrip
- missing-arg error paths for navigate, fill_form, add_annotations
- command queue isolation across distinct viewUUIDs
- fill_form passthrough when viewFieldNames not registered
- (skip) unknown-UUID poll (LONG_POLL_TIMEOUT_MS not exported, can't
  bypass the 30s wait without changing server.ts)

Surprises found:
- interact never validates viewUUID exists; enqueues to any string
- batch-mode early-exits on first error, silently dropping later commands

* style: auto-fix prettier formatting

* refactor(pdf-server): extract annotation panel to src/annotation-panel.ts

Split ~930 lines from mcp-app.ts into two new modules:

- src/viewer-state.ts (50 lines): shared Maps/Sets/Arrays + types
  (annotationMap, formFieldValues, selectedAnnotationIds, fieldNameTo*,
  undoStack/redoStack, TrackedAnnotation, EditEntry). Only containers
  with stable bindings — both modules mutate contents, never reassign.

- src/annotation-panel.ts (1084 lines): floating panel positioning,
  accordion rendering, annotation/form-field cards, reset/clear-all.
  Coupling to mcp-app flows through PanelDeps injected at init —
  scalars like currentPage/pdfDocument reach the panel via a state()
  getter so the ~100 use sites in mcp-app stay unwrapped.

mcp-app.ts: 5666 → 4721 lines. panelState.open replaces
annotationPanelOpen; panelState.openAccordionSection is written by
selectAnnotation. syncSidebarSelection moved to the panel module.

* style: auto-fix prettier formatting

* test(pdf-server): document computeDiff same-id modification behavior

Two new tests:
- User-added annotation modification: captured in diff.added (id not
  in baseline, so latest content persists correctly).
- Baseline annotation in-place modification: KNOWN LIMITATION. Same-id
  edit produces empty diff (id-set based), so the change vanishes on
  reload. Viewer's addAnnotation() works around via remove+add, but
  updateAnnotation() (interact tool's update_annotations) does not.

* chore(pdf-server): revert unrelated screenshot regenerations

Pre-commit hook runs build:all which regenerates all example
screenshots. These 24 PNGs have no semantic change; reverting
to keep the PR diff focused on pdf-server.

* fix(pdf-server): gate resolveImageAnnotation on validateUrl (arbitrary file read)

resolveImageAnnotation() did fs.readFile on whatever imageUrl the model
sent, bypassing the allowedLocalFiles/allowedLocalDirs machinery that
protects display_pdf and save_pdf. Model could request
{imageUrl:"/Users/x/.ssh/id_rsa"}, server would base64 the bytes into
the add_annotations command, iframe stores it, get_screenshot reads it
back.

Also: fetch() branch accepted plain http:// (SSRF to local network).

Fix: call validateUrl(imageUrl) before touching the filesystem or
network. Throws on rejection; caller converts to {isError:true} so the
model sees a clear error instead of silent skip.

3 tests: reject local path outside roots, reject http://, accept path
under allowed dir (with real readFile + imageData population).

* fix(pdf-server): deleted-baseline zombies, clear-all form strip, height-only coord shift

Three bugs from PR review, all in viewer state/coord handling:

1. Deleted baseline annotations zombied back on reload (mcp-app.ts:2512).
   restoreAnnotations' apply loop was add-only. loadBaselineAnnotations
   runs between the two restore calls and re-seeds annotationMap with
   every baseline id, including the ones in diff.removed. The zombie
   survives, and the next persistAnnotations sees it in currentIds ->
   computeDiff produces removed=[] -> deletion permanently lost.
   Fix: delete diff.removed ids after the merge loop.

2. Clear All didn't strip form values from the saved PDF (mcp-app.ts:2756).
   clearAllItems() empties formFieldValues, but buildAnnotatedPdfBytes
   gates on formFields.size > 0 and only writes entries present in the
   map. Empty map -> zero setText/uncheck calls -> pdf-lib keeps the
   original /V values. Fix: at getAnnotatedPdfBytes time, inject an
   explicit clearing sentinel ("" or false by baseline type) for every
   baseline field absent from formFieldValues.

3. update_annotations shifted rect/circle/image when patching height
   without y (mcp-app.ts:4161). The old code spread existing.def
   (internal coords, y = bottom edge) with update (model coords, y =
   top-left), then key-filtered back to only update keys. But internal
   y = pageHeight - modelY - height; patching height with top fixed
   requires rewriting internal y, which the key-filter discarded.
   Fix: convert existing to model coords, merge in model space, convert
   back, pass the full merged def. Also uses the target page height
   when update.page differs (fixes the low-severity page-move bug too).

* fix(pdf-server): sweep aux maps independently of commandQueues; line annotations sort

Two lower-severity findings from review:

Leak (server.ts:265): pruneStaleQueues iterated commandQueues to clean
up viewFieldNames/viewFieldInfo/viewFileWatches, but display_pdf
populates those maps without creating a commandQueues entry (only
enqueueCommand does), and dequeueCommands deletes the entry on every
poll. Net: the sweep found nothing; aux maps leaked every display_pdf.
viewFileWatches entries hold an fs.StatWatcher -> slow FD exhaustion
on HTTP --enable-interact. Fix: new viewLastActivity heartbeat map,
touched at display_pdf/enqueue/dequeue, swept on TTL. Dead second
loop (entry.commands.length===0 was unreachable) removed.

Line-annotation sort (annotation-panel.ts:400): getAnnotationY checked
'y' in def and 'rects' in def, but LineAnnotation has only x1/y1/x2/y2.
Fell through to return 0 -> all line annotations sorted to panel
bottom. Added y1 branch returning max(y1, y2).

* feat(pdf-server): rewrite display_pdf prompts to steer model toward interact

The model was calling display_pdf again to add annotations instead of
using interact with the existing viewUUID. Root cause: the description's
first two sentences invited exactly that --

  'Use this to display, annotate, edit, and fill form fields'
  'when the user wants to ... annotate, edit, sign, stamp'

-- then the CRITICAL warning arrived three paragraphs later, after the
model had already pattern-matched 'user wants to sign' -> display_pdf.

Three changes:
- First sentence is now narrow: 'Open a PDF. Call this ONCE per PDF.'
- Follow-up-actions-go-through-interact is sentence 2, not paragraph 3.
- Result text is directive at the decision point: tells the model to
  call interact with the viewUUID, warns against re-calling display_pdf.

* fix(pdf-server): correct the 'discards state' claim in display_pdf prompts

Both viewers coexist; nothing is discarded. The actual failure mode is
the model now holds a new viewUUID and addresses the wrong viewer --
interact calls land on the fresh empty one, not the one with the user's
existing annotations. Rewording to say that.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-19 17:41:55 +00:00
Mel Ludowise
83d82a1caa
fix(examples): improve mobile support for map, pdf, and threejs examples (#555)
* Fix touch gesture conflicts in WebGL example apps on mobile

* Format with prettier

* fix(examples): improve mobile support for map, pdf, and threejs examples

- map-server: fullscreen button respects safe area insets, hover/opacity
  pattern with always-visible on touch devices
- pdf-server: fit-to-width scale for narrow viewports, responsive toolbar
  with compact controls on mobile, safe area in fullscreen toolbar via
  CSS custom properties
- threejs-server: fullscreen button respects safe area and always visible
  on touch, ResizeObserver for responsive width, host containerDimensions
  for fullscreen height, no container padding in fullscreen, replace
  useHostStyles with inline style application to avoid overwriting
  onhostcontextchanged handler
2026-03-18 13:47:47 +00:00
Olivier Chafik
704b6e0af9
chore: bump ext-apps to 1.2.2 (#549)
Changes since 1.2.1:
- fix(schemas): import from zod/v4 to match SDK's zod API (#548)
- test(e2e): fix no-op security injection test via frames[] traversal (#540)
2026-03-10 19:19:15 +00:00
Olivier Chafik
eeb81c08bc
fix(schemas): import from zod/v4 to match SDK's zod API (#548)
The generated schemas compose with schemas imported from
@modelcontextprotocol/sdk/types.js (e.g. z.array(ContentBlockSchema)).
The SDK constructs its schemas via `import * as z from 'zod/v4'`, but
our generated schema.ts used `import { z } from "zod"` — which returns
the v3 API when the consumer has zod 3.25.x installed.

Mixing v3-created and v4-created schema instances throws at parse time
with errors like `keyValidator._parse is not a function` because zod v4
dropped the `_parse` / `_parseSync` internals that v3 wrappers call.

This was masked before #534 because zod was bundled — all schema
creation shared the same inlined zod copy. Externalizing zod exposed
the mismatch on consumers pinned to zod 3.x (e.g. anthropics/apps).

The `zod/v4` subpath is exported by both zod 3.25+ and zod 4.x, so the
existing peerDependency range `^3.25.0 || ^4.0.0` is preserved.

https://claude.ai/code/session_013xQUegrvVGct5xpqeSL5av

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 19:10:36 +00:00
Anton Pidkuiko
1aa3c9c106
Merge pull request #547 from modelcontextprotocol/ochafik/bump-1.2.1
chore: bump ext-apps to 1.2.1
2026-03-10 17:44:28 +00:00
Olivier Chafik
e13732ad38
chore: bump ext-apps to 1.2.1
Changes since 1.2.0:
- fix: bundle SDK+zod in react-with-deps (was byte-identical to ./react) (#539)
- fix(build): copy schema.json to dist and externalize zod (#534)
- fix: skip debug log for high-frequency tool-input-partial notifications (#546)
- fix(deps): drop @hono/node-server override to patch GHSA-wc8c-qw6v-h7f6 (#535)
- fix(readme): use picture element for theme-aware logo (#545)
- fix(ci): require maintainer association for /update-snapshots trigger (#532)
- fix: pre-commit stages only originally-staged files; add .npmrc (#538)
- ci: use npm ci with caching, validate typedoc links, align Node versions (#533)
- test: exclude screenshot-gen from default E2E run; wire pdf-server tests (#537)
2026-03-10 17:36:49 +00:00
Olivier Chafik
5e4839d1f1
test(e2e): fix no-op security injection test via frames[] traversal (#540)
The cross-app message injection test at security.spec.ts:313 has been a
no-op since it was added in 2411c71a. It attempted to reach the inner app
iframe via:

  outerIframe.contentDocument?.querySelector('iframe')

but contentDocument is null across the 8080→8081 origin boundary (the test
at :247 in the same file explicitly asserts this). The ?. short-circuited,
postMessage never fired, and the test passed without exercising the
defense in message-transport.ts.

Fix:
- Use window.frames[0].frames[0] — cross-origin accessible per HTML spec,
  and the actual attack path a malicious sibling app would use
- Return a sentinel from page.evaluate() and assert it, so the test fails
  loudly if frame traversal ever breaks again
- Actually assert the rejection log (was previously captured but never
  checked)
- Use expect.poll() instead of waitForTimeout(500)
- Reuse captureConsoleLogs helper

Also fixed: comment at :293 claimed 'Outer frame should NOT have
allow-same-origin' but the assertion checks allow-top-navigation. The
outer frame DOES have allow-same-origin (implementation.ts:167);
isolation comes from the separate port, not the sandbox attribute.
2026-03-10 17:36:15 +00:00
Olivier Chafik
22205e6d03
fix: bundle SDK+zod in react-with-deps (was byte-identical to ./react) (#539)
* chore: remove dead ./react-with-deps export (copy-paste bug from #221)

The react-with-deps build entry was introduced in PR #221 (e078250a) as a
sibling to app-with-deps for CDN/unpkg no-bundler use. But its external
array was duplicated without editing — it never dropped @modelcontextprotocol/sdk
from externals, so it has shipped byte-identical to ./react for 10 releases
(v0.3.1 → v1.2.0).

After #534 merged, both entries use ['react', 'react-dom', ...PEER_EXTERNALS]
— still identical, just smaller.

Delete rather than fix: React apps always use a bundler; the CDN/unpkg
use case (HTML-string apps like qr-server) doesn't apply to React hooks.
Zero consumers verified (examples/docs/tests/plugins).

* fix: bundle SDK+zod in react-with-deps (keep React external)

Reverts the deletion from the previous commit and applies the actual fix.

The react-with-deps external array was copy-pasted in PR #221 without
dropping PEER_EXTERNALS — now it mirrors app-with-deps: bundles
@modelcontextprotocol/sdk + zod for CDN/import-map consumers.

React and react-dom MUST remain external — bundling them would cause
dual-React-instance hooks errors. Consumer provides them via import map.

Before: index.js 26KB == react-with-deps.js 26KB (identical MD5)
After:  index.js 26KB != react-with-deps.js 323KB (SDK+zod bundled)
2026-03-10 17:33:49 +00:00
Den Delimarsky
a870456893
fix(readme): use picture element for theme-aware logo (#545)
* fix(readme): use picture element for theme-aware logo

The #gh-light-mode-only / #gh-dark-mode-only URL fragments only work
on github.com. On npm, VS Code preview, and other markdown renderers
both img tags render, showing two stacked logos.

Switch to <picture> with prefers-color-scheme, which GitHub recommends
and which degrades gracefully to a single logo elsewhere.

* ci: add Cloudflare Pages preview for docs PRs

Deploys a per-PR preview of the TypeDoc site to Cloudflare Pages project
mcp-ext-apps-docs-preview when docs, src JSDoc, README, or typedoc config
change. Uses the shared modelcontextprotocol/actions composite action that
injects noindex headers, posts a sticky PR comment with preview URLs, and
cleans up deployments on PR close. Fork PRs are skipped (no secret access).

* fix(docs): sync README logo with TypeDoc theme switcher

The README <picture> logo uses prefers-color-scheme, which only follows
the OS setting. TypeDoc's theme switcher sets data-theme on <html>, so
manually picking Light/Dark would show the wrong logo (e.g. white logo
on light background if OS is dark but user picked Light).

Rewrite the <picture> in the generated docs into two class-tagged <img>s
via the mcpstyle plugin, and hide the inactive one via CSS keyed on both
data-theme (explicit selection) and prefers-color-scheme (OS mode). The
README source is unchanged so GitHub/npm still get <picture>.

Also: prettier normalized mcp-theme.css (tabs to spaces, quote style).
2026-03-10 17:33:40 +00:00
Anton Pidkuiko
a2d2d8c6d0
Merge pull request #546 from modelcontextprotocol/antonp/quiet-partial-log
fix: skip debug log for high-frequency tool-input-partial notifications
2026-03-10 17:33:19 +00:00
Anton Pidkuiko MacBook
20d623f20c
fix: skip debug log for high-frequency tool-input-partial notifications
These fire dozens of times per second during streaming and flood the
console. All other JSON-RPC messages still log at debug level.
2026-03-10 17:25:12 +00:00
Olivier Chafik
2b0d11e64c
fix(ci): require maintainer association for /update-snapshots trigger (#532)
The issue_comment trigger previously ran for any commenter on a PR.
On a public repo this lets drive-by users trigger a job with
contents:write that checks out the PR branch, runs playwright
--update-snapshots, and pushes a [skip ci] commit.

Gate the issue_comment path on author_association being OWNER,
MEMBER or COLLABORATOR. workflow_dispatch is unchanged (already
requires repo write access).
2026-03-06 17:44:08 +00:00
Olivier Chafik
bf623f832d
fix(deps): drop @hono/node-server override to patch GHSA-wc8c-qw6v-h7f6 (#535)
The override pinning @hono/node-server to 1.19.7 was added in 71976101
as a workaround when @modelcontextprotocol/sdk 1.25.3 required ^1.19.9,
which did not yet exist on the public npm registry.

That constraint is now obsolete: the SDK is pinned to 1.25.2 (requires
^1.19.7), and 1.19.8-1.19.11 have since been published. Removing the
override lets npm naturally resolve 1.19.11, which includes the fix for
the HIGH-severity auth bypass in the Serve Static middleware
(GHSA-wc8c-qw6v-h7f6, fixed in 1.19.10).

Lockfile regenerated against registry.npmjs.org (no artifactory URLs).
2026-03-06 17:22:34 +00:00
Olivier Chafik
7eb72c7b4c
fix: pre-commit stages only originally-staged files; add .npmrc (#538)
Bug 1: .husky/pre-commit line 21 ran:
  git diff --name-only --diff-filter=M | xargs -r git add
which stages EVERY modified tracked file in the worktree. If a
contributor has unstaged WIP elsewhere, it gets silently swept
into the commit.

Fix: capture `git diff --name-only --cached` before running
build/prettier, then only re-add that set. src/generated/ is
staged explicitly since build:all regenerates it from
spec.types.ts.

Bug 2: no repo-local .npmrc meant the maintainer's global
artifactory registry leaked into package-lock.json, requiring
the Docker regen dance. Committing .npmrc with
registry=https://registry.npmjs.org/ overrides user-global
config. Docker regen is now optional (still useful for
linux-amd64 optionalDeps).
2026-03-06 17:22:25 +00:00
Olivier Chafik
b093e39560
ci: use npm ci with caching, validate typedoc links, align Node versions (#533)
- Switch npm install -> npm ci in ci.yml (build, build-wsl) and docs.yml
  to fail on lockfile drift rather than silently tolerating it
- Add cache: npm to all setup-node steps that have a lockfile to hash
  (skipped test-git-install which has no checkout, and WSL which has no
  setup-node action)
- Add TypeDoc link validation step after build (Linux x64 only, matching
  existing single-OS gate pattern for MCPB). Verified passing on main.
- Align all workflows on Node 22 (active LTS) — previously CI used 20
  but publish workflows used 22, so releases were tested on a different
  Node than PRs
- Add engines.node >=20 to package.json so consumers on Node 18 get a
  warning
2026-03-06 17:13:58 +00:00
Olivier Chafik
79744afd41
fix(build): copy schema.json to dist and externalize zod (#534)
Two packaging bugs:

1. The './schema.json' export pointed to dist/src/generated/schema.json
   but nothing copied it there (tsc is emitDeclarationOnly, Bun.build
   doesn't emit JSON). Consumers got ERR_MODULE_NOT_FOUND.
   Fix: cpSync after tsc runs.

2. zod is a peerDependency but was bundled into every entry point
   (~270KB each). This causes dual-instance bugs where consumer-side
   'instanceof ZodError' and schema.extend() break across boundaries.
   Fix: add zod to external list for all non-with-deps entries.

Bundle size impact:
  app.js          293952 -> 24561  (-91.6%)
  app-bridge.js   299517 -> 30112  (-89.9%)
  react/index.js  295558 -> 26153  (-91.2%)
  server/index.js 291500 -> 22135  (-92.4%)
  app-with-deps   321814 -> 321814 (unchanged, intentional)
2026-03-06 17:13:48 +00:00
Olivier Chafik
1ea1e9ef43
test: exclude screenshot-gen from default E2E run; wire pdf-server tests (#537)
Bug 1: generate-grid-screenshots.spec.ts was running on every CI E2E
invocation because playwright.config.ts had no testIgnore. This spec
writes examples/*/grid-cell.png as a side effect — it's meant to be
invoked only via npm run generate:screenshots.

Added testIgnore gated on GENERATE_SCREENSHOTS env var (Playwright's
testIgnore is stronger than explicit CLI file args, so a plain ignore
would break generate:screenshots). Passed the env var through the
docker invocation in generate:screenshots.

  Before: npx playwright test --list → 82 tests in 3 files
  After:  npx playwright test --list → 65 tests in 2 files
  GENERATE_SCREENSHOTS=1 ... --list → 17 tests in 1 file (still works)

Bug 2: examples/pdf-server/server.test.ts (448 LOC, 34 tests) was
never run — root script was 'bun test src'. Changed to 'bun test src
examples'. Only one .test. file exists in examples/ so no noise.

  Before: npm test → 87 pass across 3 files
  After:  npm test → 121 pass across 4 files
2026-03-06 17:13:40 +00:00
Olivier Chafik
6b12fff1d0
chore: bump ext-apps to 1.2.0 (#527)
* chore: bump ext-apps to 1.1.3

Changes since 1.1.2:
- fix: autoResize reporting viewport height when content is taller (#525)
- fix: remove duplicate --color-text-ghost from McpUiStyleVariableKey (#523)
- fix: examples servers' npm run start:stdio (#507)
- pdf-server: ignore client roots by default (#510)
- docs: add Security documentation section and SEO improvements (#524)
- docs: add MCP client configuration examples to CONTRIBUTING.md (#508, #526)
- docs: use apps.extensions.modelcontextprotocol.io domain for docs URLs (#499)
- docs: add SECURITY.md with GitHub Security Advisories guidance (#472)

* chore: switch to minor bump 1.2.0 (new APIs in #470)
2026-03-06 15:43:55 +00:00
Andrew
9228662557
fix CI (#456)
Co-authored-by: Andrew Lyu <andrewlyu@Andrews-MacBook-Air.local>
2026-03-06 15:32:21 +00:00
Olivier Chafik
7046d7b1dc
docs: ask contributors to allow maintainer edits on PRs (#529)
Enables maintainers to rebase stale fork branches and allows the
/update-snapshots workflow to push updated screenshots.
2026-03-06 15:18:42 +00:00
Jonathan Hefner
5d6fff2910
Update basic-host imports to use package exports (#400)
Change `sandbox.ts` imports from relative paths into `dist/` to use the
proper `@modelcontextprotocol/ext-apps/app-bridge` package entry point.
This aligns the example with how external consumers would import from
the package.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 14:38:12 +00:00
Hryhorii (Greg) Avcharov
38e1f7af14
refactor: remove unnecessary type assertions (#471) 2026-03-06 14:35:04 +00:00
Jonathan Hefner
61951a5c78
Refactor create-mcp-app skill to reference patterns.md (#416)
Move detailed implementation recipes (tool visibility, host styling, streaming,
fullscreen, etc.) out of the skill and point to `docs/patterns.md` instead.
This keeps the skill focused on essentials while the patterns doc provides
comprehensive examples.

Key changes:
- Fix protocol flow diagram: host renders UI before server returns result
- Update template file list to reflect actual contents (`main.ts`, `global.css`)
- Add critical CSP/CORS warnings to "Common Mistakes" section
- Replace "Advanced Examples" table with categorized patterns list
- Add note clarifying `basic-host` is for testing, not a host behavior guarantee

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 14:32:47 +00:00
Jonathan Hefner
c000ba6dd5
Work around tsx watch hang on Windows in quickstart (#428)
On Windows, `tsx watch` can hang when running under `concurrently` due
to stdin handling conflicts (see [privatenumber/tsx#623][]). Adding the
`--raw` flag to `concurrently` avoids this by preventing stdin
interception.

Since `--raw` mode disables concurrently's automatic output prefixing, a
custom Vite logger is added to prefix messages with `[vite]` so users
can still distinguish which process produced each line of output.

[privatenumber/tsx#623]: https://github.com/privatenumber/tsx/issues/623

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-06 14:32:30 +00:00
Hryhorii (Greg) Avcharov
19fe73a08a
Replace deprecated RESOURCE_URI_META_KEY usage with modern format (#457) 2026-03-06 14:32:04 +00:00
Olivier Chafik
ef8fc6e2f0
fix: make listServerResources() params optional (#528)
Follow-up to #470. Matches MCP SDK Client.listResources(params?) signature
so callers can omit the empty {} for the common case.

Also reflows a line in app-bridge.test.ts that prettier flagged.
2026-03-06 14:30:36 +00:00
Hryhorii (Greg) Avcharov
4f9a7ebc74
feat: add readServerResource() and listServerResources() api to App (#470)
* feat: add readServerResource() and listServerResources() api to App class

* test: add listServerResources and readServerResource integration tests
2026-03-06 14:20:45 +00:00
Antoine Lévêque
62c75203b6
Replace platform-specific optionalDependencies with bun devDependency (#518)
* Replace platform-specific optionalDependencies with bun devDependency

Fixes #222. The bun meta-package handles platform detection internally.
Rollup resolves its own platform binaries transitively. Removes ~400MB
of bloat for SDK consumers. Simplifies setup-bun.mjs accordingly.

* Remove setup-bun.mjs, simplify prepare script

No longer needed since the bun npm package handles binary
installation and PATH registration automatically.
2026-03-06 13:56:34 +00:00
Suriya Prakash
0af4cdc305
fix: remove duplicate --color-text-ghost from McpUiStyleVariableKey (#523) 2026-03-06 13:46:04 +00:00
Den Delimarsky
76f2de7b19
Add Security documentation section and SEO improvements (#524)
* Add Security docs section, SEO improvements, and doc site enhancements

- Add Authorization guide covering per-server and per-tool OAuth patterns
- Add CSP & CORS guide extracted from Patterns doc
- Group both under a new Security section in the documentation nav
- Replace CSP & CORS section in Patterns with cross-reference to new guide
- Rename doc site title from package name to 'MCP Apps'
- Rename 'Modules' nav section to 'API Documentation'
- Ensure Security section appears before API Documentation in TOC
- Add SEO plugin with JSON-LD structured data (TechArticle/WebPage)
- Add per-page meta descriptions from frontmatter
- Add description frontmatter to all documentation pages
- Add hostedBaseUrl for sitemap.xml generation and canonical URLs
- Normalize document page slugs to lowercase hyphenated format
- Update compressed navigation and search index data to match new slugs

* Add favicons with light/dark mode support

- Download favicons from modelcontextprotocol.io (light + dark variants)
- Inject favicon link tags into every page with correct relative paths
- Support prefers-color-scheme media queries for light/dark mode
- Include apple-touch-icon for iOS
- Copy favicon assets to output directory during doc build

* Fix HTML sanitization in SEO plugin with proper parser

Replace regex-based HTML tag stripping with htmlparser2 for
extracting plain-text descriptions from rendered pages.

* Add custom theme to align with modelcontextprotocol.io styling

- Import Inter and JetBrains Mono fonts (matching MCP site)
- Override light mode colors: pure white backgrounds, softer gray borders
- Override dark mode colors: match MCP site dark background (#0f1117)
- Taller header (3.5rem) with backdrop-blur frosted glass effect
- Sidebar: adjusted spacing, font size, and hover/active states
- Content: increased line-height (1.6), more heading breathing room
- Code blocks: JetBrains Mono, subtle border, more padding
- Fix CSS load order: custom.css now loads after theme stylesheets

* Align sidebar nav styling with modelcontextprotocol.io

- Active item: tinted blue background with blue text (matching MCP's
  primary-color tinted active state)
- Non-active items: muted gray text, subtle hover background
- Larger border-radius (0.75rem/rounded-xl) matching MCP sidebar
- Group headers: semibold with slight letter spacing
- Proper dark mode variants for all states

* Fix sidebar active item: dark gray background with bold text

* Make sidebar active item more prominent: bolder weight, stronger background

* Highlight current page in sidebar nav, normalize code font size

Inject a small script via the SEO plugin that marks the active sidebar
link with a "current" class (TypeDoc doesn't do this natively for
document pages). Uses a MutationObserver since the sidebar is populated
asynchronously. Pathname comparison strips .html extensions to support
clean-URL servers.

Also bumps code/pre font-size from 0.85em to 1em so code snippets match
the surrounding body text size.

* Split styling into dedicated plugin, fix auth terminology, improve breadcrumbs

Extract CSS cascade fix, breadcrumb labels, and sidebar nav highlighting
from the SEO plugin into a new typedoc-plugin-mcpstyle.mjs to keep
concerns separated.

Fix authorization doc: use "authorization" consistently instead of mixing
with "authentication", link to MCP spec and RFC 9728 for Protected
Resource Metadata, remove redundant spec link paragraph.

Rename "Documents" nav section to "Getting Started" and add explicit
group frontmatter to all doc pages. Breadcrumbs now show the section
name instead of duplicating the page title.
2026-03-06 13:45:15 +00:00
Olivier Chafik
b8a1349c07
docs: use cd-based MCP config in CONTRIBUTING local dev examples (#526)
Switch local development MCP server config from `npm --prefix` to
`bash -c "cd DIR && npm --silent run start:stdio"`. The --prefix form
caused connection instability in Claude Desktop (reconnect loops with
orphan builds competing on the same dist/).

Also updated example paths from ~/code/ext-apps to ~/src/ext-apps.
2026-03-06 13:44:57 +00:00
Mel Ludowise
26928b8622
Fix autoResize reporting viewport height when content is taller (#525)
fit-content clamps to the containing block's available size. For <html>,
that's the iframe viewport, so when content exceeds the viewport height
the SDK reported the viewport height instead of the true content height,
causing internal scrolling instead of iframe growth.

max-content has no available-space constraint and correctly reports the
intrinsic content height in both directions (shrink and grow). Width
stays fit-content to preserve responsive wrapping.
2026-03-06 13:30:13 +00:00
Olivier Chafik
134c71c29c
Add MCP client configuration examples to CONTRIBUTING.md (#508)
* docs: restore full MCP client JSON configs to CONTRIBUTING.md

Reinstate the copy-pasteable JSON configurations for running all examples
with MCP clients (npx-based and local development), which were removed
from README.md in 2af02ba. Placed in CONTRIBUTING.md as a better home
for contributor-facing setup instructions.

https://claude.ai/code/session_014pjt7bE4ckHns5QFPkdWQp

* Use start:stdio in local dev config, add missing scripts to 9 examples

Update the local development JSON config in CONTRIBUTING.md to use
`npm --silent run start:stdio` instead of manual bash commands.

Add `serve:stdio` and `start:stdio` scripts to the 9 examples that
were missing them (basic-server-*, sheet-music, transcript,
video-resource, debug) to match the pattern already in place for the
other 11 showcase examples.

https://claude.ai/code/session_014pjt7bE4ckHns5QFPkdWQp

* style: auto-fix prettier formatting

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-06 13:16:42 +00:00
Olivier Chafik
30f79b9e3f
ignore client roots by default in pdf-server (#510)
* security: ignore client roots by default in pdf-server

When the pdf-server is started with --stdio, MCP clients may advertise
roots that refer to directories on the *client's* file system. Because
the server resolves those paths locally, accepting them by default would
give the remote client access to arbitrary directories on the server's
machine.

This commit makes client roots opt-in via the --use-client-roots flag.
Without the flag, the server logs a notice and skips roots setup
entirely. The createServer() function now accepts a CreateServerOptions
object with a `useClientRoots` boolean (defaults to false).

https://claude.ai/code/session_014ohk5NMEPe8TBKpqp4ZRSw

* Enable client roots by default for HTTP, keep off for stdio

HTTP mode serves a local client so roots are safe. Stdio mode may
have a remote client whose roots would resolve against the server's
filesystem, so roots stay off unless --use-client-roots is passed.

https://claude.ai/code/session_014ohk5NMEPe8TBKpqp4ZRSw

* fix: correct transport-aware defaults for useClientRoots

stdio = local client (e.g. Claude Desktop) → auto-enable roots
HTTP = remote client → ignore roots unless --use-client-roots passed

The previous commit had the logic inverted.

https://claude.ai/code/session_014ohk5NMEPe8TBKpqp4ZRSw

* refactor: inline useClientRoots per transport branch

Remove the intermediate effectiveUseClientRoots variable — just pass
`true` in the stdio branch and the flag value in the HTTP branch.

https://claude.ai/code/session_014ohk5NMEPe8TBKpqp4ZRSw

* docs: update README to match transport-aware client roots defaults

Stdio always enables roots (client is local); HTTP ignores them by
default. The previous README text had these reversed.

https://claude.ai/code/session_014ohk5NMEPe8TBKpqp4ZRSw

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 00:42:11 +00:00
Den Delimarsky
87e30edb03
Merge pull request #472 from modelcontextprotocol/localden/sec-doc-update
Update SECURITY.md to use GitHub Security Advisories
2026-02-26 22:48:01 -08:00
Olivier Chafik
627ead3805
fix examples servers' npm run start:stdio (#507)
* fix examples servers' npm run start:stdio

* rm mistakenly added file
2026-02-26 04:00:23 +00:00
Jonathan Hefner
37533ff4c7
Merge pull request #499 from jonathanhefner/update-docs-url
Use `apps.extensions.modelcontextprotocol.io` domain for docs URLs
2026-02-25 08:36:18 -06:00
Olivier Chafik
5373f01d6f
chore: bump ext-apps to 1.1.2 (#503)
* chore: bump ext-apps to 1.1.2

Changes since 1.1.1:
- feat: add ui/download-file method for host-mediated file downloads (#475)
- pdf-server: robust path validation + folder/file root support (#497)
- ci: auto-fix prettier formatting in pre-commit hook and CI (#498)

* fix: sync local SDK build into node_modules for examples

npm workspaces hoists example dependencies to the root node_modules,
but installs the published registry copy of @modelcontextprotocol/ext-apps
instead of linking to the local source. This causes type-check failures
when examples use features not yet published to npm.

Add scripts/link-self.mjs which copies the freshly-built dist/ and
package.json into the hoisted node_modules copy after each SDK build,
ensuring examples always type-check against the latest local types.

See: https://github.com/npm/feedback/discussions/774

* Revert "fix: sync local SDK build into node_modules for examples"

This reverts commit e3387e8434f3def030f5a223a70632092c929119.
2026-02-25 01:43:51 +00:00
Olivier Chafik
02737db779
fix: sync local SDK build into node_modules for examples (#504)
npm workspaces hoists example dependencies to the root node_modules,
but installs the published registry copy of @modelcontextprotocol/ext-apps
instead of linking to the local source. This causes type-check failures
when examples use features not yet published to npm.

Add scripts/link-self.mjs which copies the freshly-built dist/ and
package.json into the hoisted node_modules copy after each SDK build,
ensuring examples always type-check against the latest local types.

See: https://github.com/npm/feedback/discussions/774
2026-02-24 21:20:59 +00:00
Olivier Chafik
1728617194
ci: auto-fix prettier formatting in pre-commit hook and CI (#498)
Pre-commit hook:
- Stage all files modified by prettier:fix, not just src/generated/

CI (GitHub Actions):
- Add prettier-fix job that runs on PRs from the same repo
- Auto-commits formatting fixes back to the PR branch
- Skips fork PRs (no write access to fork branches)

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-24 18:37:57 +00:00
Anton Pidkuiko
e4a9766592
Merge pull request #475 from modelcontextprotocol/feat/download-file
feat: add ui/download-file method for host-mediated file downloads
2026-02-24 18:30:54 +00:00
Olivier Chafik
281a26b021
pdf-server: robust path validation + folder/file root support (#497)
* pdf-server: support folders in cli + files in roots

* fix(pdf-server): use path.relative for robust ancestor dir matching

- Replace string prefix matching (startsWith) with path.relative-based
  isAncestorDir() which correctly handles trailing slashes, path
  normalization, and prevents both .. traversal and prefix attacks.
- Use path.resolve consistently when adding paths to allowedLocalFiles
  and allowedLocalDirs (main.ts was missing this).
- Use resolved path for exact file match check in validateUrl.
- Improve error message formatting for easier debugging.
- Add tests for trailing slashes, grandparent dirs, and isAncestorDir.

* chore(pdf-server): remove JSONL interaction logging

Remove the withLogging transport wrapper, logInteraction helper,
InteractionLogger type, and all log parameter threading that were
added for debugging.

* fix(pdf-server): support computer:// URL scheme for local files

Some clients (e.g. Claude Code) use computer:// instead of file://
for local file references. Handle both schemes in isFileUrl and
fileUrlToPath.

* fix(pdf-server): add debug logging for rejected files, ignore .jsonl logs

* fix(pdf-server): accept bare file paths in addition to file:// URLs

Clients like Claude Desktop may pass raw absolute paths (e.g.
/Users/.../file.pdf) instead of file:// URLs. Handle these in
validateUrl and readPdfRange.

* debug(pdf-server): add hex char diagnostics for path mismatch

* fix(pdf-server): resolve symlinks to handle sandbox path remapping

Use fs.realpathSync to resolve symlinks/bind mounts when comparing
file paths against allowed directories. This fixes the path namespace
mismatch where Claude Desktop sends container paths (e.g. /sessions/...)
while MCP roots use host paths (e.g. /Users/...).

Both the file path and directory paths are resolved through symlinks
before comparison, so either side can be a symlink.

Also stores realpath of roots in allowedLocalDirs/Files so matching
works in both directions.

Removes verbose hex diagnostics (no longer needed).

* fix(pdf-server): decode percent-encoded bare paths (e.g. %20 for spaces)

Clients may send bare file paths with percent-encoded characters
(e.g., Application%20Support). Apply decodeURIComponent to bare paths
before resolving, matching the behavior already used for file:// URLs.

* fix(pdf-server): add full diagnostics to client-facing error message

Include url, resolved path, realpath, allowedFiles, and per-directory
path.relative results in the error returned to the client, so we can
diagnose why isAncestorDir fails when paths look identical.

* debug(pdf-server): add hex dump to diagnose invisible char differences

path.relative returns 10 levels of .. despite paths looking identical,
meaning they differ at the byte level. Add hex dump of first 30 chars
of both paths to catch invisible Unicode or encoding differences.

* fix(pdf-server): use inode comparison for bind-mount path matching

The cowork VM remaps paths via bind mounts: the tool input uses
VM-internal paths (/sessions/...) while MCP roots use host paths
(/Users/...). Since path.relative can't match across bind mounts,
compare directory inodes instead (dev+ino from fs.statSync).

Walks up the file's parent directories checking each against allowed
dirs by inode, which works regardless of path namespace differences.

* fix(pdf-server): resolve VM-internal paths via directory basename matching

When the MCP server runs on the host but receives unrewritten VM paths
(e.g., /sessions/name/mnt/uploads/file.pdf), the tool call url argument
isn't rewritten by the cowork VM proxy because it doesn't know which
arguments are file paths.

Fix: when path validation fails, try to match the file by finding the
directory basename (e.g., 'uploads') in the VM path and looking for
the relative path suffix under each allowed directory on the host.

Also returns the resolved host path so readPdfRange reads the correct
file on the host filesystem.

* revert(pdf-server): remove resolveVmPath hack

The VM path mismatch (VM-internal paths not rewritten in tool call
arguments) should be fixed in the cowork VM proxy layer, not worked
around in the MCP server.

Removes:
- resolveVmPath() function
- resolvedPath field from validateUrl return type
- effectiveUrl remapping in tool handlers
- VM path resolution test

* nit
2026-02-24 18:23:57 +00:00
Jonathan Hefner
b4642b4338
Use apps.extensions.modelcontextprotocol.io domain for docs URLs
Update all documentation links from `modelcontextprotocol.github.io/ext-apps`
to the new custom domain across README.md, docs/, and example READMEs.

Add `cname` to the `peaceiris/actions-gh-pages` deploy action in
`.github/workflows/docs.yml` so the CNAME file persists across deploys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-24 11:38:54 -06:00
Anton Pidkuiko MacBook
9340bd800e
test: add App → AppBridge integration test for downloadFile 2026-02-24 16:57:18 +00:00
Olivier Chafik
ec389f473b
chore: bump ext-apps to 1.1.1 (#496)
Bug fixes / improvements since 1.1.0:
- fix: add audio/video support for basic-host (#460)
- fix(pdf-server): remove page-loading overlay (#493)
- pdf-server: remove domain allowlist, require HTTPS only (#487)
- Set audioSession.type to playback in sheet music app (#489)
- Add MCPB packaging and Claude Code plugin for pdf-server (#491)
2026-02-24 02:39:24 +00:00
Mel Ludowise
82b1f29085
Set audioSession.type to playback in sheet music app (#489)
Use the Audio Session API to declare audio as media playback so the
platform handles it correctly: background audio, lock screen controls,
Dynamic Island integration, and bypassing the iOS silent switch.
2026-02-24 01:58:32 +00:00
Olivier Chafik
0be854b244
ci: build and publish MCPB bundle for pdf-server (#495)
- Add *.mcpb to .gitignore (built artifacts)
- Validate MCPB pack in CI (Linux x64 only)
- Build and upload .mcpb as GitHub release asset on publish
2026-02-24 01:48:17 +00:00
Bryan Thompson
3de035dc8d
Add MCPB packaging and Claude Code plugin for pdf-server (#491)
* Add MCPB packaging and Claude Code plugin for pdf-server

Two distribution paths for the pdf-server:

1. **MCPB bundle** (Claude Desktop MCP Directory):
   - manifest.json: v0.3 manifest with tools, compatibility, privacy
   - .mcpbignore: excludes source/tests from bundle
   - icon.png: 64x64 PDF document icon

2. **Claude Code plugin** (plugin/):
   - .mcp.json: declares local stdio server via npx
   - commands: /pdf-research:read, /pdf-research:summarize
   - skills: pdf-reading skill for document navigation
   - Tested end-to-end: MCP server connects, all tools available

The plugin uses a local stdio MCP server pattern — the first known
instance in a knowledge-work plugin (all existing plugins use remote
HTTP connectors).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Run prettier:fix to satisfy CI formatting checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-24 00:51:43 +00:00
Olivier Chafik
47e437b631
ci: add missing examples to deploy workflows (#494)
Add basic-server-preact, basic-server-solid, basic-server-svelte,
basic-server-vue, and debug-server to the publish workflows so they
are included in pkg-pr-new and npm deployments.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:31:37 +00:00
Olivier Chafik
4d9ba1ea0e
pdf-server: remove domain allowlist, require HTTPS only (#487)
Replace the hardcoded list of allowed remote origins with a simple
HTTPS protocol check. Any HTTPS URL is now accepted for remote PDFs.
Local file allowlisting remains unchanged.
2026-02-23 21:27:15 +00:00
Olivier Chafik
219b4b1605
fix(pdf-server): remove page-loading overlay (#493)
* fix(pdf-server): remove page-loading overlay

The overlay had multiple issues:
- Hardcoded white background broke dark mode
- Could get stuck visible, breaking sizing/layout
- Added little value since previous page stays visible during render
  and the 150ms delay meant most renders completed before it showed

* prettier:fix
2026-02-23 21:25:25 +00:00
Anton Pidkuiko MacBook
f07bdfbe27
docs: remove redundant resource-type bullets from download-file spec
Remove bullets that restate standard EmbeddedResource/ResourceLink behavior
already defined in the MCP resource spec. Keep download-specific host behavior
(confirmation dialog, filename derivation, security policy, path sanitization).
2026-02-23 13:08:34 +00:00
Den Delimarsky
b4ea8c53a2
Merge pull request #486 from modelcontextprotocol/readme-updates
Adding ChatGPT to the readme
2026-02-21 18:12:15 -08:00
Conor Kelly
6e40513dbf Adding ChatGPT to the readme
Added ChatGPT as a supporting client in the readme. Also updated links to re-direct to the relevant MCP Apps docs on respective client pages
2026-02-21 18:09:18 -08:00
Hryhorii (Greg) Avcharov
c49b4b5e5b
fix: add audio/video support for basic-host (#460) 2026-02-22 01:21:22 +00:00
Olivier Chafik
5bbf277882
chore: bump ext-apps to 1.1.0 (#485)
* chore: bump ext-apps to 1.1.0

Features:
- registerAppResource now returns the registered resource (#370)
- Accept UIResourceMeta in both resources/list and resources/read (#410)
- callServerTool throws helpful error when called with a string (#449)
- Add double-connect guard to prevent protocol message handling errors (#450)

Bug fixes:
- Only ignore messages that lack jsonrpc 2.0 in message-transport (#448)
- Align basic-host dark mode styles (#438)

Spec:
- Change Host <> Sandbox communication protocol to SHOULD (#435)
- Clarify UIResourceMeta in both list and read responses (#410)

* fix(debug-server): add missing npm package config (main, bin, types, exports)

Align debug-server package.json with all other server examples:
- main: dist/server.js (was server.ts)
- types: dist/server.d.ts
- bin: mcp-server-debug -> dist/index.js
- exports with types
- files: only dist (remove source server.ts)
2026-02-22 01:18:47 +00:00
Olivier Chafik
d185099ead
feat(pdf-server): lazy range-based loading + eager background preloading (#480)
* lazy PDF loading w/ PDFDataRangeTransport

* lint

* feat: lazy PDF loading via range transport + eager background preloading

Replace full-file download with PDFDataRangeTransport so PDF.js fetches
byte ranges on demand. First page renders immediately; remaining pages
load in background for search.

**Range transport (server + client)**
- server.ts: add read_pdf_bytes tool returning base64 chunks with offset
- mcp-app.ts: AppRangeTransport feeds PDF.js via fetchRange/fetchChunk
  with parallel sub-requests for large ranges, Promise-based dedup of
  concurrent requests, and a persistent range cache

**Background preloader**
- Sequential page 1→N text extraction, pauses during interactive
  navigation (preloadPaused flag set in goToPage, cleared in renderPage)
- Debounced live search refresh (500ms) as pages load
- Search UI shows '(loading…)' suffix until all pages processed

**Loading indicators**
- SVG pie in toolbar: fills as pages load, fades on completion,
  turns red on errors with tooltip listing failed pages
- Debounced page-loading overlay (150ms) for uncached page navigation

**Cleanup**
- Remove old full-file chunked download and progress bar
- Remove extractAllPageText (replaced by preloader)
- Remove verbose debug logs (Coverage, RangeReq, cache hit)
2026-02-21 16:33:48 -08:00
Olivier Chafik
90ba41720e
pdf: ignore safe area insets in fullscreen mode (#484)
Add padding: 0 !important to .main.fullscreen so that
safe area insets applied via inline styles are overridden,
making the PDF viewer truly fullscreen edge-to-edge.

Also add .build directories to .prettierignore to fix
pre-commit hook failures from Swift build artifacts.
2026-02-21 16:25:58 -08:00
Den Delimarsky
32f7f40f57
Merge pull request #483 from modelcontextprotocol/localden-patch-1
Update README.md
2026-02-21 13:04:00 -08:00
Den Delimarsky
5710a7e462
Update README.md 2026-02-21 13:03:42 -08:00
Den Delimarsky
221e372a28
Merge pull request #478 from modelcontextprotocol/localden/readme-ext
Lead README with agent skills for faster onboarding
2026-02-21 13:03:03 -08:00
Den Delimarsky
910c417fae
Format README with prettier 2026-02-21 08:33:29 +00:00
Den Delimarsky
da4406edc2
Update skill 2026-02-21 08:22:47 +00:00
Olivier Chafik
96c0af577d
feat(pdf-server): use MCP roots to auto-allow local directories (#479)
When the client advertises roots capability, the server queries roots/list
on initialization and subscribes to roots/list_changed notifications.
Any file:// roots pointing to existing directories are added to an
allowedLocalDirs set, enabling access to all PDFs under those directories
without needing to enumerate them via CLI args.

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

fix: revert unrelated changes from bad rebase

Restores files that were accidentally modified alongside the MCP roots
feature: README, pdf-server UI (search feature), threejs example,
SDK guards (app.ts, app-bridge.ts), typedoc theme, media assets,
and e2e snapshots.
2026-02-21 01:37:47 +00:00
Den Delimarsky
7e0fee6b03
Use GitHub note admonition for clients note 2026-02-20 22:12:20 +00:00
Den Delimarsky
2281bcf176
Add supported clients section to README 2026-02-20 22:10:43 +00:00
Den Delimarsky
7eec75164a
Format README with prettier 2026-02-20 20:53:10 +00:00
Den Delimarsky
2af02baf46
Update README hero gif to Excalidraw, add new agent skills 2026-02-20 20:47:02 +00:00
Den Delimarsky
8543d8d5dc Lead README with agent skills for faster onboarding
Move the agent skills section to the top of the README so readers
immediately see how to install and start building. Keep Claude Code
install commands front and center with a brief pointer to the agent
skills guide for other agents. Specification section moves below
Examples.
2026-02-20 19:37:58 +00:00
Anton Pidkuiko MacBook
3757271641
refactor: use MCP resource types for ui/download-file params
Replace custom {filename, content, mimeType, encoding} params with
standard MCP resource types: EmbeddedResource for inline content and
ResourceLink for host-resolved references.

New params shape:
  params: { contents: (EmbeddedResource | ResourceLink)[] }

This reuses existing MCP primitives instead of inventing new ones,
and enables hosts to show resource 'bubbles' or resolve ResourceLink
URIs via resources/read on the MCP server.
2026-02-20 12:00:35 +00:00
Anton Pidkuiko MacBook
494c73e7ca
feat: add ui/download-file method for host-mediated file downloads
Add a new ui/download-file JSON-RPC request that allows MCP App views to
request file downloads through the host. Since MCP Apps run in sandboxed
iframes where direct downloads are blocked (allow-downloads not set),
this provides a host-mediated mechanism for file exports.

Spec changes (draft):
- Add ui/download-file request to Requests (View → Host) section
- Add downloadFile capability to HostCapabilities
- Document host behavior requirements (confirmation dialog, filename
  sanitization, base64 decoding, size limits)

SDK changes:
- Add McpUiDownloadFileRequest/Result interfaces to spec.types.ts
- Add downloadFile() method to App class
- Add ondownloadfile setter to AppBridge class
- Add DOWNLOAD_FILE_METHOD constant
- Generate Zod schemas

Example:
- Add download demo to integration-server (exports JSON with server time)
- Capability check: only shows download button when host advertises
  downloadFile capability

Based on developer feedback requesting file export capabilities for
visualization tools, document editors, and data analysis apps.
2026-02-19 21:25:48 +00:00
Olivier Chafik
37e127034f
Add fullscreen button to threejs-server (#446)
* Add fullscreen button to threejs-server

* Simplify wrapper: pass app instance directly instead of useCallback wrappers

* Update examples/threejs-server/src/threejs-app.tsx

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* Update examples/threejs-server/src/threejs-app.tsx

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* prettier

---------

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2026-02-19 19:17:04 +00:00
Olivier Chafik
8cad9f04c0
Add PDF search feature to pdf-server (#445)
* Add PDF search feature to pdf-server

Adds full-text search functionality to the PDF viewer:

- Search button in toolbar (Ctrl/Cmd+F to open)
- Search bar with input, prev/next navigation, match count
- Text extraction and caching across all pages
- Highlight overlays using DOM Range API
- Case-insensitive search across entire document
- Keyboard shortcuts: Ctrl+F (open), Enter (next), Shift+Enter (prev), Escape (close)

Changes:
- mcp-app.html: Add search button, search bar, highlight layer
- src/mcp-app.css: Add search/highlight styles and PDF.js text layer rules
- src/mcp-app.ts: Add search state, functions, event listeners

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

* update screenshots

* Add fullscreen button to threejs-server

* Fix prettier formatting

* Move search bar below toolbar, right-aligned

* Make search bar compact and right-aligned

* Float search bar over page content

* Ctrl/Cmd+F toggles search; pass through to browser when empty

* Hide search nav controls until query is entered

* Skip scroll when search highlight is already visible

* Fix search bar right-edge alignment with container border

* Prevent browser find when our search is open with text

* Cmd+F when search input is focused passes through to browser find

* Fix prettier formatting in README

* Remove unrelated formatting and threejs changes from PR

* prettier

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 18:56:40 +00:00
Den Delimarsky
05b1f22cd0 Add SECURITY.md with GitHub Security Advisories guidance 2026-02-18 20:54:05 +00:00
Olivier Chafik
f615123711
Add double-connect guard to prevent protocol message handling errors (#450)
* fix: guard against double-connect in App and AppBridge (#429)

Calling connect() twice on the same transport instance caused the MCP
SDK's Protocol class to chain onmessage handlers, so each incoming
message was processed twice. This made incoming responses get consumed
by the first processing (resolving the handler) and then trigger a
spurious "Received a response for an unknown message ID" error on the
second processing pass, since the handler was already removed from the
response map.

Add an explicit guard in both App.connect() and AppBridge.connect() that
throws if the instance is already connected. This converts the silent
message-corruption into a clear, actionable error and prevents
downstream confusion.

Adds three regression tests:
- AppBridge.connect() throws when already connected
- App.connect() throws when already connected
- Demonstrates the double-processing root cause and verifies it is fixed

Fixes #429

https://claude.ai/code/session_01XU3FW1TpjBkyRbkBUH5gqR

* test: replace redundant regression test with cleaner same-transport guard check

The previous third test in "double-connect guard" never actually attempted
a double-connect — it created fresh instances and verified normal operation,
which every other test already covers. The stray bridge.onupdatemodelcontext
and bridgeTransport.close() at the top were leftover noise.

Replace it with a focused test that verifies the guard fires even when
the caller passes the *same* transport object (not just a different one),
which is the exact scenario described in #429.

https://claude.ai/code/session_01XU3FW1TpjBkyRbkBUH5gqR

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-17 18:15:24 +00:00
Jonathan Hefner
8dbc06a355
Merge pull request #462 from jonathanhefner/remove-typedoc-theme-css-patch
Upgrade `typedoc-github-theme` to v0.4.0 and remove local CSS workaround
2026-02-17 08:06:34 -06:00
Anton Pidkuiko
0b48dfab7d
Merge pull request #463 from modelcontextprotocol/localden/readme
README update
2026-02-17 10:45:25 +00:00
Den Delimarsky
5eab6da3fc
README update 2026-02-16 23:03:52 -08:00
Den Delimarsky
62e1a5a0ae
README update 2026-02-16 23:01:59 -08:00
Den Delimarsky
512016053d
README update 2026-02-16 23:00:57 -08:00
Den Delimarsky
c2a0b5ccad
Update with media 2026-02-16 22:59:17 -08:00
Den Delimarsky
291006ab73
Update with media 2026-02-16 22:57:28 -08:00
Den Delimarsky
a427e7127d
Update with media 2026-02-16 22:52:59 -08:00
Den Delimarsky
22bffa8782
Update with media 2026-02-16 22:51:16 -08:00
Den Delimarsky
8242496f3e
Update README 2026-02-16 21:26:21 -08:00
Den Delimarsky
716d7c1c5c
Update README 2026-02-16 21:23:08 -08:00
Den Delimarsky
09e1684db4
Update README 2026-02-16 21:19:48 -08:00
Den Delimarsky
c372c1f6a1
Update README 2026-02-16 21:16:47 -08:00
Jonathan Hefner
5c4000bafd
Upgrade typedoc-github-theme to v0.4.0 and remove local CSS workaround
v0.4.0 includes the typography fixes from upstream PR
JulianWowra/typedoc-github-theme#7, so the local `customCss` override
(`scripts/typedoc-github-theme-fixes.css`) added in 0bbbfee is no
longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-16 13:27:39 -06:00
Jonathan Hefner
a8ff280145
Merge pull request #461 from Avcharov/fix/flaky-e2e-tests
fix: update e2e test snapshots for shadertoy and wiki-explorer images
2026-02-16 11:15:33 -06:00
Hryhorii Avcharov
91c135350d fix: update e2e test snapshots for shadertoy and wiki-explorer images 2026-02-15 12:01:16 +01:00
Olivier Chafik
666952980f
feat(app): throw helpful error when callServerTool is called with a string (#449)
Users frequently call callServerTool("tool_name", args) instead of the
correct callServerTool({ name: "tool_name", arguments: args }), resulting
in a silent/confusing failure. Detect this and throw immediately with
a message that shows the correct call shape.

Also fixes the incorrect example in examples/pdf-server/README.md that
demonstrated this same wrong usage pattern.

Fixes #386

https://claude.ai/code/session_01GqAyN4Ux7svWoU2HqsSLZF

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-13 21:05:21 +00:00
Jonathan Hefner
0bbbfee8c2
Merge pull request #442 from jonathanhefner/typedoc-theme-css-patch
Apply `typedoc-github-theme` typography fixes locally via `customCss`
2026-02-12 10:22:47 -06:00
Salman Chishti
a4fda18caa
Upgrade GitHub Actions for Node 24 compatibility (#392)
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-02-12 15:31:10 +00:00
Salman Chishti
93b619cae6
Upgrade GitHub Actions to latest versions (#393)
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-02-12 15:30:57 +00:00
Max Stoiber
c60ee352ae
Change Host <> Sandbox communication protocol to "SHOULD" (#435)
In ChatGPT, we have our own sandbox <> host communication protocol that doesn't match the names of the protocol enforced by the "MUST" in the spec.

This changes that by marking the specific messages sent back and forth as a "SHOULD".
2026-02-12 15:29:03 +00:00
Hryhorii (Greg) Avcharov
10d1f9dc6d
fix: align basic-host dark mode styles (#438) 2026-02-12 15:28:51 +00:00
Health Kowshik
3b15f063fe
Update theme toggle button position in CSS from top to bottom (#441) 2026-02-12 15:28:44 +00:00
Olivier Chafik
fd8cc978d4
fix(message-transport): only ignore messages that lack jsonrpc: "2.0" (#448)
Tighten the parse-failure handler: silently ignore postMessage frames
where event.data?.jsonrpc !== "2.0" (i.e. not JSON-RPC at all — null,
undefined, a primitive, or an object from the host environment that
doesn't carry the jsonrpc field). Messages that do carry jsonrpc: "2.0"
but are otherwise malformed still surface via onerror so genuine protocol
errors aren't swallowed.

https://claude.ai/code/session_01DaJpUtSJf4hA7tnu3nJz8s

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-12 15:28:32 +00:00
Jonathan Hefner
a815f31392
Merge pull request #444 from jonathanhefner/fix-migrate-oai-app-skill-name
Rename `migrate-oai-app` skill `name` to align with spec
2026-02-10 09:02:19 -06:00
Jonathan Hefner
f9c96d973c
Merge pull request #443 from vgshenoy/patch-1
Rename skill to 'create-mcp-app' to align with spec
2026-02-10 09:01:58 -06:00
Jonathan Hefner
6d79123b97
Rename migrate-oai-app skill name to align with spec
The skill name spec requires lowercase letters, numbers, and hyphens
only. Change `name` from "Migrate from OpenAI App" to `migrate-oai-app`.

Companion to #443, which does the same for the `create-mcp-app` skill.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-10 08:50:49 -06:00
Vignesh Shenoy
0ea69081a0
Rename skill to 'create-mcp-app' to align with spec
As per the spec (https://agentskills.io/specification) `name` should be:

 "Max 64 characters. *Lowercase letters*, numbers, and *hyphens* only. Must not start or end with a hyphen."
2026-02-10 11:23:20 +01:00
Jonathan Hefner
faabb6d1af
Apply typedoc-github-theme typography fixes locally via customCss
Backport the `.tsd-typography` CSS fixes from upstream PR
JulianWowra/typedoc-github-theme#7 so document pages render with
GitHub-accurate heading spacing, list bullets, line-height, and code
block overflow. The override file and `customCss` config can be removed
once the PR is merged and the dependency is updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-09 11:15:30 -06:00
Olivier Chafik
bcfffb6585
Clarify spec and SDK: accept UIResourceMeta in both resources/list and resources/read (#410)
* Clarify spec and SDK: accept UIResourceMeta in both resources/list and resources/read

The spec defines UIResource with _meta.ui, implicitly as an extension of the Resource type (resources/list), but examples and practice place _meta.ui on TextResourceContents (resources/read). The registerAppResource helper accepts _meta.ui in its config (which ends up in resources/list), but examples only return said _meta.ui from resources/read — and current hosts might be just ignoring it.

This change:
- Adds a 'Metadata Location' subsection to the draft spec explicitly stating that _meta.ui is valid on both the resource listing entry AND the content item, with content-item taking precedence.
- Updates the spec example to show _meta on the resource declaration (resources/list).
- Updates the CSP construction example to reference both locations.
- Clarifies McpUiAppResourceConfig JSDoc: _meta here appears in resources/list and serves as a listing-level fallback for when content items omit _meta.ui.
- Fixes the map-server example comment that incorrectly stated _meta must only be on the content item.

* Add McpUiReadResourceResult type for UI metadata in resources/read

* basic-host: support UIResourceMeta fallback from resources/list

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-04 14:26:09 +00:00
Jonathan Hefner
2dd56c3dce
Merge pull request #422 from jonathanhefner/everything-is-external
Replace "external" with "network" in CSP documentation
2026-02-03 10:29:52 -06:00
Jonathan Hefner
78b319e29a
Merge pull request #421 from jonathanhefner/use-double-quotes-in-npm-scripts
Use escaped double quotes in npm scripts for Windows compatibility
2026-02-03 09:41:28 -06:00
Anton Pidkuiko
398f5b4e4a
Merge pull request #294 from matteo8p/create-helper-functions-tool-visibility
Add `isToolVisibilityModelOnly` and `isToolVisibilityAppOnly` helper functions
2026-02-02 23:15:46 +00:00
Jonathan Hefner
c39b40e7c8
Replace "external" with "network" in CSP documentation
The term "external" is misleading for MCP Apps because developers
typically interpret it as "third-party." However, MCP App HTML runs in
a sandboxed iframe with no same-origin server, so *all* origins must be
declared—including where your own bundled JS/CSS is served from. Using
"network" instead avoids this confusion and aligns with the detailed
explanations in `patterns.md` and `spec.types.ts`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 10:50:06 -06:00
Jonathan Hefner
27196b3b05
Use escaped double quotes in npm scripts for Windows compatibility
Single quotes in `concurrently` commands work on Unix shells but not on
Windows Command Shell (`cmd.exe`), causing the `INPUT` environment
variable to not be set and npm scripts to fail with errors like:

    'cross-env' is not recognized as an internal or external command
    Error: INPUT environment variable is not set

Replace single quotes with escaped double quotes (`\"`) in npm scripts
in all example `package.json` files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 09:05:40 -06:00
Jonathan Hefner
ec0f2174f3
Merge pull request #415 from jonathanhefner/csp-and-cors-example
Add CSP and CORS section to patterns docs
2026-01-30 14:47:37 -06:00
Jonathan Hefner
13b6d06399
Add CSP and CORS section to patterns docs
Adds a new section to the patterns guide explaining how to configure
Content Security Policy and CORS for MCP Apps that make network requests:

- CSP via `_meta.ui.csp` controls browser-side permissions (`connectDomains`,
  `resourceDomains`)
- CORS via `_meta.ui.domain` provides a stable origin for API servers to
  allowlist

Includes a type-checked example showing `computeAppDomainForClaude()` helper
that hashes an MCP server URL to create a stable `*.claudemcpcontent.com`
subdomain. Also adds `@see` links to `McpUiResourceMeta` and `McpUiResourceCsp`
in `registerAppResource` JSDoc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 14:33:57 -06:00
Olivier Chafik
bb4e5c57f3
Cache full PDF bodies from servers without Range support (#411)
* fix(pdf-server): handle HTTP 200 fallback in remote range requests

When a remote server ignores the Range header and returns HTTP 200
with the full body, readPdfRange previously passed the entire file
(potentially 10MB+) through as a single chunk. This bypassed the
512KB limit because:

1. The error check `!response.ok && status !== 206` short-circuits
   on 200 (ok is true), so the full body is read via arrayBuffer()
2. No Content-Range header on a 200 response leaves totalBytes at 0
3. hasMore becomes `offset + fullSize < 0` = false, so the client
   stops after one oversized message

Fix: detect HTTP 200, cache the full body in memory (to avoid
re-downloading on every subsequent chunk request), then slice to the
requested range. The 512KB per-message limit is now enforced for all
remote URLs regardless of Range request support.

* Add session-based PDF caching with timeout cleanup and size limits

- Add dual timeout strategy for cache cleanup:
  - 10s inactivity timeout (resets on each access)
  - 60s max lifetime (absolute timeout from creation)
- Add 50MB max size limit with both Content-Length and actual size checks
- Add unit tests for caching behavior including:
  - Cache on HTTP 200 response (no range support)
  - No cache on HTTP 206 response (range supported)
  - Slice cached data for subsequent range requests
  - Reject PDFs exceeding size limits
- Export getCacheSize() and clearCache() for testing

* refactor(pdf-server): make PDF cache session-local via factory pattern

- Replace module-level global cache with createPdfCache() factory
- Each server instance now gets its own isolated cache
- Export PdfCache interface for type-safe usage
- Update tests to use per-test cache instances
- Add test verifying cache isolation between sessions

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-30 20:24:18 +00:00
Ido Salomon
0278af6a2a
examples: remove arcade (#414) 2026-01-30 22:06:50 +02:00
Jonathan Hefner
5c4f4969b1
Merge pull request #406 from jonathanhefner/extract-getUiCapability-example
Extract `getUiCapability` example to type-checked examples file
2026-01-30 13:52:31 -06:00
Jonathan Hefner
d7ff999d40
Merge pull request #404 from jonathanhefner/basic-server-css-variables
Improve example CSS files to better demonstrate host style integration
2026-01-30 13:52:17 -06:00
Jonathan Hefner
d37a789693
Merge pull request #409 from jonathanhefner/spec-types-api-docs
Improve documentation for CSP and permissions types
2026-01-30 13:51:42 -06:00
Jonathan Hefner
c02eb4d006
Improve documentation for CSP and permissions types
Add detailed JSDoc comments to `McpUiResourceCsp`, `McpUiResourcePermissions`,
and `McpUiResourceMeta` interfaces:

- Document CSP directive mappings for `connectDomains`, `resourceDomains`,
  `frameDomains`, and `baseUriDomains`
- Add usage examples showing typical domain patterns
- Clarify Permission Policy mappings for `camera`, `microphone`, `geolocation`,
  and `clipboardWrite`
- Explain `domain` field's host-dependent behavior with concrete examples
- Document `prefersBorder` semantics including true/false/omitted behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 11:44:31 -06:00
Jonathan Hefner
c3d91a13ad
Improve example CSS files to better demonstrate host style integration
Update `mcp-app.css`, `mcp-app.module.css`, and Svelte/Vue component
styles across all `basic-server-*` examples (and `integration-server`)
to use CSS variables instead of hardcoded values:

- Remove component-local color variables (`--color-primary`, etc.)
- Add derived spacing variables to `global.css` based on `--spacing-unit`
- Replace hardcoded spacing with `--spacing-xs/sm/md/lg` variables
- Add app-defined accent color variables (`--color-accent`,
  `--color-text-on-accent`) to `global.css` for brand customization
- Use app accent colors for button styling
- Update button hover to use `color-mix()` for theme-adaptive lightening
- Change focus-visible to use `--color-ring-primary` outline

This makes the templates more instructive for users and AI coding agents
building new MCP Apps by showing how to consume host-provided variables
semantically, define app-specific accent colors, and create adaptive
styling that integrates with host themes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 09:32:26 -06:00
Jonathan Hefner
52cc193e1b
Extract getUiCapability example to type-checked examples file
Move the inline example from the `getUiCapability()` JSDoc to
`index.examples.ts` for type checking, following the pattern used
by other examples in the codebase.

The example now uses the correct MCP SDK API:
- `server.server.oninitialized` callback for post-initialization hook
- `server.server.getClientCapabilities()` to retrieve capabilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 19:55:33 -06:00
Jonathan Hefner
6905aa8edc
Improve global.css as scaffolding template (#403)
Update `global.css` in all `basic-server-*` examples (and `integration-server`
for consistency) to better serve as templates for AI coding agents scaffolding
new MCP Apps.

Changes:
- Use CSS custom properties (`--font-sans`, `--color-text-primary`, etc.) with
  sensible fallbacks, enabling host design system integration
- Add `color-scheme: light dark` and `light-dark()` fallbacks for theme support
- Include comprehensive typography: h1-h6, body text, `code`/`pre`/`kbd`, `b`/`strong`
- Move `#server-time` styles from global CSS to component stylesheets (classes
  instead of IDs for better CSS practices)

Updates E2E test selectors in `DYNAMIC_MASKS` to match the new class-based
selectors.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 16:55:24 -06:00
Rafael Thayto
90b5ac520b
docs: fix basic-host setup instructions in quickstart (#398)
* docs: fix basic-host setup instructions in quickstart

Add missing root-level npm install and build steps before running
the basic-host example. Without these steps, the example fails because
it imports from dist/src/ which doesn't exist until the root package
is built.

Fixes setup failure when following quickstart tutorial.

* Update quickstart.md

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

---------

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2026-01-29 14:40:54 -06:00
Ido Salomon
adf0bbd611
examples: add arcade example to servers (#399) 2026-01-29 20:55:57 +02:00
Ido Salomon
2187bd4498
examples: add MCP Apps arcade server (#346) 2026-01-29 19:15:32 +02:00
Jonathan Hefner
e93f8a0d8b
Merge pull request #397 from jonathanhefner/patterns-guide-server-side-view-state
Add server-side persistence guidance to view state pattern
2026-01-29 10:28:56 -06:00
Jonathan Hefner
e4f1a56c76
Add server-side persistence guidance to view state pattern
Clarifies when to use `localStorage` vs server-side storage for view state:
- Scope section intro to "recoverable" view state (easy to recreate)
- Add guidance for state representing user effort (bookmarks, annotations,
  custom configurations) to use app-only tools instead
- Note that `viewUUID` should also be passed to app-only tools for scoping
- Clarify that example links are specifically for `localStorage` approach

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 10:04:05 -06:00
Jonathan Hefner
ef46849c09
Merge pull request #391 from jonathanhefner/add-blob-resource-to-patterns-guide
Add binary blob resource pattern to patterns guide
2026-01-28 15:05:15 -06:00
Jonathan Hefner
13615613d4
Add binary blob resource pattern to patterns guide
Document how to serve binary content (e.g., video) via MCP resources
as base64-encoded blobs. Includes server-side registration using the
`blob` field and client-side fetching with conversion to data URIs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:50:34 -06:00
Jonathan Hefner
246e1ab138
Merge pull request #389 from jonathanhefner/add-polling-to-patterns-guide
Add polling pattern to patterns guide
2026-01-28 14:30:10 -06:00
Gilad Shoham
7f617b94c1
docs: add root npm install step for basic-host setup (#371)
* docs: add root npm install step for basic-host setup

* Remove not needed npm install command
2026-01-28 21:39:23 +02:00
Jonathan Hefner
818bd71c9f
Add polling pattern to patterns guide
Documents the recommended approach for real-time dashboards that need to
poll server tools at regular intervals. Includes both Vanilla JS and
React examples with proper cleanup handling via `app.onteardown` and
`useEffect` cleanup functions respectively.

Also updates the "Tools that are private to Apps" section to mention
polling as a use case for app-only visibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 12:42:34 -06:00
Jonathan Hefner
6a2905d17d
Use note callouts for pattern guide example references
Convert italic `_See example..._` text to `> [!NOTE]` callouts for better
visual prominence, and expand cross-references to include additional
relevant examples for each pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 12:42:34 -06:00
Jonathan Hefner
55f0ef0198
Merge pull request #388 from jonathanhefner/system-monitor-static-vs-dynamic
Separate static and dynamic data in `system-monitor-server` tools
2026-01-28 12:41:38 -06:00
Jonathan Hefner
9acebd7f9f
Separate static and dynamic data in system-monitor-server tools
The two-tool architecture previously had both tools returning identical
`SystemStats` data. This refactors them to properly separate concerns:

- `get-system-info` (Model-visible): Returns static system configuration
  (hostname, platform, CPU model/count, total memory) sent once via
  `ontoolresult`

- `poll-system-stats` (App-only): Returns dynamic metrics (per-core CPU
  timing, memory usage, uptime) polled every 2 seconds

Moves `formatBytes()` and `formatUptime()` utilities to the client since
the server now returns raw values for client-side formatting.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 11:04:35 -06:00
Anton Pidkuiko
6028cf255f
Merge pull request #373 from modelcontextprotocol/ochafik/pdf-server-screenshot-context
feat(pdf-server): send screenshot to updateModelContext
2026-01-28 13:56:57 +00:00
Jonathan Hefner
782756c833
Merge pull request #381 from jonathanhefner/improve-patterns-guide
Improve patterns guide with concrete examples and safe area insets
2026-01-27 17:43:31 -06:00
Jonathan Hefner
c6c2428e0b
Improve patterns guide with concrete examples and safe area insets
- Rename "host styling" section to "host context" and add safe area
  insets handling
- Replace abstract `renderLoadingUI`/`renderFinalUI` example with
  concrete code preview pattern for `ontoolinputpartial`
- Rename callback parameter `params` → `ctx` for clarity across all
  `onhostcontextchanged` handlers
- Add CSS snippet for fullscreen border-radius handling
- Expand display mode example to show `availableDisplayModes` check
- Fix minor typos and wording consistency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 17:32:41 -06:00
Jonathan Hefner
6c8379ff3e
Merge pull request #376 from jonathanhefner/fix-code-snippets-sync
Fix `extractRegion()` matching region names that are substrings of others
2026-01-27 13:09:38 -06:00
Jonathan Hefner
1215bf33c3
Fix extractRegion() matching region names that are substrings of others
The `extractRegion()` function in `sync-snippets.ts` used `indexOf()` to
find region markers like `//#region persistData`. This caused incorrect
matching when one region name was a prefix of another—searching for
`persistData` would match `//#region persistDataServer` first if it
appeared earlier in the file.

The fix detects the file's line ending style and appends it to region
markers, ensuring exact matches:
- `//#region persistData\n` no longer matches `//#region persistDataServer\n`

This corrects `docs/patterns.md` where the `persistData` code fence was
incorrectly showing server-side code instead of client-side localStorage
helpers.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 12:57:11 -06:00
Jonathan Hefner
f90e0bd090
Merge pull request #375 from jonathanhefner/fix-shadertoy-readme-json
Use escaped newlines in ShaderToy README JSON examples
2026-01-27 11:57:00 -06:00
Jonathan Hefner
9b1da10da1
Use escaped newlines in ShaderToy README JSON examples
The tool input JSON examples for Gradient and Kaleidoscope had literal
newlines in the string values, making them invalid JSON. Changed to use
`\n` escape sequences to match the Interactive Julia Set example.

Fixes regression from 574a768.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 11:37:40 -06:00
Olivier Chafik
d6705d970d
feat(pdf-server): send screenshot to updateModelContext
Apply the same pattern as map-server: capture the PDF canvas as a
scaled-down screenshot and include it in the model context update
when the host supports image content.

- Check host capability via getHostCapabilities().updateModelContext.image
- Scale canvas to max 768px dimension to reduce token usage
- Convert to base64 PNG and include with text content
2026-01-27 14:05:18 +00:00
Konstantin Konstantinov
47a5787ec3
return registered app resource from method (#370) 2026-01-27 09:58:19 +00:00
Olivier Chafik
605e31b6c5
chore: bump ext-apps to 1.0.1 (#369)
* chore: bump ext-apps to 1.0.1

* chore: update examples to depend on ext-apps ^1.0.1

* fix: revert examples to ext-apps ^1.0.0 and fix cheerio version

- Revert ext-apps dependency to ^1.0.0 (works with 1.0.1 once published)
- Fix accidental cheerio bump in wiki-explorer-server
- Remove broken override attempt

* fix: allow compatible semver ranges in version check

The check-versions script now accepts any compatible semver range,
not just exact version matches. For example, ^1.0.0 is allowed when
root version is 1.0.1, since semver compatibility is maintained.

* style: fix indentation in package.json
2026-01-26 22:32:34 +00:00
Olivier Chafik
ae9b96e1fd
chore: bump to 1.0.1 - fix npm reference for ext-apps dependency in examples + update zod to 4.x (#368)
* fix(examples): use npm reference for ext-apps dependency

Replace local path '../..' with '^1.0.0' npm reference in all example
packages. The local path was accidentally published to npm, causing
npm install to hang when installing server-* packages.

Bump versions to 1.0.1 for all public packages to fix the published
versions.

Fixes npm install hanging issue in dependent projects like
example-remote-server.

Note: npm workspaces automatically resolves the dependency to the
local package during development, so no overrides are needed.

* fix: update zod to 4.x in transcript-server and fix update-lock:docker script

- Update transcript-server to use zod ^4.1.13 for compatibility
- Fix update-lock:docker script to properly clean workspace node_modules
- Regenerate package-lock.json with clean npmjs.org URLs
2026-01-26 21:55:15 +00:00
Olivier Chafik
01fa6287bf
fix(say-server): pin mcp to released version instead of main branch (#366)
The main branch of the MCP Python SDK has removed `FastMCP` (likely
moving it to the standalone `fastmcp` package), causing import errors.
Pin to mcp>=1.26.0 which is the latest release that still includes
`mcp.server.fastmcp`.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:55:26 +00:00
Maurizio Albari
f9abb74fc0
fix(qr-server): use stable mcp SDK from PyPI (#360)
* fix(qr-server): use stable mcp SDK from PyPI

- Replace git-based mcp dependency with mcp>=1.26.0 from PyPI for stability
- Remove TransportSecuritySettings that was blocking browser connections
- Keep simple FastMCP initialization like other Python examples

Fixes ModuleNotFoundError and 403 Forbidden errors when connecting from basic-host

* fix(qr-server): use stable mcp SDK from PyPI

- Replace git-based mcp dependency with mcp>=1.26.0 from PyPI
- Add stateless_http=True for proper HTTP transport handling
- Remove TransportSecuritySettings that blocked browser connections
2026-01-26 18:52:10 +00:00
Ido Salomon
aec5240f22
docs: update MCP-UI SDK description in README (#365)
Clarified the description of the MCP-UI client SDK and its usage.
2026-01-26 19:44:48 +02:00
Olivier Chafik
298e884ec3
chore: stable spec release 2026-01-26 and SDK v1.0.0 (#362)
* chore: add stable spec release 2026-01-26

- Use git mv to copy specification from draft to specification/2026-01-26/apps.mdx (preserves history)
- Update status from 'Draft' to 'Stable (2026-01-26)'
- Add specification version table to README
- Update Resources section with links to both stable and draft specs

* chore: bump version to 1.0.0

- Update main package version from 0.4.2 to 1.0.0
- Update all example package versions to 1.0.0
- Regenerate package-lock.json

* prettier

* chore: align protocol version with spec release 2026-01-26

- Add 'Protocol Version: 2026-01-26' to spec header
- Update LATEST_PROTOCOL_VERSION from '2025-11-21' to '2026-01-26'
- Update spec examples to use '2026-01-26' protocol version
- Add '2025-11-21' to SUPPORTED_PROTOCOL_VERSIONS for backwards compatibility

* chore: update spec references to stable 2026-01-26

- Update typedoc.config.mjs navigation link
- Update docs/overview.md spec links (10 occurrences)
- Update plugins/mcp-apps/skills/create-mcp-app/SKILL.md spec link
- Update src/spec.types.ts @see links
- Update AGENTS.md spec path
- Regenerate src/generated/schema.ts with updated links

* chore: simplify versioning - first stable release

- Remove backwards compatibility version (2025-11-21) - this is the first stable release
- Remove redundant 'Protocol Version' header - spec version IS the protocol version

* chore: add history reference comment to stable spec
2026-01-26 17:31:33 +00:00
Jonathan Hefner
dc948d1af9
Merge pull request #363 from jonathanhefner/fix-claude-docs-links
Fix Claude documentation URLs
2026-01-26 11:08:47 -06:00
Jonathan Hefner
9c0ba003b4
Fix Claude documentation URLs
Update links from `claude.ai/docs/` to `claude.com/docs/` for the
remote and local MCP server configuration documentation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 11:02:25 -06:00
Olivier Chafik
d03123bb5c
docs: add local development MCP client configuration to READMEs (#361)
* docs: add local development MCP client configuration to READMEs

Add instructions for configuring MCP clients to use locally-cloned
and modified servers. This allows developers to test local changes
with MCP clients like Claude Desktop or VS Code.

- Top-level README: Added 'Local Development' section after npx config
- All TypeScript examples: Added 'Local Development' subsection with
  bash one-liner that builds and runs the server
- Python examples (qr-server, say-server): Added local path config

* docs: include all servers in local development config

* docs: use bash wrapper for Python server configs to enable tilde expansion
2026-01-26 15:22:00 +00:00
Olivier Chafik
2d4311a25d
chore: bump version to 0.4.2 (#334)
* Adding a simple (bad) pdf icon for directory (#330)

* chore: bump version to 0.4.2

- Update package version from 0.4.1 to 0.4.2
- Pin @types/node to 22.10.0 for dev compatibility
- Override seroval to ^1.4.1 to fix security vulnerabilities
- Use '*' for workspace SDK dependency (works with unpublished versions)

* fix(e2e): update Map Server display name to CesiumJS Map Server (#324)

* fix(test): update map-server test to expect 'CesiumJS Map Server'

The server name was renamed but the E2E test config was not updated.

* fix: change in another file

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>

* feat(server): add getUiCapability helper for capability negotiation (#313)

* feat(server): add hasUiSupport/getUiCapability for experimental+extensions

Support double-tagging for MCP Apps capability negotiation:
- Check both experimental and extensions fields in client capabilities
- Add hasUiSupport() to easily check if client supports MCP Apps
- Add getUiCapability() to retrieve the capability settings
- Update spec to document both capability locations

This enables forward compatibility as MCP transitions from experimental
to the extensions field (SEP-1724).

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0

* fix: update @types/node version in package-lock.json

Replace invalid 22.19.5/22.19.6/22.19.7 versions with valid 22.19.3

* docs(spec): clarify hosts SHOULD double-tag, servers SHOULD check both

- Hosts SHOULD advertise capabilities in both experimental and extensions
- Servers SHOULD check both locations, preferring extensions when present
- Updated example to show double-tagging pattern

* chore: remove double-tagging recommendation, keep hasUiSupport helper

Reverts the spec changes that recommended clients double-tag capabilities
in both experimental and extensions fields. The helper functions remain
as they're useful for checking capability in either location.

* refactor(server): simplify to only getUiCapability, remove hasUiSupport

- Remove hasUiSupport function (use getUiCapability directly)
- Remove double-tagging logic (only check extensions field)
- Add ClientCapabilitiesWithExtensions type using SDK's ClientCapabilities
- Update spec to use getUiCapability
- Simplify tests

* fix: update @see link to getUiCapability

* refactor(server): inline ClientCapabilitiesWithExtensions type in getUiCapability

* refactor: move McpUiClientCapabilities to spec.types.ts

* fix: update playwright docker image to v1.58.0

* fix: use -i instead of -it for docker commands (no TTY needed)

* fix: Python server dependency resolution and Docker host validation (#333)

* fix(say-server): add --index flag to uv run for PyPI resolution

The pocket-tts dependency wasn't resolving without explicit --index flag
when invoked via npm scripts.

* fix(qr-server): allow Docker bridge IP in transport security

Add 172.17.0.1:* to allowed_hosts to fix 421 Misdirected Request errors
when the server is accessed from Docker containers.

* feat(basic-host): Add theme toggle and MCP style variables (#336)

* feat(basic-host): add theme toggle and MCP style variables

Add theme support to basic-host example:
- theme.ts: Global theme manager with light/dark toggle
- host-styles.ts: Full set of MCP CSS variables using light-dark()
- ThemeToggle component with sun/moon icons
- Theme-aware CSS using CSS custom properties
- Pass theme + styles to apps via hostContext
- Notify apps when theme changes via sendHostContextChange

Apps now receive:
- hostContext.theme: 'light' | 'dark'
- hostContext.styles.variables: All 60+ MCP CSS variables
- onhostcontextchanged notifications when theme changes

* test: update e2e goldens for theme changes

* [MCP Apps] Add appCapabilities.availableDisplayModes documentation to spec (#331)

* Add appCapabilities.availableDisplayModes documentation to spec

Document the availableDisplayModes field in McpUiAppCapabilities that allows
apps to declare which display modes they support. This enables hosts to
compute the intersection of host and app capabilities and only offer
appropriate display mode controls.

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

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

* Address PR review feedback

- Change "Guest UI" to "View" terminology
- Add "View" prefix to availableDisplayModes heading for disambiguation
- Remove intersection logic - host only checks its own supported modes
- Remove assumption that apps must support inline mode
- Simplify host behavior to honor/reject based on its own capabilities

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

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

* Add appCapabilities.availableDisplayModes to types

- Add `availableDisplayModes` field to `McpUiAppCapabilities` so apps can
  declare which display modes they support
- Change `HostContext.availableDisplayModes` type from `string[]` to
  `McpUiDisplayMode[]` for type safety
- Regenerate schemas

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

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

* Address Olivier's feedback - consolidate display mode docs

- Remove separate "App Capabilities" section for availableDisplayModes
- Add display mode requirements to existing ui/request-display-mode section:
  - Host MUST NOT switch View to mode not in appCapabilities.availableDisplayModes
  - Host MAY decline requests from Views that didn't declare modes
  - View MUST declare supported modes during initialization

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

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

* Restore McpUiAppCapabilities docs and fix display mode clause

- Restore McpUiAppCapabilities interface documentation with availableDisplayModes
- Restore "View MUST check if the requested mode is in availableDisplayModes
  from host context" clause that was accidentally removed

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

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

* Create unified Display Modes section

Consolidate all display mode documentation into a single section:
- Display mode types (inline, fullscreen, pip)
- Declaring support from View (appCapabilities.availableDisplayModes)
- Declaring support from Host (HostContext.availableDisplayModes)
- Requesting changes (ui/request-display-mode)
- Notifying changes (ui/notifications/host-context-changed)
- All MUST/SHOULD requirements for View and Host behavior

The ui/request-display-mode in Requests section now references
the Display Modes section for behavior details.

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

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

* Remove mode coercion clause from display modes

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

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

* Regenerate schemas with correct version

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>

* feat(say-server): add light/dark theme support (#338)

- Add importmap entry for main SDK exports (applyDocumentTheme, etc.)
- Import and apply host theme and styles on context changes
- Apply initial host context theme/styles when app connects
- Update CSS to use [data-theme="dark"] selector for host-provided themes
- Keep @media prefers-color-scheme as fallback for standalone mode
- Add color-scheme meta tag for native element theming

* fix: followup app -> view terminology (#339)

* chore: husky env fix, version pins, update-lock:docker script (#341)

* fix(husky): load Node.js env for GUI apps + add update-lock:docker script

Cherry-picked non-e2e changes from ochafik/fix-e2e-flaky-tests:
- Load nvm/fnm/volta/homebrew paths in pre-commit for GitHub Desktop
- Add update-lock:docker script to regenerate lockfile with public registry

* pin package versions for dev compatibility

- solid-js: 1.9.10
- caniuse-lite: 1.0.30001763
- cheerio: 1.1.2
- electron-to-chromium: 1.5.267
- @playwright/test: 1.57.0
- playwright: 1.57.0
- playwright-core: 1.57.0

* regen

* fix: pin @types/node to existing version and regenerate lockfile

- Fix @types/node 20.19.28 -> 20.19.27 (typo, version didn't exist)
- Regenerate package-lock.json to resolve versions available on both
  public npm and Anthropic artifactory registries

* fix(basic-server-solid): bump solid-js 1.9.10 -> 1.9.11 to fix seroval vulnerabilities

* fix: pin seroval to 1.4.1 for dev compatibility

* Refactor server startup functions

Rename `startServer` to `startStreamableHTTPServer` and add a new
`startStdioServer` helper to make the transport type explicit. Remove
the `ServerOptions` interface in favor of reading `PORT` directly from
the environment inside the function (defaulting to `3001`).

Also update Python examples to default to port `3001` for consistency.

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

* Support full-file inclusion in synced code snippets

Previously, the script only supported extracting regions from `.ts/.tsx`
files using the `#regionName` syntax. Now it also supports including
entire files without a region specifier, enabling sync of any file type
(JSON, YAML, shell scripts, etc.) into documentation.

Changes:
- Make `#regionName` optional in the `source=""` attribute
- Accept any fence language (not just `ts`/`tsx`)
- Simplify cache to flat map with composite keys
- Add validation error for region extraction on non-TypeScript files

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

* Rewrite Quickstart guide with type-checked code examples

Rewrites the Quickstart tutorial to provide a more focused, step-by-step
introduction to building MCP Apps. The guide now uses synced code fences
that pull from `examples/quickstart/`, ensuring all code shown in the
documentation is type-checked and tested.

Key improvements:
- Clearer project setup instructions with explicit commands
- Streamlined explanations of the tool + resource registration pattern
- Added screenshot showing the completed app
- E2E test coverage validates the example works end-to-end

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

* Add MCP Apps Overview document

Adds `docs/overview.md` covering the core concepts: architecture,
lifecycle, UI resources, bidirectional communication, theming, display
modes, and security. Includes mermaid flowchart and sequence diagrams.

Also adds the `@boneskull/typedoc-plugin-mermaid` TypeDoc plugin for
mermaid diagram support. However, the plugin handles HTML entities from
TypeDoc incorrectly, so `scripts/typedoc-plugin-fix-mermaid-entities.mjs`
decodes HTML entities before the plugin processes them.

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

* Add Agent Skills installation guide

Add `docs/agent-skills.md` with installation instructions for multiple AI
coding agents including Claude Code, VS Code/Copilot, Gemini CLI, Cline,
and Goose. The guide provides three installation methods: Claude Code
plugin, Vercel Skills CLI (`npx skills add`), and manual installation.

Update `README.md` to link to the new guide and reframe the plugin
section around the broader "Agent Skills" concept.

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

* Polish OpenAI migration reference for readability

Add framing prose to the migration guide to improve navigation:
- Expanded intro describing document purpose and how to use it
- Added note about features marked "Not yet implemented"
- Added brief transition sentences before Server-Side and Client-Side sections

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

* Fix mermaid diagram arrowheads not rendering

The `@boneskull/typedoc-plugin-mermaid` creates both dark and light theme
variants of each diagram, each with identical marker IDs (e.g., `#arrowhead`).
When mermaid renders SVGs at runtime, lines reference `url(#arrowhead)`, but
the browser resolves this to the first match in document order — the hidden
dark-theme SVG — causing arrows to be invisible.

The fix removes dark-theme `<div class="mermaid dark">` elements at build time,
leaving only one SVG per diagram with unique marker IDs. A CSS filter
(`filter: invert(1) hue-rotate(180deg)`) handles dark mode styling instead.

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

* fix(docs): improve OpenAI migration guide accuracy (#349)

- Add missing setOpenInAppUrl API to 'Not Yet in MCP Apps' section
- Clarify Event Handling table: specify exact OpenAI properties
  (toolInput/toolOutput) instead of vague 'window.openai.*'
- Improve notes to distinguish sync property reads vs callbacks

* Add Testing MCP Apps guide

Document how to test MCP Apps using `basic-host` and MCP Apps-compatible
hosts like Claude.ai and VS Code Copilot. Cover debugging features for
`basic-host`, and using `cloudflared` to expose local servers for remote
hosts.

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

* Fix `onhostcontextchanged` handler invocation in examples

`App.onhostcontextchanged` is a setter-only property with no getter.
TypeScript doesn't prevent reading from setter-only properties, so
`app.onhostcontextchanged?.(ctx)` compiles but silently returns
`undefined` at runtime.

Extract the handler into a named `handleHostContextChanged()` function
and call it directly after `connect()`, matching the pattern used in
`basic-server-vanillajs` and other examples.

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

* Expand Agent Skills guide

Add intro explaining what Agent Skills are and link to the two skill
files (`create-mcp-app`, `migrate-oai-app`). Reorganize headings and add
"Invoke a Skill" and "Test Your App" sections. Update `README.md` link
text and add tip to quickstart suggesting agent skills as an alternative.

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

* fix(e2e): improve test stability and developer experience (#337)

* fix(e2e): add masks for dynamic content and increase timeouts for slow servers

- Add HOST_MASKS for Tool Input/Result panels with dynamic timestamps
- Add say-server to SLOW_SERVERS (10s) for TTS model download
- Add say-server to EXTRA_WAIT_MS (30s) in generate-grid-screenshots

* feat: add update-lock:docker script to regenerate lockfile with public registry

* pin package versions for dev compatibility

* feat(basic-host): add ?theme=hide query param to hide theme toggle in screenshots

- Add hideThemeToggle param to getQueryParams()
- Conditionally render ThemeToggle based on ?theme=hide
- Update e2e tests to use ?theme=hide for consistent screenshots

* update screenshots

* update screenshots

* fix(husky): load Node.js environment for GUI apps like GitHub Desktop

* fix(e2e): increase map-server wait time to 15s for tile loading

* fix(e2e): increase waitForAppLoad timeout to 30s for nested iframe loading

* ci: add workflow to update e2e snapshots

* ci: trigger snapshot update on push to this branch

* chore: update e2e snapshots [skip ci]

* ci: trigger CI after snapshot update

* style: fix prettier formatting in update-snapshots workflow

* fix: pin seroval, seroval-plugins, and solid-js for dev compatibility

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Improve README structure and clarity

Add "Why MCP Apps?" section to explain the motivation upfront. Reorganize
the "How It Works" section to lead with tool definition. Rename "Overview"
to "Using the SDK" and add "For" prefix to audience headings.

Restructure the Examples section with clearer subsections for running
examples with `basic-host` vs MCP clients using stdio transport. Move the
`qr` server note outside the collapsed details block.

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

* Add Codex and Goose to documentation

Add Codex to the list of agents supporting skills in `agent-skills.md`.
Add Goose to the list of supported clients in `testing-mcp-apps.md` and
rename "VS Code Copilot" to "VS Code (Insiders)" for accuracy.

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

* chore: regenerate lockfile with public registry, use ^0.4.2 for ext-apps deps

* fix: update quickstart example to use workspace reference

* chore: remove internal registry references from lockfile

* feat(ci): add /update-snapshots comment trigger for PRs

- Comment '/update-snapshots' on any PR to update E2E snapshots
- Adds rocket reaction to acknowledge the command
- Comments back with result (updated or no changes needed)
- Removes hardcoded branch trigger
- Keeps workflow_dispatch for manual runs

* docs: document /update-snapshots PR comment trigger

* fix: pin @hono/node-server to 1.19.7 for dev compatibility

SDK 1.25.3 requires @hono/node-server@^1.19.9 which doesn't exist
on the public npm registry (latest is 1.19.7).

* fix: pin @modelcontextprotocol/sdk to 1.25.2

SDK 1.25.3 has broken dependency on @hono/node-server@^1.19.9
which doesn't exist on public npm registry.

* fix: restore flexible SDK peerDep range (^1.24.0)

devDependencies stays pinned to 1.25.2 for our builds,
but peerDependencies should allow users flexibility.

* fix: pin @types/node to avoid unpublished versions

- Pin quickstart @types/node to 22.19.5 (latest public 22.x)
- Add @types/node override to root package.json
- Regenerate lockfile with public registry only

---------

Co-authored-by: Tobin South <tobin.south@gmail.com>
Co-authored-by: Liad Yosef <liady@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: martinalong <martinalong10@gmail.com>
Co-authored-by: Ido Salomon <idosalomon@gmail.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-26 14:30:00 +00:00
Olivier Chafik
9967693621
feat(map-server): add screenshot to updateModelContext and remove reverse geocode + rounded corners (#340)
* feat(map-server): add screenshot to updateModelContext and remove rounded corners

- Remove border-radius and overflow: hidden from cesiumContainer CSS
- Capture map canvas as base64 PNG and include in updateModelContext
- Only include image if host supports image content blocks

* feat(map-server): reduce screenshot size for fewer tokens

- Scale down to max 256px (longest dimension)
- Use PNG for better quality with map graphics

* prettier
2026-01-26 13:04:43 +00:00
Jonathan Hefner
c2037f3474
Merge pull request #357 from jonathanhefner/supporting-clients
Add Codex and Goose to documentation
2026-01-25 20:37:06 -06:00
Jonathan Hefner
a57bbd3278
Add Codex and Goose to documentation
Add Codex to the list of agents supporting skills in `agent-skills.md`.
Add Goose to the list of supported clients in `testing-mcp-apps.md` and
rename "VS Code Copilot" to "VS Code (Insiders)" for accuracy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 20:28:11 -06:00
Jonathan Hefner
ab88e79ec5
Merge pull request #356 from jonathanhefner/tweak-readme
Improve README structure and clarity
2026-01-25 17:02:51 -06:00
Jonathan Hefner
b695b2e9ba
Improve README structure and clarity
Add "Why MCP Apps?" section to explain the motivation upfront. Reorganize
the "How It Works" section to lead with tool definition. Rename "Overview"
to "Using the SDK" and add "For" prefix to audience headings.

Restructure the Examples section with clearer subsections for running
examples with `basic-host` vs MCP clients using stdio transport. Move the
`qr` server note outside the collapsed details block.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 16:52:19 -06:00
Jonathan Hefner
3eb467965a
Merge pull request #352 from jonathanhefner/expand-agent-skills-guide
Expand Agent Skills guide
2026-01-25 14:40:49 -06:00
Olivier Chafik
e479497371
fix(e2e): improve test stability and developer experience (#337)
* fix(e2e): add masks for dynamic content and increase timeouts for slow servers

- Add HOST_MASKS for Tool Input/Result panels with dynamic timestamps
- Add say-server to SLOW_SERVERS (10s) for TTS model download
- Add say-server to EXTRA_WAIT_MS (30s) in generate-grid-screenshots

* feat: add update-lock:docker script to regenerate lockfile with public registry

* pin package versions for dev compatibility

* feat(basic-host): add ?theme=hide query param to hide theme toggle in screenshots

- Add hideThemeToggle param to getQueryParams()
- Conditionally render ThemeToggle based on ?theme=hide
- Update e2e tests to use ?theme=hide for consistent screenshots

* update screenshots

* update screenshots

* fix(husky): load Node.js environment for GUI apps like GitHub Desktop

* fix(e2e): increase map-server wait time to 15s for tile loading

* fix(e2e): increase waitForAppLoad timeout to 30s for nested iframe loading

* ci: add workflow to update e2e snapshots

* ci: trigger snapshot update on push to this branch

* chore: update e2e snapshots [skip ci]

* ci: trigger CI after snapshot update

* style: fix prettier formatting in update-snapshots workflow

* fix: pin seroval, seroval-plugins, and solid-js for artifactory compatibility

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-25 22:18:38 +02:00
Jonathan Hefner
ff86ef2ec4
Expand Agent Skills guide
Add intro explaining what Agent Skills are and link to the two skill
files (`create-mcp-app`, `migrate-oai-app`). Reorganize headings and add
"Invoke a Skill" and "Test Your App" sections. Update `README.md` link
text and add tip to quickstart suggesting agent skills as an alternative.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 14:18:04 -06:00
Jonathan Hefner
d61b5ab4cf
Merge pull request #351 from jonathanhefner/no-onhostcontextchanged-getter
Fix `onhostcontextchanged` handler invocation in examples
2026-01-25 14:16:32 -06:00
Jonathan Hefner
040ba82d59
Merge pull request #350 from jonathanhefner/testing-mcp-apps-doc
Add Testing MCP Apps guide
2026-01-25 14:16:22 -06:00
Jonathan Hefner
def96b392a
Fix onhostcontextchanged handler invocation in examples
`App.onhostcontextchanged` is a setter-only property with no getter.
TypeScript doesn't prevent reading from setter-only properties, so
`app.onhostcontextchanged?.(ctx)` compiles but silently returns
`undefined` at runtime.

Extract the handler into a named `handleHostContextChanged()` function
and call it directly after `connect()`, matching the pattern used in
`basic-server-vanillajs` and other examples.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:38:33 -06:00
Jonathan Hefner
4d059e631e
Add Testing MCP Apps guide
Document how to test MCP Apps using `basic-host` and MCP Apps-compatible
hosts like Claude.ai and VS Code Copilot. Cover debugging features for
`basic-host`, and using `cloudflared` to expose local servers for remote
hosts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:24:36 -06:00
Liad Yosef
82a1353f09
fix(docs): improve OpenAI migration guide accuracy (#349)
- Add missing setOpenInAppUrl API to 'Not Yet in MCP Apps' section
- Clarify Event Handling table: specify exact OpenAI properties
  (toolInput/toolOutput) instead of vague 'window.openai.*'
- Improve notes to distinguish sync property reads vs callbacks
2026-01-25 19:03:43 +02:00
Jonathan Hefner
93df6141c0
Merge pull request #347 from jonathanhefner/fix-typedoc-mermaid-diagram-arrowheads
Fix mermaid diagram arrowheads not rendering
2026-01-24 17:10:21 -06:00
Jonathan Hefner
4546a686c3
Fix mermaid diagram arrowheads not rendering
The `@boneskull/typedoc-plugin-mermaid` creates both dark and light theme
variants of each diagram, each with identical marker IDs (e.g., `#arrowhead`).
When mermaid renders SVGs at runtime, lines reference `url(#arrowhead)`, but
the browser resolves this to the first match in document order — the hidden
dark-theme SVG — causing arrows to be invisible.

The fix removes dark-theme `<div class="mermaid dark">` elements at build time,
leaving only one SVG per diagram with unique marker IDs. A CSS filter
(`filter: invert(1) hue-rotate(180deg)`) handles dark mode styling instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:52:49 -06:00
Jonathan Hefner
6c767c4bd4
Merge pull request #345 from jonathanhefner/polish-oai-migration-reference
Polish OpenAI migration reference for readability
2026-01-24 15:41:44 -06:00
Jonathan Hefner
0058ad85bc
Merge pull request #344 from jonathanhefner/skill-how-to
Add Agent Skills installation guide
2026-01-24 15:28:36 -06:00
Jonathan Hefner
00aecd1baf
Polish OpenAI migration reference for readability
Add framing prose to the migration guide to improve navigation:
- Expanded intro describing document purpose and how to use it
- Added note about features marked "Not yet implemented"
- Added brief transition sentences before Server-Side and Client-Side sections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:28:10 -06:00
Jonathan Hefner
655231ded0
Add Agent Skills installation guide
Add `docs/agent-skills.md` with installation instructions for multiple AI
coding agents including Claude Code, VS Code/Copilot, Gemini CLI, Cline,
and Goose. The guide provides three installation methods: Claude Code
plugin, Vercel Skills CLI (`npx skills add`), and manual installation.

Update `README.md` to link to the new guide and reframe the plugin
section around the broader "Agent Skills" concept.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:10:23 -06:00
Jonathan Hefner
ed637a7552
Merge pull request #343 from jonathanhefner/overview-guide
Add MCP Apps Overview document
2026-01-24 15:08:20 -06:00
Jonathan Hefner
d856b8bda6
Add MCP Apps Overview document
Adds `docs/overview.md` covering the core concepts: architecture,
lifecycle, UI resources, bidirectional communication, theming, display
modes, and security. Includes mermaid flowchart and sequence diagrams.

Also adds the `@boneskull/typedoc-plugin-mermaid` TypeDoc plugin for
mermaid diagram support. However, the plugin handles HTML entities from
TypeDoc incorrectly, so `scripts/typedoc-plugin-fix-mermaid-entities.mjs`
decodes HTML entities before the plugin processes them.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 14:05:00 -06:00
Jonathan Hefner
1b537192ca
Merge pull request #342 from jonathanhefner/type-checked-quickstart
Rewrite Quickstart guide with type-checked code examples
2026-01-24 13:51:34 -06:00
Jonathan Hefner
2109dccadb
Rewrite Quickstart guide with type-checked code examples
Rewrites the Quickstart tutorial to provide a more focused, step-by-step
introduction to building MCP Apps. The guide now uses synced code fences
that pull from `examples/quickstart/`, ensuring all code shown in the
documentation is type-checked and tested.

Key improvements:
- Clearer project setup instructions with explicit commands
- Streamlined explanations of the tool + resource registration pattern
- Added screenshot showing the completed app
- E2E test coverage validates the example works end-to-end

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:41:51 -06:00
Jonathan Hefner
56a2e79bba
Support full-file inclusion in synced code snippets
Previously, the script only supported extracting regions from `.ts/.tsx`
files using the `#regionName` syntax. Now it also supports including
entire files without a region specifier, enabling sync of any file type
(JSON, YAML, shell scripts, etc.) into documentation.

Changes:
- Make `#regionName` optional in the `source=""` attribute
- Accept any fence language (not just `ts`/`tsx`)
- Simplify cache to flat map with composite keys
- Add validation error for region extraction on non-TypeScript files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:27:54 -06:00
Jonathan Hefner
6eb82b5562
Refactor server startup functions
Rename `startServer` to `startStreamableHTTPServer` and add a new
`startStdioServer` helper to make the transport type explicit. Remove
the `ServerOptions` interface in favor of reading `PORT` directly from
the environment inside the function (defaulting to `3001`).

Also update Python examples to default to port `3001` for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:27:54 -06:00
Olivier Chafik
9557d41ce7
chore: husky env fix, version pins, update-lock:docker script (#341)
* fix(husky): load Node.js env for GUI apps + add update-lock:docker script

Cherry-picked non-e2e changes from ochafik/fix-e2e-flaky-tests:
- Load nvm/fnm/volta/homebrew paths in pre-commit for GitHub Desktop
- Add update-lock:docker script to regenerate lockfile with public registry

* pin package versions for dev compatibility

- solid-js: 1.9.10
- caniuse-lite: 1.0.30001763
- cheerio: 1.1.2
- electron-to-chromium: 1.5.267
- @playwright/test: 1.57.0
- playwright: 1.57.0
- playwright-core: 1.57.0

* regen

* fix: pin @types/node to existing version and regenerate lockfile

- Fix @types/node 20.19.28 -> 20.19.27 (typo, version didn't exist)
- Regenerate package-lock.json to resolve versions available on both
  public npm and Anthropic artifactory registries

* fix(basic-server-solid): bump solid-js 1.9.10 -> 1.9.11 to fix seroval vulnerabilities
2026-01-24 01:35:45 +00:00
Ido Salomon
90ccd3a728
fix: followup app -> view terminology (#339) 2026-01-24 03:16:59 +02:00
Olivier Chafik
8c4168a092
feat(say-server): add light/dark theme support (#338)
- Add importmap entry for main SDK exports (applyDocumentTheme, etc.)
- Import and apply host theme and styles on context changes
- Apply initial host context theme/styles when app connects
- Update CSS to use [data-theme="dark"] selector for host-provided themes
- Keep @media prefers-color-scheme as fallback for standalone mode
- Add color-scheme meta tag for native element theming
2026-01-23 22:15:55 +00:00
martinalong
89f58bf555
[MCP Apps] Add appCapabilities.availableDisplayModes documentation to spec (#331)
* Add appCapabilities.availableDisplayModes documentation to spec

Document the availableDisplayModes field in McpUiAppCapabilities that allows
apps to declare which display modes they support. This enables hosts to
compute the intersection of host and app capabilities and only offer
appropriate display mode controls.

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

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

* Address PR review feedback

- Change "Guest UI" to "View" terminology
- Add "View" prefix to availableDisplayModes heading for disambiguation
- Remove intersection logic - host only checks its own supported modes
- Remove assumption that apps must support inline mode
- Simplify host behavior to honor/reject based on its own capabilities

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

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

* Add appCapabilities.availableDisplayModes to types

- Add `availableDisplayModes` field to `McpUiAppCapabilities` so apps can
  declare which display modes they support
- Change `HostContext.availableDisplayModes` type from `string[]` to
  `McpUiDisplayMode[]` for type safety
- Regenerate schemas

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

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

* Address Olivier's feedback - consolidate display mode docs

- Remove separate "App Capabilities" section for availableDisplayModes
- Add display mode requirements to existing ui/request-display-mode section:
  - Host MUST NOT switch View to mode not in appCapabilities.availableDisplayModes
  - Host MAY decline requests from Views that didn't declare modes
  - View MUST declare supported modes during initialization

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

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

* Restore McpUiAppCapabilities docs and fix display mode clause

- Restore McpUiAppCapabilities interface documentation with availableDisplayModes
- Restore "View MUST check if the requested mode is in availableDisplayModes
  from host context" clause that was accidentally removed

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

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

* Create unified Display Modes section

Consolidate all display mode documentation into a single section:
- Display mode types (inline, fullscreen, pip)
- Declaring support from View (appCapabilities.availableDisplayModes)
- Declaring support from Host (HostContext.availableDisplayModes)
- Requesting changes (ui/request-display-mode)
- Notifying changes (ui/notifications/host-context-changed)
- All MUST/SHOULD requirements for View and Host behavior

The ui/request-display-mode in Requests section now references
the Display Modes section for behavior details.

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

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

* Remove mode coercion clause from display modes

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

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

* Regenerate schemas with correct version

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-23 21:13:26 +00:00
Olivier Chafik
7bbf2bb250
feat(basic-host): Add theme toggle and MCP style variables (#336)
* feat(basic-host): add theme toggle and MCP style variables

Add theme support to basic-host example:
- theme.ts: Global theme manager with light/dark toggle
- host-styles.ts: Full set of MCP CSS variables using light-dark()
- ThemeToggle component with sun/moon icons
- Theme-aware CSS using CSS custom properties
- Pass theme + styles to apps via hostContext
- Notify apps when theme changes via sendHostContextChange

Apps now receive:
- hostContext.theme: 'light' | 'dark'
- hostContext.styles.variables: All 60+ MCP CSS variables
- onhostcontextchanged notifications when theme changes

* test: update e2e goldens for theme changes
2026-01-23 20:41:26 +00:00
Olivier Chafik
ee5d3f85ac
fix: Python server dependency resolution and Docker host validation (#333)
* fix(say-server): add --index flag to uv run for PyPI resolution

The pocket-tts dependency wasn't resolving without explicit --index flag
when invoked via npm scripts.

* fix(qr-server): allow Docker bridge IP in transport security

Add 172.17.0.1:* to allowed_hosts to fix 421 Misdirected Request errors
when the server is accessed from Docker containers.
2026-01-23 19:33:59 +00:00
Olivier Chafik
cfcbc9605f
feat(server): add getUiCapability helper for capability negotiation (#313)
* feat(server): add hasUiSupport/getUiCapability for experimental+extensions

Support double-tagging for MCP Apps capability negotiation:
- Check both experimental and extensions fields in client capabilities
- Add hasUiSupport() to easily check if client supports MCP Apps
- Add getUiCapability() to retrieve the capability settings
- Update spec to document both capability locations

This enables forward compatibility as MCP transitions from experimental
to the extensions field (SEP-1724).

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0

* fix: update @types/node version in package-lock.json

Replace invalid 22.19.5/22.19.6/22.19.7 versions with valid 22.19.3

* docs(spec): clarify hosts SHOULD double-tag, servers SHOULD check both

- Hosts SHOULD advertise capabilities in both experimental and extensions
- Servers SHOULD check both locations, preferring extensions when present
- Updated example to show double-tagging pattern

* chore: remove double-tagging recommendation, keep hasUiSupport helper

Reverts the spec changes that recommended clients double-tag capabilities
in both experimental and extensions fields. The helper functions remain
as they're useful for checking capability in either location.

* refactor(server): simplify to only getUiCapability, remove hasUiSupport

- Remove hasUiSupport function (use getUiCapability directly)
- Remove double-tagging logic (only check extensions field)
- Add ClientCapabilitiesWithExtensions type using SDK's ClientCapabilities
- Update spec to use getUiCapability
- Simplify tests

* fix: update @see link to getUiCapability

* refactor(server): inline ClientCapabilitiesWithExtensions type in getUiCapability

* refactor: move McpUiClientCapabilities to spec.types.ts
2026-01-23 16:26:49 +00:00
Liad Yosef
1003c17c2c
fix(e2e): update Map Server display name to CesiumJS Map Server (#324)
* fix(test): update map-server test to expect 'CesiumJS Map Server'

The server name was renamed but the E2E test config was not updated.

* fix: change in another file

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
2026-01-23 16:13:51 +00:00
Tobin South
25319cf226
Adding a simple (bad) pdf icon for directory (#330) 2026-01-23 16:04:46 +00:00
Jonathan Hefner
1d9b23d0b9
Merge pull request #328 from jonathanhefner/synced-code-snippets
Replace `{@includeCode}` with synced code fences
2026-01-23 08:11:02 -06:00
Jonathan Hefner
58019a48ce
Replace {@includeCode} with synced code fences
Migrate JSDoc and markdown documentation from `{@includeCode ./file#region}`
tags to inline code fences with `source="./file#region"` attributes.
Code examples are now visible directly in source files while remaining
type-checked via their `.examples.ts` source files.

Add `scripts/sync-snippets.ts` to extract code from `//#region` blocks
and sync it into labeled fences. Run with `npm run sync:snippets`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 07:34:56 -06:00
Olivier Chafik
ac65f46026
spec: remove Intents reference from rationale (#187)
* Update spec: drop Intents reference, rename Notify to Log

- Remove "intent" from the list of MCP-UI message types in the rationale
  section (Intents concept is being dropped)
- Rename "Notify" to "Log" in the lifecycle diagram to better reflect
  that notifications/message is for debugging/telemetry logging, not
  for sending messages to the chat (which is now ui/message)

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

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

* spec: remove Intents reference from rationale

Remove 'intent' from the list of MCP-UI message types - Intents concept was dropped in earlier discussions.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 13:01:45 +00:00
Olivier Chafik
8a877b36b4
feat(debug-server): add interactive debug server example (#321)
* feat(examples): add debug-server example

A comprehensive debug/development server for testing MCP Apps SDK features:
- Vanilla TypeScript UI with collapsible panels
- Message visualization (sent, received, errors)
- Server tool calling interface
- Multiple tool endpoints for testing

* refactor(debug-server): convert to React

- Replace vanilla TypeScript with React components
- Add comprehensive Host Styles panel showing all CSS variables grouped by category
- Use React hooks (useApp, useHostStyles, useAutoResize)
- Cleaner state management with useReducer for event log
- Collapsible sections for better organization
- CSS modules for scoped styling

* fix(debug-server): correct server name in main.ts

* Revert "refactor(debug-server): convert to React"

This reverts commit 5d22d2daa9a98ba412892447202ef10fbdb62f5b.

* fix(debug-server): update default values for multipleBlocks and includeMeta

* fix(debug-server): correct serve script and server name

- serve script now runs main.ts instead of server.ts
- Updated server name to 'Debug Server'
- Fixed npx command in comment

* fix: increase maxHeight to 6000px and add tools capability to debug app

- basic-host: Increase maxHeight from 600 to 6000px
- debug-server: Add tools capability to enable oncalltool/onlisttools handlers

* fix(debug-server): use consistent callback names for event tracking

Event types now match callback names (ontoolinput, ontoolresult, etc.)
so the Callback Status panel shows correct counts and payloads.

* feat(debug-server): add Update (Image) button for model context

* feat(debug-server): add expandable event log entries

- Click on any event log row to expand/collapse
- Arrow indicator rotates when expanded
- Full JSON payload shown indented in pre block
- Max height with scroll for large payloads

* style(debug-server): format long line in fullPayload function

* test(debug-server): add to E2E tests and screenshot specs

* chore: regenerate package-lock.json for cross-platform compatibility

* update dep
2026-01-23 12:39:40 +00:00
Olivier Chafik
023f1fd233
fix: misc fixes for pdf-server, map-server, and say-server (#329)
* fix(map-server): simplify location update message format

* fix(say-server): pass host to streamable_http_app for non-localhost deployments

* refactor(pdf-server): simplify to stateless range-query architecture

- Remove caching/indexing in favor of direct HTTP Range requests
- Use HEAD requests to get file size (no full download)
- Use Range requests to proxy byte chunks (no caching)
- Local files: fs.read with offset/length
- Remote files: HTTP Range header
- Zero memory footprint (no PDF caching)
- Truly stateless (no session management needed)
- Single server.ts (~350 lines) + main.ts CLI entry point
- Instant startup (no preloading)
- Security: whitelist local files, allowed origins

Removed files:
- src/pdf-indexer.ts
- src/pdf-loader.ts
- src/types.ts

* feat(pdf-server): add dark mode support

- Add CSS variables with light-dark() function for automatic theme switching
- Sync theme with host via applyDocumentTheme()
- Apply host CSS variables via applyHostStyleVariables()

* feat(pdf-server): add list_pdfs tool back

Lists local files and allowed remote origins.
Also moves CHUNK_SIZE to module level constant.

* feat(pdf-server): improve tool descriptions and increase context limit

- display_pdf: list all allowed domains in description
- Increase MAX_MODEL_CONTEXT_LENGTH to 15000
- Move CHUNK_SIZE to module level constant

* chore(say-server): remove deployment docs from repo

Keep server.py fix for non-localhost deployments.
Deployment docs kept locally but not in repo.

* refactor(pdf-server): simplify updateModelContext format

- Add widget ID for multi-widget disambiguation
- Use concise header: 'PDF viewer (id) | title | Current Page: X/Y'
- Add 'Page content:' prefix before text
- Keep selection markers (<pdf-selection>...</pdf-selection>)

* refactor: rename widgetUUID to viewUUID for consistency

Aligns with main's standardization on 'view' terminology (PR #325).
Also dynamically generate allowed domains in display_pdf description.

* viewUUID

* fix(map-server): fix syntax error in location update message

* fix(pdf-server): restore package.json, improve server docs

- Restore --external pdfjs-dist in build
- Rewrite server.ts header for newcomers (list tools, not implementation details)
- Minimize main.ts diff

* chore(pdf-server): remove unused addAllowedOrigin/addAllowedLocalFile

* docs(pdf-server): update README for new architecture

* docs(pdf-server): restore full README, add theming section, remove get_pdf_info

* rm

* fix(pdf-server): restore createMcpExpressApp and error handling

- Use createMcpExpressApp for DNS rebinding protection
- Restore error callback in app.listen()

* fix(say-server): pass host to streamable_http_app for non-localhost deployments

* chore(say-server): remove deployment docs from repo

* widgetId->tool id

* fix(map-server): add null check for center in location update

chore(pdf-server): dedupe and sort allowed origins

* fix(map-server): use transparent background for rounded corners

* prettier
2026-01-23 12:35:07 +00:00
Liad Yosef
4fc71656a3
docs: standardize UI terminology to use "view" consistently (#325)
* docs: standardize UI terminology to use "view" consistently

Replaces "widget" and "Guest UI" terminology with "view" across
the codebase as decided in the community survey.

This change updates:
- Specification document
- SDK source code and JSDoc comments
- Example servers and documentation
- Plugin documentation and skills

Closes #71

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 04:36:52 +02:00
Jonathan Hefner
9e42ee05a3
Merge pull request #323 from jonathanhefner/format-typedoc-links
Format `{@link}` tags with monospace display text
2026-01-22 17:32:33 -06:00
Jonathan Hefner
d12c2cccfa
Merge pull request #322 from jonathanhefner/typedoc-theme
Use `typedoc-github-theme` for API documentation
2026-01-22 17:32:23 -06:00
Jonathan Hefner
9dd995562a
Format {@link} tags with monospace display text
Add explicit backtick-wrapped display text to all `{@link}` tags for
monospace rendering in TypeDoc output. This improves readability by:

- Using monospace font for code references (types, methods, functions)
- Stripping module prefixes from display text (e.g., `app!App.connect`
  displays as `App.connect`)

Updated files across the SDK including `App`, `AppBridge`,
`PostMessageTransport`, React hooks, server helpers, types, and
documentation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 14:37:04 -06:00
Jonathan Hefner
eb803723ef
Use typedoc-github-theme for API documentation
Configure TypeDoc to use the GitHub-styled theme for better visual
consistency with the project's GitHub presence. Also reorganize the
config options and enable group navigation for improved documentation
structure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 13:24:01 -06:00
dependabot[bot]
43c30a7665
build(deps): bump the npm_and_yarn group across 1 directory with 2 updates (#284)
Bumps the npm_and_yarn group with 1 update in the / directory: [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte).


Updates `svelte` from 5.46.3 to 5.46.4
- [Release notes](https://github.com/sveltejs/svelte/releases)
- [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.46.4/packages/svelte)

Updates `devalue` from 5.6.1 to 5.6.2
- [Release notes](https://github.com/sveltejs/devalue/releases)
- [Changelog](https://github.com/sveltejs/devalue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/devalue/compare/v5.6.1...v5.6.2)

---
updated-dependencies:
- dependency-name: svelte
  dependency-version: 5.46.4
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: devalue
  dependency-version: 5.6.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-21 19:25:46 +00:00
Jonathan Hefner
e719be3cd7
Merge pull request #314 from jonathanhefner/patterns-doc
Add Patterns guide doc
2026-01-21 11:00:26 -06:00
Tobin South
c76f827368
fix(example qr server): Embed widget HTML in QR server (#317)
* feat(qr-server): embed widget HTML for self-contained usage

Embed the widget HTML directly in server.py following the same pattern
as the say-server. This allows the QR server to be run without any
local file dependencies using `uv run <url>`.

The get_widget_html() function prefers the built version from dist/
for local development but falls back to the embedded HTML for unbundled
usage.

* refactor(qr-server): remove widget.html and simplify embedded HTML

Address PR review feedback:
- Remove separate widget.html file (no longer needed)
- Remove get_widget_html() function and dist/mcp-app.html reference
- Inline EMBEDDED_WIDGET_HTML directly in widget() resource
- Remove unused Path import

The server is now fully self-contained in a single file.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-21 16:51:23 +00:00
Olivier Chafik
17813898c0
docs(spec): clarify ui.domain is host-dependent (#309)
Update the spec to clarify that:
- ui.domain format and validation rules are host-dependent
- Servers must consult host-specific documentation for expected format
- Common patterns: hash-based subdomains, URL-derived subdomains
- Updated examples to reflect realistic host-controlled domains

This addresses the gap between the spec's previous implied flexibility
(arbitrary domains like weather-widget.example.com) and real-world
constraints where hosts must control sandbox domains for security.
2026-01-21 14:09:38 +00:00
Jonathan Hefner
36268be7ef Add links to example implementations in patterns documentation
Each pattern section now includes a "See examples/..." link pointing
to a working implementation in the repository. This helps readers
quickly find real code demonstrating each pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
8cd96adc5a Include OAI migration guide in TypeDoc documentation
- Add migration guide to `projectDocuments` in `typedoc.config.mjs`
- Add YAML frontmatter with title to `migrate_from_openai_apps.md`
- Fix heading hierarchy (### → ####) for proper nesting
- Fix "Client-side" → "Client-Side" capitalization
- Rename "Full Migration Example" → "Client-Side Migration Example"
- Remove TODO placeholder sections from `patterns.md`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
e626b84d44 Complete "Sending large follow-up messages" pattern documentation
Add new pattern showing how to combine `updateModelContext()` with
`sendMessage()` when data exceeds message size limits. The example
demonstrates offloading a large transcript to model context first,
then sending a brief trigger message.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
89653cbb7a Complete "Passing contextual information" pattern documentation
Documents `updateModelContext` for keeping the model informed about user
interactions. The example shows a shopping cart use case with YAML frontmatter
for structured, parseable context.

Also refines the `updateModelContext` JSDoc to clarify the difference from
`sendMessage` and removes the `structuredContent` example.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
e5b2a13996 Complete "Reading large amounts of data" pattern documentation
Adds type-checked code examples demonstrating how to bypass host platform
size limits on tool call responses using app-only chunked tools:

- Server-side: `registerAppTool` with `visibility: ["app"]`, chunked response
  schema with `hasMore`/`offset` pagination, base64 encoding
- Client-side: `app.callServerTool()` loop with progress callback, base64
  decoding, chunk reassembly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
3398ed49b4 Complete "Pausing computation-heavy widgets" pattern documentation
Add `IntersectionObserver` pattern for pausing expensive operations
(animations, WebGL, polling) when widgets scroll out of view.

- Add `visibilityBasedPause` example to `patterns.tsx` with play/pause
  callbacks and `onteardown` cleanup
- Document pattern in `patterns.md` with link to MDN docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
05e07a76e7 Complete "Persisting widget state" pattern documentation
Replaces the TODO stub with complete server-side and client-side examples
based on the `pdf-server` implementation pattern:

- Server: shows returning `widgetUUID` via `CallToolResult._meta`
- Client: shows receiving UUID via `ontoolresult`, using it as localStorage
  key with generic `saveState<T>()` / `loadState<T>()` helpers

Also clarifies the distinction between widget state persistence (UI state
surviving page reloads) and `updateModelContext()` (model-visible state).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
a0265f92d2 Complete "Giving errors back to model" pattern documentation
Adds JSDoc example `App_updateModelContext_reportError` showing how to
report runtime errors (e.g., microphone permission denied) back to the
model using `updateModelContext`.

Updates patterns.md with clearer framing:
- Server-side (tool handler): return `{ isError: true, ... }`
- Client-side (app): use `updateModelContext` for runtime errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
a1ae100ef0 Complete "Lowering perceived latency" pattern documentation
- Expand `App_ontoolinputpartial_progressiveRendering` example in
  `app.examples.ts` to show full state management pattern with
  `toolInputs` and `toolInputsPartial` variables
- Add healed JSON caveat to `ontoolinputpartial` JSDoc in `app.ts`
- Update `patterns.md` to reference the expanded example and document
  the streaming preview pattern
- Remove empty `lowerPerceivedLatency` placeholder from `patterns.tsx`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
dce143e92e Complete "Entering / exiting fullscreen" pattern documentation
Add `App_onhostcontextchanged_respondToDisplayMode` example showing how to
listen for display mode changes and update UI with `classList.toggle()`.

Update `App_requestDisplayMode_fullscreen` to `App_requestDisplayMode_toggle`
to show the common toggle pattern checking `availableDisplayModes` first.

Refactor full examples (pdf-server, shadertoy-server) to use
`classList.toggle(class, condition)` instead of add/remove branches.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
566ac58ab7 Consolidate host styling patterns into unified documentation
Merge the separate "Support native host styling using CSS variables" and
"Reacting to light/dark theme changes" sections into a single "Matching
host styling (CSS variables, theme, and fonts)" pattern. This provides
clearer guidance by covering all three concerns together.

- Rename `docs/patterns.ts` to `docs/patterns.tsx` for JSX support
- Add `hostStylingVanillaJs` and `hostStylingReact` consolidated examples
- Enhance `applyHostFonts_fromHostContext` to apply initial fonts after
  `connect()`, not just on context change
- Add CSS usage examples to `applyHostStyleVariables()` JSDoc
- Update font example to use `var(--font-sans, system-ui, sans-serif)`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
77306d1aaf Complete "Reacting to light/dark theme changes" pattern documentation
Reference existing JSDoc examples instead of maintaining a separate one in
`docs/patterns.ts`:
- Vanilla JS: `applyDocumentTheme_fromHostContext` from `styles.examples.ts`
- React: `useDocumentTheme_conditionalRender` for reactive theme reading

Expand `applyDocumentTheme_fromHostContext` example to show both the
`onhostcontextchanged` handler and initial theme application after `connect()`.

Note in the docs that `useHostStyles()` applies theme automatically, so React
apps don't need additional setup unless they want to read the theme reactively
for conditional rendering.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
fb5e96a719 Complete "Support native host styling using CSS variables" pattern documentation
References existing type-checked examples instead of a stub:
- Vanilla JS: `applyHostStyleVariables_fromHostContext` from `src/styles.examples.ts`
- React: `useHostStyles_basicUsage` from `src/react/useHostStyles.examples.tsx`

Expanded `applyHostStyleVariables_fromHostContext` to show the full pattern:
using CSS variables in styles, applying on `onhostcontextchanged`, and
applying initial styles after `connect()`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
7c3d1e817f Complete "Tools that are private to Apps" pattern documentation
Document the `_meta.ui.visibility: ["app"]` feature in `docs/patterns.md`
by referencing the existing type-checked example from
`src/server/index.examples.ts#registerAppTool_toolVisibility`.

- Fix typo: `["apps"]` → `["app"]` (singular)
- Remove placeholder function from `docs/patterns.ts`
- Expand description to explain use cases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
78b24ae2ac Add skeleton docs/patterns.md with type-checked companion file
Introduces a patterns documentation page for common MCP Apps recipes.
The skeleton includes TODO sections for: perceived latency, iframe/MCP Apps
coexistence, authenticated calls, private tools, theme changes, fullscreen,
host styling, data persistence, OpenAI migration, and error handling.

- Add `docs/patterns.md` to `projectDocuments` in `typedoc.config.mjs`
- Add `docs/patterns.ts` companion file with region-delimited code stubs
- Include `docs/**/*.ts` in `tsconfig.json` for type-checking
- Document the standalone docs pattern in `CLAUDE.md`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 13:31:11 -06:00
Jonathan Hefner
26ae13df0f
Merge pull request #302 from jonathanhefner/checked-examples
Move JSDoc examples to type-checked `.examples.ts` files
2026-01-20 13:30:01 -06:00
Jonathan Hefner
121034709e
Merge pull request #306 from jonathanhefner/top-level-plugin-marketplace
Add `.claude-plugin/marketplace.json` for plugin distribution
2026-01-20 12:32:44 -06:00
Maurizio Albari
b594fc2dbd
fix: update serve:http to run main.ts instead of server.ts (#315)
After commit 65a62a9f, the server startup code was moved from server.ts
to main.ts, but the serve:http script was not updated accordingly.

This caused 9 example servers to fail to start when running npm start:
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- integration-server
- map-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- wiki-explorer-server

The server.ts file now only exports createServer(), while main.ts
contains the actual server startup logic.
2026-01-20 17:57:08 +00:00
Jonathan Hefner
7078f81ceb Add .claude-plugin/marketplace.json for plugin distribution
This enables users to install the `mcp-apps` plugin via:

    /plugin marketplace add modelcontextprotocol/ext-apps
    /plugin install mcp-apps@modelcontextprotocol-ext-apps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 11:38:14 -06:00
Anton Pidkuiko
f6ee5d5bdc
Merge pull request #286 from modelcontextprotocol/ochafik/readme-mcp-config
docs: add MCP client configuration to READMEs
2026-01-20 11:11:51 +00:00
Olivier Chafik
864bb6dc2d
docs: clarify that npm start runs examples in dev mode 2026-01-20 10:54:04 +00:00
Olivier Chafik
24f43927e5
Merge remote-tracking branch 'origin/ochafik/fix-say-server-readme' into ochafik/readme-mcp-config
# Conflicts:
#	examples/say-server/README.md
2026-01-20 10:51:14 +00:00
Olivier Chafik
198a1be034
fix(say-server): use isolated HuggingFace cache for Docker
Avoid sharing host's HF cache with Docker container since
downloaded models may contain unsafe pickled files or code.
2026-01-20 10:46:21 +00:00
Olivier Chafik
cba7b71e2b
docs(say-server): use raw GitHub URL for direct uv run 2026-01-20 10:42:31 +00:00
Olivier Chafik
ccd41ac721
docs: add MCP client config for say-server
- Add MCP client configuration section to say-server README
- Add say-server to combined config in top-level README
2026-01-20 10:18:19 +00:00
Olivier Chafik
5ef3120e0c
Merge remote-tracking branch 'origin/main' into ochafik/readme-mcp-config 2026-01-20 10:17:45 +00:00
Olivier Chafik
ad67b3e4bb
Merge remote-tracking branch 'origin/main' into ochafik/fix-say-server-readme 2026-01-20 10:16:59 +00:00
Jonathan Hefner
f0c7cc1b14 Refine JSDoc examples for consistency and type safety
Follow-up to the examples extraction: pass dependencies as function
parameters instead of using global stub declarations, rename region
names to match method names (e.g., `AppBridge_guestCapabilities_*` →
`AppBridge_getAppCapabilities_*`), and simplify examples using optional
chaining.

Also clarifies `RESOURCE_URI_META_KEY` documentation to indicate it's
for tool definition metadata (from `tools/list`), not tool result
metadata, and removes the redundant `setNotificationHandler` example
in favor of the simpler event setter approach.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 20:43:47 -06:00
Jonathan Hefner
ad04e89745 Move JSDoc examples to type-checked .examples.ts files
Extract inline code examples from JSDoc comments into companion
`.examples.ts`/`.examples.tsx` files. Examples are now:

- Type-checked by wrapping them in functions
- Referenced via `{@includeCode ./file.examples.ts#regionName}` tags
- Organized using `//#region` and `//#endregion` markers

This keeps the main source files cleaner while ensuring examples
compile correctly. Updated TypeDoc config with `exampleTag: false`
to support title text on `@example` tags.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 20:41:19 -06:00
Jonathan Hefner
e5c90c7385 Use absolute GitHub URLs for example links in README
Relative directory links like `](examples/map-server)` caused TypeDoc
warnings ("relative path is not a file") and resulted in broken links
in the generated API documentation. Converted all 20 example directory
links to full GitHub URLs while keeping image paths relative.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 20:36:28 -06:00
Jonathan Hefner
cac214f2cf
Merge pull request #290 from jonathanhefner/migrate-oai-app-skill
Add "Migrate from OpenAI App" skill to Claude Code plugin
2026-01-19 20:17:14 -06:00
Olivier Chafik
def51a70ad
fix(say-server): clean up README
- Add screenshot reference
- Remove GPU prerequisite (not needed, wasn't requested)
- Use GitHub raw URLs instead of local file paths
- Simplify Docker section
- Fix Claude Desktop config to use remote URL
2026-01-20 01:08:01 +00:00
Olivier Chafik
e6983b36d0
feat(examples): add say-server - streaming Pocket TTS with karaoke highlighting (#304)
* Add say-server: streaming TTS MCP App with karaoke UI

- Streaming TTS MCP App with real-time word highlighting
- React widget with pause/resume controls
- Debounced model context updates
- Self-contained server.py for standalone uv run

Co-authored-by: Claude <noreply@anthropic.com>

* Update server.py

* fix(say-server): fix React/Zod version conflicts in widget

- Pin Zod to v3.25.1 via esm.sh deps param (fixes .custom() error)
- Add external=react,react-dom to prevent duplicate React instances
- Update React from 19.1.0 to 19.2.0
- Import React for Babel's classic JSX transform
- Consolidate imports: only use @modelcontextprotocol/ext-apps/react
- Use Babel from unpkg.com instead of esm.sh
- Simplify resource decorator with meta= parameter
- Update CSP to allow both esm.sh and unpkg.com

* fix(say-server): show play button when paused, restart when finished

* feat(say-server): add fullscreen button, remove inline scroll

- Add fullscreen toggle button (appears on hover when available)
- Remove max-height/scroll in inline mode for cleaner display
- Enable scrolling only in fullscreen mode
- Track displayMode and fullscreenAvailable state
- Handle onHostContextChanged to detect fullscreen support

* fix(say-server): fix fullscreen button visibility and prevent multiple audio streams

- Check initial host context for fullscreen availability (not just updates)
- Add audioOperationInProgressRef guard to prevent concurrent audio operations
- Prevents race conditions from rapid clicks/double-clicks

* fix(qr-server): use meta parameter instead of manual handler patching

Simplified CSP metadata handling to match say-server's approach:
- Use meta parameter on @mcp.resource decorator directly
- Removed custom _read_resource_with_meta handler and handler replacement

* fix(say-server): simplify controls to prevent multiple audio streams

- Remove onClick from text display (was conflicting with double-click)
- Always show overlay button (with pause icon when playing)
- Lower opacity (0.3) when playing, full on hover
- Button shows: ▶️ (play), ⏸️ (pause), 🔄 (restart)
- Double-click text to restart still works
- Eliminates click/dblclick race condition

* fix(say-server): only show pause button on hover

- Play/restart buttons visible normally
- Pause button hidden until hover (doesn't block karaoke text)

* fix(say-server): fix partial input handling and session management

- Add promise lock to initTTSQueue to prevent concurrent initialization
- Reset queueIdRef and lastTextRef in ontoolresult for next tool call
- Close existing AudioContext when starting new session
- Reset all playback state (chunks, timings, position) on new session
- Fixes: text deltas not sent when new tool call has shorter text

* fix(say-server): detect new sessions by text comparison, not queueIdRef reset

- Don't reset queueIdRef in ontoolresult - audio should keep playing
- Detect new sessions in ontoolinputpartial/ontoolinput by checking if
  new text starts with lastTextRef (continuation) or not (new session)
- Only reset and create new queue when genuinely new session detected
- Fixes: pause/play was creating new streams because queueIdRef was reset

* feat(say-server): add theming, floating button, and keyboard shortcuts

Theming:
- Use CSS variables (--font-sans, --color-text-primary/secondary) with fallbacks
- Apply host fonts via applyHostFonts() on connect and context changes
- Apply host style variables via applyHostStyleVariables()
- Apply document theme via applyDocumentTheme()

Floating button:
- Button follows cursor position (below current line being spoken)
- Uses invisible cursor marker element for accurate positioning
- Shows ⏸️ when playing, ▶️ when paused, 🔄 when finished
- Low opacity when playing, full on hover

Keyboard shortcuts:
- Space: toggle play/pause
- Enter: toggle fullscreen (when available)

UI changes:
- Large overlay only shown for initial play (idle state)
- Floating button used during playback for less intrusive controls

* docs(say-server): add TODO items for future improvements

* fix(say-server): simplify to fixed play button at top right

- Remove floating cursor-following button (was too complex)
- Fixed play button at top-right corner
- Low opacity when playing, full on hover
- Keep theming and keyboard shortcuts (Space/Enter)
- Add say-server to E2E tests

* test(e2e): add say-server to tests but skip in CI

Say-server requires Pocket TTS model (~500MB) and GPU support,
which isn't available in the Docker CI environment. Skip it for now.

* test(e2e): add say-server and qr-server to screenshot generation

- Remove say-server from SKIP_SERVERS (widget works without TTS model)
- Add say-server masks for play buttons
- Add both say-server and qr-server to grid screenshot generator

* fix(say-server): restore working play/pause overlay, keep theming and keyboard shortcuts

Reverted to working commit 413beca and carefully reapplied:
- CSS variables for theming (--font-sans, --color-text-primary/secondary)
- Dark mode support via @media (prefers-color-scheme)
- Host theming via applyHostStyleVariables/applyHostFonts/applyDocumentTheme
- Keyboard shortcuts: Space = play/pause, Enter = fullscreen

Preserved working behavior:
- Play overlay visible when not playing, hover to show when playing
- Pause on click works correctly
- Karaoke highlighting works

* fix(say-server): minimal CSS-only theming, no JS changes from working version

Only CSS changes from working commit 413beca:
- Added CSS variables for theming foundation
- No JavaScript changes (reverted theming calls and keyboard shortcuts)

This isolates whether the issue is CSS or JS related.

* feat(say-server): add autoPlay param and Reset button

- Add autoPlay parameter (default: true) with Pydantic Field description
  - Description visible in MCP tool schema: 'browsers may block autoplay'
- Add Reset button next to Fullscreen button (bottom right)
- Both buttons share controlBtn base styles, shown on hover
- Widget reads autoPlay from tool input params

* fix(say-server): update FastMCP API for new SDK version

Python SDK refactored transport params from constructor to run():
- Remove port, stateless_http from FastMCP() constructor
- Pass stateless_http=True to streamable_http_app() instead

* debug(say-server): add console logs to trace audio stream issue

Logging added to:
- togglePlayPause (all branches)
- initTTSQueue (entry and AudioContext creation)
- restartPlayback (entry)
- ontoolinputpartial/ontoolinput (entry and new session detection)

Open browser DevTools console and reproduce the issue - logs will show what's being called.

* feat(say-server): add play/pause buttons in top-left and bottom-right corners

- Top left: playPauseTopBtn for quick access
- Bottom right: playPauseBtn alongside reset and fullscreen
- Both show on hover with same styling as other control buttons
- Icons: ▶️ (play), ⏸️ (pause), 🔄 (restart when finished)

* fix(say-server): simplify UI and fix pause/resume audio duplication

- Remove central play overlay, use single toolbar in top-right
- Fix audio duplication on pause/resume (only defer to pendingChunks for initial autoplay)
- Remove double-click to restart (use toolbar restart button)
- Sync displayMode when host changes it externally
- Click on text to play/pause

* feat(say-server): add voice selection support

- Add list_voices tool to show available voices
- Add voice parameter to say tool (predefined names, HuggingFace URLs, or local paths)
- Predefined voices: alba, marius, javert, jean, cosette, eponine, azelma, fantine
- Update README with voice documentation

* docs(say-server): improve tool description for better LLM triggering

- Add English-only constraint prominently in first line and note
- List common trigger patterns (say, speak, read aloud, etc.)
- Update text parameter description to mention English
- Keep description concise for quick LLM scanning

* feat(say-server): add speak lock for multi-widget coordination

When multiple TTS widgets exist, only one plays at a time:
- Widget announces to localStorage when starting playback
- Playing widgets poll localStorage every 200ms
- If another widget starts, previous one pauses automatically
- Lock is cleared on pause/finish/teardown

Uses _meta.widgetUUID in tool result for widget identification.

* update package.json of say

* feat(say-server): add screenshots and update README gallery

- Add say-server to demo gallery with 300x300 grid-cell.png
- Fix qr-server grid-cell.png dimensions (was 672x668, now 300x300)
- Add golden snapshot for say-server E2E tests
- Add full screenshot and grid-cell thumbnail for say-server

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

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

* style(say-server): apply prettier formatting

- Add blank lines in README for better readability
- Add trailing newline to package.json

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

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

* feat(say-server): add Escape key to exit fullscreen and update docs

- Add onKeyDown handler to main element for Escape key
- Update README with full list of MCP App features:
  - Model context updates
  - Native theming
  - Fullscreen mode
  - Multi-widget speak lock
- Update server.py docstring with feature documentation

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

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

* fix(say-server): add queue timeout to prevent infinite polling

The TTS queue could hang forever if:
- The LLM stops generating mid-stream (no end signal)
- User cancels before tool completes
- Network interruption prevents end_tts_queue call

Changes:
- Add last_activity timestamp to track queue activity
- Add 30-second timeout (QUEUE_TIMEOUT_SECONDS)
- Poll with 5-second timeout, checking for stale queues
- Mark stale queues as "error" so widget stops polling
- Return error message in poll_tts_audio response
- Add error logging in widget polling loop

This prevents tool calls from running forever when queues are abandoned.

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

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

* debug(say-server): add logging to track queue lifecycle

Add console logging to debug why end_tts_queue might not be called:

Widget-side:
- Log queueId in ontoolinputpartial, ontoolinput, ontoolresult
- Log when new session is detected (queue reset)
- Log when initTTSQueue fails
- Log when end_tts_queue is called/not called

Server-side:
- Log when end_tts_queue is called
- Log warnings for unknown queues
- Log info for already-ended queues

This will help identify if the issue is:
- User interruption (ontoolresult never called)
- Session reset (queueId becomes null)
- Host bug (callback dropped)

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

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

* perf(say-server): add initial delay before polling

Add 150ms delay before first poll to give TTS model time to generate
the first audio chunk. This reduces unnecessary polling and server load.

Current backoff after that:
- 30ms if chunks received (keep streaming fast)
- 80ms if no chunks (wait longer for generation)

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

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

* fix(say-server): use adaptive polling instead of fixed initial delay

Previous approach: Always wait 150ms before first poll
Problem: Even when chunks are ready, we wait unnecessarily

New approach: Adaptive polling
- Start polling immediately (no initial delay)
- 20ms backoff when receiving chunks (fast streaming)
- Exponential backoff when no chunks: 50ms → 100ms → 150ms max
- Reset backoff when chunks start flowing

This reduces latency when TTS is fast while being polite when it's slow.

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

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

* feat(say-server): add speaker icon to server info

Uses SVG data URI for a speaker with sound waves icon.

* docs(say-server): add comprehensive third-party licensing information

- Expand License section in README with component attribution table
- Add voice collection license matrix with commercial use indicators
- Add warning about non-commercial expresso/ears voice collections
- Clarify NON-COMMERCIAL restriction in list_voices() output
- Use consistent CC-BY formatting (hyphenated)

* Update last activity to prevent timeout during active polling

* feat(say-server): add info button, SVG icons, and UI fixes

- Replace emoji play/pause buttons with neutral white SVG icons
- Add (i) info button in bottom-right with attribution popup
- Use app.openLink() API for external links in popup
- Add dynamic padding to fit popup when shown
- Make create_tts_queue async to fix AnyIO event loop error
- Add better error logging for TTS queue initialization
- Update README with openLink feature documentation
- Remove unnecessary HuggingFace login prerequisites

* docs(say-server): document multi-widget speak lock mechanism

* prettier:fix

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-20 00:41:50 +00:00
Jonathan Hefner
f3b3c48a83
Merge pull request #289 from jonathanhefner/basic-host-sse-transport
Add SSE transport fallback to `basic-host`
2026-01-19 16:26:53 -06:00
Olivier Chafik
801e1b043a
add EXAMPLE=folder-name support for tests & basic-host (#292)
* add EXAMPLE=folder-name support for tests & basic-host

* style: format e2e test files

* style: format threejs-server files
2026-01-19 20:16:37 +00:00
Olivier Chafik
6c5aa52c95
feat(shadertoy): add proper mouse/touch interaction support (#293)
* feat(shadertoy): add proper mouse/touch interaction support

- Fix iMouse uniform to match official Shadertoy specification:
  - iMouse.xy only updates while button/touch is held down
  - iMouse.zw uses positive values when down, negative when released
  - Enables proper click detection via iMouse.z > 0

- Add touch event support for mobile devices:
  - touchstart/touchmove/touchend/touchcancel handlers
  - Same behavior as mouse events

- Update tool description with mouse interaction documentation
- Update README with:
  - Mouse & Touch Interaction feature
  - Improved Julia Set example with click detection
  - iMouse specification table and usage examples

The iMouse uniform is a core Shadertoy feature used by popular shaders
like Star Nest, raymarching examples, and particle systems.

* style: fix Prettier formatting in shadertoy README
2026-01-19 19:11:53 +00:00
Olivier Chafik
de8048cb6d
feat(qr-server): use Python SDK meta parameter for resource CSP metadata (#303)
* update qr-server w/ python sdk fix

* feat(qr-server): use Python SDK meta parameter for resource CSP metadata

- Update qr-server to use the new meta parameter on @mcp.resource decorator
  instead of manually overriding the read_resource handler
- Add ui.resourceUri to tool meta alongside legacy ui/resourceUri for forward compatibility
- Simplify server.py by removing the custom _read_resource_with_meta handler
- Add qr-server to grid screenshot generation tests
- Update grid-cell.png screenshot

This leverages the Python SDK's native support for resource metadata,
eliminating the need for low-level handler manipulation.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-19 19:11:42 +00:00
Anton Pidkuiko
73ab853dc5
Merge pull request #297 from modelcontextprotocol/feat/shadertoy-streaming-visibility
feat(shadertoy): add streaming partial input, visibility optimization, host styling
2026-01-19 18:36:43 +00:00
Anton Pidkuiko
f71155ac7c
Merge pull request #299 from modelcontextprotocol/feat/threejs-perf-styling
feat(threejs): add visibility optimization, host styling, transparent background
2026-01-19 18:36:33 +00:00
Anton Pidkuiko
a9d8e40fe6
Merge pull request #305 from modelcontextprotocol/fix/spec-permissions-type
fix(spec): align permissions type with SDK implementation
2026-01-19 18:26:05 +00:00
Anton Pidkuiko MacBook
8c87715628
fix(spec): align permissions type with SDK implementation
The spec document incorrectly defined permission fields (camera, microphone,
geolocation, clipboardWrite) as boolean types, but the SDK implementation
uses empty object types ({}).

This caused validation errors for MCP servers (like Hex) that followed
the spec and sent boolean values like `clipboardWrite: true`, when the
Zod schema expects `clipboardWrite: {}`.

The empty object design was intentional to allow future extensibility
(e.g., adding options like `{ required: true }`). This commit aligns
the spec documentation with the actual TypeScript types in spec.types.ts.

Fixes validation error:
`{ code: 'invalid_type', expected: 'object', received: 'boolean',
   path: ['permissions', 'clipboardWrite'] }`
2026-01-19 17:26:13 +00:00
Olivier Chafik
fcd243a6f3
fix(e2e): make server-time element fill width for consistent mask (#283)
* fix(e2e): make server-time element fill width for consistent mask

- Add flex layout to .action p to make server-time stretch
- Add #server-time flex: 1 rule across all basic-server variants
- Update golden screenshots with full-width masked time field

This ensures the Playwright mask covers a consistent width regardless
of the timestamp content, fixing screenshot comparison flakiness.

* fix(e2e): wait for select to be enabled in screenshot generator

Match the logic in servers.spec.ts - wait for select to be enabled
(servers connected) instead of just visible.

* chore: trigger CI re-run
2026-01-19 17:01:55 +00:00
Jonathan Hefner
90dad1a86b Add "Migrate from OpenAI App" skill to Claude Code plugin
Adds a new skill (`plugins/mcp-apps/skills/migrate-oai-app/SKILL.md`)
that guides developers through migrating OpenAI Apps SDK applications
to the MCP Apps SDK. Includes API mapping tables, CSP configuration
guidance, and a "Before Finishing" checklist.

Also fixes the `registerAppResource()` JSDoc example in `src/server/index.ts`
to show CSP configuration in the correct location (on the content item,
not the resource config).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 07:59:41 -06:00
Anton Pidkuiko MacBook
d60224f363
feat(threejs): add visibility optimization, host styling, transparent background
- Add visibility-based animation pause (IntersectionObserver + wrapped rAF)
- Zero CPU usage when widget scrolls out of view
- Apply host style variables via useHostStyles
- Support transparent WebGL background (alpha: true)
- Use light-dark() for theme-aware fallback colors
- Tool no longer echoes code back in result
- Document performance features in README
2026-01-17 23:19:34 +00:00
Anton Pidkuiko MacBook
f1a00f9e15
feat(shadertoy): add streaming partial input, visibility optimization, host styling
Shadertoy server improvements:
- Add ontoolinputpartial handler to show code preview during generation
- Add IntersectionObserver to pause/play shader based on visibility
- Apply host style variables (theme, colors, fonts)
- Add border-radius with fullscreen mode support
- Use gradient background and ghost text color for code preview

Skill documentation updates:
- Add streaming partial input section with use cases
- Add visibility-based resource management pattern
- Add fullscreen mode API and CSS patterns
- Expand host styling with CSS variable usage examples
2026-01-17 22:29:57 +00:00
Matthew Wang
d27acc42b1 Add and helper functions 2026-01-16 21:20:10 -08:00
Jonathan Hefner
7ce5504f9f Add SSE transport fallback to basic-host
Implement the conventional MCP pattern for transport backward compatibility:
try `StreamableHTTPClientTransport` first (modern), then fall back to
`SSEClientTransport` (deprecated but needed for older servers).

This follows the approach documented in the MCP TypeScript SDK's
`streamableHttpWithSseFallbackClient.ts` example.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 10:23:07 -06:00
Olivier Chafik
ac1130cda4
style: format JSON in READMEs with prettier 2026-01-16 15:14:48 +00:00
Olivier Chafik
ed51361511
docs: add combined MCP client config to top-level README
Add a collapsible details section with configuration for all example
servers in the Examples section of the main README.
2026-01-16 14:31:42 +00:00
Olivier Chafik
94c5dff983
docs(qr-server): clarify that repo clone is required 2026-01-16 14:03:46 +00:00
Olivier Chafik
cce8d5c7a3
docs: add MCP client configuration to example READMEs
Add stdio transport configuration snippets near the top of each example
README to help users quickly set up the servers in their MCP clients.

- Node.js examples use npx with the published npm packages
- Python qr-server uses uv with local path
- Removed Claude Desktop-specific references from qr-server
2026-01-16 14:00:57 +00:00
Olivier Chafik
8c3b1daf7e
chore: bump version to 0.4.1 (#282) 2026-01-15 16:11:53 +00:00
Anton Pidkuiko
be8d725974
Merge pull request #272 from modelcontextprotocol/uv-qr-server
feat(qr-server): migrate to uv with PEP 723 inline dependencies
2026-01-15 14:29:33 +00:00
Olivier Chafik
0936d52d0a
feat(shadertoy): show fullscreen button only on widget hover, add min-height (#281) 2026-01-15 14:15:17 +00:00
Olivier Chafik
b78cd74c1b
feat(shadertoy): add fullscreen support + ditch insets for max visual effect (#278)
* feat(shadertoy): add fullscreen support

Add fullscreen toggle button that appears when the host supports fullscreen
display mode. Uses the same pattern as pdf-server:
- Check availableDisplayModes in host context
- Show/hide button based on capability
- Toggle between inline and fullscreen via requestDisplayMode()
- Update UI state on host context changes

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

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

* fix(shadertoy): button visibility after exiting fullscreen + Escape key

- Only update button visibility when availableDisplayModes is present
  in context (partial updates don't include all fields)
- Add Escape key handler to exit fullscreen mode

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 13:01:55 +00:00
Olivier Chafik
89a684546b
fix(examples): resolve DIST_DIR path for npm package execution (#277)
* fix(examples): resolve DIST_DIR path for npm package execution

When servers are compiled to dist/server.js and published to npm,
`import.meta.dirname` already points to dist/, so joining with "dist"
results in dist/dist/ which doesn't exist.

This fix detects whether we're running from TypeScript source or
compiled JavaScript and adjusts the path accordingly:
- From source (server.ts): use dist/ subdirectory
- From compiled (dist/server.js): use current directory

Fixes ENOENT errors like:
"ENOENT: no such file or directory, open '.../dist/dist/mcp-app.html'"

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

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

* fix(pdf-server): mark pdfjs-dist as external in bundle

The pdf.worker.mjs file wasn't being found when running from npm
because bun bundled pdfjs-dist but didn't include the worker file.

By marking pdfjs-dist as external, it uses the installed package
from node_modules at runtime where the worker is available.

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 12:23:13 +00:00
Olivier Chafik
847427aeef
pdf-server: fix scrolling gap in inline mode (#280)
* map-server: fix async/await in ontoolresult, remove unused getViewStorageKey

* pdf-server: fix scrolling gap in inline mode

- Set html/body background to match .main background color
  This hides any gap between .main and iframe border
- Change .main from height:100% to min-height:100%
  Ensures .main fills container properly
2026-01-15 12:22:58 +00:00
Olivier Chafik
b94982cc35
fix[examples/transcript]: fix transitional deltas (#275)
* fix transcript deltas

* Update mcp-app.css
2026-01-15 12:06:02 +00:00
Anton Pidkuiko MacBook
489d28b6da
fix: increase webServer timeout for uv dependency download
uv needs extra time on first run to download Python dependencies
2026-01-15 11:53:27 +00:00
Copilot
c73d36343b
fix: change unknown source message log from error to debug level (#239)
* Initial plan

* Change console.error to console.debug for unknown message sources

Co-authored-by: liady <7003853+liady@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: liady <7003853+liady@users.noreply.github.com>
2026-01-15 11:51:27 +00:00
Anton Pidkuiko
dbc7a41550
Merge pull request #274 from matteo8p/use-tool-type-from-official-sdk
Use `Tool` as the input type in `getToolUiResourceUri`
2026-01-15 11:45:46 +00:00
Anton Pidkuiko MacBook
656a0292ae
feat: add QR Code to README gallery + rename to QR Code Server
- Add grid-cell.png screenshot for README gallery
- Rename 'QR Server' to 'QR Code Server' everywhere
- Add QR Code (Python) entry to examples table in README
2026-01-15 11:36:21 +00:00
Jerome
65a62a9fae
feat: compile example servers to JavaScript for npm consumption (#259)
* feat: compile example servers to JavaScript for npm consumption

Add TypeScript compilation for server code so the example packages can be
imported and used by consumers without requiring TypeScript compilation.

Changes:
- Add tsconfig.server.json to each example for compiling server code
- Update package.json exports to point to compiled dist/server/server.js
- Add types field pointing to dist/server/server.d.ts
- Update build scripts to include server compilation step
- Fix .ts import extensions to .js in customer-segmentation-server

This enables importing the createServer function from published packages:
  import { createServer } from '@modelcontextprotocol/server-basic-react';

* fix: add type assertion for Nominatim API response

* chore: regenerate schemas

* refactor: split server examples into library and binary entry points

- server.ts: exports createServer() only (library entry point)
- main.ts: entry point with main() that runs the server (binary)
- package.json: adds bin field for npx support, uses inline bun build
- tsconfig.server.json: emitDeclarationOnly, outputs to dist/

This allows:
1. Importing the server as a library: import { createServer } from '@modelcontextprotocol/server-basic-react'
2. Running with npx: npx @modelcontextprotocol/server-basic-react [--stdio]

Added migration script at scripts/migrate-server-examples.ts

* refactor: migrate remaining server examples to library/binary structure

Apply the same refactoring to all 13 remaining server examples:
- scenario-modeler-server, shadertoy-server, map-server
- cohort-heatmap-server, customer-segmentation-server, video-resource-server
- system-monitor-server, budget-allocator-server, sheet-music-server
- threejs-server, integration-server, transcript-server, wiki-explorer-server

Each now has:
- server.ts: exports createServer() (library)
- main.ts: runs the server (binary with shebang)
- bin field in package.json for npx support

* chore: remove unused server-utils.ts from video-resource-server/src

* update

* fix(build): make Windows-compatible by removing chmod and fixing bun externals

- Remove 'chmod +x dist/index.js' which doesn't exist on Windows
- Add '--external ./server.js' to prevent bun from treating the import as a second entry point

* fix(build): use .ts import for bun compatibility

- Change import from './server.js' to './server.ts' in main.ts files
- Remove --external flag since bun should inline everything
- Fixes Windows build where bun couldn't resolve .js to .ts properly

* fix(build): use --external for Windows bun compatibility

Bun on Windows incorrectly treats imported .ts files as entry points.
Use --external './server.js' to work around this, creating a runtime
dependency between index.js and server.js.

* style: fix prettier formatting in pdf-server/main.ts

* fix(build): remove quotes from --external for Windows cmd.exe compatibility

* fix(build): use glob pattern for --external to work on all platforms

The literal path --external ./server.js was not matching the import.
Using glob pattern --external "./server*" correctly externalizes the server module.

* fix(build): use */server.js pattern for --external

The "./server*" pattern was not matching on Windows.
Using "*/server.js" matches any path ending in /server.js which works cross-platform.

* fix(build): use *server.js pattern without slash for Windows compatibility

* update package-lock

* chore: regenerate package-lock.json for cross-platform compatibility

* fix(build): use ./server.js instead of glob pattern for Windows compatibility

* fix(build): use double quotes for banner to fix Windows cmd.exe compatibility

---------

Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
2026-01-14 23:46:17 +00:00
Olivier Chafik
75cbf8612c
feat(basic-host): URL params, fullscreen mode, unified layout (#266)
* basic-host: handle containerDimensions

* feat(basic-host): Unified tool call layout with shared collapsible panels

* snapshots

* format
2026-01-14 22:09:41 +00:00
Olivier Chafik
75ecd62384
fix(examples): improve updateModelContext with structured YAML frontmatter (#271)
* map-server: fix async/await in ontoolresult, remove unused getViewStorageKey

* fix(map-server): improve updateModelContext and fix initialization

- Use structured YAML frontmatter format for model context (like pdf-server)
- Add tool name, altitude, and cleaner data format
- Fix initialization deadlock: app.connect() was inside ontoolresult handler
  which could never fire until connected
- Fix view state persistence: widgetUUID now properly extracted from
  ontoolresult and used to restore persisted camera position
- Server now includes widgetUUID in tool result _meta for widget identification

* fix(pdf-server): use widgetUUID for page persistence

- Add widgetUUID to tool result _meta for widget identification
- Use widgetUUID instead of generated key (pdfUrl+toolId) for localStorage
- Enables reliable page restoration when revisiting PDF widgets

* fix(transcript-server): use structured YAML frontmatter for model context

- Add tool name, listening status, and unsent entry count in frontmatter
- Consistent format with pdf-server and map-server

* update names

* drop bogus altitude

* Update mcp-app.ts
2026-01-14 22:08:09 +00:00
Matthew Wang
5bb0c6b4c7 Use as the input type in 2026-01-14 13:07:08 -08:00
Anton Pidkuiko MacBook
2ee15420a0
fix: add default text value for QR code generation
Without a default, the test would fail since text is required.
Uses modelcontextprotocol.io as the default URL.
2026-01-14 20:41:19 +00:00
Anton Pidkuiko MacBook
0927445025
feat(qr-server): migrate to uv with PEP 723 inline dependencies
- Replace requirements.txt with inline script dependencies in server.py
- Add package.json with npm scripts for run-all.ts integration
- Update CI workflow to install uv via astral-sh/setup-uv@v5
- Update Docker commands to install uv for e2e tests
- Add Playwright golden screenshot for qr-server
- Update README with uv installation and usage instructions
2026-01-14 20:27:35 +00:00
Olivier Chafik
db1b48552b
examples[pdf]: limit context length (#270)
* feat(pdf-server): Interactive PDF viewer example

PDF viewer with PDF.js featuring:
- Chunked binary loading with progress bar
- Text extraction for AI context
- arXiv paper support (fetch by ID)
- Page navigation with keyboard shortcuts
- Zoom controls (including Ctrl+0 reset)
- Fullscreen mode support
- Horizontal swipe for page changes (disabled when zoomed)
- Page persistence in localStorage
- Text selection via PDF.js TextLayer
- Clickable title link to source URL
- Rounded corners and subtle border styling

* chore: Add pdf-server to screenshot generation list

* refactor(pdf-server): Simplify and generalize PDF loading

- Accept any HTTP(s) URLs instead of ArXiv-only
- Use HTTP Range requests for chunked binary loading
- Remove ArXiv-specific code (arxiv.ts, metadata fetching)
- Remove CLAUDE.md index generation
- Flatten hierarchical folder structure to simple entries list
- Remove dead code: getPdfSummary, httpFileSizes
- Simplify base64 encoding using Buffer
- Simplify chunk extraction using slice()
- Consolidate DEFAULT_PDF_URL constant

The server now works with any PDF URL, not just arXiv papers.
HTTP Range requests stream chunks on-demand when supported.

* feat(pdf-server): Include title and selection in model context

- Add pdfTitle to updateModelContext structuredContent
- Include selection position (text, start, end) when text is selected
- Add debounced selectionchange listener to update context on selection

* fix(pdf-server): Restore default URL in view_pdf schema

The UI needs the default value in the schema to show it properly.

* refactor(pdf-server): Further simplifications

- Remove hard-coded test paths from main()
- Remove unused resources: pdfs://metadata/{pdfId}, pdfs://content/{pdfId}
- Remove unused metadata fields: subject, creator, producer, creationDate, modDate
- Remove unused entry fields: relativePath, estimatedTextSize
- Remove filterEntriesByFolder and folder filter from list_pdfs
- Remove redundant output schema validation (trust typed returns)
- Simplify scanDirectory and createLocalEntry signatures

Total: 1836 → 1666 lines (-170 lines, -9%)

* refactor(pdf-server): Major simplification for didactic focus

Simplified the example to focus on key MCP Apps SDK patterns:
- Chunked data through size-limited tool calls
- Model context updates (page text + selection)
- Display modes (fullscreen vs inline)
- External links (openLink)

Changes:
- Remove local file support (HTTP URLs only)
- Restrict dynamic URLs to arxiv.org for security
- Simplify types: url instead of sourcePath/sourceType
- Simplify indexer: 168 → 44 lines
- Simplify loader: 318 → 171 lines
- Simplify server: 337 → 233 lines
- Fix selection text normalization
- Rewrite README with didactic focus

Total: 1836 → 1236 lines (-33%)

* feat(pdf-server): Add file:// URL support for local files

- Local paths are converted to file:// URLs on startup
- file:// URLs must be in the initial list (strict validation)
- Dynamic URLs still restricted to arxiv.org only
- Updated README with local file examples

* fix(pdf-server): Improve selection detection with logging

- Add logging to selectionchange handler to verify it fires
- Add fallback matching without spaces (TextLayer spans may lack spaces)
- Log selection detection success/failure for debugging

The issue: PDF.js TextLayer renders text as positioned spans without
space characters between them. When selecting across spans:
- pageText has spaces (items joined with ' ')
- sel.toString() may not have spaces
- indexOf fails to match

The fix tries exact match first, then falls back to spaceless matching.

* feat(pdf-server): Format model context as markdown with front matter

Model context now looks like:
```markdown
---
url: https://arxiv.org/pdf/...
page: 5/144
---

Page text with <pdf-selection>selected text</pdf-selection> inline.
```

This is cleaner for the model to parse and includes the source URL.

* refactor(pdf-server): Extract smart truncation helpers

Added two well-designed helpers:

formatPageContent(text, maxLength, selection?)
- Centers truncation window around selection if present
- Adds <truncated-content/> markers at elision points
- Wraps selection in <pdf-selection> tags
- Allocates 60% context before, 40% after for readability

findSelectionInText(pageText, selectedText)
- Tries exact match first
- Falls back to spaceless match for TextLayer quirks
- Returns { start, end } or undefined

Example output with selection:
```
<truncated-content/>
...context before... <pdf-selection>selected text</pdf-selection> ...context after...
<truncated-content/>
```

* fix(pdf-server): Truncate inside selection tags when selection too long

When selection is too large for the budget:
<truncated-content/><pdf-selection><truncated-content/>start...end<truncated-content/></pdf-selection><truncated-content/>

This keeps the selection structure intact while showing beginning and end.

* refactor(pdf-server): Remove unused read_pdf_text, use Attention paper as default

- Remove read_pdf_text tool (viewer extracts text client-side with pdfjs)
- Remove PdfTextChunk and ReadPdfTextInput types
- Remove loadPdfTextChunk from pdf-loader
- Change default PDF to 'Attention Is All You Need' (1706.03762)
- Update README with modest language

* refactor(pdf-server): Simplify to use URL as ID, rename view_pdf to display_pdf

Major simplifications:
- Use URL directly as identifier (no hashing)
- Remove displayName - show elided URL with full URL as tooltip
- Rename view_pdf to display_pdf with better description
- Update all references from pdfId to url
- Simplify storage key and model context

The tool description now explains it displays an interactive viewer in the chat.

* feat(pdf-server): Normalize arxiv URLs to PDF format

arxiv.org/abs/... -> arxiv.org/pdf/...

Applied both at startup and when loading dynamic URLs.

* docs(pdf-server): Add prompt engineering to display_pdf description

* fix(pdf-server): Sharp rendering on retina displays

Account for devicePixelRatio when rendering canvas:
- Scale canvas dimensions by dpr
- Scale context by dpr
- Keep CSS size at logical pixels

* fix(pdf-server): Normalize arxiv URLs in read_pdf_bytes too

* add to e2e spec

* add to e2e spec

* add to e2e spec

* add to e2e spec

* regen

* chore: regenerate package-lock.json and fix hono vulnerability

* docs: add pdf-server screenshot to READMEs

* regen

* ci: add missing examples to pkg-pr-new publish

* ci: add pdf-server to npm publish examples

* Update README.md

* pdf-server: improve tool response text for better model context

* revert unrelated screenshot changes

* pdf-server: dynamically add arxiv URLs in read_pdf_bytes

Fixes 'PDF not found' error when server restarts between display_pdf
(which adds the entry) and read_pdf_bytes (which previously only looked
up existing entries). Now read_pdf_bytes mirrors display_pdf's logic
and dynamically adds arxiv URLs to the index.

* cap length of context update in pdf-server

* format
2026-01-14 15:26:25 +00:00
Olivier Chafik
96daa46a1d
examples: add PDF viewer w/ chunked data loading, full-screen, model context updates, private tool (#267)
* feat(pdf-server): Interactive PDF viewer example

PDF viewer with PDF.js featuring:
- Chunked binary loading with progress bar
- Text extraction for AI context
- arXiv paper support (fetch by ID)
- Page navigation with keyboard shortcuts
- Zoom controls (including Ctrl+0 reset)
- Fullscreen mode support
- Horizontal swipe for page changes (disabled when zoomed)
- Page persistence in localStorage
- Text selection via PDF.js TextLayer
- Clickable title link to source URL
- Rounded corners and subtle border styling

* chore: Add pdf-server to screenshot generation list

* refactor(pdf-server): Simplify and generalize PDF loading

- Accept any HTTP(s) URLs instead of ArXiv-only
- Use HTTP Range requests for chunked binary loading
- Remove ArXiv-specific code (arxiv.ts, metadata fetching)
- Remove CLAUDE.md index generation
- Flatten hierarchical folder structure to simple entries list
- Remove dead code: getPdfSummary, httpFileSizes
- Simplify base64 encoding using Buffer
- Simplify chunk extraction using slice()
- Consolidate DEFAULT_PDF_URL constant

The server now works with any PDF URL, not just arXiv papers.
HTTP Range requests stream chunks on-demand when supported.

* feat(pdf-server): Include title and selection in model context

- Add pdfTitle to updateModelContext structuredContent
- Include selection position (text, start, end) when text is selected
- Add debounced selectionchange listener to update context on selection

* fix(pdf-server): Restore default URL in view_pdf schema

The UI needs the default value in the schema to show it properly.

* refactor(pdf-server): Further simplifications

- Remove hard-coded test paths from main()
- Remove unused resources: pdfs://metadata/{pdfId}, pdfs://content/{pdfId}
- Remove unused metadata fields: subject, creator, producer, creationDate, modDate
- Remove unused entry fields: relativePath, estimatedTextSize
- Remove filterEntriesByFolder and folder filter from list_pdfs
- Remove redundant output schema validation (trust typed returns)
- Simplify scanDirectory and createLocalEntry signatures

Total: 1836 → 1666 lines (-170 lines, -9%)

* refactor(pdf-server): Major simplification for didactic focus

Simplified the example to focus on key MCP Apps SDK patterns:
- Chunked data through size-limited tool calls
- Model context updates (page text + selection)
- Display modes (fullscreen vs inline)
- External links (openLink)

Changes:
- Remove local file support (HTTP URLs only)
- Restrict dynamic URLs to arxiv.org for security
- Simplify types: url instead of sourcePath/sourceType
- Simplify indexer: 168 → 44 lines
- Simplify loader: 318 → 171 lines
- Simplify server: 337 → 233 lines
- Fix selection text normalization
- Rewrite README with didactic focus

Total: 1836 → 1236 lines (-33%)

* feat(pdf-server): Add file:// URL support for local files

- Local paths are converted to file:// URLs on startup
- file:// URLs must be in the initial list (strict validation)
- Dynamic URLs still restricted to arxiv.org only
- Updated README with local file examples

* fix(pdf-server): Improve selection detection with logging

- Add logging to selectionchange handler to verify it fires
- Add fallback matching without spaces (TextLayer spans may lack spaces)
- Log selection detection success/failure for debugging

The issue: PDF.js TextLayer renders text as positioned spans without
space characters between them. When selecting across spans:
- pageText has spaces (items joined with ' ')
- sel.toString() may not have spaces
- indexOf fails to match

The fix tries exact match first, then falls back to spaceless matching.

* feat(pdf-server): Format model context as markdown with front matter

Model context now looks like:
```markdown
---
url: https://arxiv.org/pdf/...
page: 5/144
---

Page text with <pdf-selection>selected text</pdf-selection> inline.
```

This is cleaner for the model to parse and includes the source URL.

* refactor(pdf-server): Extract smart truncation helpers

Added two well-designed helpers:

formatPageContent(text, maxLength, selection?)
- Centers truncation window around selection if present
- Adds <truncated-content/> markers at elision points
- Wraps selection in <pdf-selection> tags
- Allocates 60% context before, 40% after for readability

findSelectionInText(pageText, selectedText)
- Tries exact match first
- Falls back to spaceless match for TextLayer quirks
- Returns { start, end } or undefined

Example output with selection:
```
<truncated-content/>
...context before... <pdf-selection>selected text</pdf-selection> ...context after...
<truncated-content/>
```

* fix(pdf-server): Truncate inside selection tags when selection too long

When selection is too large for the budget:
<truncated-content/><pdf-selection><truncated-content/>start...end<truncated-content/></pdf-selection><truncated-content/>

This keeps the selection structure intact while showing beginning and end.

* refactor(pdf-server): Remove unused read_pdf_text, use Attention paper as default

- Remove read_pdf_text tool (viewer extracts text client-side with pdfjs)
- Remove PdfTextChunk and ReadPdfTextInput types
- Remove loadPdfTextChunk from pdf-loader
- Change default PDF to 'Attention Is All You Need' (1706.03762)
- Update README with modest language

* refactor(pdf-server): Simplify to use URL as ID, rename view_pdf to display_pdf

Major simplifications:
- Use URL directly as identifier (no hashing)
- Remove displayName - show elided URL with full URL as tooltip
- Rename view_pdf to display_pdf with better description
- Update all references from pdfId to url
- Simplify storage key and model context

The tool description now explains it displays an interactive viewer in the chat.

* feat(pdf-server): Normalize arxiv URLs to PDF format

arxiv.org/abs/... -> arxiv.org/pdf/...

Applied both at startup and when loading dynamic URLs.

* docs(pdf-server): Add prompt engineering to display_pdf description

* fix(pdf-server): Sharp rendering on retina displays

Account for devicePixelRatio when rendering canvas:
- Scale canvas dimensions by dpr
- Scale context by dpr
- Keep CSS size at logical pixels

* fix(pdf-server): Normalize arxiv URLs in read_pdf_bytes too

* add to e2e spec

* add to e2e spec

* add to e2e spec

* add to e2e spec

* regen

* chore: regenerate package-lock.json and fix hono vulnerability

* docs: add pdf-server screenshot to READMEs

* regen

* ci: add missing examples to pkg-pr-new publish

* ci: add pdf-server to npm publish examples

* Update README.md

* pdf-server: improve tool response text for better model context

* revert unrelated screenshot changes

* pdf-server: dynamically add arxiv URLs in read_pdf_bytes

Fixes 'PDF not found' error when server restarts between display_pdf
(which adds the entry) and read_pdf_bytes (which previously only looked
up existing entries). Now read_pdf_bytes mirrors display_pdf's logic
and dynamically adds arxiv URLs to the index.
2026-01-14 14:25:24 +00:00
Jonathan Hefner
63b05b4fdd
Merge pull request #252 from jonathanhefner/update-examples-for-skill
Improve consistency and organization of examples for AI learning
2026-01-13 20:55:40 -06:00
Jonathan Hefner
4140565b77 Improve consistency and organization of examples for AI learning
- Add `.gitignore` files to all 19 examples (`node_modules/`, `dist/`)
- Replace custom `log` objects with standard `console` methods
- Add `ontoolcancelled` handler to all `basic-server-*` examples
- Add host styling integration (`applyDocumentTheme`, `applyHostFonts`,
  `applyHostStyleVariables`) to non-React `basic-server-*` examples
- Add section headers for better code organization in feature examples
- Update READMEs with Build System section explaining single-file
  bundling

These changes make examples more consistent and easier to adapt.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 20:48:02 -06:00
Jonathan Hefner
6166242136
Add Claude Code plugin with "Create MCP App" skill (#256)
Add a Claude Code plugin (`plugins/mcp-apps/`) for distribution via the
plugin marketplace. The plugin provides the "Create MCP App" skill that
guides the agent through building MCP Apps with interactive UIs.

The skill includes:
- Instructions to clone SDK repo for reference templates
- Framework selection guidance (React, Vanilla JS, Vue, Svelte, etc.)
- Tool visibility configuration (`_meta.ui.visibility`)
- Host styling integration patterns
- Testing workflow using `basic-host`
- Common mistakes to avoid

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 00:50:18 +00:00
Ido Salomon
969b905c10
disambiguate resource in csp sample (#257)
* disambiguate resource in csp sample

Clarified comment on CSP extraction from resource metadata.

* Update specification/draft/apps.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 00:49:40 +00:00
adam jones
aa6775485d
chore: update licensing to Apache 2.0 for new contributions (#251) 2026-01-14 00:42:13 +00:00
Jonathan Hefner
d48e35d88a
Improve JSDoc comments with cross-references and clarifications (#254)
Key improvements:
- Add `{@link}` cross-references throughout the codebase to make API
  documentation navigable and help AI models understand relationships
- Use `{@link module!Export}` syntax for cross-module references
- Add backticks for inline code formatting (`ResizeObserver`, `App`, etc)
- Fix `PostMessageTransport` examples to show required two-argument form
- Clarify `onteardown` callback return type requirement
- Document React hooks cleanup behavior and Strict Mode considerations
- Add tool visibility example for `registerAppTool`
- Add CSP example for `registerAppResource`
- Fix notification direction documentation in `spec.types.ts`

TypeDoc config migrated from `typedoc.json` to `typedoc.config.mjs` to:
- Support custom `@description` block tag
- Add `intentionallyNotExported` for internal types
- Include `src/server/index.ts` entry point

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 00:41:17 +00:00
Jonathan Hefner
978d8e6fff
Merge pull request #263 from jonathanhefner/oai-migration-doc-server-side-section
Add "Server-Side" section to OpenAI migration guide
2026-01-13 17:49:28 -06:00
dependabot[bot]
631090f41e
build(deps): bump hono in the npm_and_yarn group across 1 directory (#268)
Bumps the npm_and_yarn group with 1 update in the / directory: [hono](https://github.com/honojs/hono).


Updates `hono` from 4.11.3 to 4.11.4
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.11.3...v4.11.4)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.11.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 23:47:06 +00:00
Jonathan Hefner
b231396d2b Add "Server-Side" section to OpenAI migration guide
Add "Server-Side" migration documentation covering:
- Tool metadata mapping (`_meta["openai/..."]` → `_meta.ui.*`)
- Resource metadata (CSP, permissions, domain, border preferences)
- Resource MIME type (`text/html+skybridge` → `text/html;profile=mcp-app`)
- Before/after code examples using `registerAppTool()` and
  `registerAppResource()`
- Key differences summary including not-yet-implemented features

Reorganize existing client-side content under updated heading structure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:18:05 -06:00
Olivier Chafik
152b953ae6
docs: how to migrate from openai apps (#260)
* Create migrate_from_openai_apps.md

* Update AGENTS.md

* Update docs/migrate_from_openai_apps.md

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* address PR review comments

- Remove AGENTS.md reference to migration guide
- Add note that OpenAITransport is not yet available (PR #172)
- Remove unnecessary note about return type ordering

---------

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2026-01-13 15:23:26 +00:00
Olivier Chafik
574a768e09
feat: add example gallery with auto-generated screenshots (#253)
* feat: add script to generate 300x300 grid-cell.png screenshots for examples

- Add generate-grid-screenshots.spec.ts Playwright test to capture screenshots
- Add npm run generate:screenshots command (Docker-based for Santa compatibility)
- Add sharp dependency for image resizing with centered-fill mode
- Generate grid-cell.png for 14 example servers (only React for basic-server-* series)

The screenshots are 300x300 PNG images cropped with centered fill mode,
suitable for use in a grid layout showcasing the examples.

* docs: add example gallery grid to README

Add a 3-column visual grid showcasing 14 example MCP Apps with:
- Thumbnail screenshots (300x300 grid-cell.png)
- Clickable links to example directories
- Hover tooltips with brief descriptions

Organized by visual impact: 3D/WebGL examples first, then
visualizations, charts, monitoring, and starter templates.

* fix: use aspect-fit instead of crop for screenshots

Change from fit: 'cover' (crops) to fit: 'contain' (letterbox)
so the full app content is visible without cropping.

* feat: improve screenshot generation

- Constrain viewport to 500px width for consistent rendering
- Wait 5 seconds for full app load (tiles, animations, data)
- Screenshot just the inner iframe (no extra whitespace)
- Save both screenshot.png (full-size) and grid-cell.png (300x300 thumbnail)
- Use contain mode to preserve aspect ratio

* fix: use cover mode with top alignment for grid thumbnails

No letterboxing - crops to fill 300x300, aligned to top so headers
and important content are always visible.

* feat: improve screenshot gallery

- Remove integration-server from gallery (it's for E2E testing only)
- Add 30s extra wait for Map and Video servers to load fully
- Improve Three.js default scene: isometric rotation + better lighting
- Update README: Starter Templates row with links to all framework variants
  (React, Vue, Svelte, Preact, Solid, Vanilla JS)
- Regenerate all screenshots with updated settings

* fix: improve README gallery layout

- Fix uneven column widths by using minimal separator
- Move Starter Templates to separate 2-cell table with description
- Add screenshot.png to top of each example README (13 examples)
- Shorten tooltips for cleaner table

* revert to document.write again; cesiumjs doesn't like srcdoc, period.

* fix: map-server screenshot now renders properly

- Merged ochafik/post-fixes which reverts sandbox from srcdoc to document.write
  (CesiumJS doesn't work with srcdoc iframes)
- Fixed map-server name in screenshot test ('Map Server' not 'CesiumJS Map Server')
- Simplified screenshot generation script (removed complex debugging code)
- Increased map-server wait time to 45s for reliable tile loading
- Regenerated all 13 screenshots

* fix: remove redundant screenshots from READMEs

Remove the simple \![Screenshot](screenshot.png) line from READMEs that
already had nicer HTML table screenshots. Keep it only for examples
that didn't have existing screenshots.

* docs: move gallery to Examples section

- Move screenshot gallery from top of README to Examples section
- Remove redundant list of basic examples (now shown visually in gallery)
- Clean up duplicate run instructions

* fix: remove empty header row from starter templates table

* fix: remove empty header rows from both tables

* fix: add prettier-ignore to prevent table header expansion

* feat: use custom screenshot for video-resource-server

- Use better screenshot from PR #253 comment showing actual video playback
- Skip video-resource-server in screenshot generation script
- Add SKIP_SERVERS set for manually maintained screenshots

* fix: revert unrelated map-server name change

* fix: use correct server name 'CesiumJS Map Server' in screenshot test
2026-01-13 15:12:18 +00:00
Olivier Chafik
8fa2961ffc
basic-host: filter app-only tools, prioritize UI tools (#262)
* basic-host: filter non-ui tools

* basic-host: filter app-only tools, prioritize UI tools

- Filter out tools with visibility: ['app'] from host tool list
- Sort tools to show UI-enabled tools first, then alphabetically
- Add local isToolVisibleToModel helper in basic-host
2026-01-13 15:05:12 +00:00
Olivier Chafik
bd892b6ce4
revert to document.write again; cesiumjs doesn't like srcdoc, period. (#258)
* revert to document.write again; cesiumjs doesn't like srcdoc, period.

* Update server.ts
2026-01-13 02:32:07 +00:00
Olivier Chafik
231f23fd72
chore: release 0.4.0 (#249)
* chore: release 0.4.0

* chore: add new examples to npm publish workflow

Add map-server, shadertoy-server, and transcript-server to the
publish-examples matrix.
2026-01-12 22:40:24 +00:00
Olivier Chafik
ba550f1aeb
fix: follow-up to PR #234 (remove accidental file, revert from document.write to equiv. srcdoc) (#248)
* chore: remove tamper-detection.ts (not meant to be submitted)

Follow-up to PR #234. This file was accidentally included.

* revert to srcdoc

Follow-up to PR #234 per domfarolino's review feedback.

* regen

* regen
2026-01-12 22:19:31 +00:00
Olivier Chafik
9a9ecf27d4
feat(examples): add --watch to bun serve commands for auto-restart, and have npm start hit dev mode of all examples (#246)
* feat(examples): add --watch to bun serve commands for auto-restart on file changes

* Update package.json

* regen
2026-01-12 22:17:12 +00:00
Olivier Chafik
6bb63c3bae
examples: add transcript-server example with live speech transcription (#240)
* feat: add transcript-server example with live speech transcription

- New example: transcript-server with Web Speech API transcription
  - Start/stop recording with timer display
  - Live transcript with interim results
  - Send button to send transcript as ui/message to host
  - Copy button to copy full transcript
  - Sent messages marked with divider, greyed out
  - Experimental ui/update-model-context for live context updates
  - Transparent background, respects host theme

- Enable microphone and clipboard in basic-host sandbox iframes
  - Add allow="microphone; clipboard-write" to both outer and inner iframes
  - Required for Web Speech API, audio capture, and clipboard access

- Add alert popup in basic-host for ui/message from apps

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

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

* style: format transcript-server files

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

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

* test: add e2e test and golden for transcript-server

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

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

* refactor: use @types/dom-speech-recognition instead of hand-rolled types

Removes ~50 lines of manually defined Web Speech API types in favor of
the community-maintained type package.

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

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

* fix: add newlines between transcript chunks when copying to clipboard

* refactor: unify transcript formatting with timestamps

- Add formatEntry() helper that includes [timestamp] prefix
- Add formatEntries() helper that joins entries with newlines
- Use same format for clipboard copy, send message, and model context
- DRY up duplicate code in send button handler

* fix: log model context update failures instead of silently swallowing

* fix: log model context updates for debugging

* refactor: pass permissions through loadSandboxProxy like csp

- Add permissions parameter to loadSandboxProxy in implementation.ts
- Build iframe allow attribute from permissions instead of hardcoding
- Update sandbox.ts to not hardcode allow attribute (set via notification)
- Add microphone + clipboardWrite permissions to transcript-server resource
- Update README to reflect permission configuration via resource _meta.ui

* fix(transcript-server): auto-expand instead of scroll

- Remove min-height: 100vh and overflow-y: auto
- Let content grow naturally with autoResize (default)
- Remove unnecessary scrollTop calls

* test: remove transcript timer mask (no longer dynamic)

* refactor: pass permissions through loadSandboxProxy like csp

* docs: document updateModelContext for transitional speech

* test: update e2e screenshots

* refactor: use app.updateModelContext() instead of raw request

* package-lock.json

* src/generated/schema.json

* test: update e2e screenshots from CI

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 21:27:31 +00:00
Olivier Chafik
53142efd2d
refactor(map): use app.updateModelContext() instead of adhoc request (#245)
* feat(map): add keyboard shortcuts for fullscreen control

- Escape: exit fullscreen when in fullscreen mode
- Ctrl/Cmd+Enter: toggle fullscreen from any mode

* feat(map): persist view state to localStorage

- Save camera position/orientation on moveEnd (debounced 500ms)
- Restore persisted view when no explicit tool input is received
- Key by toolInfo.id (tool invocation ID) - localStorage is per-conversation
- Tool input (explicit bbox) takes precedence over persisted state

* refactor(map): use app.updateModelContext() instead of adhoc request
2026-01-12 20:47:07 +00:00
Anton Pidkuiko
9fcee5b5a6
Merge pull request #247 from modelcontextprotocol/fix/qr-server-sdk-version
fix(qr-server): Update SDK to 0.3.1 with app-with-deps bundle
2026-01-12 20:41:02 +00:00
Anton Pidkuiko MacBook
6b6c5ce63f
fix(qr-server): Update SDK to 0.3.1 with app-with-deps bundle
- Use app-with-deps export for CDN imports (includes all dependencies)
- Fixes widget crash on Claude Desktop/Nest
2026-01-12 20:09:10 +00:00
Jonathan Hefner
b6fc151094
Add {AGENTS,CLAUDE}.md for AI assistant guidance (#218)
Provides context about this repository including:

- Common commands for building, testing, and running examples
- SDK entry points and their purposes
- Communication flow between Guest UI, Host, and MCP Server
- Key source files and their responsibilities
- Protocol lifecycle from initialization to teardown
- Type generation workflow

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:54:24 +00:00
Olivier Chafik
741b1d3925
feat(basic-host): improve host UI with collapsible panels and SDK types (#244)
* refactor(basic-host): use SDK types for ModelContext and AppMessage

- Import McpUiUpdateModelContextRequest and McpUiMessageRequest from SDK
- Replace custom ModelContext with McpUiUpdateModelContextRequest['params']
- Replace custom AppMessage with McpUiMessageRequest['params']
- Pass full message params instead of extracting text
- Format messages by extracting text from content blocks in display

* feat(basic-host): handle non-text content blocks in display

- Add formatContentBlock helper for text, image, audio, resource types
- Show placeholders like <image: image/png> for non-text content
- Apply to both messages and model context display

* test: update shadertoy screenshot

* test: update screenshots from CI (linux x64)
2026-01-12 19:42:11 +00:00
Jonathan Hefner
e29523c113
Merge pull request #217 from jonathanhefner/examples-replace-resource_uri_meta_key
Replace deprecated `RESOURCE_URI_META_KEY` with `_meta.ui.resourceUri`
2026-01-12 13:07:44 -06:00
Jonathan Hefner
94bc708963 Replace deprecated RESOURCE_URI_META_KEY with _meta.ui.resourceUri
Update all example servers to use the new recommended metadata format
for associating UI resources with tools. The old `RESOURCE_URI_META_KEY`
constant is deprecated in favor of the nested `_meta.ui.resourceUri`
structure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 13:00:11 -06:00
Jonathan Hefner
7f1c9c0dde
Merge pull request #155 from jonathanhefner/update-quickstart-guide
Update quickstart guide
2026-01-12 12:58:53 -06:00
Jonathan Hefner
093faa9a0d
Merge pull request #209 from jonathanhefner/readme-how-it-works
Add "How It Works" section to README
2026-01-12 12:58:41 -06:00
Jonathan Hefner
dadbb6ac3d
Merge pull request #212 from jonathanhefner/video-resource-server-safeareainsets
Support `safeAreaInsets` + type-checking in video-resource-server
2026-01-12 12:58:28 -06:00
Jonathan Hefner
6d2d4b22b6
Merge pull request #227 from jonathanhefner/system-monitor-refresh-stats-tool
Add missing `refresh-stats` tool to `system-monitor-server`
2026-01-12 12:58:03 -06:00
Jonathan Hefner
6e4eb32d1e
Merge pull request #228 from jonathanhefner/restore-182
Restore educational comments in `basic-server-*` examples
2026-01-12 12:57:47 -06:00
Jonathan Hefner
ec3409aa91
Add shadertoy-server example for WebGL shader rendering (#226)
Introduces a new MCP App example that renders ShaderToy-compatible GLSL
fragment shaders in real-time using WebGL 2.0 and ShaderToyLite.js.

Features:
- Standard ShaderToy uniforms (`iTime`, `iResolution`, `iMouse`, etc.)
- Multi-pass rendering with buffers A-D for feedback effects
- Shader compilation error overlay

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 18:26:02 +00:00
Jonathan Hefner
8b8ecbea64 Restore educational comments in basic-server-* examples
PR #184 accidentally removed/reverted educational comments from
`basic-server-react` and `basic-server-vanillajs` that were established
in PR #182. This restores consistency with the other basic-server
examples (Vue, Svelte, Preact, Solid).

Changes:
- Add back the three-comment pattern explaining two-part registration
- Restore full tool description ("as an ISO 8601 string")
- Change vanillajs to return plain text instead of JSON (matching #182)
- Use local `resourceUri` variable instead of top-level constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 11:57:46 -06:00
Jonathan Hefner
996f028e7f Add missing refresh-stats tool to system-monitor-server
The `mcp-app.ts` UI was calling a `refresh-stats` tool that didn't exist
on the server, causing the example to fail. This was introduced in
PR #131 when the squash merge included the client-side change but lost
the server-side tool registration.

Fixes #200

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 11:56:09 -06:00
Ido Salomon
b273279849
feat: add ui/update-model-context (#125)
---------

Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
2026-01-12 15:21:45 +00:00
Ido Salomon
9acc52cb96
feat: enhance sandbox capability negotiation (#158)
* feat: enhance sandbox capability negotiation

* Update specification/draft/apps.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add all csp and permissions to host capabilities + update SEP

* feat: add clipboard-write permission support

Adds clipboardWrite to McpUiResourcePermissions for clipboard access.
Maps to Permission Policy 'clipboard-write' feature.

* rm files added by mistake

* refactor: change McpUiResourcePermissions properties to empty objects

* chore: remove scratch files and redundant cross-env dependency

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
2026-01-12 15:10:27 +00:00
Olivier Chafik
9f09932baf
feat: use structuredContent with outputSchema in example servers (#220)
* build app-with-deps

* make registerAppTool more interchangeable w/ server.registerTool

* feat: use structuredContent in all example servers

All servers now return structuredContent alongside JSON text content.
All mcp-apps now read structuredContent directly (no JSON.parse fallback).

Updated servers:
- basic-server-vanillajs
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- integration-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- video-resource-server
- wiki-explorer-server

* feat: add outputSchema to all example servers

Define output schemas for structuredContent validation:
- basic-server-vanillajs: { time: string }
- budget-allocator-server: BudgetDataResponseSchema
- cohort-heatmap-server: CohortDataSchema
- customer-segmentation-server: customers/segments schema
- integration-server: { time: string }
- scenario-modeler-server: templates/defaultInputs schema
- system-monitor-server: SystemStatsSchema
- threejs-server: { code, height }
- video-resource-server: { videoUri, description }
- wiki-explorer-server: { page, links, error }

* prettier:fix

* make registerAppTool more interchangeable w/ server.registerTool

* feat: use structuredContent in all example servers

All servers now return structuredContent alongside JSON text content.
All mcp-apps now read structuredContent directly (no JSON.parse fallback).

Updated servers:
- basic-server-vanillajs
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- integration-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- video-resource-server
- wiki-explorer-server

* feat: add outputSchema to all example servers

Define output schemas for structuredContent validation:
- basic-server-vanillajs: { time: string }
- budget-allocator-server: BudgetDataResponseSchema
- cohort-heatmap-server: CohortDataSchema
- customer-segmentation-server: customers/segments schema
- integration-server: { time: string }
- scenario-modeler-server: templates/defaultInputs schema
- system-monitor-server: SystemStatsSchema
- threejs-server: { code, height }
- video-resource-server: { videoUri, description }
- wiki-explorer-server: { page, links, error }

* prettier:fix

* revert: remove unrelated workflow, version, and arch changes

* chore: update package-lock.json

* refactor(basic-server-vanillajs): address PR review comments

- Simplify server.ts to return plain time string in text content (not JSON),
  matching the quickstart guide pattern
- Add defensive coding in mcp-app.ts with optional chaining and fallback
  to handle potential type mismatches gracefully

* refactor: restore rich text formatting for LLM-readable content

Restore the human-readable text formatting functions that were removed,
providing meaningful summaries in the text content while keeping
structuredContent for programmatic UI consumption:

- budget-allocator-server: formatBudgetSummary with config overview
- cohort-heatmap-server: formatCohortSummary with retention stats
- scenario-modeler-server: formatScenarioSummary with financial metrics
2026-01-12 14:25:16 +00:00
Olivier Chafik
366aa9b66e
examples: Add CesiumJS Map App example (#235)
* feat: Add CesiumJS map server example

A WebGL-based 3D globe example that demonstrates:
- CesiumJS integration with MCP Apps
- CSP configuration for external tile servers
- worker-src directive usage for tile decoding

Tools:
- geocode: Search places via OpenStreetMap Nominatim
- show-map: Display interactive 3D globe at coordinates

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

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

* comment out app.registerTool call for now

* prettier

* fix error handling in server-utils.ts

* refactor: rename cesium-map-server to map-server

- Rename examples/cesium-map-server to examples/map-server
- Update package name to @modelcontextprotocol/server-map
- Add README.md with documentation

* style: add min-height of 400px to map container

* feat(map-server): add fullscreen support, clean UI, sharp rendering

- Add fullscreen toggle button (only shown if host supports it)
- Use autoResize: false and manually send height of 400px
- Hide Cesium UI controls (home, scene mode picker)
- Remove location label (gets stale quickly)
- Use full device pixel ratio for sharp rendering on high-DPI displays
- Resize Cesium viewer when display mode changes

* feat(map-server): add reverse geocoding (logging only) and rounded corners

- Add camera move end listener for reverse geocoding
- Debounce Nominatim API calls (1.5s) to respect rate limits
- Log location name to console on camera move
- Add nominatim.openstreetmap.org to CSP connectDomains
- Add 8px rounded corners to map container

* fix(map-server): fix pixelated rendering on high-DPI displays

- Set canvas imageRendering to 'auto' (CesiumJS defaults to pixelated)
- Set resolutionScale to window.devicePixelRatio for native resolution

* tweaks

* update model context w/ current location

* fix(map-server): improve rendering quality and add visible extent logging

Rendering fixes:
- Remove incorrect resolutionScale = devicePixelRatio which was doubling
  the scaling factor on Retina displays (2x2=4x), causing blurriness
- Disable FXAA anti-aliasing which can cause blurriness on high-DPI displays
- Keep useBrowserRecommendedResolution: false to use native devicePixelRatio

Reverse geocoding improvements:
- Add getVisibleExtent() using camera.computeViewRectangle() to get the
  visible bounding box (west, south, east, north)
- Add getScaleDimensions() to calculate visible area in km
- Add calculateNominatimZoom() to map visible extent to appropriate
  Nominatim zoom level (3=country to 18=building)
- Log visible extent with dimensions on each camera move
- Pass zoom parameter to Nominatim for context-aware geocoding results
- Update model context with visible area dimensions

* fix(map-server): direct positioning + wait for tiles before showing

Changes:
- Remove zoom granularity logic from reverse geocoding (was not working)
- Keep extent logging in both console and model context
- Replace flyTo animation with instant setView positioning
- Add waitForTilesLoaded() to detect when tiles finish loading
- Keep loading spinner visible until:
  1. Tool input positions camera to destination
  2. Tiles at destination are fully loaded
- Add 5-second fallback timeout if no tool input received
- Add 10-second timeout for tile loading to prevent infinite wait

* feat(map-server): use viewbox search to find visible places

Replace center-point reverse geocoding with Nominatim viewbox search:
- Add searchPlacesInBox() that queries Nominatim with bounded viewbox
- Uses layer=address&featuretype=settlement to find cities/towns
- Returns up to 5 visible places in the current view
- Update model context with 'Visible places: City1, City2, ...'
- Still includes center coordinates and extent dimensions

This provides better context about what's actually visible on the map
rather than just the address at the center point.

* fix(map-server): use CARTO @2x retina tiles for sharp rendering

Root cause: Standard OSM tiles are 256x256 pixels. On Retina displays
(devicePixelRatio=2), these get scaled up 2x, making them look pixelated.

Solution: Switch to CARTO Voyager tiles which provide @2x retina support:
- Detect high-DPI displays using window.devicePixelRatio
- Use 512x512 tiles with @2x suffix on Retina displays
- Use 256x256 tiles on standard displays
- Set tileWidth/tileHeight to match actual tile size
- Use CARTO subdomains (a,b,c,d) for load balancing
- Update CSP to allow CARTO tile domains

CARTO Voyager provides clean OSM-based tiles with free @2x retina support,
no API key required.

* fix(map-server): use OSM tiles and fix reverse geocoding

- Switch back from CARTO to standard OSM tiles (tile.openstreetmap.org)
  for better detail and label rendering
- Fix geocoding: use reverse geocode API instead of search API
  (search API requires a q= parameter, returns 400 without it)
- Update CSP domains for OSM tile server

* fix(map-server): use *.openstreetmap.org wildcard in CSP

Simplify CSP to use wildcard for both tiles and geocoding:
- connectDomains: *.openstreetmap.org covers tile.* and nominatim.*
- resourceDomains: *.openstreetmap.org for tile images

* feat(map-server): multi-point sampling for visible places

- Sample multiple points in visible extent to discover places
- Adaptive sampling based on extent size:
  - < 30km: center only (city zoom)
  - 30-100km: center + 2 corners
  - > 100km: center + 4 quadrants
- Proper rate limiting for Nominatim (1.1s between requests)
- Shows 'Visible places: City1, City2, ...' in model context
- Fix: move geocode tool registration inside createServer()

* format

* fix(map-server): fix Express listen callback + add e2e tests

- Fix incorrect Express listen() callback signature (err param doesn't exist)
- Add map-server to e2e test suite with SLOW_SERVERS timeout (5s for tiles)
- Improve comment about experimental ui/update-model-context API

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:13:56 +00:00
Olivier Chafik
081dad83c4
fix: add error handling for server startup failures (#236)
Previously, if app.listen() failed (e.g., port already in use), the
server would fail silently. This adds proper error handling to log
the error and exit with a non-zero status code.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 16:52:55 +00:00
Olivier Chafik
8b09f4278c
fix: proper CSP handling in basic-host sandboxing (HTTP headers, worker-src, document.write) (#234)
* fix: Move CSP to HTTP headers + add worker-src, frameDomains, baseUriDomains, permissions

Security improvements:
- CSP is now set via HTTP headers in serve.ts instead of meta tags
  (meta tag CSP can be tampered with by same-origin content)
- CSP passed as query param to sandbox.html for header-based enforcement

New CSP/permissions features (borrowed from PR #158):
- frameDomains: control frame-src directive for nested iframes
- baseUriDomains: control base-uri directive
- permissions: camera, microphone, geolocation via iframe allow attribute

WebGL fix:
- Use document.write() instead of srcdoc for inner iframe content
  (srcdoc creates opaque origin that breaks WebGL canvas updates)
- Add worker-src directive with blob: support (critical for WebGL apps
  like CesiumJS/Three.js that use workers for tile decoding, terrain
  processing, image processing)

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

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

* refactor: Remove permissions support (defer to PR #158)

Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.

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

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

* cleanup McpUiResourceCsp type usage / duplicate defs

* fix: guard against CSP injection in domain parameters

Validate CSP domain entries to reject characters that could:
- Break out of CSP directives (semicolons, newlines)
- Inject CSP keywords like 'unsafe-eval' (quotes)
- Inject multiple sources in one entry (spaces)

This prevents injection attacks where malicious domains could
override the security policy.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 14:32:48 +00:00
Ido Salomon
df49e88b2d
fix: align CSP sample with resourceDomains spec (#233) 2026-01-11 12:54:18 +00:00
Olivier Chafik
4c51eb0301
chore: release 0.3.1 (#224)
* chore: release 0.3.1
2026-01-09 18:22:27 +00:00
Olivier Chafik
f8e31eb97f
fix: ensure lib is built before examples:build (#185)
* fix: ensure lib is built before examples:build

Add preexamples:build hook to automatically run `npm run build` before
`examples:build`. This ensures examples always type-check against the
latest library code, catching breaking changes during development.

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

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

* feat: add version consistency check for examples

Add scripts/check-versions.mjs to verify that example package.json files
reference the correct version of @modelcontextprotocol/ext-apps.

- Allows "../.." (local dev) or "^{root-version}" as valid dependencies
- Runs in CI before npm install to catch mismatches early
- Available locally via `npm run check:versions`

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

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

* chore: regenerate package-lock.json for cross-platform compatibility

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 01:36:42 +00:00
Olivier Chafik
e078250adf
build app-with-deps (#221) 2026-01-09 01:26:31 +00:00
Olivier Chafik
2730f796f7
make registerAppTool more interchangeable w/ server.registerTool (#219) 2026-01-09 01:22:39 +00:00
Olivier Chafik
ecc593bcec
chore: release 0.3.0 (#204)
* chore: bump version to 0.3.0

Breaking changes:
- `viewport` replaced with `containerDimensions` in host context

New examples:
- video-resource-server
- sheet-music-server

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

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

* fix(video-resource-server): add missing server-utils and fix import path

The video-resource-server was importing from a non-existent
../shared/server-utils.js path. Added the server-utils.ts file
and fixed the import to use ./src/server-utils.js like other examples.

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

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

* chore: update ext-apps version refs to 0.3.0

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 00:55:45 +00:00
Olivier Chafik
7eb98ad121
fix: E2E test fixes and host resilience improvements (#206)
1. video-resource-server: Add missing server-utils.ts and fix import path
   - Was importing from non-existent ../shared/server-utils.js
   - Now correctly imports from ./src/server-utils.js

2. basic-host: Be resilient to individual server connection failures
   - Use Promise.allSettled instead of Promise.all
   - Failed connections are logged as warnings but don't crash the UI
   - Only throws if ALL servers fail to connect

3. threejs-server: Add id to canvas for reliable screenshot masking
   - Added id="threejs-canvas" to the canvas element
   - Updated e2e test masks to use #threejs-canvas and .threejs-container
   - Fixes flaky Three.js screenshot comparisons in CI

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-09 00:30:49 +00:00
Jerome
c25d464d18
feat: publish example servers to npm (#184)
* feat: publish example servers to npm

Make example MCP App servers publishable to npm under the
@modelcontextprotocol scope:

- server-basic-react
- server-basic-vanillajs
- server-budget-allocator
- server-cohort-heatmap
- server-customer-segmentation
- server-scenario-modeler
- server-system-monitor
- server-threejs
- server-wiki-explorer

Changes:
- Remove `private: true` from example package.json files
- Add proper npm metadata (description, repository, license, files)
- Update ext-apps dependency from relative to ^0.2.2
- Copy server-utils.ts into each package for standalone use
- Add publish-examples job to npm-publish.yml workflow

The examples will be published automatically when a GitHub release
is created, after the main SDK package is published.

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

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

* feat: publish examples with pkg-pr-new for PR testing

* feat: export createServer from example servers

* style: format integration-server/server.ts

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

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

* fix: update e2e snapshots for basic-server examples

Update golden screenshots for basic-react and basic-vanillajs after
the tool output was simplified from JSON to plain text in #182.

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

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

* merge

* refactor: update sheet-music-server and video-resource-server to new publishing pattern

- Move server-utils.ts from src/ to root level
- Add npm publishing metadata (scoped name, repository, license, files)
- Change ext-apps dependency from local to versioned ^0.2.2
- Update server.ts to handle stdio mode inline

* update package-lock

* fix: update package-lock.json for renamed server packages

* feat: add NASA public domain video to video-resource-server

Added 'nasa-earth' option - NASA ISS Earth observations (6.4MB).
Source: archive.org/details/NASA-Ultra-High-Definition (public domain)

* fix: add default videoId for video-resource-server

* fix: use existing bunny-1mb as default video, remove nasa-earth

* refactor: update new framework examples to publishing pattern

Add publishing metadata for:
- @modelcontextprotocol/server-basic-preact
- @modelcontextprotocol/server-basic-solid
- @modelcontextprotocol/server-basic-svelte
- @modelcontextprotocol/server-basic-vue

Changes:
- Move server-utils.ts from src/ to root
- Add npm publishing metadata
- Update ext-apps dep to versioned ^0.2.2
- Add stdio mode support in main()

* fix: add missing @rollup/rollup-win32-arm64-msvc to optionalDependencies

This fixes the Windows ARM64 CI build failure caused by npm's optional
dependency bug. The package.json was missing this platform-specific
rollup binding.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
2026-01-08 20:08:50 +00:00
Jonathan Hefner
2a3c2ce034 Add "How It Works" section to README
Provides a concise 4-step overview of the MCP Apps architecture:
tool call → `ui://` resource → sandboxed iframe → bidirectional
communication via notifications.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 13:58:34 -06:00
Jonathan Hefner
e205e26e4e Update quickstart guide
- Reorganize prerequisites to emphasize MCP concepts first
- Add tip linking to official MCP quickstart for newcomers
- Update install command to use published npm package
- Remove zod dependency and `structuredContent` usage
- Use `RESOURCE_MIME_TYPE` constant for consistency
- Add clearer comments explaining tool/resource registration
- Simplify UI to extract time from text content

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 12:55:01 -06:00
Jonathan Hefner
8acec0eaad
Add basic-server-* examples for Vue, Svelte, Preact, and Solid (#141)
* Add basic-server-vue example

Demonstrates MCP App SDK usage with Vue 3, providing framework
parity alongside the existing React and vanilla JS examples.

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

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

* Add basic-server-svelte example

Demonstrates MCP App SDK usage with Svelte, providing parity
with the existing basic-server-react and basic-server-vue examples.

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

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

* Add basic-server-preact example

Demonstrates MCP App SDK usage with Preact, providing another
lightweight framework option alongside the existing React, Vue,
and Svelte examples.

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

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

* Add basic-server-solid example

Demonstrates MCP App SDK usage with Solid.js, providing another
reactive framework option alongside the existing React, Vue,
Svelte, and Preact examples.

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

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

* Add framework GitHub links to README

Link each framework name to its official GitHub repo in basic examples
list.

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 18:44:36 +00:00
Jonathan Hefner
79670b4dcd
Add safeAreaInsets support to all example apps (#202)
Apply host-provided safe area insets as padding on main containers
across all examples. This ensures content doesn't render under
system UI elements like phone notches.

React apps use `useState` + `onhostcontextchanged` handler with
initial fetch via `getHostContext()` in `useEffect`. Vanilla JS
apps apply padding in the handler and invoke it after `connect()`.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 18:03:13 +00:00
Olivier Chafik
97990b6b28
Make ui.resourceUri optional (for tools that just need visibility) (#210)
* Make ui.resourceUri optional (for tools that just need visibility)

* chore: regenerate schemas for optional resourceUri
2026-01-08 17:05:00 +00:00
Ido Salomon
773943cad1
feat: export method names as consts (#192)
* feat: export method names as consts

* rename

* fix

* format
2026-01-08 18:50:59 +02:00
Anton Pidkuiko
c475dc3c17
Merge pull request #216 from modelcontextprotocol/antonp/toolinfo-id-optional
fix: make toolInfo.id optional per spec
2026-01-08 13:24:34 +00:00
Anton Pidkuiko MacBook
4012136885
fix: make toolInfo.id optional per spec
The spec (apps.mdx) defines toolInfo.id as optional (id?: RequestId),
but the TypeScript types and generated schemas had it as required.

This allows hosts to pass just the tool definition without needing
a request ID, which is useful when the tool name is the primary
information needed by the widget.
2026-01-08 11:48:34 +00:00
Jonathan Hefner
f27b7decd0 Support safeAreaInsets + type-checking in video-resource-server
Apply host-provided safe area insets as padding on the main container,
and update `tsconfig.json` to use `moduleResolution: "bundler"` for
correct resolution of `McpUiHostContext` type with Vite.

Also adds `tsc --noEmit` to the build script to catch type errors, and
fixes pre-existing type safety issue with `blob` property access by
using proper type narrowing.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-07 17:14:20 -06:00
Olivier Chafik
2411c71aa5
fix!: require eventSource in PostMessageTransport + add security tests (#208) 2026-01-07 20:57:50 +00:00
Olivier Chafik
c7ef5f009b
fix: Add missing origin parameter to PostMessageTransport default constructor, verify origins in example sandbox proxy (#207)
* fix(app): add source validation to default PostMessageTransport

App.connect() now passes window.parent as both eventTarget and eventSource,
enabling source validation by default. This ensures apps only accept
messages from their parent window, preventing potential cross-app
message spoofing attacks.

Previously, the default transport only specified the target but not the
source for validation, meaning apps would accept messages from ANY window.

* fix(sandbox): add origin validation for host messages

The sandbox proxy now validates that messages from the parent window
come from the expected host origin (derived from document.referrer).
This prevents malicious pages from sending spoofed messages to the sandbox.

Changes:
- Extract EXPECTED_HOST_ORIGIN from document.referrer
- Validate event.origin against expected origin for parent messages
- Use specific origin instead of '*' when sending to parent
- Reject and log messages from unexpected origins

This addresses the TODO comment that was previously in the code.

* validate messages from app come from same origin as sandbox proxy

* fix(basic-host): be resilient to individual server connection failures

Use Promise.allSettled instead of Promise.all when connecting to
servers, so that a single server failure doesn't crash the entire UI.
Failed connections are logged as warnings but the UI continues with
the servers that connected successfully.

Also fixes video-resource-server missing server-utils.ts.

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-07 14:20:49 +00:00
Anton Pidkuiko
7837a8ccc2
fix(examples): add missing server-utils.ts to video-resource-server (#205)
The server was importing from ../shared/server-utils.js which doesn't exist.
Added the standard server-utils.ts file (matching other examples) and updated
the import path to ./src/server-utils.js.
2026-01-07 14:08:15 +00:00
Anton Pidkuiko
299a4423fb
Merge pull request #175 from modelcontextprotocol/antonp/video-resource-example
feat(examples): Add video-resource-server example
2026-01-06 16:44:35 +00:00
Anton Pidkuiko MacBook
ea7d2ee4ad
fix: address PR feedback - remove broken 50mb video, use registerResource 2026-01-06 16:43:47 +00:00
Anton Pidkuiko MacBook
ba45157d55
feat(examples): Add video-resource-server example
Demonstrates serving binary content (video) via MCP resources using the
base64 blob pattern:

1. Server fetches video from CDN
2. Returns as base64 blob via `videos://{id}` resource template
3. Widget fetches resource and plays in `<video>` element

Includes multiple video sizes for testing (1MB to 150MB).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 16:43:46 +00:00
Anton Pidkuiko
d925af5257
Merge pull request #196 from jonathanhefner/sheet-music-example
Add `sheet-music-server` example
2026-01-06 11:26:24 +00:00
Jonathan Hefner
012fb3260c Add sheet-music-server example
Demo MCP App showcasing ABC notation rendering and audio playback with:
- Server-side ABC notation validation
- Built-in audio player with play/pause and loop controls
- Sheet music visualization using abcjs library

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 16:06:09 -06:00
Anton Pidkuiko
38126f7c7f
Merge pull request #153 from modelcontextprotocol/martinalong/mcp-apps/max-height-only
[MCP Apps] Update `viewport` type
2026-01-05 16:08:31 +00:00
martinalong
dc226be962
Remove viewport 2025-12-31 21:36:29 -08:00
martinalong
e90c7945bc
Rebased schemas 2025-12-26 14:16:26 -08:00
martinalong
82e64d56bb
Separate viewport and containerDimensions 2025-12-26 14:15:54 -08:00
martinalong
c22e86233b
Allow undefined values 2025-12-26 14:15:54 -08:00
martinalong
06c0e08a17
Clarify documentation 2025-12-26 14:15:53 -08:00
martinalong
4e01a6c200
Make height/width optional as well 2025-12-26 14:15:53 -08:00
martinalong
87f6f3f27e
Update documentation and revert type change 2025-12-26 14:15:53 -08:00
martinalong
93b9da9e7a
Updating viewport type and documentation 2025-12-26 14:15:53 -08:00
Rizel Scarlett
e514e6c745
docs: fix tsc command to use tsconfig.json (#188)
Remove 'server.ts' argument from 'npx tsc --noEmit' command.

When a file is specified directly, TypeScript ignores tsconfig.json,
causing skipLibCheck and other options to not apply. This results in
errors from node_modules (especially zod v4 locale files).

The correct command lets tsc use the project's tsconfig.json settings.
2025-12-20 17:39:34 +02:00
Jonathan Hefner
9e9f5f0210
Clean up examples (#182)
* Centralize example server transport logic in shared utils

Move stdio/HTTP transport handling into `server-utils.ts` so example
servers only need to call `startServer(createServer)`. Switch HTTP
transport to stateless mode and remove legacy SSE endpoints.

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

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

* Make example servers self-contained with local server-utils

Copy `server-utils.ts` into each example's `src/` directory so examples
can be used standalone without the shared directory. Updates imports
from `"../shared/server-utils.js"` to `"./src/server-utils.js"`.

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

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

* Add `integration-server` example for E2E testing

Add dedicated `integration-server` example (duplicated from
`basic-server-react`) to preserve E2E testing capabilities while
allowing `basic-server-react` to be simplified as a minimal example.

Changes:
- Add `examples/integration-server` exercising SDK communication APIs
- Update E2E tests to use `integration-server` instead of
  `basic-server-react` and `basic-server-vanillajs`
- Use server name labels instead of indices for more robust test
  selection
- Remove `basic-react.png` and `basic-vanillajs.png` golden snapshots

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

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

* Simplify `basic-server-*` examples

- Clarify comments
- Return plain text from `get-time` tool instead of JSON
- Remove simulated cleanup delay from `onteardown`

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 11:44:27 +00:00
Jonathan Hefner
3222c87c56
Add npm start alias for examples:start (#183)
Allows running `npm start` as a shorthand for `npm run examples:start`,
providing a conventional entry point to launch the example servers.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 11:36:02 +00:00
Nicholas Lim
9963d0f17d
fix: move prettier to dev dependency (#179) 2025-12-18 17:10:49 +00:00
Jonathan Hefner
4ff22d0097
Fix build errors in examples (#180)
* Update `package.json`

* Add TypeScript type checking to example build scripts

Add `tsc --noEmit &&` before `vite build` in all example `package.json`
files to catch type errors during builds. Previously, type errors
in `server.ts` files were not detected because Vite/Bun transpile
without type checking.

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

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

* Make `_meta` optional in `McpUiAppResourceConfig`

The `_meta` and `ui` properties were required, causing TypeScript errors
when calling `registerAppResource()` without providing metadata.

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

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

* Fix Zod type compatibility in `registerAppTool()`

Use `ZodRawShapeCompat | AnySchema` from SDK's `zod-compat` module
instead of raw `ZodRawShape` to resolve type errors when examples
pass `ZodObject` schemas to `registerAppTool()`.

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

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

* Remove unused imports in example apps

- `basic-server-react`: Remove unused `McpUiResourceTeardownResult`
- `customer-segmentation-server`: Remove unused `PostMessageTransport`

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

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

* Fix CI git install test for fork PRs

Use `github.event.pull_request.head.repo.full_name` to correctly
reference the fork repository when testing git installs from PRs.

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 11:17:23 +00:00
Olivier Chafik
82ab3c894c
chore: bump to 0.2.2 + make Zod schemas version-agnostic (v3.25+/v4) (#178)
* feat: make generated Zod schemas version-agnostic (v3.25+/v4)

- Change generated schema imports from `zod/v4` to standard `zod`
- Replace v4-only `z.looseObject()` with `z.object().passthrough()`
- Works with both Zod v3.25+ and v4, matching MCP TS SDK approach
- Bump version to 0.2.2

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

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

* chore: regenerate package-lock.json in Docker

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 20:55:59 +00:00
Olivier Chafik
bbea41f27b
chore: release 0.2.1 - widen @oven/bun-* version range (#176)
* fix: widen @oven/bun-* version range to improve install compatibility

Lower the minimum version of @oven/bun-* optional dependencies from
^1.3.4 to ^1.2.21. This is the lowest common version available across
all platform-specific packages, improving compatibility for contributors
who may not have access to the latest package versions.

The caret range (^) still allows npm to install newer versions when
available.

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

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

* chore: bump version to 0.2.1

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 15:56:17 +00:00
Anton Pidkuiko
3e95d6421c
Merge pull request #174 from modelcontextprotocol/martinalong/mcp-apps/fix-style-variables
[MCP Apps] Add forgotten style variable
2025-12-17 17:49:42 +07:00
Anton Pidkuiko
242ec23856
Merge pull request #159 from modelcontextprotocol/martinalong/mcp-apps/custom-fonts
[MCP Apps] Add way to pass custom fonts
2025-12-17 17:45:54 +07:00
martinalong
d797ed11f8
Use more of the style variables in customer-segmentation-server 2025-12-16 22:39:40 -08:00
martinalong
9888c6a20b
Add forgotten style variable 2025-12-16 22:20:45 -08:00
Olivier Chafik
c3e9d4d694
fix(examples): use server.registerTool for non-UI tool + fix missing imports (#173)
* fix(threejs-server): use server.registerTool for non-UI tool

The learn_threejs tool doesn't have a UI component - it only returns
documentation text. Using registerAppTool without _meta causes a
runtime error when the function tries to access config._meta.ui.

Fixes crash: "Cannot read property 'ui' of undefined"

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

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

* fix(examples): add missing registerAppTool and registerAppResource imports

All example servers were using registerAppTool and registerAppResource
but not importing them from @modelcontextprotocol/ext-apps/server,
causing "ReferenceError: registerAppTool is not defined" at runtime.

Affected servers:
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- wiki-explorer-server

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 00:17:45 +00:00
Olivier Chafik
894f7936f5
chore: bump version to 0.2.0 (#170) 2025-12-16 22:23:50 +00:00
Olivier Chafik
d9006bd69a
feat: make MCP SDK a peer dependency (#168)
* feat: make MCP SDK a peer dependency

- Move @modelcontextprotocol/sdk from dependencies to peerDependencies (^1.20.0)
- Add to devDependencies for local development (^1.24.3)
- Externalize SDK from all build entry points (app, app-bridge, react, server)

Benefits:
- Consumers control their SDK version
- Deduplication when consumer already has SDK
- Follows pattern of react/zod peer deps

* fix: require SDK ^1.24.0 for Zod 4 compatibility

Earlier SDK versions have Zod type incompatibilities that cause build errors.

* chore: update examples to SDK ^1.24.0 to match peer dep
2025-12-16 22:09:59 +00:00
Olivier Chafik
2dd223b611
refactor: rename request methods to remove send prefix (#161)
* refactor: rename request methods to remove send prefix

Following the MCP TS SDK convention where request methods don't have
the "send" prefix (reserved for notifications):

- App.sendMessage() → App.message()
- App.sendOpenLink() → App.openLink()
- AppBridge.sendResourceTeardown() → AppBridge.resourceTeardown()

The old method names are kept as deprecated aliases for backwards
compatibility.

Updated all examples to use the new method names.

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

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

* test: add tests for deprecated method aliases

- Update existing tests to use new method names (message, openLink)
- Add tests verifying deprecated aliases are same reference as new methods
- Add tests verifying deprecated aliases still work functionally

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

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

* refactor: rename message→sendMessage, resourceTeardown→teardownResource

- App.message() → App.sendMessage() (with deprecated message alias)
- AppBridge.resourceTeardown() → AppBridge.teardownResource() (with deprecated aliases)

This better follows the pattern where methods that send data have 'send' or action verb prefix.

* fix: update examples to use sendMessage instead of message

* fix: update basic-host to use teardownResource

* fix: update threejs-server to use sendMessage

* refactor: remove deprecated app.message alias

The app.message alias was removed since sendMessage is the correct name.
sendResourceTeardown already exists in AppBridge.

* refactor: remove resourceTeardown alias, keep only sendResourceTeardown

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 22:07:06 +00:00
Anton Pidkuiko
37aab03de0
docs: add tool visibility and restructure _meta.ui (#131)
* docs: add tool visibility and restructure _meta.ui

- Restructure tool metadata: `_meta["ui/resourceUri"]` → `_meta.ui.resourceUri`
- Add `visibility` array field: ["model"], ["apps"], or ["model", "apps"]
- Default ["model"] preserves standard MCP behavior
- ["apps"] enables widget-only tools hidden from agent
- Add McpUiToolMeta interface for type safety
- Add Design Decision #4 explaining approach vs OpenAI's two-field model

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

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

* address PR review comments

- Change default visibility to ["model", "apps"]
- Add deprecation notice for flat ui/resourceUri format
- Add McpUiToolMeta and McpUiToolVisibility to spec.types.ts
- Improve tools/list and tools/call behavior wording

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

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

* chore: regenerate schemas for McpUiToolMeta types

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

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

* feat: tool visibility and nested _meta.ui format

- Rename visibility "apps" → "app" in McpUiToolVisibility type
- Add _meta.ui.resourceUri nested format (deprecate flat format)
- Add getToolUiResourceUri() utility with backward compatibility
- Add visibility demo to system-monitor-server:
  - get-system-stats: visibility ["model"] with resourceUri
  - refresh-stats: visibility ["app"] (app-only polling)
- Update all example servers to use new _meta.ui format
- Add 11 unit tests for getToolUiResourceUri()

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

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

* chore: regenerate schemas with updated dependencies

* feat: export McpUiToolMeta type and add type annotations

- Export McpUiToolMeta and McpUiToolVisibility types from types.ts
- Export corresponding Zod schemas (McpUiToolMetaSchema, McpUiToolVisibilitySchema)
- Add `as McpUiToolMeta` type annotations to all example servers
- Update docs/quickstart.md with proper typing

Ensures type safety for `_meta.ui` tool metadata across the codebase.

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

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

* feat: add server helpers and optional connect() transport

Add `src/server/` with convenience functions for registering MCP App tools
and resources:

- `registerAppTool(server, name, config, handler)`
- `registerAppResource(server, name, uri, config, callback)`

The `transport` parameter in `App.connect()` is now optional, defaulting to
`PostMessageTransport(window.parent)`.

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

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

* Update src/server/index.ts

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* feat: update McpUiAppToolConfig to support both _meta.ui and flat formats

* refactor: simplify server helper imports

* docs: add deprecation notice and improve resourceUri documentation

* feat: add backward compat normalization in registerAppTool

- If _meta.ui.resourceUri is set, also set legacy flat key
- If legacy flat key is set, also set _meta.ui.resourceUri
- Preserves existing visibility when merging
- Does not overwrite if both formats already set

* refactor: avoid mutating config arg in registerAppTool

* style: format with prettier

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2025-12-16 21:47:40 +00:00
Olivier Chafik
bea0ae27f5
feat: add server helpers, make connect() to default to parent post transport (#165)
* feat: add server helpers and optional connect() transport

Add `src/server/` with convenience functions for registering MCP App tools
and resources:

- `registerAppTool(server, name, config, handler)`
- `registerAppResource(server, name, uri, config, callback)`

The `transport` parameter in `App.connect()` is now optional, defaulting to
`PostMessageTransport(window.parent)`.

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

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

* Update src/server/index.ts

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* fix: refine server helper types

- Make RESOURCE_URI_META_KEY required in McpUiAppToolConfig (tools need a UI resource)
- Make _meta optional in McpUiAppResourceConfig (not all resources need custom metadata)

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

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

* refactor: simplify RESOURCE_URI_META_KEY re-export

* refactor: consolidate export statements

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2025-12-16 21:25:26 +00:00
Olivier Chafik
50cf6a4679
Fix sandbox-ready notification name in spec to match implementation (#160)
The spec used `ui/notifications/sandbox-ready` but the implementation
uses `ui/notifications/sandbox-proxy-ready`. Updated the spec to match.

Also fixed a typo: `ui/sandbox-resource-ready` → `ui/notifications/sandbox-resource-ready`

Fixes #156

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 21:12:16 +00:00
Olivier Chafik
31e05d31fa
fix: make react a peer dependency to support multiple versions (#163) (#164)
Move react and react-dom from dependencies to peerDependencies with
support for React 17, 18, and 19. This allows consumers to use their
preferred React version instead of forcing v19.

- Added peerDependencies for react/react-dom (^17 || ^18 || ^19)
- Marked react dependencies as optional in peerDependenciesMeta
- Moved react/react-dom to devDependencies for local development
- React is already externalized in the build (build.bun.ts)

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:33:05 +00:00
Olivier Chafik
974862237b
fix: Windows compatibility - bun as optional dep, cross-env for examples (#145)
* fix: Windows compatibility for npm scripts and cross-platform CI

Fixes #142 - Windows PowerShell install bug

Changes:
- Replace single quotes with escaped double quotes in generate:schemas
  (cmd.exe passes single quotes literally, breaking prettier glob matching)
- Simplify prettier scripts: remove redundant --ignore-path flags
  (prettier auto-loads .gitignore and .prettierignore by default)
- Remove $(pwd) from prettier scripts (bash-only syntax)
- Add cross-platform CI matrix: Linux x64, Windows x64, macOS ARM64, macOS x64
- Use shell: bash for grep commands in CI (not available in Windows cmd.exe)

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

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

* fix: remove bun npm package from all workspaces

The bun npm package tries to install platform-specific binaries which
fails on Windows CI. Since bun is installed via oven-sh/setup-bun action
in CI (and developers install it globally), we don't need the npm package.

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

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

* fix: skip examples:build on Windows (uses Unix-only INPUT=... syntax)

The examples use `INPUT=mcp-app.html vite build` which is bash syntax
for setting environment variables. This doesn't work on Windows cmd.exe.
Since examples aren't published to npm, we can skip building them on Windows.

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

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

* fix: force LF line endings for cross-platform prettier consistency

Git on Windows auto-converts line endings to CRLF, causing prettier to
report formatting issues. Force LF for all text files.

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

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

* ci: add test for git-based npm install (cross-platform)

This verifies that `npm install git+https://...` works on all platforms,
which triggers the prepare script and requires bun to be available.

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

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

* ci: add Linux ARM64 and Windows ARM64 to CI matrix

- Added ubuntu-24.04-arm (Linux ARM64)
- Added windows-11-arm (Windows ARM64)

Note: WSL is not available on GitHub-hosted runners (requires nested
virtualization which isn't supported).

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

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

* ci: disable Windows ARM64 (Bun lacks stable ARM64 binaries)

The oven-sh/setup-bun action fails with 404 when trying to download
Bun for Windows ARM64. Re-enable when Bun adds proper support.

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

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

* ci: add Windows WSL build test

Uses Vampire/setup-wsl action to install Ubuntu-24.04 in WSL and run
the full build/test cycle. This tests the Linux-in-Windows scenario
that was reported in issue #142.

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

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

* ci: fix WSL build - install unzip for Bun installer

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

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

* ci: add setup-bun to publish.yml workflow

The pkg-pr-new publish workflow was missing bun setup, causing
"bun: not found" errors during npm ci (which triggers prepare script).

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

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

* ci: enable Windows ARM64 with x64-baseline Bun (via emulation)

Windows ARM64 can run x64 binaries via emulation. Use bun-download-url
to force x64-baseline build since native ARM64 Bun isn't available yet.

Reference: https://github.com/oven-sh/bun/issues/9824

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

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

* ci: remove macOS x64 (macos-13 runner retired)

The macos-13 based runner images are now retired.

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

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

* docs: add Bun installation instructions per platform

Documents that Bun is required for development, with platform-specific
install commands in collapsible sections.

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

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

* docs: note Bun requirement in README examples section

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

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

* feat: add bun as optional dependency (no global install required)

- Add @oven/bun-* platform packages as optionalDependencies
- Create postinstall script to set up bun binary from optional deps
- Handle Windows ARM64 by downloading x64-baseline for emulation
- Remove setup-bun from CI workflows (npm install handles it now)
- Remove manual Bun install instructions from docs

This alternative approach eliminates the need for global Bun installation
or setup-bun action in CI. npm install automatically installs the
platform-appropriate bun binary.

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

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

* chore: add debug logging to setup-bun script

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

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

* chore: force stdout flush in setup-bun for CI debugging

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

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

* chore: add more debugging to setup-bun

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

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

* chore: remove accidental test artifacts

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

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

* chore: run setup-bun in prepare script (before build)

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

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

* feat: use cross-env for Windows-compatible example builds

- Add cross-env to devDependencies
- Update all example package.json scripts to use cross-env for INPUT and NODE_ENV
- Enable examples:build on Windows in CI (no longer Unix-only)
- Add examples:build to WSL CI job

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

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

* chore: remove unused imports in setup-bun.mjs

Address github-code-quality bot comments.

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

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

* fix: use run-bun.mjs wrapper for prepare hook compatibility

During npm install's prepare hook, node_modules/.bin is not in PATH,
so calling 'bun' directly fails. The new run-bun.mjs wrapper finds
bun at node_modules/.bin/bun and runs it with the correct path.

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

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

* ci: restore setup-bun action for build jobs

- Add setup-bun action to build and test-git-install jobs
- Disable Windows ARM64 (Bun doesn't support this platform)
- Add bun installation to WSL build

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

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

* fix: remove bun devDep, add libc detection for Linux ARM64

- Remove 'bun' from devDependencies (its postinstall fails on Windows ARM64)
- Add libc detection (glibc vs musl) to setup-bun.mjs
- Only try compatible binaries based on detected libc
- Re-enable Windows ARM64 in CI (now uses x64 emulation fallback)
- Remove setup-bun action from CI (no longer needed)

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

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

* ci: simplify WSL build - let setup-bun.mjs handle bun installation

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 14:07:38 +00:00
Jonathan Hefner
ce95bb2559
Mark src/generated as linguist-generated (#154)
Files in `src/generated/` are produced by `scripts/generate-schemas.ts`
and should be collapsed by default in PR diffs.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 13:42:10 +00:00
Olivier Chafik
7a5bf9c7f1
Add pre-commit check for private registry URLs in package-lock.json (#133)
Same check as in CI to catch issues before push.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 13:41:50 +00:00
martinalong
6397ec1511
Add way to pass fonts 2025-12-15 22:54:53 -08:00
martinalong
c516cf0894
Merge pull request #152 from modelcontextprotocol/martinalong/mcp-apps/display-mode-updates
[MCP Apps] Have apps request display mode instead
2025-12-15 16:12:45 -08:00
martinalong
46d38f44a7
Update spec wording 2025-12-15 16:05:06 -08:00
martinalong
e316259f58
Merge pull request #127 from modelcontextprotocol/martinalong/mcp-apps/add-styles
[MCP Apps] Add `styles` prop to host context
2025-12-15 13:38:52 -08:00
martinalong
8f591b69f2
Update naming of functions to reflect styles.variables nesting 2025-12-15 11:42:22 -08:00
martinalong
277a41b14c
Update e2e screenshots for basic server examples
Updated screenshots to reflect UI changes from styles addition.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 11:42:21 -08:00
martinalong
2f8d1ecce0
Update generated schemas with stricter datetime pattern
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 11:42:19 -08:00
martinalong
5db8076854
Fix markdown typo 2025-12-15 11:42:19 -08:00
martinalong
aa79cb7c5e
Finalized variable set 2025-12-15 11:42:19 -08:00
martinalong
445a5836cf
Make it styles.variables instead of styles 2025-12-15 11:42:18 -08:00
martinalong
2fa243ae8c
Updates example to use theme colors and adds border radius 2025-12-15 11:42:18 -08:00
martinalong
35781865af
Add color-scheme meta tag to all example apps
Prevents iframe from automatically applying a default background color.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 11:42:18 -08:00
martinalong
db2872643c
Make properties optional 2025-12-15 11:42:18 -08:00
martinalong
aaedd858bb
Suggested edits 2025-12-15 11:42:17 -08:00
Anton Pidkuiko MacBook
bcc05439c7
docs: add styles field to MCP Apps specification
- Add styles field to HostContext interface with reference to Theming section
- Add Theming section documenting 36 standardized CSS variables
- Document Host/App behavior for theming including light-dark() usage
- Add Design Decision #4 explaining CSS variables approach
- Include JSON example showing light-dark() pattern

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 11:42:17 -08:00
martinalong
f276ec6df9
Regenerate schemas after npm install
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 11:42:17 -08:00
martinalong
a8b3bf0ccd
Add utility to set + read theme 2025-12-15 11:42:17 -08:00
martinalong
aa8968808a
Add utility for using styles 2025-12-15 11:42:17 -08:00
martinalong
9e97bf3dfc
Add styles prop to host context 2025-12-15 11:42:16 -08:00
Olivier Chafik
6f14cf301d
fix: ensure E2E screenshot tests work consistently across CI and local (#150)
- Remove platform-specific browser channel (was using Chrome on macOS,
  Chromium in CI) - now uses default Chromium everywhere
- Add Docker-based test scripts for CI-identical local testing:
  - `npm run test:e2e:docker` - run tests in Docker
  - `npm run test:e2e:docker:update` - regenerate golden screenshots
- Regenerate golden screenshots using Docker/Linux environment

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-15 15:12:36 +00:00
Anton Pidkuiko
8550545ccb
fix: add mimeType to resource declarations in examples (#149)
According to spec, mimeType should be present in both the resource
declaration and the resource content. This adds the missing mimeType
to the registerResource() declaration (third parameter) in all example
servers and the quickstart documentation.

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

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: ochafik <olivier.chafik@gmail.com>
2025-12-15 12:09:47 +00:00
martinalong
83860d7c31
Have apps request display mode instead 2025-12-14 19:00:56 -08:00
Ido Salomon
372c24181e
docs: typo and formatting issues in spec header (#148) 2025-12-13 23:09:54 +02:00
Olivier Chafik
54344552b7
api: optional Client in AppBridge (allow custom forwarding), better protocol types (AppRequest, AppNotification, AppResult) (#146)
* Add setter-based handlers for MCP request/notification forwarding

- Add oncalltool, onlistresources, onlistresourcetemplates, onreadresource, onlistprompts setters for Guest UI → Host requests
- Add sendToolListChanged, sendResourceListChanged, sendPromptListChanged methods for Host → Guest UI notifications
- Refactor connect() to use new setters with inlined callbacks
- Update documentation to clarify optional client parameter behavior
- Remove unused forwardRequest/forwardNotification helper methods
- Add comprehensive tests for AppBridge without MCP client (manual handlers)

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

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

* Potential fix for pull request finding 'Unused variable, import, function or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* fix: move prettier to dependencies for git-based installs

* fix: add explicit prettierrc to prevent inheriting parent configs during git-based installs

* Apply suggestions from code review

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* refactor(tests): factor out test values and add missing assertions

Address PR review feedback:
- Factor out test value objects (toolCall, resultContent, etc.) for clearer tests
- Add missing assertions on receivedRequests[0] for list handlers
- Improve test readability by defining expected values at the top of each test

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

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

* docs: improve TypeDoc formatting and use `as const` for notification methods

- Use proper TypeDoc syntax with `@param callback.params` and `@param callback.extra`
- Add {@link ResultType} references for return types
- Replace `as Notification` with `as const` on method literals where possible
  (sendResourceListChanged, sendPromptListChanged)
- Simplify sendHostContextChange cast syntax

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

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

* refactor(types): use explicit type unions for AppBridge protocol types

- Add AppBridgeRequest, AppBridgeNotification, AppBridgeResult type unions
  that explicitly list all valid types for the protocol
- Replace generic SDK Request/Notification/Result with our specific types
- Remove all type casts by using `as const` on method literals
- Add index signature to McpUiHostContext for forward compatibility

This improves type safety and documents exactly what the AppBridge supports.

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

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

* refactor: move AppRequest/AppNotification/AppResult to types.ts

Move protocol type unions from app-bridge.ts to types.ts where they
belong with other type definitions.

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

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

* chore: regenerate schemas with clean npm install

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2025-12-13 21:07:13 +00:00
Olivier Chafik
c9e37204de
tests: add Playwright E2E tests with screenshot golden testing (+ fix examples session handling) (#115)
* Add Playwright E2E tests with screenshot golden testing

- Add E2E tests for all 8 MCP server examples
- Screenshot golden images for visual regression testing
- CI workflow for running E2E tests
- npm scripts: test:e2e, test:e2e:update, test:e2e:ui

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

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

* Add default demo code for Three.js example

When no code is provided, show a rotating green cube demo.

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

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

* Add E2E testing documentation to CONTRIBUTING.md

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

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

* Update Three.js golden screenshot with 3D cube

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

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

* Add explicit permissions to CI workflow

Set minimal read-only permissions for security best practices.

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

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

* Fix test.setTimeout() to be inside describe blocks

Playwright requires setTimeout to be called within a test or describe block.

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

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

* Simplify E2E tests - remove excessive timeouts

- Remove explicit setTimeout calls (default 30s is sufficient)
- Replace waitForTimeout(5000/6000) with proper waitForAppLoad()
- Wait for inner iframe visibility instead of fixed delays
- Keep only 500ms stabilization for screenshot animations

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

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

* Auto-generate missing snapshots in CI

Use updateSnapshots: 'missing' in CI to handle cross-platform
screenshot differences (macOS vs Linux).

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

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

* Use platform-agnostic golden screenshots

- Remove -chromium-darwin suffix from snapshot filenames
- Configure snapshotPathTemplate for cross-platform compatibility
- Increase tolerance to 5% for rendering differences between platforms

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

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

* Refactor tests to use forEach instead of for-of loop

May help with Playwright test discovery issues in CI.

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

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

* Use npm ci and list reporter in CI

- Use npm ci for exact package versions
- Use --reporter=list instead of html to avoid potential re-evaluation issues
- Only upload test-results on failure

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

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

* Exclude e2e tests from bun test

Run bun test only on src/ directory to avoid running Playwright tests
with Bun's test runner.

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

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

* Fix prettier formatting

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

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

* Add interaction tests for basic server apps

Test that clicking buttons in the MCP App triggers the corresponding
host callbacks:
- Send Message → host logs "Message from MCP App"
- Send Log → host logs "Log message from MCP App"
- Open Link → host logs "Open link request"

Tests both React and Vanilla JS implementations.

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

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

* Mask dynamic content in E2E screenshot tests

Add Playwright mask option to handle servers with dynamic/random content:
- basic-react/vanillajs: mask server time display
- system-monitor: mask CPU chart, memory stats, uptime
- cohort-heatmap: mask heatmap grid (random data)
- customer-segmentation: mask scatter chart (random data)

This addresses PR feedback about handling examples with non-deterministic
output. Masking replaces the previous 10% tolerance with proper exclusion
of dynamic elements, allowing tighter 1% tolerance for the rest of the UI.

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

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

* Add wiki-explorer to E2E tests with default URL param

- Add default URL "https://en.wikipedia.org/wiki/Model_Context_Protocol" to
  wiki-explorer-server's get-first-degree-links tool inputSchema
- Update basic-host to automatically populate input field with tool defaults
  extracted from inputSchema.properties
- Add wiki-explorer-server to E2E test suite with dynamic masking for the
  force-directed graph

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

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

* Use .default() for threejs tool schema instead of .optional()

This exposes the default code and height values in the JSON Schema,
allowing basic-host to auto-populate the input field with defaults.

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

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

* Enable parallel E2E tests with timeouts and canvas masking

- Enable fullyParallel with 4 workers locally (2 in CI)
- Add 30s timeout per test
- Mask threejs canvas for stable screenshots
- Update snapshots to reflect default values in input fields

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

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

* Add pre-commit check for private registry URLs in package-lock.json

Same check as in CI to catch issues before push.

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

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

* Increase screenshot diff tolerance to 6% for cross-platform rendering

Font rendering differs between macOS and Linux, causing ~5% pixel differences.

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

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

* Revert pre-commit artifactory check (moved to separate PR #133)

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

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

* Format threejs server.ts

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

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

* Update CONTRIBUTING.md to reflect platform-agnostic screenshots

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

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

* fix(e2e): use factory pattern for MCP servers to support parallel connections

McpServer only supports one transport at a time. When multiple browser
contexts connected in parallel, calling server.connect(transport) for
each session overwrote the previous transport's callbacks, causing
connection corruption and test timeouts.

Changes:
- server-utils.ts: accept factory function instead of single instance
- All 9 example servers: wrap server creation in createServer() function
- playwright.config.ts: re-enable parallel execution (4 workers)
- servers.spec.ts: add toBeEnabled wait for server connection
- Update screenshot baselines for basic-vanillajs and cohort-heatmap

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-13 20:57:27 +00:00
Olivier Chafik
cfd1d1dc43
fix: Make MCP App UIs responsive for narrow viewports (#135)
Changed from min-width: 425px to:
- width: 100%
- max-width: 425px
- box-sizing: border-box

This allows the apps to shrink on narrow viewports (like mobile
or when embedded in native WebViews) instead of causing horizontal
scrolling.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-13 12:12:06 +00:00
Jonathan Hefner
8448ffe4fc
Merge pull request #49 from alpic-ai/update-zod-major
Add BYO zod version support
2025-12-12 13:53:11 -06:00
Olivier Chafik
3a30e94501
feat(examples): add SSE transport support and shared server utility (#136)
* feat(examples): add SSE transport support to all example servers

Add backwards-compatible SSE transport support alongside the current
Streamable HTTP transport. Each server now exposes:

- /mcp (GET, POST, DELETE) - Streamable HTTP transport (current spec)
- /sse (GET) - Legacy SSE transport stream endpoint
- /messages (POST) - Legacy SSE transport message endpoint

This enables older clients using the deprecated HTTP+SSE protocol
(version 2024-11-05) to connect to the example servers.

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

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

* refactor(examples): migrate 6 servers to use shared server utility

Refactored the following example servers to use the shared startServer utility from examples/shared/server-utils.ts:
- threejs-server
- system-monitor-server
- cohort-heatmap-server
- budget-allocator-server
- customer-segmentation-server
- scenario-modeler-server

Changes for each server:
- Removed direct imports: SSEServerTransport, StdioServerTransport, StreamableHTTPServerTransport, cors, express
- Removed PORT constant (now handled by shared utility)
- Added import for startServer from ../shared/server-utils.js
- Replaced entire async main() function with single startServer() call
- Preserved all business logic (tool registration, resource registration, helper functions)

This reduces code duplication by ~700 lines and ensures consistent transport handling across all example servers.

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

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

* refactor(examples): migrate remaining servers to shared utility

Complete the migration of example servers to use the shared
server-utils.ts module for transport setup:

- basic-server-react
- basic-server-vanillajs
- wiki-explorer-server
- Add shared/server-utils.ts

This centralizes all transport handling (stdio, Streamable HTTP, SSE)
in one place, reducing code duplication across ~500 lines.

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

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

* fix(examples): improve server-utils with stateful sessions and security

Address critical issues in the shared server utility:

1. Stateful sessions: Use sessionIdGenerator + onsessioninitialized
   to persist StreamableHTTPServerTransport across requests
2. Unified session store: Single Map for both transport types with
   proper type discrimination
3. Error handling: Try/catch on all endpoints with JSON-RPC errors
4. DNS rebinding protection: Use SDK's createMcpExpressApp helper

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

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

* fix(examples): allow connections from any host for development

Use host: "0.0.0.0" in createMcpExpressApp to disable DNS rebinding
protection, allowing connections from Android emulators and other
devices on the network. Also re-add CORS middleware.

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

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

* refactor(examples): use getPort() helper for transport selection

- Add getPort() helper that returns undefined for --stdio, else port
- Caller now explicitly passes port to startServer()
- port: undefined → stdio mode, port: number → HTTP mode

This makes the transport selection explicit at the call site.

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

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

* fix(examples): startServer returns promise that resolves/rejects on listen

Use httpServer.on('listening') and httpServer.on('error') events
to properly resolve or reject the returned promise.

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

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

* fix(examples): add error handling to startServer calls

Exit with code 1 if server fails to start (e.g., port in use).

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

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

* refactor(examples): update server files to use main() pattern with stdio support

Update all example server files to use the new main() pattern that supports both HTTP and stdio transports:
- Add StdioServerTransport import
- Remove getPort import (replaced with direct env var parsing)
- Replace direct startServer call with async main() function
- Add --stdio flag detection to choose between transports
- Use PORT env var with default 3001 for HTTP mode

Updated servers:
- basic-server-vanillajs
- wiki-explorer-server
- threejs-server
- system-monitor-server
- cohort-heatmap-server
- budget-allocator-server
- customer-segmentation-server
- scenario-modeler-server

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

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

* fix(examples): use correct default ports matching run-all.ts

Each server now defaults to its assigned port from run-all.ts:
- basic-server-react: 3101
- basic-server-vanillajs: 3102
- budget-allocator-server: 3103
- cohort-heatmap-server: 3104
- customer-segmentation-server: 3105
- scenario-modeler-server: 3106
- system-monitor-server: 3107
- threejs-server: 3108
- wiki-explorer-server: 3109

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

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

* fix(examples): expose mcp-session-id header for CORS requests

Without exposedHeaders in the CORS config, browsers block JavaScript
from reading the mcp-session-id response header. This caused the SDK's
StreamableHTTPClientTransport to never capture the session ID, breaking
all subsequent requests with "Bad request: not initialized" errors.

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

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

* pretty:fix

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 13:10:10 +00:00
Frédéric Barthelet
e99517d75c Implement BYO zod v3 or v4
Following recommendations from https://zod.dev/library-authors
2025-12-12 11:29:02 +01:00
Olivier Chafik
0408a6e9c7
release: 0.1.1 (prettier fix to unlock release) (#138)
* fix: prettify generated files on the fly

- Add prettier --write to generate:schemas script to format generated
  files immediately after generation
- Remove redundant prettier:fix from CI schema verification step
  (now that generation includes formatting)

This fixes the release CI failure where generated files weren't
formatted. Previously, PR CI masked this by running prettier:fix
before the final prettier check, while release CI ran the check
directly on freshly-generated (unformatted) files.

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

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

* format

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 00:44:04 +01:00
Olivier Chafik
eaea3f9e07
feat: store and expose hostContext in App class (#139)
* feat: store and expose hostContext in App class

Fixes #129 - The App class now stores the initial hostContext received
during initialization and exposes it via getHostContext().

Changes:
- Add _hostContext private property to App class
- Store result.hostContext in connect() method
- Add getHostContext() getter method (parallel to getHostCapabilities())
- Update onhostcontextchanged setter to merge partial updates into
  stored context
- Add default notification handler to update context even when
  user hasn't set onhostcontextchanged

This enables apps to:
- Access toolInfo immediately after connection (only in initial context)
- Render with correct theme/locale without waiting for first notification
- Synchronously access viewport, timezone, and other initial state

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

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

* fix: use sendHostContextChange for partial update accumulation test

Address PR review feedback: The test was using setHostContext with full
context spreads which didn't clearly demonstrate partial update
accumulation. Now uses sendHostContextChange directly to send truly
partial updates (only theme, then only viewport), making it clear
the App correctly merges multiple partial notifications.

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 00:39:58 +01:00
Olivier Chafik
736da14e38
release: 0.1.0 (#137) 2025-12-11 23:21:20 +01:00
Olivier Chafik
70b393a7e6
feat: implement ui/notifications/tool-cancelled + add missing App.onteardown (#120)
* feat: implement ui/notifications/tool-cancelled throughout SDK

Wire the tool-cancelled notification that hosts MUST send when tool
execution is cancelled (user action, timeout, classifier intervention, etc.):

- Add McpUiToolCancelledNotification type in spec.types.ts
- Generate Zod schema via ts-to-zod
- Add AppBridge.sendToolCancelled() method for hosts
- Add App.ontoolcancelled setter for guest UIs
- Export type and schema from types.ts
- Add tests for notification with/without reason

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

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

* feat: add App.onteardown handler for graceful shutdown

Wire the guest UI side of ui/resource-teardown request:

- Add onteardown setter in App class for guest UIs to handle teardown
- Allow ui/resource-teardown in assertRequestHandlerCapability
- Import McpUiResourceTeardownRequest/Result types
- Add tests for sync and async cleanup handlers

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

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

* feat(example): wire tool-cancelled and teardown in basic-host

Update basic-host example to properly use the new notifications:

- Send sendToolCancelled when tool call promise rejects
- Send sendResourceTeardown when React component unmounts
- Store appBridge ref for cleanup access

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

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

* fix(example): implement graceful teardown per spec recommendation

Address review feedback: the host now waits for guest's teardown
response before unmounting, following the spec: "Host SHOULD wait
for a response before tearing down the resource (to prevent data loss)."

Changes:
- Add close button to tool call panels
- Two-phase unmount: isDestroying state dims panel and disables events
- sendResourceTeardown() completes before component removal
- Non-app tool results close immediately

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

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

* feat(examples): add onteardown handlers to demo apps

Demonstrate graceful shutdown with simulated 500ms cleanup work.

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-11 20:31:17 +01:00
Ido Salomon
a31e48a11e
fix: host may request user consent (#126) 2025-12-11 20:07:58 +01:00
Olivier Chafik
8075bffec8
refactor(examples): use concurrently API for proper process cleanup (#128)
Replace custom spawn-based orchestration with concurrently's API.
This fixes zombie processes on Ctrl+C - concurrently uses tree-kill
internally to properly terminate entire process trees.

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-11 14:04:39 +01:00
Jonathan Hefner
366c5d5a6b
Add wiki-explorer-server example (#122)
Interactive Wikipedia link graph visualization using force-graph with:
- Force-directed graph layout for exploring page connections
- Node expansion to reveal first-degree Wikipedia links
- Visual state tracking (blue=default, green=expanded, red=error)
- Direct browser access to Wikipedia pages

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-11 13:58:05 +01:00
Jonathan Hefner
402bf48145
Merge pull request #121 from jonathanhefner/deploy-docs-no-force-push
Preserve existing files on `gh-pages` during docs deploy
2025-12-10 07:11:52 -06:00
Jonathan Hefner
555a4b4a58 Preserve existing files on gh-pages during docs deploy
Replace `force_orphan` with `keep_files` in `peaceiris/actions-gh-pages`
to prevent wiping manually-added files (like screenshots) when deploying
generated documentation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 20:33:29 -06:00
Olivier Chafik
24722394a5
chore: update @modelcontextprotocol/sdk to 1.24.3 (#117)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-10 03:22:26 +01:00
Ido Salomon
ad19d2fdb1
fix: ui/tool-cancelled -> ui/notifications/tool-cancelled (#118) 2025-12-10 03:13:59 +01:00
Olivier Chafik
f06340b6d9
build: auto-generate Zod schemas from TypeScript types using ts-to-zod (#116)
* feat: add ts-to-zod schema generation from spec.types.ts

Add automated Zod schema generation using ts-to-zod:
- New npm script `generate:schemas` to regenerate schemas from spec.types.ts
- Post-processing script for Zod v4 compatibility and MCP SDK type imports
- src/spec.types.ts: Pure TypeScript interface definitions (source of truth)
- src/schemas.generated.ts: Auto-generated Zod schemas
- src/schemas.ts: Re-exports with PascalCase naming for backwards compatibility

The generated schemas provide a simpler alternative to the hand-written schemas
in types.ts, useful for lighter validation or reference implementations.

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

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

* refactor: types.ts now re-exports from spec.types.ts and schemas.generated.ts

- types.ts imports and re-exports types from spec.types.ts (source of truth)
- types.ts imports and re-exports schemas from schemas.generated.ts
- Added compile-time VerifySchemaMatches checks to flag mismatches between
  interfaces and generated schemas
- Removed redundant schemas.ts (types.ts now handles all re-exports)
- Applied prettier formatting to schemas.generated.ts

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

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

* refactor: use ts-to-zod as library with integration tests

Replace CLI-based schema generation with library API:

- scripts/generate-schemas.ts: New script using ts-to-zod library API
  - Detailed documentation of why each post-processing step is needed
  - Generates integration tests via getIntegrationTestFile()
  - Better error reporting and control

- src/schemas.generated.test.ts: Auto-generated integration tests
  - Compile-time verification that schemas match TypeScript types
  - Uses expectType<> pattern for bidirectional type checking

- Removed scripts/postprocess-schemas.ts (merged into generate-schemas.ts)

- Fixed McpUiResourceTeardownResult to include index signature for MCP SDK
  Protocol compatibility (detected by the new integration tests)

Post-processing rationale (documented in generate-schemas.ts):
1. zod → zod/v4: Explicit v4 subpath for version clarity
2. z.any() → MCP SDK schemas: External types need real validation
3. z.record().and() → z.looseObject(): Index signatures need Zod v4 idiom

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

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

* feat: regenerate schemas during build + CI check

- build script now runs generate:schemas before bun build
- CI workflow verifies generated schemas are up-to-date
  (fails if spec.types.ts changed but schemas weren't regenerated)

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

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

* refactor: generate PascalCase schemas, move verification to tests

- Add getSchemaName option to generate PascalCase schema names directly
  (McpUiOpenLinkRequestSchema instead of mcpUiOpenLinkRequestSchema)
- Remove redundant compile-time verification from types.ts
  (already handled by generated test file's bidirectional expectType checks)
- Simplify types.ts from 235 to 57 lines (just re-exports)

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

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

* fix: add missing csp field, simplify schema generation

- Add missing csp field to McpUiSandboxResourceReadyNotification (regression from merge)
- Simplify generate-schemas.ts - remove complex .describe() conversion
- Document that ts-to-zod supports @description JSDoc tags for .describe()
- Improve z.looseObject() replacement with brace-counting for nested objects

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

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

* feat: add @description JSDoc tags for .describe() on schemas

Added @description tags to all interfaces and fields in spec.types.ts.
ts-to-zod now generates .describe() calls on all schema fields, making
descriptions available at runtime for introspection and JSON Schema generation.

Example:
  /** @description URL to open in the host's browser */
  url: string;

Generates:
  url: z.string().describe("URL to open in the host's browser")

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

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

* feat: generate JSON Schema from Zod schemas

Added JSON Schema generation to the schema generation pipeline:
- Uses Zod v4's built-in toJSONSchema() to convert schemas
- Generates src/schema.json with all message types in $defs
- Includes descriptions from @description JSDoc tags
- Added export "./schema.json" to package.json
- Updated CI to verify schema.json is up-to-date

Use cases:
- Language-agnostic validation (Python, Go, Rust)
- Documentation generation (Redoc, Swagger UI)
- Code generation (quicktype)
- IDE autocomplete for JSON/YAML

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

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

* refactor: rename schema.json to schema.generated.json

Renamed to match other generated files naming convention.
Export path unchanged: ./schema.json -> ./dist/src/schema.generated.json

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

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

* refactor: rename schemas.generated.* to schema.generated.* (singular)

Consistent singular naming for all generated files:
- schema.generated.ts
- schema.generated.test.ts
- schema.generated.json

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

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

* refactor: rename schemas.generated.* to schema.generated.* (singular)

Consistent singular naming for all generated files:
- schema.generated.ts
- schema.generated.test.ts
- schema.generated.json

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

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

* refactor: move generated files to src/generated/ with simpler names

Moved:
- src/schema.generated.ts → src/generated/schema.ts
- src/schema.generated.test.ts → src/generated/schema.test.ts
- src/schema.generated.json → src/generated/schema.json

Benefits:
- Cleaner organization (generated files isolated)
- Simpler names without .generated suffix
- Easier to gitignore if desired later

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

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

* fix: add missing type exports for backwards compatibility

Added missing exports that existed in main:
- McpUiTheme, McpUiThemeSchema
- McpUiDisplayMode, McpUiDisplayModeSchema
- McpUiResourceCsp, McpUiResourceCspSchema
- McpUiResourceMeta, McpUiResourceMetaSchema

Also fixed theme/displayMode to match original values:
- theme: 'light' | 'dark' (removed 'system')
- displayMode: 'inline' | 'fullscreen' | 'pip' (removed 'carousel')

Verified: all 47 exports from main are preserved with compatible types.

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

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

* fix: stage generated files after prettier in pre-commit hook

The pre-commit hook runs build (which generates schemas) then prettier
(which formats them). The formatted generated files weren't being staged,
causing CI to fail with 'Generated schemas are out of date'.

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

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

* update generated files

* fix: run prettier after regenerating schemas in CI verification

CI was comparing unformatted generated output against formatted committed
files, causing false 'out of date' failures.

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-10 01:05:49 +01:00
Andrew Harvard
4953cd320e
Add media-src to CSP for video and audio support (#109)
This adds the media-src directive to the Content Security Policy to enable
MCP Apps to play video and audio content.

Changes:
- Add media-src to the restrictive default CSP in the spec
- Add media-src to the CSP construction from metadata in the spec
- Update resourceDomains documentation in spec to include media
- Update resourceDomains JSDoc in src/types.ts to include media-src

Fixes modelcontextprotocol/ext-apps#106
2025-12-10 01:10:58 +02:00
Olivier Chafik
e63f0284db
add npm version badge (#113)
* add npm version badge

* Update README.md
2025-12-09 22:44:00 +01:00
Olivier Chafik
03cf620f77
release: 0.0.7 (#114) 2025-12-09 20:44:16 +01:00
Anton Pidkuiko
6a8f5031be
Fix mimeType, CSP settings and QR code example (#111)
- Fix RESOURCE_MIME_TYPE to match spec: "text/html;profile=mcp-app"
- Add CSP field to McpUiSandboxResourceReadyNotification type and schema
- Update basic-host to extract and pass CSP metadata from resources
- Update sandbox.ts to dynamically inject CSP meta tag into inner iframe
- Remove static CSP from sandbox.html (now dynamic based on resource metadata)
- Update QR server to use custom read_resource handler for _meta injection
- Update requirements.txt to require mcp[cli]>=1.23.3

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-09 20:26:34 +01:00
Olivier Chafik
9d21d8bc08
release: 0.0.6 (#112)
* release: fix NPM_SECRET reference

* release: 0.0.5

* Add repository field to package.json

Required for npm provenance verification to link the package
to the GitHub repository.

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

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

* release: 0.0.6

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-09 20:17:22 +01:00
Olivier Chafik
6b9bedaa5b
release: 0.0.5 (fix release config again) (#110)
* release: fix NPM_SECRET reference

* release: 0.0.5
2025-12-09 20:07:26 +01:00
Olivier Chafik
c8e8b4f10a
Update README.md (#107) 2025-12-09 19:20:37 +01:00
Anton Pidkuiko
546f507e49
Add QR code Python MCP server example (#105)
* Add QR code Python MCP server example

A minimal Python MCP server that generates customizable QR codes with an
interactive widget UI. Features:

- Generate QR codes from any text or URL
- Customizable colors, size, border, and error correction
- Interactive widget using MCP Apps SDK protocol
- Supports both HTTP (for web clients) and stdio (for Claude Desktop)

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

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

* Potential fix for code scanning alert no. 10: Client-side cross-site scripting

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Add semicolon

* Bind to 0.0.0.0 for Docker compatibility

* Use SDK from unpkg + add CSP resourceDomains for external scripts

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-12-09 19:20:08 +01:00
Olivier Chafik
3ac7c7935b
Add RESOURCE_MIME_TYPE constant (#108)
* Add RESOURCE_MIME_TYPE constant

* Update app-bridge.ts

* update syntax
2025-12-09 19:10:56 +01:00
Jonathan Hefner
f712008be4
Deploy docs to GitHub Pages on release (#104)
Automatically publish documentation when a new release is created,
ensuring docs stay in sync with published package versions.

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

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 16:18:19 +01:00
Jonathan Hefner
038b702497
Add checkpoints and callouts to quickstart guide (#93)
- Add top-level note linking to complete example project
- Convert "Full files:" references to [!NOTE] callouts
- Add verification checkpoint after server creation (tsc --noEmit)
- Add verification checkpoint after UI build (ls dist/)

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-09 16:16:28 +01:00
Jonathan Hefner
4ca20f21c4
Smoothen the process of getting started (#103)
* Restructure threejs-server README to match marketing examples

- Update title format to "Example: Three.js App"
- Constrain screenshot width using table layout
- Add Features section with key capabilities
- Reorganize Running section with Tool Input subsection
- Add Architecture section describing server and app components
- Remove implementation-focused sections (WidgetProps, Creating a New Widget)

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

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

* Add bun as devDependency to example packages

npm install now automatically installs bun, eliminating the need for
separate installation instructions. Removed manual bun install step
from customer-segmentation-server README.

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

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

* Add start:http and start:stdio npm scripts to examples

For the 6 examples supporting --stdio transport, add explicit scripts:
- serve:http / serve:stdio for running the server
- start:http / start:stdio for build + serve
- start now aliases to start:http for backward compatibility

Update READMEs to document both transport options.

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

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

* Default basic-host SERVERS to localhost:3001/mcp

- Change threejs-server default port from 3109 to 3001 for consistency
- Add default SERVERS value in basic-host serve.ts
- Document SERVERS env var in basic-host README

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

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

* Improve Examples section in README

Frame the three basic examples as foundational starting points and
add a link to the examples directory for additional demo apps.

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

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 16:15:32 +01:00
Olivier Chafik
c03e99df59
release: v0.0.4 (try using node token again) (#102)
* release: reinstate NODE_AUTH_TOKEN

* v0.0.4
2025-12-09 01:29:06 +01:00
Olivier Chafik
5c9665325d
release: 0.0.3, switched to npm trusted publishing (#101)
* version: 0.0.2

* Switch to npm trusted publishing (OIDC)

- Remove NPM_TOKEN secret requirement from npm-publish workflow
- Uses OIDC for authentication (more secure, no long-lived tokens)
- Update CONTRIBUTING.md with trusted publisher setup instructions

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

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

* 0.0.3

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-09 00:52:52 +01:00
Anton Pidkuiko
fd29038351
Merge pull request #100 from modelcontextprotocol/docs/add-bun-install-step
docs: add Bun installation step to customer-segmentation-server README
2025-12-08 23:48:31 +00:00
Anton Pidkuiko MacBook
2194acea5f
docs: add Bun installation step to customer-segmentation-server README
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 23:39:01 +00:00
Anton Pidkuiko
4736621512
Merge pull request #97 from jonathanhefner/running-all-examples
Add `examples/run-all.ts` script
2025-12-08 23:37:44 +00:00
Jonathan Hefner
d11b656d8e Refactor basic-host to use ServerSelect component
Extract server dropdown into ServerSelect component and simplify
ToolCallInfoPanel to read server name from toolCallInfo.serverInfo.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 16:34:52 -06:00
Jonathan Hefner
8bff1dff68 Add dynamic example orchestration script
Replace hardcoded port assignments and npm scripts with run-all.ts that:
- Auto-discovers example directories and assigns ports
- Passes server URLs to basic-host via SERVERS env var
- Supports start, dev, and build commands

Update basic-host to fetch server list from /api/servers endpoint
and display server names from MCP server metadata.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 16:34:52 -06:00
473 changed files with 82827 additions and 6949 deletions

View file

@ -0,0 +1,14 @@
{
"name": "mcp-apps",
"description": "Plugins for MCP Apps development",
"owner": {
"name": "MCP Apps"
},
"plugins": [
{
"name": "mcp-apps",
"source": "./plugins/mcp-apps",
"description": "Skills for creating MCP Apps with the MCP Apps SDK"
}
]
}

4
.gitattributes vendored
View file

@ -1 +1,5 @@
# Force LF line endings for all text files (for consistent prettier formatting)
* text=auto eol=lf
package-lock.json linguist-generated=true package-lock.json linguist-generated=true
src/generated/** linguist-generated=true

View file

@ -6,27 +6,231 @@ on:
pull_request: pull_request:
branches: [main] branches: [main]
permissions:
contents: read
jobs: jobs:
build: prettier-fix:
name: Auto-fix formatting
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with:
ref: ${{ github.head_ref }}
token: ${{ github.token }}
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run prettier:fix
- name: Commit formatting changes
run: |
git diff --quiet && exit 0
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "style: auto-fix prettier formatting"
git push
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
name: Linux x64
- os: ubuntu-24.04-arm
name: Linux ARM64
- os: windows-latest
name: Windows x64
- os: windows-11-arm
name: Windows ARM64
- os: macos-latest
name: macOS ARM64
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Verify no private URLs in package-lock.json - name: Verify no private URLs in package-lock.json
shell: bash
run: '! grep -E "\"resolved\": \"https?://" package-lock.json | grep -v registry.npmjs.org' run: '! grep -E "\"resolved\": \"https?://" package-lock.json | grep -v registry.npmjs.org'
- name: Verify example dependency versions
shell: bash
run: node scripts/check-versions.mjs
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run build
- name: Validate TypeDoc links
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
run: npm exec typedoc -- --treatValidationWarningsAsErrors --emit none
- run: npm run examples:build
- name: Verify generated schemas are up-to-date
shell: bash
run: |
npm run generate:schemas
git diff --exit-code src/generated/ || (echo "Generated schemas are out of date. Run 'npm run generate:schemas' and commit." && exit 1)
- name: Verify synced snippets are up-to-date
shell: bash
run: |
npm run sync:snippets
git diff --exit-code src/ docs/ || (echo "Synced snippets are out of date. Run 'npm run sync:snippets' and commit." && exit 1)
- run: npm test
- run: npm run prettier
- name: Build MCPB bundle (pdf-server)
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
run: node build-mcpb.mjs
working-directory: examples/pdf-server
- name: Smoke-test MCPB bundle starts
if: runner.os == 'Linux' && matrix.name == 'Linux x64'
run: |
unzip -q pdf-server.mcpb -d .mcpb-smoke
node .mcpb-smoke/dist/index.js --stdio < /dev/null 2>&1 | tee /tmp/mcpb-smoke.log
grep -q "Ready" /tmp/mcpb-smoke.log
working-directory: examples/pdf-server
dependency-isolation:
name: Dependency isolation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run test:dependency-isolation
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "20" node-version: "22"
cache: npm
- run: npm install - uses: astral-sh/setup-uv@v7
- run: npm run build:all - run: npm ci
- run: npm test # Headless runs without a channel use chromium-headless-shell, so the
# full Chromium build (about 165 MB) would only be downloaded, never run.
- name: Install Playwright browsers
run: npx playwright install --with-deps --only-shell chromium
- run: npm run prettier - name: Run E2E tests
run: npx playwright test --reporter=list
- name: Upload test results
uses: actions/upload-artifact@v6
if: failure()
with:
name: test-results
path: test-results/
retention-days: 7
# Test build in Windows WSL (Ubuntu)
build-wsl:
name: Build (Windows WSL)
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: Vampire/setup-wsl@v6
with:
distribution: Ubuntu-24.04
- name: Install Node.js in WSL
shell: wsl-bash {0}
run: |
sudo apt-get update
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
npm test
npm run prettier
# Test that the package can be installed from git (triggers prepare script)
test-git-install:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
name: Linux x64
- os: ubuntu-24.04-arm
name: Linux ARM64
- os: windows-latest
name: Windows x64
- os: windows-11-arm
name: Windows ARM64
- os: macos-latest
name: macOS ARM64
name: Test git install (${{ matrix.name }})
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v6
with:
node-version: "22"
- name: Create test project and install from git
shell: bash
env:
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
HEAD_REF: ${{ github.head_ref || github.ref_name }}
run: |
mkdir test-project
cd test-project
npm init -y
# Install from the PR branch (use head repo for fork PRs)
npm install "git+https://github.com/${HEAD_REPO}#${HEAD_REF}"
# Verify the package is usable (ESM import)
node --input-type=module -e "import { App } from '@modelcontextprotocol/ext-apps'; console.log('Import successful:', typeof App)"

65
.github/workflows/docs-preview.yml vendored Normal file
View file

@ -0,0 +1,65 @@
name: Docs Preview
on:
pull_request:
types: [opened, synchronize, reopened, closed]
paths:
- "docs/**"
- "src/**"
- "README.md"
- "typedoc.config.mjs"
- "scripts/typedoc-*.mjs"
- ".github/workflows/docs-preview.yml"
permissions:
contents: read
pull-requests: write
concurrency:
group: docs-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build-and-deploy:
# Refuse to run for fork PRs — forks do not have access to the Cloudflare
# secrets, so deploys would fail anyway.
if: >-
github.event.action != 'closed' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run build
- run: npm run docs
- name: Deploy preview
uses: modelcontextprotocol/actions/cloudflare-pages-preview/deploy@main
with:
directory: docs
project-name: mcp-ext-apps-docs-preview
api-token: ${{ secrets.CF_PAGES_PREVIEW_API_TOKEN }}
account-id: ${{ secrets.CF_PAGES_PREVIEW_ACCOUNT_ID }}
comment-title: "📖 Docs Preview Deployed"
comment-marker: "<!-- docs-preview-comment -->"
cleanup:
if: >-
github.event.action == 'closed' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: modelcontextprotocol/actions/cloudflare-pages-preview/cleanup@main
with:
project-name: mcp-ext-apps-docs-preview
api-token: ${{ secrets.CF_PAGES_PREVIEW_API_TOKEN }}
account-id: ${{ secrets.CF_PAGES_PREVIEW_ACCOUNT_ID }}
comment-marker: "<!-- docs-preview-comment -->"

View file

@ -2,9 +2,8 @@ name: Deploy Documentation
on: on:
workflow_dispatch: workflow_dispatch:
# Future: Uncomment to auto-deploy on release release:
# release: types: [published]
# types: [published]
permissions: permissions:
contents: write contents: write
@ -13,12 +12,13 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "20" node-version: "22"
- run: npm install cache: npm
- run: npm ci
- run: npm run build - run: npm run build
- run: npm run docs - run: npm run docs
- uses: peaceiris/actions-gh-pages@v4 - uses: peaceiris/actions-gh-pages@v4
@ -26,4 +26,5 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs publish_dir: ./docs
publish_branch: gh-pages publish_branch: gh-pages
force_orphan: true keep_files: true
cname: apps.extensions.modelcontextprotocol.io

View file

@ -14,11 +14,11 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "22" node-version: "22"
cache: npm cache: npm
@ -31,21 +31,25 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "22" node-version: "22"
cache: npm cache: npm
- run: npm ci - run: npm ci
- run: npm test - run: npm test
# All publish jobs depend directly on [build, test] (not on each other) so
# they all enter "waiting for approval" together and can be approved in a
# single "Review deployments" click.
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'release' if: github.event_name == 'release'
environment: release environment: Release
needs: [build, test] needs: [build, test]
permissions: permissions:
@ -53,11 +57,11 @@ jobs:
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: "22" node-version: "22"
cache: npm cache: npm
@ -82,4 +86,98 @@ jobs:
- run: npm publish --provenance --access public ${{ steps.npm-tag.outputs.tag }} - run: npm publish --provenance --access public ${{ steps.npm-tag.outputs.tag }}
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
publish-examples:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [build, test]
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
example:
- basic-server-preact
- basic-server-react
- basic-server-solid
- basic-server-svelte
- basic-server-vanillajs
- basic-server-vue
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- debug-server
- lazy-auth-server
- map-server
- pdf-server
- scenario-modeler-server
- shadertoy-server
- sheet-music-server
- system-monitor-server
- threejs-server
- transcript-server
- video-resource-server
- wiki-explorer-server
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
registry-url: "https://registry.npmjs.org"
- run: npm ci
- 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 ${{ steps.npm-tag.outputs.tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
publish-mcpb:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [build, test]
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- run: npm ci
- name: Build pdf-server + MCPB bundle
run: npm run build:mcpb --workspace examples/pdf-server
- name: Upload MCPB to release
run: gh release upload "${{ github.event.release.tag_name }}" examples/pdf-server/*.mcpb --clobber
env:
GH_TOKEN: ${{ github.token }}

View file

@ -13,11 +13,35 @@ jobs:
pkg-publish: pkg-publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: 22 node-version: 22
cache: npm cache: npm
- run: npm ci - run: npm ci
- run: npm run build - run: npm run build
- run: npx pkg-pr-new publish - run: npm run examples:build
- run: |
npx pkg-pr-new publish \
. \
./examples/basic-server-preact \
./examples/basic-server-react \
./examples/basic-server-solid \
./examples/basic-server-svelte \
./examples/basic-server-vanillajs \
./examples/basic-server-vue \
./examples/budget-allocator-server \
./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 \
./examples/shadertoy-server \
./examples/sheet-music-server \
./examples/system-monitor-server \
./examples/threejs-server \
./examples/transcript-server \
./examples/video-resource-server \
./examples/wiki-explorer-server

108
.github/workflows/update-snapshots.yml vendored Normal file
View file

@ -0,0 +1,108 @@
name: Update E2E Snapshots
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to update snapshots on"
required: true
default: "main"
issue_comment:
types: [created]
permissions:
contents: write
pull-requests: write
jobs:
update-snapshots:
# Run on workflow_dispatch OR when a maintainer comments "/update-snapshots" on a PR.
# author_association check prevents arbitrary commenters from triggering a job with
# contents:write that pushes a [skip ci] commit to the PR branch.
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request &&
contains(github.event.comment.body, '/update-snapshots') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
runs-on: ubuntu-latest
steps:
- name: Get PR branch
if: github.event_name == 'issue_comment'
id: pr
uses: actions/github-script@v8
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});
core.setOutput('ref', pr.data.head.ref);
core.setOutput('sha', pr.data.head.sha);
- name: Add reaction to comment
if: github.event_name == 'issue_comment'
uses: actions/github-script@v8
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'rocket'
});
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.branch || steps.pr.outputs.ref || github.ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: npm
- uses: astral-sh/setup-uv@v7
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Update snapshots
run: npx playwright test --update-snapshots --reporter=list
- name: Commit updated snapshots
id: commit
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add tests/e2e/**/*.png
if git diff --staged --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
echo "No snapshot changes to commit"
else
git commit -m "chore: update e2e snapshots [skip ci]"
git push
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Comment on PR
if: github.event_name == 'issue_comment'
uses: actions/github-script@v8
with:
script: |
const changed = '${{ steps.commit.outputs.changed }}' === 'true';
const body = changed
? '✅ Snapshots updated and pushed to this branch.'
: '✅ No snapshot changes needed - all snapshots are up to date.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});

12
.gitignore vendored
View file

@ -6,3 +6,15 @@ bun.lockb
.vscode/ .vscode/
docs/api/ docs/api/
tmp/ tmp/
intermediate-findings/
# Playwright
playwright-report/
test-results/
__pycache__/
*.pyc
# MCPB bundles (built artifacts)
*.mcpb
.mcpb-stage/
.mcpb-smoke/

View file

@ -1,2 +1,29 @@
# Load Node.js environment for GUI apps (GitHub Desktop, etc.)
# that don't inherit shell PATH
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # nvm
[ -s "$HOME/.fnm/fnm" ] && eval "$(~/.fnm/fnm env)" # fnm
[ -s "$HOME/.volta/bin/volta" ] && export PATH="$HOME/.volta/bin:$PATH" # volta
[ -d "/opt/homebrew/bin" ] && export PATH="/opt/homebrew/bin:$PATH" # homebrew (macOS ARM)
[ -d "/usr/local/bin" ] && export PATH="/usr/local/bin:$PATH" # homebrew (macOS Intel)
# Verify no private registry URLs in package-lock.json
if grep -E '"resolved": "https?://' package-lock.json | grep -v registry.npmjs.org > /dev/null; then
echo "ERROR: package-lock.json contains non-npmjs.org URLs"
echo "Run: docker run --rm -i -v \$PWD:/src -w /src node:latest npm i --registry=https://registry.npmjs.org/"
exit 1
fi
# Capture staged files so we only re-stage what the user intended to commit
# (avoids sweeping unrelated WIP into the commit). --diff-filter=d skips
# deletions so the xargs git add below doesn't fail on removed paths.
STAGED=$(git diff --name-only --cached --diff-filter=d)
npm run build:all npm run build:all
npm run prettier:fix npm run prettier:fix
# Re-stage originally-staged files (prettier may have reformatted them)
echo "$STAGED" | xargs -r git add
# Also stage generated files (regenerated by build:all from spec.types.ts)
git add src/generated/

1
.npmrc Normal file
View file

@ -0,0 +1 @@
registry=https://registry.npmjs.org/

View file

@ -1,6 +1,9 @@
examples/basic-host/**/*.ts examples/basic-host/**/*.ts
examples/basic-host/**/*.tsx examples/basic-host/**/*.tsx
examples/basic-server-react/**/*.ts examples/basic-server-*/**/*.ts
examples/basic-server-react/**/*.tsx examples/basic-server-*/**/*.tsx
examples/basic-server-vanillajs/**/*.ts examples/quickstart/**/*.ts
examples/basic-server-vanillajs/**/*.tsx **/vendor/**
**/.build/**
**/.claude/**
SKILL.md

7
.prettierrc.json Normal file
View file

@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2
}

103
AGENTS.md Normal file
View file

@ -0,0 +1,103 @@
# MCP Apps SDK
## Project Overview
MCP Apps SDK (`@modelcontextprotocol/ext-apps`) enables MCP servers to display interactive UIs in conversational clients.
Key abstractions:
- **View** - UI running in an iframe, uses `App` class with `PostMessageTransport` to communicate with host
- **Host** - Chat client embedding the iframe, uses `AppBridge` class to proxy MCP requests
- **Server** - MCP server that registers tools/resources with UI metadata
Specification (stable): `specification/2026-01-26/apps.mdx`
## Commands
```bash
# Install dependencies
npm install
# Build the SDK only (generates schemas + bundles, does not build examples)
npm run build
# Build everything (SDK + all examples)
npm run build:all
# Type check + build a single example
npm run --workspace examples/<example-name> build
# Run all examples (starts server at http://localhost:8080)
npm start
# Run E2E tests (primary testing mechanism - starts examples server automatically)
npm run test:e2e
# Run unit tests (E2E tests have broader coverage; unit tests cover specific modules)
npm test
# Check JSDoc comment syntax and `{@link}` references
npm exec typedoc -- --treatValidationWarningsAsErrors --emit none
# Regenerate package-lock.json
# Note: repo .npmrc pins registry to npmjs.org, so a plain `npm i` is safe even
# if your global npm config points elsewhere. The Docker step below is optional
# — it locks linux-amd64 optionalDependencies (sharp, rollup, bun) for CI.
rm -fR package-lock.json node_modules && \
docker run --rm -it --platform linux/amd64 -v $PWD:/src:rw -w /src node:latest npm i && \
rm -fR node_modules && \
npm i --cache=~/.npm-mcp-apps --registry=https://registry.npmjs.org/
```
## Architecture
### SDK Entry Points
- `@modelcontextprotocol/ext-apps` - Main SDK for Apps (`App` class, `PostMessageTransport`)
- `@modelcontextprotocol/ext-apps/react` - React hooks (`useApp`, `useHostStyleVariables`, etc.)
- `@modelcontextprotocol/ext-apps/app-bridge` - SDK for hosts (`AppBridge` class)
- `@modelcontextprotocol/ext-apps/server` - Server helpers (`registerAppTool`, `registerAppResource`)
### Key Source Files
- `src/app.ts` - `App` subclasses `Protocol` from `@modelcontextprotocol/client`, handles View initialization, tool calls, and messaging
- `src/app-bridge.ts` - `AppBridge` subclasses `Protocol` from `@modelcontextprotocol/client` for the iframe channel and proxies through a separate outer `Client`
- `src/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
- `src/react/` - React hooks: `useApp`, `useHostStyles`, `useAutoResize`, `useDocumentTheme`
### Protocol Flow
```
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` (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
7. Host calls `teardownResource()` before unmounting iframe
## Documentation
JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `app.ts` → `app.examples.ts`). Use ` ```ts source="./file.examples.ts#regionName" ` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `useApp_basicUsage`, `App_hostCapabilities_checkAfterConnection`). For whole-file inclusion (any file type), omit the `#regionName`. Run `npm run sync:snippets` to sync.
Standalone docs in `docs/` (listed in `typedoc.config.mjs` `projectDocuments`) can also have type-checked companion `.ts`/`.tsx` files using the same pattern.
## Full Examples
Uses npm workspaces. Full examples in `examples/` are separate packages:
- `basic-server-*` - Starter templates (vanillajs, react, vue, svelte, preact, solid). Use these as the basis for new examples.
- `basic-host` - Reference host implementation
- Other examples showcase specific features (charts, 3D, video, etc.)
## Claude Code Plugin
The `plugins/mcp-apps/` directory contains a Claude Code plugin distributed via the plugin marketplace. It provides the following Claude Code skills files:
- `plugins/mcp-apps/skills/create-mcp-app/SKILL.md` — for creating an MCP App
- `plugins/mcp-apps/skills/migrate-oai-app/SKILL.md` — for migrating an app from the OpenAI Apps SDK to the MCP Apps SDK

1
CLAUDE.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md

View file

@ -25,6 +25,15 @@ We welcome contributions to the MCP Apps SDK! This document outlines the process
- Update documentation as needed - Update documentation as needed
- Keep changes focused and atomic - Keep changes focused and atomic
- Provide a clear description of changes - Provide a clear description of changes
- **Keep "Allow edits by maintainers" checked** when opening your PR — this lets maintainers rebase your branch and lets the [`/update-snapshots` workflow](#updating-snapshots-in-ci) push updated screenshots to it
## Examples Policy
The examples in `examples/` are maintained by the core team. **We do not accept pull requests that add new example servers from outside contributors.** Each example carries an ongoing maintenance cost — keeping it building, tested, and up to date with SDK changes — so we are deliberate about what we add.
If you have an idea for an example that would provide significant educational value, please [open an issue](https://github.com/modelcontextprotocol/ext-apps/issues) describing what the example would demonstrate and why it would be useful, and maintainers will evaluate the request.
PRs that add new examples without a prior approved issue may be closed.
## Running Examples ## Running Examples
@ -40,9 +49,460 @@ Or build and run examples:
npm run examples:start npm run examples:start
``` ```
### With MCP Clients
To use these examples with MCP clients that support the stdio transport (such as Claude Desktop or VS Code), add this MCP server configuration to your client's settings:
<details>
<summary>MCP client configuration for all examples (using stdio)</summary>
```json
{
"mcpServers": {
"basic-react": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-react",
"--stdio"
]
},
"basic-vanillajs": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-vanillajs",
"--stdio"
]
},
"basic-vue": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-vue",
"--stdio"
]
},
"basic-svelte": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-svelte",
"--stdio"
]
},
"basic-preact": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-preact",
"--stdio"
]
},
"basic-solid": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-basic-solid",
"--stdio"
]
},
"budget-allocator": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-budget-allocator",
"--stdio"
]
},
"cohort-heatmap": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-cohort-heatmap",
"--stdio"
]
},
"customer-segmentation": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-customer-segmentation",
"--stdio"
]
},
"map": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-map",
"--stdio"
]
},
"pdf": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-pdf",
"--stdio"
]
},
"scenario-modeler": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-scenario-modeler",
"--stdio"
]
},
"shadertoy": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-shadertoy",
"--stdio"
]
},
"sheet-music": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-sheet-music",
"--stdio"
]
},
"system-monitor": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-system-monitor",
"--stdio"
]
},
"threejs": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-threejs",
"--stdio"
]
},
"transcript": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-transcript",
"--stdio"
]
},
"video-resource": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-video-resource",
"--stdio"
]
},
"wiki-explorer": {
"command": "npx",
"args": [
"-y",
"--silent",
"--registry=https://registry.npmjs.org/",
"@modelcontextprotocol/server-wiki-explorer",
"--stdio"
]
},
"qr": {
"command": "uv",
"args": [
"run",
"/path/to/ext-apps/examples/qr-server/server.py",
"--stdio"
]
},
"say": {
"command": "uv",
"args": [
"run",
"--default-index",
"https://pypi.org/simple",
"https://raw.githubusercontent.com/modelcontextprotocol/ext-apps/refs/heads/main/examples/say-server/server.py",
"--stdio"
]
}
}
}
```
</details>
> [!NOTE]
> The `qr` server requires cloning the repository first. See [qr-server README](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server) for details.
### Local Development
To test local modifications with MCP clients, first clone and install the repository:
```bash
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
```
Then configure your MCP client to build and run the local server. Replace `~/src/ext-apps` with your actual clone path.
Most example servers have a `start:stdio` script that builds and launches in stdio mode:
<details>
<summary>MCP client configuration for local development (all examples)</summary>
```json
{
"mcpServers": {
"basic-react": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-react && npm --silent run start:stdio"
]
},
"basic-vanillajs": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-vanillajs && npm --silent run start:stdio"
]
},
"basic-vue": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-vue && npm --silent run start:stdio"
]
},
"basic-svelte": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-svelte && npm --silent run start:stdio"
]
},
"basic-preact": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-preact && npm --silent run start:stdio"
]
},
"basic-solid": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/basic-server-solid && npm --silent run start:stdio"
]
},
"budget-allocator": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/budget-allocator-server && npm --silent run start:stdio"
]
},
"cohort-heatmap": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/cohort-heatmap-server && npm --silent run start:stdio"
]
},
"customer-segmentation": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/customer-segmentation-server && npm --silent run start:stdio"
]
},
"map": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/map-server && npm --silent run start:stdio"
]
},
"pdf": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/pdf-server && npm --silent run start:stdio"
]
},
"scenario-modeler": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/scenario-modeler-server && npm --silent run start:stdio"
]
},
"shadertoy": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/shadertoy-server && npm --silent run start:stdio"
]
},
"sheet-music": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/sheet-music-server && npm --silent run start:stdio"
]
},
"system-monitor": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/system-monitor-server && npm --silent run start:stdio"
]
},
"threejs": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/threejs-server && npm --silent run start:stdio"
]
},
"transcript": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/transcript-server && npm --silent run start:stdio"
]
},
"video-resource": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/video-resource-server && npm --silent run start:stdio"
]
},
"wiki-explorer": {
"command": "bash",
"args": [
"-c",
"cd ~/src/ext-apps/examples/wiki-explorer-server && npm --silent run start:stdio"
]
},
"qr": {
"command": "bash",
"args": [
"-c",
"uv run ~/src/ext-apps/examples/qr-server/server.py --stdio"
]
},
"say": {
"command": "bash",
"args": [
"-c",
"uv run --index https://pypi.org/simple ~/src/ext-apps/examples/say-server/server.py --stdio"
]
}
}
}
```
</details>
This configuration rebuilds each server on launch, ensuring your local changes are picked up.
## Testing
### Unit Tests
Run unit tests with Bun:
```bash
npm test
```
### E2E Tests
E2E tests use Playwright to verify all example servers work correctly with screenshot comparisons.
```bash
# Run all E2E tests
npm run test:e2e
# Run a specific server's tests
npm run test:e2e -- --grep "Budget Allocator"
# Run tests in interactive UI mode
npm run test:e2e:ui
```
### Updating Golden Screenshots
When UI changes are intentional, update the golden screenshots:
```bash
# Update all screenshots
npm run test:e2e:update
# Update screenshots for a specific server
npm run test:e2e:update -- --grep "Three.js"
```
**Note**: Golden screenshots are platform-agnostic. Tests use canvas masking and tolerance thresholds to handle minor cross-platform rendering differences.
#### Updating Snapshots in CI
If E2E tests fail in CI due to screenshot mismatches, you can update snapshots directly from your PR:
1. Comment `/update-snapshots` on the PR
2. The workflow will update snapshots and push to your branch
3. A comment will confirm when complete
Alternatively, use the [workflow dispatch](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/update-snapshots.yml) to manually trigger updates for any branch.
## Code of Conduct ## 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 ## Reporting Issues
@ -60,54 +520,36 @@ Please review our [Security Policy](SECURITY.md) for reporting security vulnerab
### Repository Setup ### Repository Setup
This repository uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) with OIDC - no secrets required.
Before publishing releases, ensure the following are configured: Before publishing releases, ensure the following are configured:
1. **NPM_TOKEN secret**: Add an npm automation token to the repository secrets 1. **Trusted publisher on npm**: Configure the package to trust this GitHub repository
- Go to Settings <20> Secrets and variables <20> Actions - Go to https://www.npmjs.com/package/@modelcontextprotocol/ext-apps/access
- Create a new secret named `NPM_TOKEN` - Under "Trusted Publishers", click "Add trusted publisher"
- Value: an npm automation token with publish permissions for `@modelcontextprotocol/ext-apps` - Select "GitHub Actions"
- Repository: `modelcontextprotocol/ext-apps`
- Workflow filename: `npm-publish.yml`
- Environment: `Release` (optional, for additional protection)
2. **`release` environment** (optional): Create a protected environment for additional safeguards 2. **`Release` environment** (optional): Create a protected environment for additional safeguards
- Go to Settings <20> Environments <20> New environment - Go to Settings > Environments > New environment
- Name it `release` - Name it `Release`
- Add required reviewers or other protection rules as needed - Add required reviewers or other protection rules as needed
### Publishing a Release ### Publishing a Release
Releases are published automatically via GitHub Actions when a GitHub Release is created. 1. **Bump the version** across the root and all workspace packages:
#### Steps to publish:
1. **Update the version** in `package.json`:
```bash ```bash
# For a regular release npm run bump -- minor # or: patch | major | prerelease --preid=beta | 1.7.0
npm version patch # or minor, or major
# For a beta release
npm version prerelease --preid=beta
``` ```
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 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.
git add package.json
git commit -m "Bump version to X.Y.Z"
git push origin main
```
3. **Create a GitHub Release**: 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.
- Go to [Releases](https://github.com/modelcontextprotocol/ext-apps/releases)
- Click "Draft a new release"
- Create a new tag matching the version (e.g., `v0.1.0`)
- Set the target branch (usually `main`)
- Write release notes describing the changes
- Click "Publish release"
4. **Monitor the workflow**:
- The [npm-publish workflow](https://github.com/modelcontextprotocol/ext-apps/actions/workflows/npm-publish.yml) will trigger automatically
- It runs build and test jobs before publishing
- On success, the package is published to npm with provenance
#### npm Tags #### npm Tags
@ -131,7 +573,7 @@ To release a patch for an older version:
### Testing Pre-releases ### Testing Pre-releases
Every commit and PR automatically publishes a preview package via [pkg-pr-new](https://github.com/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.
--- ---

197
LICENSE
View file

@ -1,6 +1,193 @@
The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License.
No rights beyond those granted by the applicable original license are conveyed for such contributions.
---
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright
owner or by an individual or Legal Entity authorized to submit on behalf
of the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
---
MIT License MIT License
Copyright (c) 2025 Anthropic, PBC Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -19,3 +206,11 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
---
Creative Commons Attribution 4.0 International (CC-BY-4.0)
Documentation in this project (excluding specifications) is licensed under
CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
the full license text.

303
README.md
View file

@ -1,75 +1,284 @@
# @modelcontextprotocol/ext-apps <!-- LOGO -->
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="media/mcp-white.svg">
<source media="(prefers-color-scheme: light)" srcset="media/mcp.svg">
<img src="media/mcp.svg" alt="MCP Apps" width="128">
</picture>
<h1>MCP Apps</h1>
<p>
Build interactive UIs for MCP tools — charts, forms, dashboards — that render inline in Claude, ChatGPT and any other compliant chat client.
<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/">API Docs</a>
·
<a href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx">Spec</a>
·
<a href="CONTRIBUTING.md">Contributing</a>
</p>
</div>
[![API Documentation](https://img.shields.io/badge/docs-API%20Reference-blue)](https://modelcontextprotocol.github.io/ext-apps/api/) <p align="center">
<a href="https://github.com/modelcontextprotocol/ext-apps/actions/workflows/ci.yml"><img src="https://github.com/modelcontextprotocol/ext-apps/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue" alt="License: Apache 2.0"></a>
<a href="https://www.npmjs.com/package/@modelcontextprotocol/ext-apps"><img src="https://img.shields.io/npm/v/@modelcontextprotocol/ext-apps.svg" alt="npm version"></a>
<a href="https://www.npmjs.com/package/@modelcontextprotocol/ext-apps"><img src="https://img.shields.io/npm/dm/@modelcontextprotocol/ext-apps.svg" alt="npm downloads"></a>
<a href="https://github.com/modelcontextprotocol/ext-apps"><img src="https://img.shields.io/github/stars/modelcontextprotocol/ext-apps" alt="GitHub stars"></a>
<a href="https://apps.extensions.modelcontextprotocol.io/api/"><img src="https://img.shields.io/badge/docs-API%20Reference-blue" alt="API Documentation"></a>
</p>
This repo contains the SDK and [specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) for MCP Apps Extension ([SEP-1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)). <p align="center">
<img src="media/excalidraw.gif" alt="MCP Apps demo" width="600">
<br><em>Excalidraw built with MCP Apps, running in Claude</em>
</p>
MCP Apps are a proposed standard inspired by [MCP-UI](https://mcpui.dev/) and [OpenAI's Apps SDK](https://developers.openai.com/apps-sdk/) to allow MCP Servers to display interactive UI elements in conversational MCP clients / chatbots. ## Table of Contents
## Overview - [Build with Agent Skills](#build-with-agent-skills)
- [Supported Clients](#supported-clients)
- [Why MCP Apps?](#why-mcp-apps)
- [How It Works](#how-it-works)
- [Getting Started](#getting-started)
- [Using the SDK](#using-the-sdk)
- [Examples](#examples)
- [Specification](#specification)
- [Resources](#resources)
- [Contributing](#contributing)
This SDK serves two audiences: ## Build with Agent Skills
### App Developers The fastest way to build an MCP App is to let your AI coding agent do it. This
repo ships four [Agent Skills](https://agentskills.io/) — install them once,
then just ask:
Build interactive UIs that run inside MCP-enabled chat clients. | Skill | What it does | Try it |
| --------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------- |
| [`create-mcp-app`](./plugins/mcp-apps/skills/create-mcp-app/SKILL.md) | Scaffolds a new MCP App with an interactive UI from scratch | _"Create an MCP App"_ |
| [`migrate-oai-app`](./plugins/mcp-apps/skills/migrate-oai-app/SKILL.md) | Converts an existing OpenAI App to use MCP Apps | _"Migrate from OpenAI Apps SDK"_ |
| [`add-app-to-server`](./plugins/mcp-apps/skills/add-app-to-server/SKILL.md) | Adds interactive UI to an existing MCP server's tools | _"Add UI to my MCP server"_ |
| [`convert-web-app`](./plugins/mcp-apps/skills/convert-web-app/SKILL.md) | Turns an existing web app into a hybrid web + MCP App | _"Add MCP App support to my web app"_ |
- **SDK for Apps**: `@modelcontextprotocol/ext-apps` — [API Docs](https://modelcontextprotocol.github.io/ext-apps/api/modules/app.html) ### Install the Skills
- **React hooks**: `@modelcontextprotocol/ext-apps/react` — [API Docs](https://modelcontextprotocol.github.io/ext-apps/api/modules/_modelcontextprotocol_ext-apps_react.html)
### Host Developers **Claude Code** — install via the plugin marketplace:
Embed and communicate with MCP Apps in your chat application. ```
/plugin marketplace add modelcontextprotocol/ext-apps
- **SDK for Hosts**: `@modelcontextprotocol/ext-apps/app-bridge` — [API Docs](https://modelcontextprotocol.github.io/ext-apps/api/modules/app-bridge.html) /plugin install mcp-apps@modelcontextprotocol-ext-apps
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) example).
We have [contributed a tentative implementation](https://github.com/MCP-UI-Org/mcp-ui/pull/147) of hosting / iframing / sandboxing logic to the [MCP-UI](https://github.com/idosal/mcp-ui) repository, and expect OSS clients may use it, while other clients might roll their own hosting logic.
## Installation
This repo is in flux and isn't published to npm yet. When it is, it will use the `@modelcontextprotocol/ext-apps` package.
In the meantime you can depend on the SDK library in a Node.js project by installing it with its git URL:
```bash
npm install -S git+https://github.com/modelcontextprotocol/ext-apps.git
``` ```
Your `package.json` will then look like: **Other agents** — any AI coding agent that supports
[Agent Skills](https://agentskills.io/) can use these skills. See the
[agent skills guide](./docs/agent-skills.md) for manual installation
instructions.
Once installed, verify by asking your agent _"What skills do you have?"_ — you
should see `create-mcp-app`, `migrate-oai-app`, `add-app-to-server`, and
`convert-web-app` in the list. Then just ask it to create or migrate an app and
it will guide you through the rest.
## Supported Clients
<p align="center">
<a href="https://developers.openai.com/apps-sdk/mcp-apps-in-chatgpt/"><img src="https://img.shields.io/badge/ChatGPT-docs-74aa9c?logo=openai&logoColor=white" alt="ChatGPT"></a>
<a href="https://claude.com/docs/connectors/building/mcp-apps/getting-started"><img src="https://img.shields.io/badge/Claude-docs-d97706?logo=claude&logoColor=white" alt="Claude"></a>
<a href="https://code.visualstudio.com/blogs/2026/01/26/mcp-apps-support"><img src="https://img.shields.io/badge/VS_Code-docs-007ACC?logo=visualstudiocode&logoColor=white" alt="VS Code"></a>
<a href="https://block.github.io/goose/docs/tutorials/building-mcp-apps/"><img src="https://img.shields.io/badge/Goose-docs-000000?logo=goose&logoColor=white" alt="Goose"></a>
<a href="https://learning.postman.com/docs/postman-ai/mcp-requests/interact"><img src="https://img.shields.io/badge/Postman-docs-FF6C37?logo=postman&logoColor=white" alt="Postman"></a>
<a href="https://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]
> MCP Apps is an extension to the
> [core MCP specification](https://modelcontextprotocol.io/specification). Host
> support varies — see the
> [clients page](https://modelcontextprotocol.io/clients) for the full list.
## Why MCP Apps?
MCP tools return text and structured data. That works for many cases, but not
when you need an interactive UI, like a chart, form, design canvas or video player.
MCP Apps provide a standardized way to deliver interactive UIs from MCP servers.
Your UI renders inline in the conversation, in context, in any compliant host.
## How It Works
MCP Apps extend the Model Context Protocol by letting tools declare UI
resources:
1. **Tool definition** — Your tool declares a `ui://` resource containing its
HTML interface
2. **Tool call** — The LLM calls the tool on your server
3. **Host renders** — The host fetches the resource and displays it in a
sandboxed iframe
4. **Bidirectional communication** — The host passes tool data to the UI via
notifications, and the UI can call other tools through the host
## Getting Started
Requires Node.js 20+. The base MCP SDK packages are `^2.0.0` peers of
`ext-apps` (`@modelcontextprotocol/core` is a required peer that `client` already depends on, so npm installs it for you).
For a View or host:
```bash
npm install -S @modelcontextprotocol/ext-apps \
@modelcontextprotocol/client@^2.0.0 \
zod@^4.2.0
```
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)
to build your first MCP App.
## Using the SDK
The SDK serves three roles: app developers building interactive Views, host
developers embedding those Views, and MCP server authors registering tools with
UI metadata.
| Package | Purpose | Docs |
| ------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `@modelcontextprotocol/ext-apps` | Build interactive Views (App class, PostMessageTransport) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app.html) |
| `@modelcontextprotocol/ext-apps/react` | React hooks for Views (useApp, useHostStyles, etc.) | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/_modelcontextprotocol_ext-apps_react.html) |
| `@modelcontextprotocol/ext-apps/app-bridge` | Embed and communicate with Views in your chat client | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/app-bridge.html) |
| `@modelcontextprotocol/ext-apps/server` | Register tools and resources on your MCP server | [API Docs →](https://apps.extensions.modelcontextprotocol.io/api/modules/server-helpers.html) |
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)
example).
The [MCP-UI](https://github.com/idosal/mcp-ui) client SDK offers a
fully-featured MCP Apps framework used by a few hosts. Clients may choose to use
it or roll their own implementation.
## Examples
The
[`examples/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples)
directory contains demo apps showcasing real-world use cases.
<!-- prettier-ignore-start -->
| | | |
|:---:|:---:|:---:|
| [![Map](examples/map-server/grid-cell.png "Interactive 3D globe viewer using CesiumJS")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) | [![Three.js](examples/threejs-server/grid-cell.png "Interactive 3D scene renderer")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server) | [![ShaderToy](examples/shadertoy-server/grid-cell.png "Real-time GLSL shader renderer")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) |
| [**Map**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) | [**Three.js**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server) | [**ShaderToy**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) |
| [![Sheet Music](examples/sheet-music-server/grid-cell.png "ABC notation to sheet music")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) | [![Wiki Explorer](examples/wiki-explorer-server/grid-cell.png "Wikipedia link graph visualization")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/wiki-explorer-server) | [![Cohort Heatmap](examples/cohort-heatmap-server/grid-cell.png "Customer retention heatmap")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/cohort-heatmap-server) |
| [**Sheet Music**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) | [**Wiki Explorer**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/wiki-explorer-server) | [**Cohort Heatmap**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/cohort-heatmap-server) |
| [![Scenario Modeler](examples/scenario-modeler-server/grid-cell.png "SaaS business projections")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/scenario-modeler-server) | [![Budget Allocator](examples/budget-allocator-server/grid-cell.png "Interactive budget allocation")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/budget-allocator-server) | [![Customer Segmentation](examples/customer-segmentation-server/grid-cell.png "Scatter chart with clustering")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server) |
| [**Scenario Modeler**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/scenario-modeler-server) | [**Budget Allocator**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/budget-allocator-server) | [**Customer Segmentation**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server) |
| [![System Monitor](examples/system-monitor-server/grid-cell.png "Real-time OS metrics")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server) | [![Transcript](examples/transcript-server/grid-cell.png "Live speech transcription")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) | [![Video Resource](examples/video-resource-server/grid-cell.png "Binary video via MCP resources")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server) |
| [**System Monitor**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server) | [**Transcript**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) | [**Video Resource**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server) |
| [![PDF Server](examples/pdf-server/grid-cell.png "Interactive PDF viewer with chunked loading")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server) | [![QR Code](examples/qr-server/grid-cell.png "QR code generator")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server) | [![Say Demo](examples/say-server/grid-cell.png "Text-to-speech demo")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/say-server) |
| [**PDF Server**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server) | [**QR Code (Python)**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server) | [**Say Demo**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/say-server) |
### Starter Templates
| | |
|:---:|:---|
| [![Basic](examples/basic-server-react/grid-cell.png "Starter template")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) | The same app built with different frameworks — pick your favorite!<br><br>[React](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) · [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue) · [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte) · [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact) · [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) · [Vanilla JS](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) |
<!-- prettier-ignore-end -->
### Running the Examples
#### With basic-host
To run all examples locally using
[basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host)
(the reference host implementation included in this repo):
```bash
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
npm start
```
Then open http://localhost:8080/.
#### With MCP Clients
Every Node.js example is published as `@modelcontextprotocol/server-<name>`. To
add one to an MCP client that supports stdio (Claude Desktop, VS Code, etc.),
use this pattern:
```json ```json
{ {
"dependencies": { "mcpServers": {
"@modelcontextprotocol/ext-apps": "git+https://github.com/modelcontextprotocol/ext-apps.git" "<name>": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-<name>", "--stdio"]
}
} }
} }
``` ```
> [!NOTE] For example, to add the map server: `@modelcontextprotocol/server-map`. The
> The build tools (`esbuild`, `tsx`, `typescript`) are in `dependencies` rather than `devDependencies`. This is intentional: it allows the `prepare` script to run when the package is installed from git, since npm doesn't install devDependencies for git dependencies. Python examples (`qr-server`, `say-server`) use `uv run` instead — see their
> READMEs for details.
> Once the package is published to npm with pre-built `dist/`, these can be moved back to `devDependencies`.
## Examples #### Local Development
- [`examples/basic-server-react`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) — Example MCP server with tools that return UI Apps (React) To test local modifications with an MCP client, clone the repo, install, then
- [`examples/basic-server-vanillajs`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) — Example MCP server with tools that return UI Apps (vanilla JS) point your client at a local build:
- [`examples/basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) — Bare-bones example of hosting MCP Apps
To run the examples end-to-end: ```json
{
``` "mcpServers": {
npm i "<name>": {
npm run examples:start "command": "bash",
"args": [
"-c",
"cd ~/code/ext-apps/examples/<name>-server && npm run build >&2 && node dist/index.js --stdio"
]
}
}
}
``` ```
Then open http://localhost:8080/ ## Specification
<div align="center">
| Version | Status | Link |
| -------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
| **2026-01-26** | Stable | [specification/2026-01-26/apps.mdx](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx) |
| **draft** | Development | [specification/draft/apps.mdx](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) |
</div>
## Resources ## Resources
- [Quickstart Guide](https://modelcontextprotocol.github.io/ext-apps/api/documents/Quickstart.html) - [Quickstart Guide](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html)
- [API Documentation](https://modelcontextprotocol.github.io/ext-apps/api/) - [API Documentation](https://apps.extensions.modelcontextprotocol.io/api/)
- [Draft Specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) - [Specification (2026-01-26)](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx)
([Draft](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx))
- [SEP-1865 Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) - [SEP-1865 Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)
## Contributing
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for
guidelines on how to get started, submit pull requests, and report issues.

64
RELEASES.md Normal file
View file

@ -0,0 +1,64 @@
# Release Notes
## 0.3.0
### Breaking Changes
- **`viewport` replaced with `containerDimensions`** — The `viewport` property in host context has been replaced with `containerDimensions`, which provides a clearer distinction between fixed dimensions and maximum constraints. The new type allows specifying either `height` or `maxHeight`, and either `width` or `maxWidth` by @martinalong in [#153](https://github.com/modelcontextprotocol/ext-apps/pull/153)
### New Examples
- **Video resource server** — Demonstrates video resource handling with proper mimeType declarations by @antonpk1 in [#175](https://github.com/modelcontextprotocol/ext-apps/pull/175)
- **Sheet music server** — Interactive sheet music notation example by @jonathanhefner in [#196](https://github.com/modelcontextprotocol/ext-apps/pull/196)
### Developer Experience
- **`npm start` alias** — Added `npm start` as alias for `npm run examples:start` by @jonathanhefner in [#183](https://github.com/modelcontextprotocol/ext-apps/pull/183)
- **Examples cleanup** — Improved consistency across example servers by @jonathanhefner in [#182](https://github.com/modelcontextprotocol/ext-apps/pull/182)
- **Documentation fixes** — Fixed tsc command in docs to use tsconfig.json by @blackgirlbytes in [#188](https://github.com/modelcontextprotocol/ext-apps/pull/188)
### Bug Fixes
- **Move prettier to dev dependency** — Fixed incorrect dependency classification by @niclim in [#179](https://github.com/modelcontextprotocol/ext-apps/pull/179)
- **Fix build errors in examples** — Resolved build issues across example servers by @jonathanhefner in [#180](https://github.com/modelcontextprotocol/ext-apps/pull/180)
**Full Changelog**: https://github.com/modelcontextprotocol/ext-apps/compare/v0.2.2...v0.3.0
---
## 0.2.2
Changes from 0.1.x to 0.2.2.
### Highlights
- **Server helpers** — New `registerAppTool()` and `registerAppResource()` simplify server setup with proper type safety. `connect()` now defaults to `PostMessageTransport(window.parent)`, enabling simpler initialization with just `await app.connect()` by @ochafik in [#165](https://github.com/modelcontextprotocol/ext-apps/pull/165)
- **Tool visibility control** — New `visibility` array field controls whether tools are visible to the agent, apps, or both. Restructured `_meta` format from flat `"ui/resourceUri"` to nested `_meta.ui.resourceUri` by @jonathanhefner in [#131](https://github.com/modelcontextprotocol/ext-apps/pull/131)
- **Host-provided theming** — Apps receive 36 standardized CSS variables for colors, typography, and spacing via `styles.variables`, enabling visual consistency with the host by @martinalong in [#127](https://github.com/modelcontextprotocol/ext-apps/pull/127)
- **Display mode requests** — Apps can request display mode changes (e.g., fullscreen) via `requestDisplayMode`, with hosts able to accept or reject by @martinalong in [#152](https://github.com/modelcontextprotocol/ext-apps/pull/152)
- **Custom fonts support** — Apps can receive custom fonts via `styles.css.fonts` and apply them using `applyHostFonts()` or `useHostFonts()` helpers by @martinalong in [#159](https://github.com/modelcontextprotocol/ext-apps/pull/159)
### API Changes
- **MCP SDK as peer dependency** — Consumers control their SDK version, reducing duplication by @ochafik in [#168](https://github.com/modelcontextprotocol/ext-apps/pull/168)
- **React as peer dependency** — Supports React 17, 18, and 19 by @ochafik in [#164](https://github.com/modelcontextprotocol/ext-apps/pull/164)
- **Renamed request methods** — Removed `send` prefix: `openLink()` and `teardownResource()`. Deprecated aliases maintained by @ochafik in [#161](https://github.com/modelcontextprotocol/ext-apps/pull/161)
- **Optional Client in AppBridge** — Enables custom forwarding scenarios without direct MCP client access by @ochafik in [#146](https://github.com/modelcontextprotocol/ext-apps/pull/146)
- **Zod v3 and v4 support** — Bring your own Zod by @alpic-ai in [#49](https://github.com/modelcontextprotocol/ext-apps/pull/49)
- **Zod schemas now version-agnostic** — Generated schemas work with both Zod 3.25+ and v4, using `z.object().passthrough()` instead of v4-only `z.looseObject()` by @ochafik in [#178](https://github.com/modelcontextprotocol/ext-apps/pull/178)
### Platform & DX
- **Windows compatibility** — Bun as optional dependency with automatic setup, `cross-env` for examples. Just run `npm install` by @ochafik in [#145](https://github.com/modelcontextprotocol/ext-apps/pull/145)
- **Widened `@oven/bun-*` version range** — Lowered the minimum from `^1.3.4` to `^1.2.21`, helping contributors whose registries may not have the latest packages by @ochafik in [#176](https://github.com/modelcontextprotocol/ext-apps/pull/176)
- **SSE transport support** — All examples now support both stdio and HTTP transports with SSE endpoints for older clients by @ochafik in [#136](https://github.com/modelcontextprotocol/ext-apps/pull/136)
- **Playwright E2E tests** — Screenshot golden testing across all 9 example servers with parallel execution (~28s) by @ochafik in [#115](https://github.com/modelcontextprotocol/ext-apps/pull/115)
- **hostContext exposure** — Access `hostContext` directly from the `App` class by @ochafik in [#139](https://github.com/modelcontextprotocol/ext-apps/pull/139)
### Bug Fixes
- **Responsive UIs** — Fixed narrow viewport handling for mobile and sidebar experiences by @ochafik in [#135](https://github.com/modelcontextprotocol/ext-apps/pull/135)
- **Sandbox notification fix** — Fixed `sandbox-ready` notification name to match implementation by @ochafik in [#160](https://github.com/modelcontextprotocol/ext-apps/pull/160)
- **Fixed non-UI tool registration** — Use `server.registerTool` for tools without UI and fix missing imports across examples by @ochafik in [#173](https://github.com/modelcontextprotocol/ext-apps/pull/173)
**Full Changelog**: https://github.com/modelcontextprotocol/ext-apps/compare/v0.1.1...v0.2.2

21
SECURITY.md Normal file
View file

@ -0,0 +1,21 @@
# Security Policy
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
## Reporting Security Issues
If you discover a security vulnerability in this repository, please report it through
the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
for this repository.
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
or pull requests.
## What to Include
To help us triage and respond quickly, please include:
- A description of the vulnerability
- Steps to reproduce the issue
- The potential impact
- Any suggested fixes (optional)

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