Set it up from scratch in five minutes, then give any AI client live search, page, PDF, GitHub and OpenAPI reading.
Node 20 or newer, git, ~260 MB of disk, and a Serper key (2,500 free credits, no card). Unlike its sibling this one is TypeScript, so the build step is not optional.
git clone https://github.com/leanzero-srl/mcp-web-search.git cd mcp-web-search npm install npm run build # REQUIRED — creates dist/index.js pwd # copy this — the client config needs the absolute path
Cannot find module .../dist/index.js is what you get when you skip the build. It is the most common first-run failure, and the error names the file rather than the cause.
claude mcp add web-search --scope user \ -e SERPER_API_KEY=your_key_here \ -e USE_SERPER_ONLY=true \ -- node /ABSOLUTE/PATH/TO/mcp-web-search/dist/index.js
{
"mcpServers": {
"web-search": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/mcp-web-search/dist/index.js"],
"timeout": 120000,
"env": {
"SERPER_API_KEY": "your_key_here",
"USE_SERPER_ONLY": "true"
}
}
}
}printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node dist/index.js 2>/dev/null | tail -1
# A healthy server answers with all 11 tools.SERPER_API_KEY looks perfectly healthy until the first real search comes back empty. Run one live search before you trust the setup.Prerequisites, the four search engines compared honestly, whether you need the Playwright browsers, every client, and what each failure means are all in part 1 of the manual.
Three deployments, one codebase. Most people want the first, and the manual documents all three end to end.
Recommended for almost everyone
Your MCP client launches the server as a child process. No port, no listener, and your search queries go to the engine and nowhere else.
When something remote has to reach it
The HTTP transport under launchd or systemd, published through a Tailscale Funnel with per-tenant bearer tokens. The recipe our own Mac Studio runs.
To evaluate it in a minute
One instance of this exact repo on our Mac Studio. Free key by email — and you still bring your own Serper key, so every search is billed to you.
One `claude mcp add` command. Non-agent cohort — `claude-code` is not on the whitelist — so disk-writing tools embed content inline.
One entry in `claude_desktop_config.json` — with `"timeout": 120000`, which is not optional.
Non-agent cohort, so content is embedded inline. Also the bridge Forge apps reach through.
Same block everywhere. Qwen Code's 30-second default is the most-reported issue and the timeout is the fix.
CogniRunner exposes four of the eleven tools, brokered from inside Forge.
Every question this server raises, answered in order: install it, connect your client, run it as a service the way we do, tune it, use all 11 tools, read the code, wire it into Jira through CogniRunner, and fix it when it misbehaves. 36 sections.
Written against the server's own source and against the live deployment behind the hosted demo — the launchd, Tailscale and hardening steps are the configuration actually running, with the secrets removed.
Node 20 or newer, git, ~260 MB of disk, and a search key. There is a build step here — this one is TypeScript, unlike its sibling, and skipping it is the most common first-run failure.
MCP Web Search is a Node.js server that gives an AI agent live access to the web: search, full-page extraction, PDF reading, GitHub repository crawling, OpenAPI spec discovery, and multi-round progressive research. It speaks the Model Context Protocol over stdio, so your client launches it as a child process — and it also has an HTTP transport for the remote case.
| What | Version | Why | Check it |
|---|---|---|---|
| Node.js | 20 or newer (we run 24) | package.json declares engines: { node: ">=20" }. | node --version |
| npm | 10 or newer | Installs and builds. | npm --version |
| git | any | Cloning the repo. | git --version |
| Disk | ~260 MB, or ~1 GB with browsers | Measured on a clean clone: 256 MB of node_modules. Playwright's browsers are separate and optional — Chromium plus its headless shell is ~520 MB, and adding Firefox (the other default in BROWSER_TYPES) takes it past 750 MB. | df -h . |
| A Serper key | 2,500 free credits | The default search path, and in the stock configuration the only one. Sign-up takes ~2 minutes and needs no card, but the free grant is one-time, not monthly. Going without it means enabling browser fallbacks — see below. | serper.dev |
mrkrsl/web-search-mcp, MIT licensed, and this fork keeps that licence. What was added: the orchestration layer, the enterprise guardrails, the HTTP transport with tenant auth, client-aware output shaping, the semantic cache, and the specialised GitHub / OpenAPI / PDF extractors.Clone, install, build. The build is not optional — the entry point your client launches is dist/index.js, and it does not exist until you run it.
git clone https://github.com/leanzero-srl/mcp-web-search.git
cd mcp-web-search
npm install
npm run build # tsc + esbuild -> dist/index.js and dist/bundle.cjs
pwd # copy this — the client config needs the absolute pathargs in your client config.node_modules tree.npm run dev (tsx watch) runs from source if you are iterating on the code.Playwright is a dependency, but `npm install` does NOT download the browsers — verified on a clean clone: the published playwright package registers no install script, and launching with an empty browser cache fails with browserType.launch: Executable doesn't exist at …. If you intend to use browser fallbacks you must run npx playwright install chromium firefox as a separate step. You do not need them for the default Serper-only configuration — see "Do I need the Playwright browsers?" below.
Sign up at serper.dev for 2,500 free credits and paste the key into one environment variable. The grant is one-time, not monthly — and there are keyless engines too, which are worse. Here is the honest comparison.
SERPER_API_KEY in the env block of your MCP client config.USE_SERPER_ONLY at its default of true unless you specifically want browser fallbacks.| Engine | Needs a key | Also needs | Honest assessment |
|---|---|---|---|
| Serper | Yes — 2,500 free, then paid | Nothing | The right choice, and the path the defaults take. A real search API: no scraping, no bot detection, no CAPTCHA, and fastest by a wide margin. |
| DuckDuckGo | No | USE_SERPER_ONLY=false, a raised per-tool timeout, and browsers for its fallback leg | Tried second. Uniquely, it starts with a plain Axios fetch of html.duckduckgo.com and only escalates to a browser — so it is the one keyless engine that can technically answer with no browsers at all, though the page it returns is often unparseable. |
| Bing | No | USE_SERPER_ONLY=false, a raised per-tool timeout, and the Playwright browsers (~750 MB) | Tried third, through a headless browser. Works until it does not — expect intermittent blocking. |
| Brave | No | USE_SERPER_ONLY=false, a raised per-tool timeout, and the Playwright browsers (~750 MB) | Tried last, same machinery. |
getEnginePriorityOrder() in src/browser-engine.ts returns ['api','webkit','chromium','firefox'], which maps to Serper → DuckDuckGo → Bing → Brave. The only thing you configure is whether the non-Serper engines run at all (USE_SERPER_ONLY / ENABLE_BROWSER_FALLBACKS). Any guide telling you to set SEARCH_ENGINE=brave — including earlier versions of this page — is describing a variable the code never reads.USE_SERPER_ONLY=false AND the browser binaries. Worse, the per-tool timeouts cut them off: measured on this machine with browsers installed and no key, the DuckDuckGo leg alone burned 10.4 s against an 8,000 ms TOOL_TIMEOUT_SEARCH_SUMMARIES, logging Search timeout reached before attempting engine chromium — Bing and Brave were never tried. Raising it to 120,000 ms produced results, at a quality score of 0.15, below the default RELEVANCE_THRESHOLD of 0.3. Keyless is a research setting, not a deployment.USE_SERPER_ONLY=true and no working key, search-engine.ts returns { results: [], engine: "serper-failed-no-fallback" } and the tool answers "No results found for … Try different or broader keywords, or use full-web-search for a deeper crawl." That is indistinguishable from a genuinely empty search. Verified against the live hosted server. If every query comes back "no results", suspect the key first.PARALLEL_SEARCH (default true) is a misnomer — the source renamed the function to searchWithPriorityFallbacks precisely because it is sequential, trying engines in priority order and stopping at the first that clears the quality bar.get-github-repo-content uses the unauthenticated GitHub API and its 60-requests-per-hour limit, which one repo crawl can exhaust. A read-only personal access token raises that to 5,000.Not if you have a Serper key — the default configuration never launches one, and skipping them saves about 750 MB. They become close to mandatory the moment you try to run keyless.
Extraction runs in two stages. Stage 1 is a fast HTTP fetch with Axios and works for the large majority of pages. Stage 2 spins up a headless browser to get past bot detection and to render JavaScript-only pages. Stage 2 is off by default (USE_SERPER_ONLY=true) because it is dramatically slower and heavier, and because for most research the Stage-1 result is the same result.
browserType.launch: Executable doesn't exist at …, which names a path rather than telling you to install anything.# Install the browsers (once) — REQUIRED, npm install does not do this:
npx playwright install chromium firefox
# Then, in your client's env block:
# "USE_SERPER_ONLY": "false"
# "BROWSER_HEADLESS": "true"
# "BROWSER_TYPES": "chromium,firefox"
# "MAX_BROWSERS": "3"USE_SERPER_ONLY=false and a raised TOOL_TIMEOUT_*, there is effectively no search path. (DuckDuckGo alone tries a plain HTTP fetch first, but rarely returns anything parseable.)MAX_BROWSERS (default 3) times CONTEXT_POOL_SIZE (default 10) is your worst case. On a small VPS, leave fallbacks off — the OOM killer arriving mid-research is a much worse failure than an empty extraction.Drive the protocol from a shell and count the tools. Eleven means it is genuinely working, before any client is involved.
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node dist/index.js 2>/dev/null \
| tail -1 | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["result"]["tools"]), "tools")'11 tools. If you get that, the install is sound and every remaining problem is client configuration or a search key.npm test # the vitest suite
npm run test:unit # units only, fastest
npm run test:integration # hits the real network — needs SERPER_API_KEYThe gap between a summary and a full search is roughly an order of magnitude in both latency and context. Most agents reach for the expensive one every time. The decision table is in the manual.
Snippets and descriptions, no page extraction. The right tool for a quick fact check.
Top results with full, cleaned Markdown content. The semantic cache sits in this path.
Query expansion across multiple rounds and engines, for questions one query cannot express.
Targeted extraction from a URL you already have. No search cost at all.
Text from a PDF URL, with a browser fallback for PDFs behind interstitials.
Three modes — crawl the repo, list one directory, or return a single file in full.
Discovers and downloads OpenAPI / Swagger specs, JSON and YAML.
Maps a site, filters by keyword, and can extract the top N matches in the same call.
Multi-URL research written to a markdown file per result.
What did I save earlier? Filter by all, openapi or research.
Reads a saved document back inline. Refuses path-traversal characters.
Six stages between your query and the Markdown. Almost every tuning question answers itself once you can point at the stage it belongs to. The full eleven-step trace is in the manual.
The query is classified before anything is searched — does this need a summary, or real extracted content? That decision alone is most of the latency difference between tools.
Serper first when a key is present. On the default Serper-only configuration that is the only attempt — a failure returns no results rather than falling through. With browser fallbacks enabled, a blocked or rate-limited engine hands off in the hard-coded priority order (Serper → DuckDuckGo → Bing → Brave), and a circuit breaker stops hammering one that is down.
Axios and Cheerio fetch and clean each result into Markdown, `EXTRACT_CONCURRENCY` pages at a time. This handles the large majority of the web.
Only if enabled and Stage 1 failed: a pooled Playwright browser renders the page and gets past bot detection. Off by default because it is dramatically slower.
Every extraction is scored for relevance and noise, and anything under the threshold is dropped before it reaches your model. This is also why a niche query can come back empty.
A near-identical query later returns instantly, tagged `engine: semantic-cache`. Needs Upstash credentials to initialise — without them it silently skips.
A workflow rule that checks the live web while it runs — instead of reasoning from a model's training cutoff.
Summaries first, then a named URL, then a PDF, and full-web-search only when summaries are genuinely insufficient. The other seven tools are deliberately not exposed.
The hosted bridge works with every AI provider. The local path works only with LM Studio — but then the query leaves your machine to the search engine and to nowhere else.
Connect it →Search takes 30 to 90 seconds in this path. Every per-tool timeout default sits under ~20 s so the Forge → LM Studio → MCP chain fits inside Forge's ~25-second function limit.
mcp.json entry must be named exactly web-search. Forge egress reaches a *.ts.net URL on port 443 and nothing else; a differently-named entry means the tools are never offered, with no error to tell you why. Full walkthrough →One instance of this exact repo, on our Mac Studio behind a Tailscale Funnel. Endpoint: https://worksmacstudio.tailfc4700.ts.net/websearch/mcp
X-Serper-Key. It is read from the request, used for that call, and discarded.This server is one half of a pair, and both are wired into the same Jira app.
The sibling server. Its fact-check tool calls this one per claim — so document verification needs both. Same setup shape, same launchd and Tailscale recipe.
The Jira workflow app that consumes this MCP — AI validators, conditions and post-functions, with the MCP setup documented on its own page.
Everything we have written about running models on your own hardware, including the LM Studio bridge this server reaches Forge apps through.
mrkrsl/web-search-mcp — the MIT-licensed project this is forked from. Credit where it is due.
Not to get started. Signing up at serper.dev takes about two minutes, needs no card, and gives you 2,500 credits — by far the best result quality available. That grant is one-time rather than monthly, so sustained use eventually costs money. Going fully keyless is possible but not free of effort: Bing, Brave and DuckDuckGo are driven through a headless browser, so they need USE_SERPER_ONLY=false and the ~500 MB of Playwright browsers installed. On the stock configuration they are not reachable at all.
Your client's per-server timeout is too short. A full search with content extraction takes 30 to 90 seconds; most clients default to 30 or 60. Set "timeout": 120000 — milliseconds, not seconds. It is the most frequently reported issue against this server.
Because your client was identified as agentic — Cline, Claude Desktop, Roo Code, Continue and claude-ai run a sibling filesystem MCP and read the file themselves, so embedding the content would waste the context twice. Unknown clients default to the safe inline shape.
Two causes, and they look identical. First and most common: no working search key. With the default USE_SERPER_ONLY=true there is no keyless fallback, so the tool answers "No results found — try different or broader keywords" rather than an authentication error. Second: the relevance filter. RELEVANCE_THRESHOLD defaults to 0.3 and can filter everything on a niche query — set ENABLE_RELEVANCE_CHECKING=false once as a control, and if content appears, lower the threshold to about 0.15 rather than leaving the filter off.
Not if you have a Serper key — the default configuration never launches one and skipping them saves about 750 MB. They become close to mandatory if you want to run keyless, because Bing and Brave are browser-driven, and you must also raise the per-tool timeouts or the browser engines are cut off before they are reached. Turn them on with USE_SERPER_ONLY=false, also when a site you need consistently returns near-empty content, which is a sign it renders client-side.
It is a fork of mrkrsl/web-search-mcp, MIT licensed, with the orchestration layer, enterprise guardrails, HTTP transport with tenant auth, client-aware output shaping, semantic cache and the specialised GitHub / OpenAPI / PDF extractors added.
To the search engine you configured, and to the pages it returns. Self-hosted over stdio, nothing else is in the path. Through the hosted demo, requests also pass through our Mac Studio — so self-host anything sensitive.
Yes, over the hosted bridge with any AI provider, or locally through LM Studio. CogniRunner exposes four of the eleven tools in a deliberate cost order, and the manual documents both paths including the port-443 requirement that catches self-hosters.
Clone it, run it, fork it, ship it. The hosted demo is one deployment of the same repository — there is no paid tier holding anything back.