Expose a React Workflow to AI Agents with WebMCP
A person opens a trip draft and asks an assistant: Prepare my SFO to JFK trip for November 12–15, 2026, for a customer workshop. Use economy for one traveler, check what needs attention, and stop before submission. The application already knows how to edit a draft, validate its fields, and save it.

A person opens a trip draft and asks an assistant:
Prepare my SFO to JFK trip for November 12–15, 2026, for a customer workshop. Use economy for one traveler, check what needs attention, and stop before submission.
The application already knows how to edit a draft, validate its fields, and save it. WebMCP gives a compatible agent a structured way to request those operations in the page the person has open.
I first explored this pattern in a local prototype inside an event-draft wizard. We exposed its title and dates through three tools, routing changes through the same React state handler used by the form. That experiment used synthetic data and direct browser calls.
Trip Workbench, which I built and maintain, carries the pattern into a public demo with an authenticated backend and persisted drafts. Its useful scope is still small: read, update, and validate the current draft.
This walkthrough uses commit 4b3f370. The native and model-driven Trip Workbench journeys remain unverified, and its refresh and receipt-recovery paths still have known local-edit reconciliation defects. Use a clean, disposable draft for the exercise below.
The backend API continues to perform the business operation. WebMCP supplies a discoverable entry point into that operation from the active browser workflow.
That distinction matters for a stateful form. The page knows which draft is open, whether the person has unsaved edits, and how the visible controls relate to application state. A handler can consult that context before calling the backend.
Registering tools does not install an agent or select a model. A compatible agent client must discover and invoke them. This demo also does not require a separate remote MCP server for its page tools.
The WebMCP explainer describes this cooperative interaction between the page, person, and agent. Whether it reduces work for a particular user is something to evaluate.
Trip Workbench’s Gemini “Suggest trip details” feature is a separate integration. That button asks the application server to generate a proposal. The WebMCP path allows an external agent to invoke operations registered by the page; it does not depend on the application’s Gemini key.
The three tools form one bounded interaction:
These are domain operations. The agent can update a trip without learning which dropdown controls the destination or how the date picker is arranged.
The scope also has explicit edges. A person creates and opens the draft. The tool set contains no create, submit, approval, or ticket-purchase operation.
An update saves the draft immediately when accepted by the backend. Unlike the Gemini proposal feature, there is no second Apply button for this path. The tool description should make that consequence clear.
Leaving submission out of the tool set limits this interface. It does not guarantee human-only submission if an agent has additional browser capabilities and can operate the ordinary UI. Any required confirmation must be enforced at the application action itself.
An update needs more than a generic patch: object.
The agent needs to know the editable fields, allowed values, date representation, and behavior of omitted or cleared values. Trip Workbench allows:
An omitted field keeps its saved value. Explicit null clears a nullable field. The patch must contain at least one field, and unknown keys are rejected.
The shared schema module combines that patch contract with the operation context:
export function createUpdateTripDraftArgsSchema ( activeDraftId: string ) { return z. object ({ draftId: z. literal ( activeDraftId ), expectedRevision: ExpectedRevisionSchema, commandId: CommandIdSchema, patch: TripPatchSchema, }). strict (); } Enter fullscreen mode Exit fullscreen mode The literal draft ID limits this registration to the currently open resource. It is not an authorization check; the server still decides whether the current user may change that draft.
The repository derives the published JSON Schema from its Zod definition and parses incoming arguments again inside the handler. Keep runtime validation even when a browser inspects the schema. Custom checks, including calendar validity and relationships between fields, still need application code.
In the earlier event-draft prototype, an extra undeclared argument reached the handler in the tested Chrome build. The strict parser rejected it. That observation is a practical reason to keep validation at the application boundary rather than assume discovery metadata enforces every rule.
The two operation identifiers have different jobs:
A new command based on an outdated revision should conflict. An exact retry can return the original command receipt without applying the change again. These behaviors belong to the backend implementation.
Chrome’s current imperative API uses document.modelContext. Feature-detect it and preserve the manual workflow when it is unavailable.
In React, register the tools when the relevant editor context is available, then remove them when that context changes. The registration effect in Trip Workbench follows this shape:
useEffect (() => { if (! draftId || draftStatus!== " draft " ) return; const controller = new AbortController (); registerTripDraftTools ({ draftId, canMutate: Boolean ( canMutate ), coordinator: props. coordinator, signal: controller. signal, }); return () => controller. abort (); }, [ draftId, draftStatus, canMutate, props. coordinator ]); Enter fullscreen mode Exit fullscreen mode The adapter passes that signal to registerTool. An editable traveler gets all three tools. An observer gets read and validate.
Capability-based registration makes the available interface clearer, but backend permission checks remain necessary. Hiding a write tool does not replace authorization on its HTTP endpoint.
Two signals have different responsibilities: the registration signal removes tools; the execution signal requests cancellation of one invocation. Chrome documents that unregistering does not cancel executions already in flight.
The handler therefore needs its own execution checks, and asynchronous results must still belong to the active draft. A registration effect alone does not solve shared-editor concurrency.
Also verify actual discovery. The presence of document.modelContext establishes API availability, not that every registration succeeded.
Chrome’s current documentation also points React developers to the experimental use-webmcp-tool package. A hook can remove registration boilerplate, but it does not replace the contracts in this example: the active draft ID, runtime schema parsing, coordinator guards, backend authorization, and revision-aware persistence still belong to the application. I use the imperative API here so those boundaries remain visible.
The WebMCP adapter should translate and validate arguments, then delegate to the application operation. It should not directly assign DOM input values or maintain a second set of trip rules.
Here is a shortened update handler using the repository’s schema and coordinator helpers. It assumes draftId and coordinator come from the active registration context:
const updateTool = { name: " update_trip_draft ", description: " Save allowed changes to the open trip draft. " + " Read its revision first. Refuses unsaved local edits.
" + " Does not submit the request. ", inputSchema: updateTripDraftJsonSchema ( draftId ), async execute ( args: Record < string, unknown >, opts?: { signal?: AbortSignal }, ) { if ( opts?. signal?. aborted ) { return JSON. stringify ({ ok: false, error: { code: " CANCELLED ", message: " Cancelled before dispatch. " }, }); } const parsed = createUpdateTripDraftArgsSchema ( draftId ). safeParse ( args ); if (! parsed. success ) { return JSON. stringify ({ ok: false, error: { code: " INVALID_INPUT ", message: " Invalid draft update.
" }, }); } const result = await coordinator. applyAgentPatch ( parsed. data. patch, parsed. data. expectedRevision, parsed. data. commandId, { signal: opts?. signal }, ); return JSON. stringify ( result ); }, }; Enter fullscreen mode Exit fullscreen mode Inside the adapter, connect that definition to the browser. Here, registrationSignal is the signal supplied by the React effect:
const context = document. modelContext; if ( context ) { await context. registerTool ( updateTool, { signal: registrationSignal, }); } Enter fullscreen mode Exit fullscreen mode The full adapter registers all three definitions and handles registration failures, execution exceptions, and field-level input errors.
The coordinator manages the editor’s saved and local state and calls the authenticated API. The server owns access checks, revision checks, domain validation, and persistence. Keeping those responsibilities together means manual saves and agent updates use the same business rules.
The result should describe the operation’s actual outcome. Trip Workbench returns an application envelope with ok, followed by either data or error. A successful update includes its command receipt and resulting revision. Validation returns its own findings.
For example, PREMIUM_CABIN means the request requires review. It does not mean an exception was approved. Likewise, a successful tool invocation can return ok: false; the agent must inspect the application result.
Useful errors explain what can happen next:
The recovery implementation deserves separate testing. A disconnected request does not establish that a server write was rolled back.
Use a browser build that supports the documented API. Chrome provides a local WebMCP testing flag and an origin-trial path. Record the full browser version because the API is evolving.
Start the app, sign in as an editable demo user, create a fresh draft, and remain on its editor. Save or discard any local edits before starting.
Chrome’s Application → WebMCP pane lists available tools and supports inspecting and invoking them. Navigating to a different application route can unmount the editor and remove its tools.
For a repeatable console exercise, this helper discovers a tool and parses the application’s JSON-string result:
async function callTripTool ( name, args ) { const tools = await document. modelContext. getTools (); const tool = tools. find (( entry ) => entry. name === name ); if (! tool ) throw new Error ( `Tool unavailable: ${ name } ` ); const raw = await document. modelContext. executeTool ( tool, args ); if ( raw === null ) throw new Error ( " Tool navigated without a result " ); const result = JSON. parse ( raw ); if (! result. ok ) { throw new Error ( ` ${ result. error. code }: ${ result. error. message } ` ); } return result. data; } const draftId = location. pathname. match ( /^ \/ trips \/([^/] + ) $/ )?.[ 1 ]; if (! draftId ) throw new Error ( " Open a draft editor first " ); const before = await callTripTool ( " get_trip_draft ", { draftId }); const updated = await callTripTool ( " update_trip_draft ", { draftId, expectedRevision: before. revision, commandId: crypto. randomUUID (), patch: { origin: " SFO ", destination: " JFK ", departureDate: " 2026-11-12 ", returnDate: " 2026-11-15 ", tripType: " round_trip ", passengerCount: 1, cabin: " economy ", purpose: " customer workshop ", }, }); console. log ( await callTripTool ( " validate_trip_draft ", { draftId, expectedRevision: updated. toRevision, })); Enter fullscreen mode Exit fullscreen mode The current browser API takes a discovered tool descriptor and an arguments object. This adapter returns a JSON string, which is why the helper parses the result. Keep those input and output conventions distinct.
Inspect the visible fields, returned revision, and validation findings. Refresh the page to check persistence. This tests native invocation with arguments you chose.
For a developer-agent test, Chrome documents a DevTools MCP route using --categoryExperimentalWebmcp. It exposes list_webmcp_tools and execute_webmcp_tool to the connected coding agent.
That is a development and debugging connection. It should be described separately from an end-user browser-agent deployment.
With the agent connected to the signed-in browser session, use this prompt:
Use the WebMCP tools on the currently open Trip Workbench draft. Prepare a round trip from SFO to JFK, November 12–15, 2026, for one traveler in economy, with purpose “customer workshop.” Read the draft first, update it, validate the resulting revision, and explain anything requiring my attention. Stop before submission.
Check whether it discovers the tools, uses the actual draft ID and revision, sends valid arguments, and accurately explains the returned findings. Then inspect the saved result yourself.
The repository results do not yet establish the last two for Trip Workbench. Record the commit, browser, agent, model, prompt, tool calls, persisted state, and any human correction when running them.
For product feedback, ask whether delegating the task was easier than completing the form and whether the person understood what changed. Include review and correction time in that comparison.
The value of these three tools is a small, understandable interface to an existing workflow. They let us investigate whether an agent can prepare useful draft changes while the application retains its state, rules, and visible user experience.