-
-
An agent can ask. Only the page owner can grant.
-
One answer went to the agent. Twenty-eight rows stayed on the page - with a receipt saying so.
-
The agent can propose a new data source. It cannot create one. The banner waits for the page owner.
-
Rejected. No backend action occurred - no fetch, no compute, no cost.
-
Approved. The source is being created - a few seconds, in the same conversation.
-
A tool that did not exist a minute ago, now in the agent's toolbox. Created after human approval.
-
Not a claim: the source, the records consulted, the model that computed the answer, and backend verification.
-
ChatGPT calling ask_data_source in its in-app browser. The page shows every argument as it happens.
-
Ask-only by default: the agent may ask questions, but the capability is not a directly callable tool.
-
Switched to direct, the same sources become fetchable. The page owner decides, per source.
-
The same registry, published as an A2A v1.0 Agent Card - discoverable by agents that never open a browser.
-
scripts/acceptance.sh asserts the boundary against the live deployment, including that the dataset was not returned through WebMCP.
Inspiration
Agent protocols assume every company will build and host an agent. Most won't. When agents show up, most of the web will still be ordinary websites - full of things worth asking about, with no way to be asked and no way to say no.
Why WebMCP fits this: a boundary between what an agent can ask and what it can take only exists when the page has an owner. Backend MCP is server-to-server with nobody watching, and its tool list is fixed config. WebMCP is different because a person is already there.
What it does
The agent asks. You grant.
The agent can call propose_data_source(url, description). It doesn't fetch anything. It puts an approval banner on the page and stops.
You read it and click Approve. Konect4AI turns the URL into a structured API, the page refreshes its capability list, and the new tool shows up in the agent's toolbox - same conversation, no reload. Click Reject and nothing runs.
The agent asks. It doesn't get the data.
ask_data_source(jobId, question) is answered by my own model, using my own API key. The tool returns the answer. The rows behind it are shown to me, in the panel next to it, and stripped before the agent sees anything.
The page shows this split:
- SENT TO AGENT - the question and one answer.
- NOT RETURNED THROUGH WEBMCP - the rows the answer came from, with a receipt: source, records consulted, which model computed it, backend verification.
This is a boundary on what the tool returns. It is not browser-level isolation, and the page says so. I'd rather claim something smaller and have it be true.
You decide per source what an agent can do.
Every source has an access mode. ASK-ONLY (default) means the agent can ask questions but the capability isn't registered as a callable tool. DIRECT means it is. Flipping it re-registers the toolset right away.
I added this after watching ChatGPT do the obvious thing: given both a bounded ask tool and an unbounded fetch tool, it used the fetch tool.
People and agents use the same tools in the same place.
There's an ask row under the two panels. It calls the same endpoint the agent calls and lands in the same history, labeled human instead of webmcp.
And the same registry works for agents that never open a browser.
A small A2A adapter publishes an Agent Card at /.well-known/agent-card.json, built live from the same registry. A site I connected ten minutes ago is discoverable by an external agent - without anyone building an agent for it.
How we built it
The tools aren't mocks. Each one runs on Konect4AI, a production system that turns a URL into a structured, scheduled, queryable API.
POST /api/v1/scraping/requests- creates a capability, behind approvalGET /api/v1/scraping/jobs- the tool-to-job mappingPOST /api/byok/ask- the answer path that keeps the data server-sidePOST /mcp/{key}- existing MCP capabilities, turned into WebMCP toolsGET /generated/{id}- the rows shown to me, never to the agent
Registration handles the spec moving: document.modelContext ?? navigator.modelContext, a proper CallToolResult, and the spec's AbortSignal passed through to the backend fetch, so cancelling an agent actually cancels the work.
scripts/acceptance.sh runs 18 checks against the live deployment, including the boundary itself: the page gets the rows, the receipt says rawRowsReturnedThroughWebMCP: false, an unknown jobId is rejected with a list of what's actually available, and no credential shows up in any public response.
Challenges we ran into
An agent takes the shortest path you leave open. Twice. First it called the unbounded fetch tool instead of the ask tool. Then, blocked, it read the source URL out of a tool description and went to fetch the data itself. I removed source URLs from agent-visible descriptions and made ask-only the default. Both. fixes came from watching a real agent, not from thinking about one.
My own robots.txt check blocked one of my proposals. I approved a source and got back robots_disallowed. GitHub disallows /*/*/commits/ under User-agent: *, which covered three sources I was already using. I moved them to the public API. Consent isn't only about what an agent takes from a page. It's about what the page takes from the web.
Creating a capability costs real compute. A public URL that lets any visitor trigger paid fetching is a liability. Instead of removing it, I put a human in front of it - which turned out to be the design.
Accomplishments that we're proud of
The boundary is checkable, not just claimed. A judge doesn't have to believe the description: the receipt is on the page, and the acceptance script tests it against the live deployment.
I shipped against a moving spec and kept the claims small enough to survive it. WebMCP moved from navigator to document; A2A moved from v0.2.5 to v1.0 with renamed methods. The app supports both WebMCP locations, and the README names the exact A2A subset implemented and lists what isn't.
Every tool runs on a production system. The earthquake count is different every time, because the data is live.
What we learned
The interesting part of WebMCP isn't that a page can expose tools. It's that the page has an owner and the visiting agent doesn't. Most of the design questions come from that, and most of the answers are about holding back rather than adding capability.
What's next for Ask, Don't Take
The spec already lets a page expose tools to specific other origins (exposedTo) and discover theirs (getTools({fromOrigins})), within one page tree and behind the tools Permissions Policy. That would make cooperating sites into each other's tools, with the human still holding the session. After that: per-tool consent scopes, an exportable approval log, and revoking a tool mid-conversation so the agent watches it disappear.
Built With
- a2a
- byok
- codex
- hackathon
- json-rpc
- konect4ai
- model-context-protocol
- next.js
- node.js
- openai
- react
- typescript
- vercel
- webmcp
Log in or sign up for Devpost to join the conversation.