Repository navigation
Bridge App-registered tools and WebMCP in both directions (middle ground for #797) - #829
Conversation
Keep App.registerTool and the tools/list / tools/call channel as they are, and make the two tool surfaces interoperable: - Tools registered with App.registerTool are mirrored into document.modelContext when the document provides one, so WebMCP-enabled hosts discover them without speaking the Apps tools channel. - Tools the page registers on document.modelContext are served over tools/list / tools/call (declaring the tools capability when the page carries WebMCP tools at connect time), with toolchange relayed as notifications/tools/list_changed, so WebMCP-authored views work on MCP Apps hosts. - installWebMcpPolyfill() gives WebMCP-authored views a drop-in document.modelContext where neither the browser nor the host provides one. Registered tools shadow same-named WebMCP entries, which also keeps a tool's own mirror from being listed twice; only the frame's own tools are served. Both behaviors can be switched off per App (mirrorToolsToWebMcp / bridgeWebMcpTools).
Collapse mirrorToolsToWebMcp/bridgeWebMcpTools into a single webMcpBridge?: boolean (default true) that switches the whole two-way bridge off. Nobody has asked for per-direction control, and the shape can widen compatibly later (boolean -> boolean | object, as autoResize did) if someone does. Also forward webMcpBridge through useApp (previously only autoResize/strict reached the App), add a test that webMcpBridge: false disables both directions, and register test pairs for cleanup before connecting so a failed connect does not leak into later tests. Co-authored-by: ochafik <ochafik@anthropic.com>
Co-authored-by: ochafik <ochafik@anthropic.com>
PreviewPreview deployments for this PR have been cleaned up. |
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-lazy-auth
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
|
@ochafik what's the reasoning behind keeping both surfaces? Without going into either of their specifics, my main concerns are maintaining two surfaces when only one is needed, and potentially fragmenting adoption |
Minor release: the SDK gains WebMCP interoperability — tools registered with App.registerTool are mirrored into document.modelContext and tools the page registers there are served over the Apps tools/list channel, behind a new webMcpBridge option (default on), with installWebMcpPolyfill for views that need a drop-in model context (#829). The pdf-server example validates annotations with strict per-type schemas, fixing stamps rendering as the literal text "undefined" (#832). Workspace dependency ranges move to ^2.1.0 per the bump script. Co-authored-by: Claude <noreply@anthropic.com>
|
@idosal the main goal was for a spec-compliant mcp app w/ tools (e.g. pdf-server) to automatically work on hosts that only support webmcp tools (e.g. codex desktop). Also note I've removed the polyfill compared to this PR. But definitely deserves more discussion (ditched 2.1.0 😅) |
Makes sense, but I think we're still at a point where we can choose to deprecate the existing tools mechanism if we think that fits the longterm strategy (seeing as it's still in draft and not widely adopted yet). Definitely worth further discussion. |
|
+1 to choosing a single approach, I agree that we're still at a phase where we can remove the app tools implementation completely. Given the community adoption WebMCP is getting we probably don't want app developers to have to decide which one to implement, even if only because the mental models of both are different |
Summary
Middle-ground counterpart to #798, along the lines of Option 3 in #797 and of #796: instead of deprecating
App.registerToolin favor of WebMCP, keep both tool surfaces first-class and have the SDK bridge them in both directions.App.registerTool()are mirrored intodocument.modelContextwhen the document provides one, so a WebMCP-enabled host (one that consumesdocument.modelContext, e.g. natively) sees them without speaking the Appstools/listchannel. The wire path is untouched; mirroring is additive.document.modelContextare served over the existingtools/list/tools/calliframe channel — the SDK declares thetoolscapability on the View's behalf when the page carries WebMCP tools at connect time, and relaystoolchangeasnotifications/tools/list_changed.installWebMcpPolyfill()is the explore: add a thin bridge between WebMCP and MCP Apps Tools #796 "drop-in" for WebMCP-authored views that must also run where neither the browser nor the host providesdocument.modelContext.No new wire messages, no host-side API changes, no deprecations: hosts keep
bridge.listTools()/callTool(), views keepregisterTool(), and a WebMCP-only host like the ones #797 anticipates readsdocument.modelContextas it would on any page. Dedup rules: registered tools shadow same-named WebMCP entries (which also keeps a tool's own mirror from being echoed back), only the frame's own tools are served, and the spec text says a host able to discover a view's tools both ways consumes exactly one surface per view. The whole bridge is a per-App opt-out:webMcpBridge: false(defaulttrue; the shape can widen toboolean | objectlater if per-direction control is ever needed, asautoResizedid).Spec: the informative "Relation to WebMCP" section becomes a normative "WebMCP Interoperability" section with the WebMCP ↔ MCP mapping table (same shapes as #798: results as
{content: [], structuredContent}, non-object values wrapped as{result: v}, thrown errors asisError: true).Like #798, this is meant to make the option concrete for discussion, not to merge as-is — but unlike #798 it is non-breaking, so it would not need a major release.
Test plan
bun test srcpasses (357, including the interop tests driving a realServer↔Apppair overInMemoryTransportwith the SDK polyfill as the model context, and thewebMcpBridge: falseopt-out tests);tsc --noEmit,typedoc --treatValidationWarningsAsErrorsandnpm run prettierare clean. Not verified: native WebMCP (Chrome), real third-party hosts, the browser e2e suite.Known limits / open questions
tool.update()aborts-then-registers; an implementation whose signal-triggered unregistration is asynchronous could reject the re-registration as a duplicate (works with the SDK polyfill; same pattern as Prototype WebMCP for View-exposed tools, to illustrate #797 #798).connect()is picked up for mirroring at the next reconnect or tool mutation, but thetoolscapability cannot be declared retroactively — WebMCP tools registered late on a page that had none at connect time are not served (capability is fixed atui/initialize).toolchangefired by the App's own mirror registrations currently produces an extranotifications/tools/list_changedbeside the oneregisterTool()/enable()/… already send.toolsPermissions Policy delegated (feat: add support for 'tools' permission #691); where it is not, mirroring fails gracefully andtools/listremains the fallback.cc @liady @idosal
Generated by Claude Code