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
访问服务器

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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选