Skip to main content
Engineering 2026-07-07

Client-Side Approvals for WebMCP Tool Calls

MCP Trail

MCP Trail Team

Developer Relations

Client-Side Approvals for WebMCP Tool Calls

Client-Side Approvals for WebMCP Tool Calls

Short answer: @mcptrail/webmcp-consent wraps the tools your page registers so a call pauses for a human “OK?” before it runs. You pass a policy — auto, confirm, or deny per tool (wildcards and functions allowed), with writes requiring confirmation by default — and any tool set to confirm shows a prompt the user must accept before the action executes. It’s the local, single-session, open-source way to add human-in-the-loop for WebMCP right in the browser.

WebMCP tools run as the logged-in user with real side effects, and the protocol has no native “are you sure?” step. If you’re publishing tools and want a lightweight checkpoint before an agent charges a card or deletes a record — without standing up any backend — this is the smallest thing that works. (New to the protocol? Start with What is WebMCP?)

Install and wrap your tools

The library ships as MIT open source on GitHub at github.com/ElBartoTn/webmcp-consent. Install it alongside your WebMCP registration code:

npm install @mcptrail/webmcp-consent

Then install the consent layer once, before or after you register your tools. It intercepts tools/call and applies your policy:

import { installConsent } from "@mcptrail/webmcp-consent";

installConsent({
  policy: {
    "search_*": "auto",     // read-only lookups run without a prompt
    "*": "confirm",         // everything else asks first
  },
});

That’s the whole setup. Any tool matching search_* runs immediately; every other tool now pauses and asks the user to approve before it executes.

How the policy resolves

A policy maps a tool-name pattern to a decision. Three decisions exist, and the most specific matching pattern wins:

DecisionWhat happens on a call
autoRuns immediately, no prompt
confirmShows an approval prompt; runs only if the user accepts
denyBlocked outright; the call returns an error, never runs

Patterns support exact names (place_order), wildcards (search_*, *), and — for anything more nuanced — a function that inspects the call and returns a decision. The default, if you pass no policy at all, is that write-shaped tools need confirmation and obvious reads run automatically, so you get a sensible checkpoint out of the box.

Per-call decisions with a function

Static patterns cover most cases, but sometimes the decision depends on the arguments. A function receives the tool name and its input and returns "auto", "confirm", or "deny":

installConsent({
  policy: {
    "transfer_funds": (call) =>
      call.args.amount > 100 ? "confirm" : "auto",
    "delete_*": "deny",
    "*": "confirm",
  },
});

Small transfers glide through; large ones stop for a human; anything matching delete_* is refused entirely. The function only decides — your page still owns how the confirmation UI looks, so the prompt matches your app rather than a generic browser dialog.

What the user sees

When a confirm tool is called, the consent layer holds the call and surfaces the tool name and the exact arguments the agent wants to run. The user accepts or rejects. On accept, the original tool runs and its result flows back to the agent as if nothing intervened. On reject, the tool never executes and the agent gets a clean “denied” result it can reason about.

Because this all happens in the page, the checkpoint is instant and offline — there’s no round-trip to a server, no account, no token. That’s the strength and the limit: it’s perfect for a single browser session, and it lives and dies with that tab.

When you outgrow client-side approvals

Client-side consent is ideal for a solo user or a demo. It does not, however, give you a shared approval queue, cross-session history, or policy you can enforce for a whole team — the prompt is local to one browser, and once the tab closes there’s no record of who approved what.

When you need approvals that survive across sessions, route to a person who isn’t sitting at the tab, and land in an audit log, that’s the hosted version. Human-in-the-loop approvals for WebMCP shows how MCP Trail bridges the same tools through a proxy so sensitive calls pause in a shared pending queue, get routed to Slack or email, and record every Approve/Deny decision. Same idea, enforced at scale instead of in one tab.

FAQ

How is this different from MCP Trail’s hosted approvals?

@mcptrail/webmcp-consent runs entirely in the browser: no account, no server, no audit trail, and the policy applies only to the current tab. MCP Trail’s hosted approvals bridge the tools through a proxy so approvals persist across sessions, route to Slack or email, apply team-wide policy, and write every decision to an audit log. Use the library for local single-session checkpoints; use the hosted version when a team needs accountability.

Do read-only tools get prompted?

Not unless you want them to. Set them to auto — for example "search_*": "auto" or "get_*": "auto" — and they run without a prompt. Reserve confirm for state-changing tools like placing an order or deleting a record. With no policy at all, the default already lets obvious reads run and asks before writes.

Can I use my own confirmation UI?

Yes. The library decides whether a call needs confirmation and hands you the tool name and arguments; you render the prompt however you like so it matches your app. That keeps the approval step native to your page instead of a generic dialog.


Explore features · Open MCP Trail

Share this article