Inspiration
WebMCP makes web apps easier for agents to use by exposing structured tools directly. Doorman explores a boundary that becomes important as soon as those tools can change state:
capability_available != capability_exercised
A tool being discoverable should not automatically mean an agent has blanket authority to use it.
What it does
Doorman is a small shared-board demo where the human UI and the WebMCP tool surface operate over the same local state.
Four tools are normally registered: list_items, add_item, update_item, and request_approval. Their authority is intentionally narrower than their existence suggests. For example, update_item can only modify items created by the current agent session.
The more interesting case is delete_item. It is not merely registered and then denied by policy. It is genuinely absent from the exposed WebMCP surface until a human approves one specific deletion target. Once approved, the capability is registered dynamically, can be used once for that approved target, and is then unregistered again.
Each decision also produces a small local receipt that keeps policy decision and execution outcome separate.
Why WebMCP is a strong fit
Without WebMCP, an agent would need to infer what the page can do from presentation-level UI. Doorman instead exposes an explicit tool surface while preserving a separate authority boundary.
That creates a cleaner human-agent collaboration model:
- the agent can discover structured capabilities,
- application policy can constrain what those capabilities are allowed to affect,
- the human can grant a narrow one-shot capability when needed,
- and the capability can disappear again after use.
The result is not "the agent can use everything on the page." It is a negotiated command surface whose available tools can change with human approval.
How it works
The implementation is deliberately small and inspectable. src/doorman.js wraps document.modelContext.registerTool(tool, { signal }). The WebMCP callback and the local doorman.invoke() path share the same policy and execution boundary, so the demo does not maintain one policy for the UI and another for agents.
Dynamic registration uses the AbortSignal lifecycle: aborting the signal unregisters delete_item. Registration is transactional, and a browser-registration failure rolls back the local entry.
The one-shot delete grant is consumed at policy decision time rather than after the handler finishes. This prevents two concurrent invocations of the same approved target from both receiving authorization.
The application is static and has no backend, account, or database. Board state and receipts live only in browser localStorage.
Testing and evidence
The repository includes Node tests, a browser probe that exercises real getTools() / executeTool() in a compatible browser, and a recorded interactive WebMCP cycle using the public URL: add, list, request approval, human approval, one-shot delete, then dynamic unregistration.
That interactive run is treated as an integration result, not as evidence of an uncoached fresh-agent run.
Security limitation
Doorman is not a sandbox, IAM system, or strong browser security boundary. The page's own JavaScript could bypass its wrapper. The project demonstrates a cooperative application-level authority policy and the browser-visible distinction between a registered and an unregistered WebMCP tool.
Try it
Live app: https://dannybaanks.github.io/DoormanWebMCP
Public source: https://github.com/DannyBaanks/DoormanWebMCP
Demo video: https://youtu.be/3bBK1f5dAD4
License: MIT
Built With
- github
- html
- javascript
- webmcp
Log in or sign up for Devpost to join the conversation.