Vue Embedded AnalyticsWebMCP

Version 3.0.0

WebMCP is experimental. document.modelContext ships in Chrome only, behind an origin trial, and is not part of AG Studio's supported browser matrix. Treat this page as a pattern to build on, not a stable integration.

WebMCP lets a page publish its own capabilities as structured tools, so an AI agent running in the browser can call them directly.

api.getAiTools() returns Studio's built-in tools as live instances. Each one reads dashboard state when it executes, and none of them needs an AI harness, so they can be exposed to WebMCP as they are.

The tools do need the AI module registered, AgStudioModuleRegistry.registerModules([AgStudioAiModule]), because they read the field schema and dashboard context the module provides. Without it every tool still registers and runs, but returns an empty schema.

This is the clearest case of a Studio integration with no harness: the agent is the browser's, the UI is the browser's, and Studio contributes tools.

The example includes a small bridge, webmcpBridge.ts, which maps one Studio tool to one document.modelContext.registerTool call and keeps the registrations matching live state.

The Read-Only Slice Copy Link

api.getAiTools() returns fifteen tools, including a per-widget configureWidget factory and tools that only make sense inside Studio's own harness. The example publishes four: view_schema, view_page, view_widget, and execute_query.

Each is passed to the bridge as { tool, readOnly: true }, which the bridge turns into annotations.readOnlyHint. The caller declares that per tool, because a browser agent may relax its confirmation policy on a tool the page annotates as read-only.

Registering the mutating tools instead would hand an arbitrary external agent write access to the dashboard, and would push multi-step orchestration onto a client that has none of Studio's system prompts. Four read-only tools stay small, need no AI harness, and still exercise every branch of the reconcile pattern below.

ToolParametersBehaviour
view_schemaNoneStatic schema. Always callable.
view_pageNoneReports the active page. Always callable.
view_widgetLive enums of page and widget idsUncallable while the dashboard has no widgets or no pages.
execute_queryQuery shape derived from the loaded field schemaAlways callable. Its schema content changes with the field schema.

view_widget is the only one of the four that can become uncallable. view_page carries no page id, and execute_query falls back to a non-empty field list, so neither ever drops out of the advertised set.

The Reconcile Pattern Copy Link

A Studio tool's schema is derived from live state. tool.schema() re-reads that state on every call and returns undefined while the tool is uncallable. A WebMCP registration is the opposite: once registerTool resolves, the descriptor is fixed until its AbortSignal fires. Bridging the two takes a reconcile step.

The bridge keeps one AbortController per registered tool, plus a signature of the serialised inputSchema. On each pass, for every tool:

  • schema() returns undefined - abort the controller and drop the entry, so the tool is no longer advertised.
  • The signature matches the stored one - do nothing, so calling reconcile() more often than needed costs nothing.
  • The signature differs - abort the old controller, then register a fresh descriptor.
  • There is no entry - register.

A rejected registerTool drops the entry again, so the next pass retries instead of skipping a tool the browser never accepted. lastError() carries the reason until the following pass.

reconcile() is async and serialised. registerTool returns a promise, and Chrome does not specify what happens when a name is re-registered while its unregistration is still pending, so the bridge chains each abort and register rather than firing both in one tick.

What Drives a Reconcile Copy Link

The example calls reconcile() from onStudioReady, onStateUpdated and onRenderStateChanged, and calls destroy() from onStudioPreDestroyed. destroy() aborts every controller and also stops any pass still waiting on registerTool, which would otherwise register a tool against a destroyed Studio instance.

Those events do not cover every state change.

A clean api.setState() raises no stateUpdated event. Page and widget changes an application drives through setState are invisible to the public event surface, so the example calls reconcile() explicitly after each of its own setState calls.

Replacing the reactive data property swaps the rows but does not re-derive the field schema. execute_query keeps enumerating the previous source's field ids, and reconcile() cannot correct it, because the schema signature has not changed. The route to a new field set is api.destroy() followed by a fresh createStudio(), which rebuilds the bridge from a new api.getAiTools().

Adding or removing a calculated field does change the field schema, and it dispatches stateUpdated, so it reconciles on its own. The example uses that control to show execute_query re-registering, and the widget controls to show view_widget leaving and re-entering the advertised set.

Tool Parameters Copy Link

A Studio tool advertises exactly the parameters its own action needs. A command-backed tool exposes its command's schema verbatim, with no status or envelope parameters wrapped around it, and view_schema takes none at all.

The bridge advertises the schema's parameters as they come. The one exception is execute_query, whose query shape is a union of the aggregation and projection forms. The bridge nests that under a query key so the root stays an object, because several LLM providers reject a tool whose parameters root is anyOf - see JSON Schema Support.

Availability Copy Link

document.modelContext requires Chrome 149 or later, a secure context, and the origin trial enabled. For local development, turn on chrome://flags/#enable-webmcp-testing. The API is gated by the tools permissions policy, which defaults to self, so a cross-origin iframe needs allow="tools".

navigator.modelContext is the deprecated spelling of the same API. Use document.modelContext.

The example feature-detects document.modelContext. When it is missing, the example renders a notice, disables the button that calls a tool, and keeps running the reconcile bookkeeping, so the panel still shows which tools would be advertised in any browser.

Extending to the Mutating Tools Copy Link

The same bridge handles the mutating tools without change. They are AgAiTool instances with the same schema() and execute() shape. What changes is the risk.

Pass them as { tool, readOnly: false } so the bridge does not claim readOnlyHint for a tool that writes, and use exposedTo on the register options to limit which agents can reach a tool. Authenticating and permissioning the WebMCP surface is out of scope for the example, and is yours to design.

Next Copy Link