Skip to content

Bridge App-registered tools and WebMCP in both directions (middle ground for #797) - #829

Merged
ochafik merged 3 commits into
mainfrom
webmcp-interop-2
Oct 27, 2026
Merged

ochafik merged 3 commits into
mainfrom
webmcp-interop-2

Conversation

@ochafik

@ochafik ochafik commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Middle-ground counterpart to #798, along the lines of Option 3 in #797 and of #796: instead of deprecating App.registerTool in favor of WebMCP, keep both tool surfaces first-class and have the SDK bridge them in both directions.

  • Apps → WebMCP: tools registered with App.registerTool() are mirrored into document.modelContext when the document provides one, so a WebMCP-enabled host (one that consumes document.modelContext, e.g. natively) sees them without speaking the Apps tools/list channel. The wire path is untouched; mirroring is additive.
  • WebMCP → Apps: tools the page registers on document.modelContext are served over the existing tools/list / tools/call iframe channel — the SDK declares the tools capability on the View's behalf when the page carries WebMCP tools at connect time, and relays toolchange as notifications/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 provides document.modelContext.

No new wire messages, no host-side API changes, no deprecations: hosts keep bridge.listTools() / callTool(), views keep registerTool(), and a WebMCP-only host like the ones #797 anticipates reads document.modelContext as 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 (default true; the shape can widen to boolean | object later if per-direction control is ever needed, as autoResize did).

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 as isError: 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 src passes (357, including the interop tests driving a real Server ↔ App pair over InMemoryTransport with the SDK polyfill as the model context, and the webMcpBridge: false opt-out tests); tsc --noEmit, typedoc --treatValidationWarningsAsErrors and npm run prettier are clean. Not verified: native WebMCP (Chrome), real third-party hosts, the browser e2e suite.

Known limits / open questions

  • Mirror re-registration on 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).
  • A model context that appears only after connect() is picked up for mirroring at the next reconnect or tool mutation, but the tools capability cannot be declared retroactively — WebMCP tools registered late on a page that had none at connect time are not served (capability is fixed at ui/initialize).
  • toolchange fired by the App's own mirror registrations currently produces an extra notifications/tools/list_changed beside the one registerTool()/enable()/… already send.
  • Native registration inside the sandbox needs the tools Permissions Policy delegated (feat: add support for 'tools' permission #691); where it is not, mirroring fails gracefully and tools/list remains the fallback.

cc @liady @idosal


Generated by Claude Code

ochafik and others added 3 commits October 26, 2026 20:36
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>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Preview

Preview deployments for this PR have been cleaned up.

@pkg-pr-new

pkg-pr-new Bot commented Oct 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/ext-apps

npm i https://pkg.pr.new/@modelcontextprotocol/ext-apps@829

@modelcontextprotocol/server-basic-preact

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-preact@829

@modelcontextprotocol/server-basic-react

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-react@829

@modelcontextprotocol/server-basic-solid

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-solid@829

@modelcontextprotocol/server-basic-svelte

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-svelte@829

@modelcontextprotocol/server-basic-vanillajs

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vanillajs@829

@modelcontextprotocol/server-basic-vue

npm i https://pkg.pr.new/@modelcontextprotocol/server-basic-vue@829

@modelcontextprotocol/server-budget-allocator

npm i https://pkg.pr.new/@modelcontextprotocol/server-budget-allocator@829

@modelcontextprotocol/server-cohort-heatmap

npm i https://pkg.pr.new/@modelcontextprotocol/server-cohort-heatmap@829

@modelcontextprotocol/server-customer-segmentation

npm i https://pkg.pr.new/@modelcontextprotocol/server-customer-segmentation@829

@modelcontextprotocol/server-debug

npm i https://pkg.pr.new/@modelcontextprotocol/server-debug@829

@modelcontextprotocol/server-lazy-auth

npm i https://pkg.pr.new/@modelcontextprotocol/server-lazy-auth@829

@modelcontextprotocol/server-map

npm i https://pkg.pr.new/@modelcontextprotocol/server-map@829

@modelcontextprotocol/server-pdf

npm i https://pkg.pr.new/@modelcontextprotocol/server-pdf@829

@modelcontextprotocol/server-scenario-modeler

npm i https://pkg.pr.new/@modelcontextprotocol/server-scenario-modeler@829

@modelcontextprotocol/server-shadertoy

npm i https://pkg.pr.new/@modelcontextprotocol/server-shadertoy@829

@modelcontextprotocol/server-sheet-music

npm i https://pkg.pr.new/@modelcontextprotocol/server-sheet-music@829

@modelcontextprotocol/server-system-monitor

npm i https://pkg.pr.new/@modelcontextprotocol/server-system-monitor@829

@modelcontextprotocol/server-threejs

npm i https://pkg.pr.new/@modelcontextprotocol/server-threejs@829

@modelcontextprotocol/server-transcript

npm i https://pkg.pr.new/@modelcontextprotocol/server-transcript@829

@modelcontextprotocol/server-video-resource

npm i https://pkg.pr.new/@modelcontextprotocol/server-video-resource@829

@modelcontextprotocol/server-wiki-explorer

npm i https://pkg.pr.new/@modelcontextprotocol/server-wiki-explorer@829

commit: 1da5089

@ochafik
ochafik requested review from idosal and liady October 27, 2026 05:37
@ochafik
ochafik merged commit 4fb60b3 into main Oct 27, 2026
23 checks passed
@idosal

idosal commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

@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

@ochafik ochafik mentioned this pull request Oct 27, 2026
ochafik added a commit that referenced this pull request Oct 27, 2026
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>
@ochafik

ochafik commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

@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 😅)

@idosal

idosal commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

@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.

@liady

liady commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

+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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants