webmcp-guard

webmcp-guard

Drop-in replacement for Playwright MCP that adds WebMCP support, exposing page-defined tools as gated webmcp:* tools with provenance tracking, schema-change warnings, and mandatory confirmation.

Category
Visit Server

README

webmcp-guard

A WebMCP-aware drop-in replacement for @playwright/mcp.

Playwright MCP is the de facto browser-automation MCP server wired into Claude Code, Cursor, VS Code, and friends. It has no awareness of WebMCP — the emerging W3C proposal that lets a page register agent-callable tools directly (document.modelContext.registerTool, with navigator.modelContext as a legacy alias). Every agent using plain Playwright MCP on a WebMCP-enabled site falls back to click/type/scrape automation even when the page is explicitly offering a better, structured interface.

webmcp-guard sits in front of @playwright/mcp as a proxy: it passes its entire native tool surface through unchanged (zero behavior regression for sites that don't use WebMCP), detects WebMCP tools after every navigation, and — because those tools are JavaScript the page controls rather than code the server ships — exposes them behind a trust layer: session-scoped provenance tracking that flags when a previously-seen tool's schema changes, mandatory confirmation before any page-defined tool call runs, clear webmcp:* namespacing so they're never confused with native tools, and an audit log. See docs/trust-boundary-design.md for the full reasoning behind that design.

Validated against the real ecosystem, not just synthetic tests: all 168 live sites in the webmcp.com public directory, plus a hand-built adversarial test case. That process found and fixed a real cross-origin tool-invocation vulnerability in this project's own trust layer. Full methodology, evidence, and honest limitations in docs/testing-and-validation.md.

Install / config

Not yet published to npm. For now, build from source (see Install / build (from source) below) and point your MCP client config at the built CLI directly. Once published, install will be exactly npm install -g webmcp-guard / npx webmcp-guard, mirroring @playwright/mcp's own install path — the config shape below is already written for that end state, it just uses a local path today instead of a package name.

webmcp-guard is close-to-drop-in with @playwright/mcp: it only special-cases --browser and --headless/--headed (needed to build its own browser launch config), and forwards every other flag — including --allowed-origins, --blocked-origins, --allow-unrestricted-file-access, --isolated, --caps, etc. — verbatim to the underlying @playwright/mcp process it spawns. Any existing playwright-mcp config line keeps working unmodified.

Before — a typical @playwright/mcp entry in an MCP client config (mcpServers in Claude Code's or Cursor's config file):

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--browser", "chrome",
        "--allowed-origins", "https://example.com",
        "--blocked-origins", "https://evil.com"
      ]
    }
  }
}

After, once published — swap the package name in args, everything else (all flags, their values, and their order) is unchanged:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "webmcp-guard@latest",
        "--browser", "chrome",
        "--allowed-origins", "https://example.com",
        "--blocked-origins", "https://evil.com"
      ]
    }
  }
}

Today, from a local build — same flags, command/args point at the built CLI instead:

{
  "mcpServers": {
    "playwright": {
      "command": "node",
      "args": [
        "/absolute/path/to/webmcp-guard/packages/webmcp-guard/dist/cli.js",
        "--browser", "chrome",
        "--allowed-origins", "https://example.com",
        "--blocked-origins", "https://evil.com"
      ]
    }
  }
}

Once published, the entire migration is a one-line package-name swap — no flags need to be added, removed, or reordered.

What webmcp:* tools are, and what you'll see

When webmcp-guard navigates to a page that registers WebMCP tools, it lists them to your harness as new, separate MCP tools named webmcp:<name> (e.g. webmcp:add_to_cart) — distinct from and never merged into Playwright's native tool list, so it's always obvious at a glance whether a tool is server code or something the page itself defined.

Because a webmcp:* tool's implementation is JavaScript the page controls, every call into one requires explicit confirmation before it runs — this is on by default and is not something a misconfigured client can silently skip past. What that confirmation looks like depends on your client:

  • Claude Code CLI (and any client that declares MCP elicitation support): you'll see an interactive prompt showing the tool name, the page's origin, and — if the tool's schema has changed since webmcp-guard last saw it from that origin — an explicit warning that its definition changed. You accept or decline; declining, cancelling, or letting the prompt time out (30s) all result in the call being denied, never silently allowed.
  • Clients without elicitation support (e.g. Claude Desktop, or a Cursor session hitting its current elicitation-rendering bug): webmcp-guard falls back to relying on your client's own generic tool-approval settings — Claude Code's settings.json ask/allow/deny rules, or Cursor's allowlist/auto-run approvals — keyed on the webmcp:* tool name. You should configure an ask (or equivalent) rule for webmcp:* tools in these clients yourself; webmcp-guard can't force this from the server side, though every fallback-path call is still recorded to its audit log so the reliance is visible after the fact.

See docs/trust-boundary-design.md for why this two-track design exists rather than relying on elicitation alone.

Architecture

flowchart TD
    Harness["Agent Harness\n(Claude Code / Cursor / etc.)"]
    Guard["webmcp-guard\npackages/webmcp-guard"]
    Trust["@webmcp-guard/trust-layer\nprovenance · confirmation gate\nnamespacing · audit log"]
    Adapter["@webmcp-guard/playwright\nspawns & speaks to @playwright/mcp"]
    Upstream["@playwright/mcp\n(spawned subprocess, pinned version)"]
    Browser["Real Chrome"]
    Page["Web page\n(may register document.modelContext tools)"]

    Harness -- "MCP over stdio" --> Guard
    Guard -- "native tool calls, pass-through" --> Adapter
    Guard -- "webmcp:* calls, gated" --> Trust
    Trust -- "evaluated call" --> Adapter
    Adapter -- "MCP over stdio\n(internal client)" --> Upstream
    Upstream --> Browser
    Browser --> Page

    style Trust fill:#2d5a2d,stroke:#4ade80,color:#fff

webmcp-guard is a proxy, not a fork: it runs @playwright/mcp as an internal MCP client via server composition (stdio), rather than vendoring its source. See Decision 1 in research/decision-log.md for why this replaced the original fork hypothesis.

  • packages/playwright (@webmcp-guard/playwright) — the adapter that spawns/speaks to @playwright/mcp, passes tool calls through, and runs the WebMCP detection probe via @playwright/mcp's own browser_evaluate tool. Kept Playwright-specific and adapter-agnostic-by-convention so a future non-Playwright backend could reuse the same shape.
  • packages/trust-layer (@webmcp-guard/trust-layer) — provenance store, schema-diffing, confirmation gating, namespacing, and the audit log. Deliberately has no Playwright-specific imports, since it's meant to generalize to other automation backends later.
  • packages/webmcp-guard (webmcp-guard) — the MCP server itself: connects to the adapter on startup, exposes every upstream Playwright tool unchanged plus any detected webmcp:* tools gated by the trust layer, and after every browser_navigate/tab-switch, runs the detection probe and logs/caches the result.

The trust gate, per webmcp:* call

flowchart TD
    Call["Agent calls webmcp:&lt;tool&gt;"] --> Live{"Tool actually present\non the CURRENTLY active page?"}
    Live -- No --> Deny1["Denied — stale tool\nfrom a previous origin\n(closes the cross-origin bug)"]
    Live -- Yes --> Verdict{"Provenance verdict"}
    Verdict -- "new-tool / unchanged" --> Gate
    Verdict -- "schema-changed" --> Warn["Confirmation prompt\nshows explicit warning"] --> Gate
    Gate{"Client declared\nMCP elicitation?"}
    Gate -- Yes --> Elicit["Structured confirmation\nprompt via elicitation"]
    Gate -- No --> Fallback["Relies on client's own\ngeneric ask/allow/deny gate"]
    Elicit --> Decision{"Confirmed?"}
    Decision -- "No / timeout / error" --> Deny2["Denied — fail closed,\nnever silent-allow"]
    Decision -- Yes --> Allow["Tool call forwarded\nto the page"]
    Fallback --> Allow
    Allow --> Audit["Recorded to audit log"]
    Deny1 --> Audit
    Deny2 --> Audit

Install / build (from source)

npm install
npm run build

Run

node packages/webmcp-guard/dist/cli.js --browser chrome

Or wire it into an MCP client config as shown above.

Testing

npm test   # builds all packages, runs the trust-layer's unit tests (18 tests)

Unit tests cover the trust layer in isolation (provenance/schema-diffing, confirmation gating, namespacing, audit log). The larger claim — that this actually works against the real WebMCP ecosystem — is backed by validation against all 168 live sites in the public webmcp.com directory plus a hand-built adversarial test case, documented in full in docs/testing-and-validation.md. That process is what found the cross-origin invocation bug shown in the diagram above — it was not caught by unit tests or the synthetic adversarial test alone, only by testing against real, unmodified production sites. Reproducible via the scripts in research/tools/.

Versions pinned

  • @playwright/mcp@0.0.79
  • @modelcontextprotocol/sdk@^1.30.0

Further reading

  • docs/trust-boundary-design.md — the full trust-boundary design document: why WebMCP tools are a different trust boundary, the provenance/schema-diff model, the confirmation-gating design, namespacing, the audit log, and what this project is explicitly not.
  • docs/testing-and-validation.md — the consolidated testing report: methodology, every bug found and fixed with evidence, the full 168-site directory crawl results, and honestly-stated limitations.
  • research/README.md — index of the primary-source research this project was built on (WebMCP spec state, MCP protocol details, prior art, upstream internals) and the raw validation data/tools.
  • research/decision-log.md — architecture decisions and the reasoning behind them (fork vs. proxy, detection property names, confirmation mechanism, provenance persistence, etc.).

License

Apache-2.0 — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured