dsh-webmcp
August 28, 2026 · View on GitHub
English | 中文
dsh-webmcp
dsh-webmcp is a DeepSeek Harness plugin that lets an agent discover and invoke the site tools a website exposes through the W3C WebMCP protocol, using a built-in headless Chromium.
WebMCP is a W3C Web Agents Community Group standardization proposal (github.com/webmachinelearning/webmcp). In 2026-08 OpenAI shipped site-tool support in the ChatGPT desktop built-in browser and launched a challenge around it; Google Chrome Labs also open-sourced webmcp-tools. This plugin brings that capability to a DeepSeek Harness agent without relying on an external browser or manual selector maintenance.
Why: WebMCP vs. traditional automation
| Aspect | Traditional automation | WebMCP site tools |
|---|---|---|
| How a site exposes actions | Hard-coded selectors and scripts you maintain | Protocol-declared surfaces (navigator.modelContext, window.webmcp, form[data-webmcp-tool]) |
| Discovery | Reverse-engineer the page | webmcp_discover returns the normalized tool list |
| Invocation | Replay clicks and typed input | webmcp_invoke runs the site's real tool function in-page |
| Consent | The site has no say in the call | The protocol requires site-side confirmation |
| Drift | Breaks on markup changes | Follows the site's own declared contract |
Install
dsh plugin --profile web add github:T-Markus-Liang/dsh-webmcp
MCP gateway (stdio)
The plugin also ships bin/dsh-webmcp-serve.mjs, a stdio MCP gateway that bridges a site's WebMCP tools to ANY MCP client (Claude Code/Desktop, Codex, …). Point it at a URL and the site's tools become a local MCP server any MCP client can consume natively.
dsh-webmcp-serve <url> [--allow-private-hosts] [--manifest-ttl-ms N] [--no-cache]
Client config example:
{ "mcpServers": { "my-site": { "command": "node", "args": ["/path/to/dsh-webmcp/bin/dsh-webmcp-serve.mjs", "https://example.com/app"] } } }
Protocol: newline-delimited JSON-RPC 2.0 (MCP stdio) implementing initialize / ping / tools/list / tools/call. tools/list comes from page discover (dual-mount / Map / Promise / executeToolByName fully compatible); tools/call reuses the same BrowserSession pipeline. Manifests are disk-cached under ~/.dsh-webmcp/manifests/ (default TTL 300s; --no-cache disables). Diagnostics go to stderr only — stdout is the pure MCP channel. Private-network targets are refused by default (plugin parity).
The gateway also serves an HTTP transport (Streamable-HTTP style): POST /mcp for request/response and GET /sse (Server-Sent Events) for server→client notifications such as tools/list_changed. Enable with:
dsh-webmcp-serve <url> --http --host 0.0.0.0 --port 9000 --token <secret>
POST /mcp carries request/response JSON-RPC; GET /sse is the event stream.
Bearer auth: send Authorization: Bearer <secret> (missing/wrong → 401). Multiple
MCP clients can call it concurrently; tools/list_changed is broadcast to every
live SSE subscriber. A streamable-http MCP client config:
{ "mcpServers": { "my-site": { "type": "http", "url": "http://host:9000/mcp",
"headers": { "Authorization": "Bearer <secret>" } } } }
Site-side authoring kit
Sites don't have to wait for a browser to expose actions to agents. The plugin ships a zero-dependency embeddable snippet (sitekit/webmcp-register.js) so a site author can declare a tool directly on the page with one <script src> include, then expose it via WebMCP.register(...).
<script src="https://cdn.example.com/webmcp-register.js"></script>
<script>
WebMCP.register({
name: 'search_products', // required, unique
description: 'Search the catalog', // human-readable, agent-facing
inputSchema: { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] },
outputSchema: { type: 'object', properties: { count: { type: 'number' } } },
annotations: { readOnlyHint: true }, // MCP ToolAnnotations
execute: async (args) => ({ count: queryYourDb(args.q) }),
});
</script>
The snippet is designed to be safe and honest:
- Dual-mount feature-detection — it probes BOTH
document.modelContextandnavigator.modelContext. The mount position shifted between Chrome builds, so checking only one silently fails (the CloudNSite lesson). - MCP worst-case annotation normalization —
destructiveHintdefaults totrue(andopenWorldHinttotrue) unless the author explicitly opts out, so the v1.1.0 host-side confirm guard always sees a conservative, truthful value. - Zero-side-effect no-op — on browsers with no WebMCP support it returns
falseand changes nothing. window.webmcpfallback — when nomodelContextis present the tool is pushed to awindow.webmcpregistry that the bridge probes; Chrome 151+'sPromise<void>registerToolis awaited, and an idempotent guard prevents double registration.
Agent-readiness audit
dsh-webmcp-serve --check <url> runs discover + computeReadiness against a site and prints a report: url, title, tool count, schema completeness, annotation coverage, and readOnly / destructive counts (score = 100 × (0.6 × schema + 0.4 × annotations)).
dsh-webmcp-serve --check https://ai-sdk-webmcp.persona-chat.dev
Exit codes: 0 when score >= 60 (agent-ready), 1 below, 2 when discover fails.
Quick start
The plugin registers two agent tools.
# Discover the site tools a page exposes (agent tool-call; wrapper is illustrative)
agent:
tool: webmcp_discover
input:
url: https://example.com
# Invoke a discovered site tool with arguments (args is optional)
agent:
tool: webmcp_invoke
input:
url: https://example.com
tool: <name-from-discover>
args:
query: "Show the latest posts"
Both tools accept an optional refresh (boolean) parameter: forces re-navigation. Invocations may return an argsWarning field listing required args you omitted (per the tool's inputSchema.required). webmcp_invoke also accepts confirm (boolean): required true when the tool is annotated destructiveHint (host-side guard; re-invoke after reviewing the annotation).
Configuration
All options are optional and are set under the config block in cordis.patch.yml.
| Option | Default | Description |
|---|---|---|
headless | true | Run Chromium headless. |
navigationTimeoutMs | 30000 | Timeout for navigating to the page, in milliseconds. |
invokeTimeoutMs | 20000 | Timeout for a single tool invocation, in milliseconds. |
chromiumPath | "" | Explicit path to a Chromium executable; overrides automatic resolution. |
allowPrivateHosts | false | When false (default), URLs targeting loopback/private networks (localhost, 127.0.0.0/8, 10/8, 172.16/12, 192.168/16, 169.254/16, ::1, fc00::/7, *.local, *.internal) are rejected — protects intranet from prompt-injected scans. Set true for local development fixtures. |
sessionTtlMs | 30000 | If a tool targets the exact URL navigated recently (within TTL), navigation is skipped and the live page is reused. Set 0 to always navigate. A refresh: true per-call override forces navigation. |
maxResultChars | 12000 | Tool results serialized above this size are truncated to a { truncated, totalBytes, preview, hint } envelope — protects the agent's context window from huge outputs. |
maxSessions | 3 | Per-origin browser sessions, bounded pool. LRU eviction beyond capacity (1-8). |
idleTtlMs | 30000 | Idle sessions (their browsers) are closed after this; 0 disables reclamation. |
trace | true | JSONL call tracing to ~/.dsh-webmcp/trace/ (daily rotation, best-effort). Set false to disable. |
Since v0.1.1 tool discovery probes BOTH spec mounts — navigator.modelContext and document.modelContext — alongside window.webmcp and declarative <form data-webmcp-tool> elements. Since v0.2.1 it also understands polyfill/native storage shapes (Map-backed tool stores, promise-returning getTools()) and routes calls through the mount's own executeToolByName when a registered entry carries no inline function. Since v1.1.0 discovery also probes /.well-known/webmcp and /.well-known/mcp.json (surface well-known), and every tool entry carries its MCP annotations.
TypeScript types
TypeScript consumers: import type { DiscoverResult, InvokeResult, BridgeConfig } from 'dsh-webmcp/types' — the bridge API is fully typed via types/index.d.ts (no build step).
Chromium resolution order
When choosing a Chromium executable, the plugin tries, in order:
config.chromiumPath- Environment variable
DSH_WEBMCP_CHROMIUM - Playwright
ms-playwrightcache scan channel: 'chrome'(system Chrome)
HTTP status & dashboard
GET /webmcp/status
{
"plugin": "dsh-webmcp",
"version": "0.1.0",
"browser": { "launched": true },
"config": {}
}
GET /webmcp/dashboard serves a self-refreshing (5s) HTML observability page:
config summary, pool state, aggregate stats (count / success-rate / p50 / p95 / avg),
the last 20 traced calls, and manifest-drift events. /webmcp/status now also embeds
a stats block. Tracing appends one JSONL line per call to
~/.dsh-webmcp/trace/YYYY-MM-DD.jsonl (override with DSH_WEBMCP_TRACE_DIR).
Manifest drift: re-discovering a URL whose tool set changed yields
_meta.drift = { added, removed } (in-memory per-origin baseline) plus a
manifest-drift trace line.
Readiness endpoint: GET /webmcp/readiness returns the Agent-readiness view —
per-origin score, schema completeness, annotation coverage, and readOnly /
destructive counts (score = 100 * (0.6 * schema + 0.4 * annotations)). The same
dashboard hosts the interactive Tool tester (a POST /webmcp/tester form): pick
a discovered tool, enter its args, toggle the confirm checkbox, and read back the
call result JSON (body: { url, tool, args, confirm }). This is the free, open
answer to the commercial readiness scanners (web-mcp.net charges $49/mo).
Scope & relationship to the W3C proposal
WebMCP itself is standardizing fast: Chrome 149 and Edge 150 ship it behind an Origin Trial today, ChatGPT Desktop supports site tools natively, and Brave (Leo) ships an experimental integration. Firefox and Safari have only filed standards positions so far.
This plugin drives any locally available Chromium-family binary (see the resolution chain below). It deliberately does not support driving Firefox or Safari.
Honest positioning: the W3C proposal lists headless browsing and fully autonomous agents among its non-goals — its vision is human-in-the-loop use inside an agent-capable browser. This plugin is a pragmatic transition bridge and a site-debugging tool: it lets harnesses without such a built-in agent reach sites that already expose WebMCP tools today. For the full collaborative UX, prefer native implementations once your browser ships them.
Security
- Site JavaScript runs only inside an isolated, one-time headless profile; no user login state is ever reused.
- Tool invocation is explicitly initiated by the user through the agent — never silently in the background.
- The WebMCP protocol itself requires site-side user confirmation, the same model ChatGPT's site tools follow.
- Intranet shield — private-network targets are refused by default even if a prompt tricks the agent into pointing at them.
Error taxonomy
Every tool result carries an error code on failure (and argsWarning on
missing required args). Host-side codes come from the bridge itself:
| code | meaning |
|---|---|
bad-url | URL lacks an http(s):// scheme |
private-host-blocked | target is loopback/private/link-local — or resolves to one (DNS-rebinding guard, v0.2.2) |
dns-failed | hostname did not resolve |
network | connection refused/reset/unreachable |
timeout | navigation or evaluation exceeded its budget |
navigate-failed | other navigation failure |
internal | unexpected bridge failure |
confirm-required | tool is annotated destructiveHint:true — re-invoke with confirm:true |
Page-side codes come from the injected agent: unknown-tool (no such tool on
the page), not-callable (tool has no callable implementation), tool-threw
(the tool itself threw — message and truncated stack included).
Roadmap
Condensed; full ladder with acceptance criteria lives in ROADMAP.md.
| Version | Theme | Status |
|---|---|---|
| v0.2.0 | private-network shield + session reuse | ✅ shipped |
| v0.2.1 | polyfill/native runtime compat + argsWarning + result budget | ✅ shipped |
| v0.2.2 | page-agent engineering + DNS guard + error taxonomy | ✅ shipped |
| v0.3.0 | stdio MCP server gateway mode | ✅ shipped |
| v0.4.0 | per-origin session pool: concurrency + LRU + idle reclamation | ✅ shipped |
| v0.5.0 | observability: JSONL trace + dashboard + manifest drift | ✅ shipped |
| v1.1.0 | annotations passthrough + destructive guard + well-known probing | ✅ shipped |
| v1.2.0 | push (tools/list_changed) + outputSchema passthrough + dashboard readiness & tester | ✅ shipped |
| v1.3.0 | TS contract + type/runtime consistency audit | ✅ shipped |
| v1.4.0 | gateway HTTP/SSE transport + bearer auth + multi-client | ✅ shipped |
| later | polyfill auto-injection, diagnostics bundle, dsh-browser interop | exploratory |
License
MIT