DeepSeek Harness Plugin

T-Markus-Liang/dsh-webmcp

Stars ★ 1 Category Browser & Web Added 2026-08-29

WebMCP bridge: your agent discovers & calls site tools exposed over the W3C WebMCP protocol via a built-in headless Chromium.

Install

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:T-Markus-Liang/dsh-webmcp

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).

README

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.modelContext and navigator.modelContext. The mount position shifted between Chrome builds, so checking only one silently fails (the CloudNSite lesson).
  • MCP worst-case annotation normalization — destructiveHint 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.

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:

  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:

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

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.