aiengineering

Your feedback board is now agent-ready, with WebMCP

WebMCP lets a web page hand tools to the AI agent driving the browser. SeggWat's dashboard and public boards now do exactly that, built on a small open-source Rust crate.

Hauke Jung
|September 02, 2026|
6 min read

In May I wrote about building an MCP server into SeggWat. That covers one direction: an agent running somewhere else, holding an API key, asking your feedback data questions.

This post is about the other direction. The agent is already inside the browser tab, looking at the page your user is looking at. What if the page could hand it tools?

That is what WebMCP does, and as of today both the SeggWat dashboard and every public feedback board publish tools to it.

Two directions, two audiences

The hosted /mcp endpoint is for your team. You connect Claude, run a triage pass overnight, and wake up to a sorted inbox. It needs a key, and every call re-enters SeggWat through the API.

WebMCP is for whoever has the page open. No key to mint, nothing to revoke when the tab closes. The tools run inside the session the person is already signed into, through the same server functions the buttons call. One authorization path, not two.

For a feedback product that second direction matters more than it first looks. The people who give feedback are, by definition, on a page. Increasingly they have an agent with them.

What WebMCP is

WebMCP is a proposal from Google and Microsoft engineers in the W3C Web Machine Learning group. It went into a Chrome origin trial with version 149, and Edge ships it behind a flag.

The idea is small. A page calls document.modelContext.registerTool() with a name, a description, a JSON Schema for the arguments, and an execute function. An agent driving that browser discovers the tools and calls them, instead of scraping the DOM and guessing which button is "upvote".

It borrows its vocabulary from MCP on purpose: tools, input schemas, content blocks, isError. If you have written an MCP server, you already know the shape.

The spec is not finished. The API surface changed twice this year. I will come back to what that means below.

What shipped

In the dashboard, opening a project registers eight tools for the duration of the visit: list, read, create and update feedback, project stats, the project list, a navigation tool, and one that tells the agent which project it is looking at. Leave the project and the tools disappear with it. An agent never holds a tool that acts on a screen you have navigated away from.

One deliberate choice: resolving feedback through a tool does not email the submitter unless the agent passes notify_submitter. An agent doing a bulk triage pass should not mail a batch of your customers because a status flipped.

On the public board, visitors get ten tools for the things a visitor can already do: search ideas, read one, vote and unvote, submit a new idea, comment, browse the changelog, and move between pages. Search comes first for a reason. "Has anyone asked for X? Upvote it, otherwise post it for me" is the request an agent will get most, and an agent that checks before posting cuts duplicate ideas, which is what your triage queue wanted anyway.

Identity works exactly as it does for clicks. If your board requires email verification, an anonymous visitor's agent that tries to vote gets a clear error telling it the visitor needs to verify first, through the board's own prompt. The agent cannot route around that step, because the tool calls the same endpoint the button does.

It is on by default and there is a toggle under Settings → Public Board if you would rather your board stayed agent-free. Same-origin only, so a board embedded in an iframe on your site stays invisible to agents unless you opt it in with allow="tools".

Built on a small Rust crate

SeggWat's dashboard and board are Rust compiled to WebAssembly, and there were no Rust bindings for WebMCP. So I wrote them and published them as webmcp-rs, MIT licensed, with a live demo you can poke at.

Five lines of js_sys::eval will register a tool. The crate exists for the two things those five lines get wrong in practice.

The first is schema drift. The JSON Schema the agent plans against and the struct your handler deserializes are the same information written twice, and they diverge on the first refactor. register_typed derives the schema from the argument type, so doc comments become the field descriptions the model reads:

rust
use webmcp::{Tool, ToolResult};

#[derive(serde::Deserialize, schemars::JsonSchema)]
struct VoteArgs {
    /// Id of the idea to upvote.
    idea_id: String,
}

let handle = Tool::new("vote_idea")
    .description("Upvote an idea on the board the visitor has open.")
    .register_typed(|args: VoteArgs| async move {
        vote(&args.idea_id).await.map(|_| ToolResult::text("voted"))
    })?;

The second is ghost tools. A tool registered by a component that has since unmounted still answers calls and acts on state nobody is looking at. That failure mode is the reason the spec dropped provideContext(). In the crate, dropping the handle unregisters the tool, and the use_tool hook parks the handle in a Dioxus component's state so unmounting is enough.

Off the browser, on the server-rendered half of a fullstack app, registration simply reports "unsupported" instead of refusing to compile.

Build-in-public footnote: the first version shipped every tool with an empty schema. serde_wasm_bindgen turns a JSON object into a JavaScript Map, and a Map stringifies to {}. Nothing in Rust could catch that. The demo site caught it in the first minute. Test the thing in a browser.

Try it

  • Chrome 149 or newer: enable chrome://flags/#enable-webmcp-testing, install the Model Context Tool Inspector extension, and open your board or the dashboard. The extension lists the tools and lets you call them by hand.
  • Any browser: the demo falls back to Google's polyfill, so it works without the flag.
  • From the console, on a board with the flag on:
js
const tools = await document.modelContext.getTools();
tools.map(t => t.name);

What I am watching

The API moved twice in 2026 and it will move again before it ships on by default, which every projection I have seen puts in late 2026. The crate reaches the browser API reflectively rather than through hard bindings, so an older or newer Chrome degrades to "unsupported" instead of throwing. Expect a few small crate releases as the draft settles.

The declarative half of the spec, where a plain HTML form becomes a tool through attributes, is interesting for the feedback widget. That is a separate post.

If you run a SeggWat board, it is already agent-ready. If you are building a Rust web app and want the same, the crate is on crates.io and the source is on GitHub.

Related Posts

Blog