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

AspectTraditional automationWebMCP site tools
How a site exposes actionsHard-coded selectors and scripts you maintainProtocol-declared surfaces (navigator.modelContext, window.webmcp, form[data-webmcp-tool])
DiscoveryReverse-engineer the pagewebmcp_discover returns the normalized tool list
InvocationReplay clicks and typed inputwebmcp_invoke runs the site's real tool function in-page
ConsentThe site has no say in the callThe protocol requires site-side confirmation
DriftBreaks on markup changesFollows 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.modelContext and navigator.modelContext. The mount position shifted between Chrome builds, so checking only one silently fails (the CloudNSite lesson).
  • MCP worst-case annotation normalizationdestructiveHint defaults to true (and openWorldHint to true) 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 false and changes nothing.
  • window.webmcp fallback — when no modelContext is present the tool is pushed to a window.webmcp registry that the bridge probes; Chrome 151+'s Promise<void> registerTool is 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.

OptionDefaultDescription
headlesstrueRun Chromium headless.
navigationTimeoutMs30000Timeout for navigating to the page, in milliseconds.
invokeTimeoutMs20000Timeout for a single tool invocation, in milliseconds.
chromiumPath""Explicit path to a Chromium executable; overrides automatic resolution.
allowPrivateHostsfalseWhen 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.
sessionTtlMs30000If 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.
maxResultChars12000Tool results serialized above this size are truncated to a { truncated, totalBytes, preview, hint } envelope — protects the agent's context window from huge outputs.
maxSessions3Per-origin browser sessions, bounded pool. LRU eviction beyond capacity (1-8).
idleTtlMs30000Idle sessions (their browsers) are closed after this; 0 disables reclamation.
tracetrueJSONL 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:

  1. config.chromiumPath
  2. Environment variable DSH_WEBMCP_CHROMIUM
  3. Playwright ms-playwright cache scan
  4. 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:

codemeaning
bad-urlURL lacks an http(s):// scheme
private-host-blockedtarget is loopback/private/link-local — or resolves to one (DNS-rebinding guard, v0.2.2)
dns-failedhostname did not resolve
networkconnection refused/reset/unreachable
timeoutnavigation or evaluation exceeded its budget
navigate-failedother navigation failure
internalunexpected bridge failure
confirm-requiredtool 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.

VersionThemeStatus
v0.2.0private-network shield + session reuse✅ shipped
v0.2.1polyfill/native runtime compat + argsWarning + result budget✅ shipped
v0.2.2page-agent engineering + DNS guard + error taxonomy✅ shipped
v0.3.0stdio MCP server gateway mode✅ shipped
v0.4.0per-origin session pool: concurrency + LRU + idle reclamation✅ shipped
v0.5.0observability: JSONL trace + dashboard + manifest drift✅ shipped
v1.1.0annotations passthrough + destructive guard + well-known probing✅ shipped
v1.2.0push (tools/list_changed) + outputSchema passthrough + dashboard readiness & tester✅ shipped
v1.3.0TS contract + type/runtime consistency audit✅ shipped
v1.4.0gateway HTTP/SSE transport + bearer auth + multi-client✅ shipped
laterpolyfill auto-injection, diagnostics bundle, dsh-browser interopexploratory

License

MIT