Opencode Webmcp
FreeNot checkedBridges OpenCode to Chromium's WebMCP API, exposing WebMCP tools from a webpage as standard MCP tools for any MCP client.
About
Bridges OpenCode to Chromium's WebMCP API, exposing WebMCP tools from a webpage as standard MCP tools for any MCP client.
README
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 <repo-url> 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 (<form toolname="…">); 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-patch —
page.evaluateOnNewDocumentinjects a script that interceptsdocument.modelContext.registerToolandexecuteToolto capture actualexecutefunctions - Declarative API scanner — also scans the DOM for
<form toolname="…">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:
Imperative — pages call
document.modelContext.registerTool({ name, inputSchema, execute }).Declarative — pages annotate HTML with
toolname/toolname-targetattributes:<form toolname="search"> <input name="q" toolname-target="search.query" required> <input name="limit" toolname-target="search.limit" type="number"> <button>Search</button> </form>
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. Usewebmcp_evaluateto calldocument.modelContext.getTools()manually. - CORS errors in test tools —
test_fetch_titleusesfetch()which is subject to page CSP. Test on a permissive origin.
License
MIT
Installing Opencode Webmcp
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/enihcam/opencode-webmcpFAQ
Is Opencode Webmcp MCP free?
Yes, Opencode Webmcp MCP is free — one-click install via Unyly at no cost.
Does Opencode Webmcp need an API key?
No, Opencode Webmcp runs without API keys or environment variables.
Is Opencode Webmcp hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Opencode Webmcp in Claude Desktop, Claude Code or Cursor?
Open Opencode Webmcp on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Opencode Webmcp with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
