Inspiration
I grew up with a simple idea of what it means to help someone: when you see someone struggling, you step in. But there's a line between helping and deciding for them and that line gets easy to cross once the "helper" is an AI agent that can act in seconds, faster than a person can watch.
Picture yourself managing something important through an agent; trusting it to search, decide, and act on your behalf and asking: at what point did I stop delegating a task and start surrendering control of the outcome?
WebMCP made that question urgent instead of hypothetical. It gives agents hands "the ability to act directly on a website instead of guessing through a UI". That's a genuine leap forward. But it standardizes how a tool gets invoked, not whether that invocation should be trusted, permitted, or safe. Somebody has to sit in that gap. Handrail is my answer: delegate the task, keep the authority.
What it does
Handrail is an accessibility-first human authority layer for WebMCP agents, demonstrated through RefillRx, a fictional accessible prescription portal. A user sets deterministic boundaries: which medications an agent may touch, what actions it may take, how much it may spend and every WebMCP tool invocation is checked against those boundaries through a four-gate pipeline before anything happens:
- Tool-Trust — catches typosquatted tool names and instruction-injected descriptions before they ever reach the user
- Authority Check — a deterministic, zero-hallucination policy evaluator checking scope, spend limits, and eligibility
- Human Confirmation — an accessible modal, keyboard- and screen-reader-navigable, that appears only when an action is both authorized and consequential
- Execution — the actual pharmacy action, with an immutable, five-facet audit receipt generated for every decision
A tool that's unauthorized never reaches confirmation at all and approval only ever gets asked for things that were already inside the user's own stated boundaries.
How I built it
Vanilla HTML, CSS, and ES modules — zero frameworks, no build step. Tools register natively via document.modelContext.registerTool(), with an internal fallback harness running the identical gate logic when native WebMCP isn't present, so the security model never differs by environment. The audit trail persists across reloads via IndexedDB. Trust heuristics use Levenshtein distance and separator/casing normalization to catch tool-name squatting; the authority layer is a pure, deterministic evaluator with no LLM in the trust path itself — every block or approval is explainable and reproducible.
Testing wasn't limited to my own harness. I verified real native WebMCP registration in Chrome with chrome://flags/#enable-webmcp-testing enabled, and confirmed real external agents.ChatGPT's in-app browser and Gemini via Google's Model Context Tool Inspector could discover Handrail's tools and get correctly gated, blocked, or asked for confirmation, entirely outside my own UI.
Challenges I ran into
The hardest bug wasn't in my security logic, it was in trusting my own status indicators. Early on, my "Native WebMCP Active" badge reported success based purely on document.modelContext existing, without confirming registration actually reached the browser's real tool registry. Testing against Google's own Model Context Tool Inspector exposed the gap: registerTool() is an async API, and my code wasn't awaiting it, so registration promises were rejecting silently while the UI confidently claimed everything worked. A second, related bug then surfaced: a single unhandled reference error in a badge-rendering function was throwing during page load and silently preventing every button's event listeners from attaching — meaning the entire demo could look broken for reasons that had nothing to do with the security logic itself.
Both bugs shared a lesson: a demo that looks like it's working and a demo that's proven to work are not the same thing, and the gap between them only shows up when you test against ground truth you don't control, "a real browser API, a real external agent" rather than your own harness.
Accomplishments that I'm proud of
Getting a genuinely adversarial scenario right: auto_reorder_assistant has a completely ordinary name and description, passes Tool-Trust cleanly, and is still caught the moment it tries to act outside the user's authorized scope — proving the defense holds even when a tool gives away nothing suspicious about itself. I also proved the whole pipeline live, end-to-end, driven by real agents I don't control, in both Chrome and ChatGPT's own browser and not just my own click-through demo.
What I learned
That the deepest layer of agent safety isn't detecting bad actors by how they present themselves, it's enforcing what any actor, well-intentioned or not, is actually allowed to do. And that shipping an honest "known limitations" section is worth more to a technical reviewer than pretending a demo has no edges.
What's next for Handrail
Session-scoped state instead of shared module-level singletons, real backend-issued Authority Contracts tied to authenticated identity, and expanding the tool-trust layer with behavioral signals beyond name/description heuristics, the same direction Gate 2 already points toward.
Log in or sign up for Devpost to join the conversation.