@mcptrail/webmcp-consent
v0.1.0
Published
Client-side human-in-the-loop for WebMCP: gate agent tool calls behind an approval policy (auto/confirm/deny), with a built-in modal or your own approver.
Maintainers
Readme
@mcptrail/webmcp-consent
Client-side human-in-the-loop for WebMCP. It wraps navigator.modelContext.registerTool so every tool an agent tries to call is first checked against an approval policy — auto, confirm, or deny. By default, reads run automatically and writes need confirmation. Bring your own approver, or use the built-in modal.
A local complement to a hosted guard: enforce consent in the page itself, before a call ever leaves the browser.
Install
npm install @mcptrail/webmcp-consentUsage
import { installConsent } from "@mcptrail/webmcp-consent";
// Gate every tool registered on this page.
const consent = installConsent({
policy: {
"*": "confirm", // default: ask before anything
search_docs: "auto", // reads run silently
delete_account: "deny" // never allowed
},
});
// ...your app calls navigator.modelContext.registerTool(...) as usual.
// Reads run; writes pop the built-in Approve/Deny modal; denied tools throw.
consent.uninstall(); // restore the original registerToolBy default (no policy), read-only tools (annotations.readOnlyHint) are auto and everything else is confirm.
Policies
A policy is either a lookup table keyed by tool name (with a "*" wildcard default), or a function evaluated per call:
import { installConsent, type ToolDescriptor } from "@mcptrail/webmcp-consent";
installConsent({
policy: (tool: ToolDescriptor, args: unknown) => {
if (tool.readOnly) return "auto";
if ((args as { amount?: number }).amount! > 100) return "deny";
return "confirm";
},
});Decision is "auto" | "confirm" | "deny":
| Decision | Behavior |
| --------- | ---------------------------------------------------- |
| auto | run immediately |
| confirm | ask the approver; run if approved, else throw |
| deny | throw Denied by policy: <tool> without running |
Bring your own approver
Skip the built-in modal — pass any function that returns (or resolves to) a boolean:
installConsent({
policy: { "*": "confirm" },
prompt: async ({ tool, args }) => {
return await myUiConfirm(`Run ${tool.name}?`, args);
},
});If no prompt is given, a minimal centered modal is rendered (its nodes are tagged data-wmcp-consent). With no document present, confirm decisions auto-approve so headless runs never hang.
API
installConsent(options): { uninstall(): void }
// options.policy? — Policy (table or function); default: writes confirm, reads auto
// options.prompt? — ConsentPrompt approver; default: built-in modal
// options.target? — object carrying .modelContext; default: navigator
resolveDecision(policy, tool, args): Decision // pure policy lookup
createGate(policy, prompt): (tool, args, run) => Promise<unknown> // pure gateresolveDecision and createGate are exported for composing custom flows or testing without the DOM.
Development
npm install
npm run typecheck
npm test # Vitest + jsdom
npm run build # tsup → dist (ESM + CJS + d.ts)License
MIT © Zied Guetari
