Commit graph ext-apps/src
Author SHA1 Message Date
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
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
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
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
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
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
Suriya Prakash
0f6d653b4a
test: add unit tests for buildAllowAttribute (#541) 2026-04-24 11:36:00 +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
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
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
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
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
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
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
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
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 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
Hryhorii (Greg) Avcharov
38e1f7af14
refactor: remove unnecessary type assertions (#471) 2026-03-06 14:35:04 +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
Suriya Prakash
0af4cdc305
fix: remove duplicate --color-text-ghost from McpUiStyleVariableKey (#523) 2026-03-06 13:46:04 +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
Anton Pidkuiko MacBook
9340bd800e
test: add App → AppBridge integration test for downloadFile 2026-02-24 16:57:18 +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
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
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
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
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
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