Inspiration

Organizations often run their own internal apps that have access to sensitive data. They often have tools to enforce data protection and security policies on these apps, but an improperly implemented WebMCP tool may circumvent them. I wanted to create something that would allow them to have these same kind of policies for their WebMCP tools without forcing them to code it themselves. But it needed to do more than block data for the agent. I wanted it to control data access in a way that preserved the agent's ability to reason about the data.

What it does

WebMCP Guard is an SDK that wraps WebMCP functions written with the Imperative API and injects controls. It has a corresponding management console that allows the organization to define policies. It has the following types of policies

Data Protection

This gives the company control over what data the agent can see. It classifies sensitive data, including names, SSN, credit cards, and more, and transforms them before the agent sees it. It can replace this data with a unique token. If the same piece of data appears across multiple records, it will use the same token identifier, which allows the agent to reason about the data without actually knowing what it is. In some cases, it can contextualize the data while removing the specificity. For example, it can replace a DOB from a patient health record with an age range.

Human confirmation

To prevent errant destructive actions polices can be created that require the user to approve (via an on-page modal) an action before it can be completed. This is most often used before an agent takes an irreversible action, such as deleting a record.

Posture Assessment

It can restrict what types of agents can access the tools. For example, you can block unidentified agents or restrict it to agents from one provider, such as OpenAI.

Justification

Policies can require the agent to provide a written justification before a sensitive action runs — bulk export, for example. The requirement is injected directly into the tool's input schema, so the agent learns about it from the tool definition rather than from a refusal. The agent is instructed to stop and ask its user for the reason and relay it verbatim, and the justification is evaluated and stored on the audit record as the human's stated reason.

How we built it

TypeScript end to end, in a pnpm monorepo. The SDK splits into a browser half and a server half: the client package wraps document.modelContext.registerTool (with a navigator.modelContext fallback) and runs every call through a gate → execute → transform pipeline, while everything secret — the policy engine, the data classifier, the deterministic tokenizer, the encrypted token vault, and the audit writer — lives in a Node package the host app mounts as ordinary API routes.

Tokens are deterministic by construction: HMAC-SHA256 over a canonicalized value under an org secret, so the same SSN or name yields the same token in every tool, every turn, even inside the free text of a clinical note. The vault encrypts originals with AES-256-GCM, using the token itself as authenticated data so a tampered row fails closed instead of decrypting to the wrong person. Storage is a small adapter interface (SQLite for the demo, in-memory for tests, both run against one shared conformance suite) so an organization brings its own database. This way the organization isn't reliant on SaaS infrastructure to protect their audit logs, etc.

We also built an example medical app for the demo. It existing in the same repo. We used AI coding assistance for this project.

Challenges we ran into

WebMCP is brand new, and building against it seriously means finding the edges. Three real ones: tools' execute is invoked with no second options argument despite the typings declaring one (every tool crashed on its first real call); registering a live tool name again silently keeps the old tool, so policy-driven schema changes must abort-then-re-register; and feature detection has to cover both document.modelContext and the older navigator.modelContext surface.

Tokenizing a structured name field is easy. A clinical note that says "Reached Tricia by phone" is not, and an agent can de-anonymize the token if information like that leaks. We fixed this by resolving bare name fragments to the same token as the full name (never hashed independently, which would split one person into two tokens), replaced only when the fragment maps unambiguously to one known person.

Early on, the justification refusal told the agent what a justification should contain, so agents composed plausible ones themselves, defeating the control. The message now instructs the agent to stop, ask its human for the reason, and relay it verbatim. The denial text turned out to be as much a part of the security design as the policy engine. In the future, we will probably replace it with a modal like the approval workflow.

Accomplishments that we're proud of

The tokenization loop working end to end was a big win. An agent searches, gets tokens instead of names, reasons about them, passes one back into another tool, and the real value is swapped in server-side. The agent is actually able to reason about the data without access to the cleartext value.

Getting the justification flow to work correctly was also a big win. Like I said earlier, a lot of agents just wrote the justification themselves, which isn't exactly what is intended. It's not foolproof, but this tool isn't supposed to be silver bullet for security; it is meant to complement other security measures for organizations that want to implement WebMCP.

What we learned

We'll still need some kind of control surface for the DOM. Agents that cannot accomplish their task via the WebMCP tool may fall back to DOM inspection. Currently, this requires a separate security tool to address, like an enterprise browser. But I think we could come up with some kind of native solution. Alternatively, this tool might be better as a browser extension, and then the SDK would merely enforce the use of the extension before serving the page. This would give us more control over what the agent can access, but it is also a bigger deployment than the current state.

But when it comes to security, perfect can't be the enemy of good. Easily deploying controls on a WebMCP tool still greatly improves security and compliance, especially if it eliminates it as a path to circumventing other security controls.

What's next for WebMCP Guard

Smarter classification: NER-based name detection beyond the host-supplied dictionary, and free-text detectors for addresses and insurance ids. Structured export rows so bulk export gets the full transform treatment instead of leaning on the justification gate. Richer policy: combined confirmation-plus-justification actions, per-class control over inbound detokenization, and the pluggable LLM justification evaluator the interface already supports.

Real identity: SSO/OIDC session resolution in place of the demo's mock personas. This allows the user to attach policies to specific people or user groups.

LLM-as-a-judge: For justifications, I want the admin to be able to define what an acceptable justification is and use an LLM to determine if the justification given is adequate.

Better agent posture controls: I want to determine a better way to identify agents so our posture controls are more reliable.

End user notifications: Alert end users during WebMCP activity to prevent adversarial attempts to hijack the users session while hiding their activity.

An optional browser extension: A stretch goal is to use a browser extension to better lock down the DOM so agents can't circumvent controls through DOM inspection. This will have to be optional since it significantly increases the deployment complexity, but it will close a major gap.

Demo

The live demo has two sites: Example EHR app: https://webmcp-guard-portal.onrender.com/patients Admin console: https://webmcp-guard-console.vercel.app/login?token=Nit52TvyXZWpgzot1UIqMV4ktAB3oCdFbrk5M5AFSYg%3D

Built With

  • chatgpt
  • claude
  • codex
  • nextjs
  • render
  • sqlite
  • vercel
Share this project:

Updates

Submission history