The playwright-search-mcp binary exposes three tools over the standard MCP stdio protocol — usable by opencode, Claude Code, Cursor, Continue, or any other MCP-compatible host. Works with any LLM behind those hosts (LM Studio, Ollama, Anthropic, OpenAI, …).
| Tool | Inputs | Returns |
|---|---|---|
web_research |
query, top (1–20, default 3), engines (default ["ddg","brave","bing"]), max_chars_per_source (500–12000, default 3000), relevance_query (optional), headless (default true) |
Formatted text with [Source N: title — site — url] markers; paragraph-ranked by relevance: returns the BEST paragraphs matching the query, not the first N chars |
web_search |
query, limit (1–20, default 10), engines, headless |
Numbered list of titles + URLs + snippets, deduped across engines |
web_fetch |
url, max_chars (500–12000, default 8000), no_cache, relevance_query (optional) |
Header (title/byline/site/url) + extracted main article text. With relevance_query, returns the BEST paragraphs for that query. |
Architecture (as of v0.1): HTTP-first. Search engines are queried via plain fetch() + jsdom parse first — no browser launch. Browser (Playwright headless Chromium) is only invoked as fallback when the HTTP path is blocked by something a browser would help with (and not, e.g., by a Proof-of-Work captcha that a browser can't solve either). A 3-source web_research call typically returns in ~15–25s on cold cache, near-instant on warm cache.
web_research is the recommended primary — local LLMs are much better at making one well-formed tool call than chaining search → fetch → fetch → fetch. One call, formatted output, citations baked in.
web_search and web_fetch exist for power-user flows where the agent wants to triage URLs first or has a known URL it wants to read.
cd /Users/bmatthews/Code/playwright-search
npm install
npm run build
# Then one of:
node dist/mcp.js # production entry (used by opencode.json)
npm run mcp # dev (tsx, no build needed)
npm run mcp:start # production (built)Verify it works:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node dist/mcp.jsYou should see the tools/list response with all three tools.
Add to your project's opencode.json (a working example is in bpm-opencode-experts/examples/opencode.json):
{
"mcp": {
"playwright-search": {
"type": "local",
"command": ["node", "/Users/bmatthews/Code/playwright-search/dist/mcp.js"],
"enabled": true
}
}
}For dev (no build step needed), use tsx:
"command": ["npx", "tsx", "/Users/bmatthews/Code/playwright-search/src/mcp.ts"]opencode is LLM-agnostic — once registered, the same tools work whether you're running LM Studio (Qwen, Gemma, Nemotron), Ollama, Anthropic API, or OpenAI.
Project-level: add .mcp.json at the repo root:
{
"mcpServers": {
"playwright-search": {
"command": "node",
"args": ["/Users/bmatthews/Code/playwright-search/dist/mcp.js"]
}
}
}User-level (all projects): add to ~/.claude.json or via claude mcp add playwright-search node /Users/bmatthews/Code/playwright-search/dist/mcp.js.
To close the search → research → remember loop, register both this MCP and a memory MCP (claude-memory or mempalace). The agent uses web_research for findings, then memory_store / fact_store to persist them with the source URL.
{
"mcp": {
"playwright-search": { ... },
"claude-memory": {
"type": "local",
"command": ["node", "/Users/bmatthews/Code/claude-memory/mcp/memory-server/dist/index.js"],
"enabled": true
}
}
}The researcher agent prompts in both bpm-opencode-experts/agents/researcher.md and claude-experts/agents/researcher.md are already updated to expect this surface.
- Token budget: default
max_chars_per_source=3000per source × 3 sources ≈ 9k tokens of context. Comfortable for a 45k budget. Drop to 1500 for tight budgets, raise to 6000 for premium-context models. - Engine selection:
["ddg","brave","bing"]is the default. Brave may serve a Proof-of-Work captcha to repeated callers; the adapter detects this and skips the browser fallback (since browser hits the same challenge). Google is excluded by default — add it only if you're OK with frequent captcha aborts. - Caching: 24h disk cache at
~/.playwright-search/cache/— repeat queries within a day are zero-cost. Passno_cache: trueonweb_fetchif you need fresh. - Per-engine timeout: 20s. If an engine hangs, others continue.
- Progress notifications: the server emits
notifications/progressduring long calls so MCP clients (opencode, Claude Code) can keep the request alive past their default request timeout. - Response shape: all tools return one
textcontent block — no JSON-in-JSON, no nested structures. Friendlier for smaller models.
- Server runs over stdio; no network port to manage.
- One MCP process per host (opencode/Claude Code spawns it on demand and pipes stdin/stdout).
- Per-engine persistent Chromium profiles under
~/.playwright-search/profile/{ddg,brave,bing,google}/so engines can run concurrently without serializing on a single profile dir lock. Cookies and consent persist per engine. - Per-domain page-fetch rate limit (1.2–2.5s) + robots.txt respect are unconditional — no flag to disable in MCP mode (CLI has
--no-robotsfor emergencies; MCP doesn't expose it on purpose). - All agents in a project see these tools. Once
playwright-searchis in youropencode.jsonmcpblock, every agent in that project can callweb_research/web_search/web_fetch— not just a designated researcher agent. Whether each agent is aware of the tools depends on its system prompt; seebpm-opencode-experts/agents/shared/RESEARCH_TOOLS.mdfor the shared reference doc agents read at runtime.