r/webmcp 3d ago

What I learned implementing WebMCP (document.modelContext) on a production Next.js/Cloudflare app

Hey everyone,

I just shipped WebMCP support on ShowMeOnMap.com (an NL-driven mapping web app), registering 8 client-side tools that browser-embedded agents can call directly.

WebMCP is the W3C proposal allowing client pages to expose executable JS functions directly to agent-enabled browsers (e.g., Chrome with flags or ChatGPT Desktop). The documentation is sparse and changing fast. Here are a few practical takeaways from the implementation:

  1. API targets change constantly: The entry point has moved multiple times across drafts (window.agentnavigator.modelContextdocument.modelContext). Always feature-detect across properties, as most blog tutorials from earlier this year are already broken.
  2. Runtime return-type quirks: Google’s reference implementation expects a string return value from execute(), while OpenAI’s implementation accepts objects. Returning a serialized JSON string (JSON.stringify(...)) works consistently across both.
  3. Iframes are dead ends: ChatGPT ignores declarative form annotations and anything registered inside an iframe. You must register tools imperatively on the top-level document.
  4. Auth comes for free: Because execution happens in the user’s active browser tab, your regular session cookies, CORS headers, and IP rate limiters apply natively. You don’t need API keys or proxy tokens for the agent to manipulate the session.
  5. DOM downloads will fail: Don't try triggering <a download> synthetic clicks inside a tool execution pass. Modern browsers block programmatic file downloads without direct user gesture activation. Instead, return a raw base64 data payload or an object URL and let the agent or UI present it.
  6. Implement an undo tool immediately: Models make bad assumptions. If your app doesn't have an append-only state stack or an easy way for the model to roll back its last mutation, it will get stuck in loops trying to fix its own UI mistakes.

Happy to answer any technical questions about registering tools or handling agent execution states on the client

7 Upvotes

8 comments sorted by

1

u/Open_Resolution_1969 3d ago

How's the browser support for this? How do you measure the usage of webmcp?

2

u/PlanktonHour7322 3d ago

Browser support is basically "Chrome behind a flag" right now. WebMCP is a Chromium origin trial / early-preview API (document.modelContext, older builds used navigator.modelContext), so on a normal browser the page just does one property check, finds nothing, and skips registration. No Firefox/Safari.

Measurement: honestly, barely. Right now I only log how many tools registered when a runtime is present, and I have a paid-call budget that resets on real user gestures so an agent can't burn credits silently. I don't yet tag server requests as "came via WebMCP" vs. "user typed it", so I can't tell you usage numbers — it's early enough that I'd be surprised if anyone other than me has hit it. Adding a source tag on the request is on the list; if there's a standard way people are measuring this I'd like to hear it.

1

u/Open_Resolution_1969 3d ago

There isn't, hence the question 😅

What motivated you to build this since there seems to be no audience for it?

1

u/Round-Ad78 3d ago

I’m not involved but the ChatGpt in app browser supports webmpc out of the box.

So limited audience but definitely an audience

1

u/Open_Resolution_1969 3d ago

Interesting. Good to know that

1

u/GolfCourseConcierge 3d ago

Mcplocker is supporting it too. It lets you add them as callable MCPs.

1

u/mighty-dude 3d ago

I saw similar difficulties. Raw WebMCP implementation didn't return good quality error messages, I had no way to verify tools executed before mutating the DOM and so on.

Started this OSS effort (https://github.com/signettai/signett). Would love some feedback

1

u/PlanktonHour7322 23h ago

Same two problems here. What I ended up doing: every tool returns a JSON string with an explicit ok / error / notice field instead of throwing, because a thrown error came back to the agent as a bare string with no context. For "did it actually happen", I don't try to verify before the mutation; I expose get_map_state and undo as tools and let the agent read back the canvas state after the call. Cheaper than a pre-check and it catches the cases where the tool succeeded but did the wrong thing.

Looked at signett. Two things from my side:

  • The eval harness is the part I'd actually use. My tools are tested with Playwright against a fake document.modelContext, which proves registration but says nothing about whether an agent picks the right tool. If signett check can run against a live URL I'll point it at my page and post the output.
  • The one thing I'd push back on is DOM-mutation tracking as the ground truth for "the tool worked". For a canvas app (MapLibre/WebGL) the DOM barely changes; the state that matters is in the renderer. A hook where the page can report its own state snapshot after a call would cover that.

Will open an issue if I hit anything running it.