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.
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
elicitationsupport): 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.jsonask/allow/denyrules, or Cursor's allowlist/auto-run approvals — keyed on thewebmcp:*tool name. You should configure anask(or equivalent) rule forwebmcp:*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 ownbrowser_evaluatetool. 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 detectedwebmcp:*tools gated by the trust layer, and after everybrowser_navigate/tab-switch, runs the detection probe and logs/caches the result.
The trust gate, per webmcp:* call
flowchart TD
Call["Agent calls webmcp:<tool>"] --> 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
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。