AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Opencode Webmcp

mcp-enihcam-opencode-webmcp · by enihcam

MCP server bridge — exposes Chromium WebMCP tools (document.modelContext) as MCP tools. Works with OpenCode, Claude Code, and any MCP client. Enables AI agents to discover and invoke browser-registered tools.

No reviews yet
0 installs
44 views
0.0% view→install

Install

$ agentstack add mcp-enihcam-opencode-webmcp

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-enihcam-opencode-webmcp)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Opencode Webmcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

opencode-webmcp

MCP bridge connecting OpenCode to Chromium's WebMCP API.

Exposes WebMCP tools registered in a Chromium page as MCP tools consumable by any MCP client (OpenCode, Claude Code, etc.).

How It Works

OpenCode (MCP client)
    ↕  MCP stdio protocol
webmcp-bridge (MCP server)
    ↕  Puppeteer + CDP
Chromium (WebMCP enabled)
    ↕  monkey-patched document.modelContext
Web page with registered WebMCP tools

The bridge launches Chromium with --enable-features=WebMCPTesting, navigates to a target page, discovers tools via the WebMCP API, and makes them available to MCP clients.

Monkey-patch: Chrome 150+ has a bug where page.webmcp.invokeTool() returns {} instead of the actual tool result. The bridge works around this by intercepting registerTool to capture execute functions and replacing executeTool to call the captured functions directly.

Prerequisites

  • Node.js 18+
  • Chromium/Chrome with WebMCP support (Chrome 150+)
  • An MCP client (OpenCode, Claude Code, etc.)

Setup

git clone  webmcp-bridge
cd webmcp-bridge
npm install

Usage

Standalone

CHROME_PATH=/usr/bin/chromium \
WEBMCP_TARGET_URL="https://www.google.com" \
WEBMCP_HEADLESS=false \
node server.js

With a config file

node server.js --config ./webmcp.yaml

If --config is omitted, the bridge looks for ./webmcp.yaml, ./webmcp.yml, or ./webmcp.json in the current working directory before falling back to environment variables.

Layered precedence (highest wins): CLI args → config file → environment variables → built-in defaults.

For example, node server.js --no-headless overrides headless: true in a config file, which overrides WEBMCP_HEADLESS=true in the environment.

Supported keys (camelCase): chromePath, targetUrl, headless, historyMax, logHistory.

Example webmcp.yaml:

chromePath: /usr/bin/chromium
targetUrl: https://www.google.com
headless: false
historyMax: 500
logHistory: false

Or webmcp.json:

{
  "chromePath": "/usr/bin/chromium",
  "targetUrl": "https://www.google.com",
  "headless": false
}

OpenCode (opencode.json)

{
  "mcp": {
    "webmcp-bridge": {
      "command": ["node", "/path/to/webmcp-bridge/server.js"],
      "enabled": true,
      "type": "local",
      "environment": {
        "CHROME_PATH": "/usr/bin/chromium",
        "WEBMCP_TARGET_URL": "about:blank",
        "WEBMCP_HEADLESS": "false"
      }
    }
  }
}

Quick Start (OpenCode)

After adding the config above, restart OpenCode. The bridge tools are now available — just ask:

> "Navigate to a WebMCP-enabled page and tell me what tools are available"

OpenCode calls webmcp_navigate → bridge opens the page → webmcp_status to show discovered tools. From there you can invoke any page tool, take screenshots, evaluate JS, or manage tabs conversationally.

For a full 8-step walkthrough (status → navigate → discover → invoke → screenshot → evaluate → history), see examples/GETTING_STARTED.md.

Environment Variables

| Variable | Default | Description | |---|---|---| | CHROME_PATH | /usr/bin/chromium | Path to Chromium/Chrome executable | | WEBMCP_TARGET_URL | https://www.google.com | Initial page to navigate to | | WEBMCP_HEADLESS | true | Run headless (true) or with a visible window (false) | | WEBMCP_HISTORY_MAX | 1000 | Maximum number of tool invocations to keep in the in-memory history ring buffer | | WEBMCP_LOG_HISTORY | false | When true, write each history entry as one JSON line to stderr | | WEBMCP_DECLARATIVE_SCAN | true | Scan the page DOM for declarative WebMCP tools (`); set to false` to disable |

Bridge-Native Tools

These tools are always available, regardless of what the page exposes:

| Tool | Description | |---|---| | webmcp_navigate | Navigate Chrome to a URL and refresh discovered WebMCP tools | | webmcp_status | Report connection status, current URL, and available tools | | webmcp_evaluate | Execute arbitrary JavaScript in the page context | | webmcp_invoke_tool | Call any WebMCP tool by name with arguments | | webmcp_register_test_tools | Register sample tools (test_greet, test_calculator, test_fetch_title) for validation | | webmcp_screenshot | Capture a screenshot of the current page (PNG/JPEG, viewport/full-page/clip) and return it as an MCP image | | webmcp_history | Return recent tool invocations from the in-memory history buffer (most recent first; optional limit, optional toolName filter) | | webmcp_clear_history | Empty the in-memory history buffer | | webmcp_open_tab | Open a new tab, optionally navigating to a URL; returns the new tabId | | webmcp_switch_tab | Make a specific tab the active tab | | webmcp_list_tabs | List all open tabs with tabId, url, title, and isActive flag | | webmcp_close_tab | Close a tab by tabId (cannot close the last one) |

webmcp_screenshot example

Default (viewport PNG):

{ "name": "webmcp_screenshot", "arguments": {} }

Full-page JPEG at quality 75:

{ "name": "webmcp_screenshot", "arguments": { "format": "jpeg", "quality": 75, "fullPage": true } }

Region clip (200×100 at top-left):

{ "name": "webmcp_screenshot", "arguments": { "clip": { "x": 0, "y": 0, "width": 200, "height": 100 } } }

Multi-Tab Sessions

The bridge can manage multiple Puppeteer tabs sharing a single browser context. Each tab has a UUID tabId; one tab is active at a time. Tools without tabId act on the active tab; pass tabId to target a specific tab without switching.

{ "name": "webmcp_open_tab", "arguments": { "url": "https://example.com" } }
// → { "tabId": "a1b2c3d4-…", "url": "https://example.com" }

{ "name": "webmcp_list_tabs", "arguments": {} }
// → { "count": 2, "tabs": [{ "tabId": "…", "url": "…", "title": "…", "isActive": true }, …] }

{ "name": "webmcp_switch_tab", "arguments": { "tabId": "a1b2c3d4-…" } }
{ "name": "webmcp_invoke_tool", "arguments": { "name": "search", "args": { "q": "weather" }, "tabId": "a1b2c3d4-…" } }
{ "name": "webmcp_close_tab", "arguments": { "tabId": "a1b2c3d4-…" } }

Closing the last remaining tab returns an error. When the active tab is closed (with other tabs remaining), the next tab in the map becomes active.

Architecture

  • server.js — single-file MCP server using @modelcontextprotocol/sdk
  • Puppeteer (puppeteer-core) — drives Chromium via Chrome DevTools Protocol
  • Monkey-patchpage.evaluateOnNewDocument injects a script that intercepts document.modelContext.registerTool and executeTool to capture actual execute functions
  • Declarative API scanner — also scans the DOM for ` and synthesizes MCP tools from [toolname-target] attributes. Wired through a 100ms-debounced MutationObserver that flips a dirty flag whenever [toolname]` attributes change. Imperative tools take precedence on name collisions.

Tool Annotations

MCP tool annotations (title, readOnlyHint, destructiveHint, idempotentHint, openWorldHint) are surfaced to MCP clients. Defaults for bridge-native tools:

| Tool | readOnly | destructive | openWorld | |---|---|---|---| | webmcp_navigate | false | false | true | | webmcp_status | true | false | false | | webmcp_evaluate | false | true | true | | webmcp_invoke_tool | (page-determined) | (page-determined) | (page-determined) | | webmcp_screenshot | true | false | false | | webmcp_register_test_tools | false | false | false | | webmcp_history / webmcp_list_tabs | true | false | false | | webmcp_clear_history / webmcp_close_tab | false | true | false | | webmcp_open_tab / webmcp_switch_tab | false | false | false |

Page-discovered tools forward their annotations to MCP clients as-is. The bridge never overrides page-supplied annotations. If a page calls registerTool("delete_account", { annotations: { destructiveHint: true } }), that annotation flows through to tools/list. Annotation changes on the page trigger notifications/tools/list_changed.

Clients that ignore the annotations field see no behavior change.

Declarative WebMCP API

Chrome's WebMCP API has two surfaces. The bridge handles both:

  1. Imperative — pages call document.modelContext.registerTool({ name, inputSchema, execute }).
  2. Declarative — pages annotate HTML with toolname / toolname-target attributes:

```html

Search

```

The bridge synthesizes an MCP tool descriptor from this form. Invoking the tool fills the targeted fields with the provided arguments and dispatches a submit event on the form.

Name collisions: if the page registers an imperative tool with the same name, the imperative registration wins.

Opt-out: set WEBMCP_DECLARATIVE_SCAN=false to disable DOM scanning and the MutationObserver entirely.

Troubleshooting

  • "Failed to parse input arguments" — caused by document.modelContext.executeTool() failing on the page side. The monkey-patch bypasses this by calling the captured executor directly.
  • "detached Frame" — the page was navigated away or closed. The bridge auto-recovers by creating a new page. Re-run the tool call.
  • No tools discovered — ensure the page has called document.modelContext.registerTool() with tool definitions. Use webmcp_evaluate to call document.modelContext.getTools() manually.
  • CORS errors in test toolstest_fetch_title uses fetch() which is subject to page CSP. Test on a permissive origin.

License

MIT

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.