free-search-mcp
free-search-mcp is a local-first, no-API-key MCP server that gives LLMs web search, content fetching, and document reading capabilities.
search— Multi-engine web search (DuckDuckGo, Mojeek, Startpage, SearX, etc.) with RRF-merged, deduplicated results; supportsfreshness,include_domains,exclude_domains,category(news/pdf/github/paper/forum/blog),include_text, andexclude_textfiltersresearch— One-shot tool that searches, fetches top N results, and returns a citable Markdown brief with source index in a single callfetch— Retrieve a single URL as reader-mode Markdown (with author, publish date, sitename); uses HTTP fast path with Playwright browser fallbackfetch_batch— Concurrently fetch multiple URLs in parallel with per-URL error handlingread_doc— Parse local or remote PDF, DOCX, HTML, TXT, or Markdown files into Markdown, with character-offset paginationextract_structured— Pull structured data (JSON-LD, OpenGraph, Twitter cards, microdata) from web pagescache_search— Full-text search (SQLite FTS5) over previously fetched pages, supporting AND/OR/NOT and phrase queriesengines— List all available search engine names for dynamic selection
Key properties: No API keys required; LLM-optimized Markdown output with token estimates and smart truncation; anti-detection via curl_cffi Chrome fingerprints and Playwright stealth; integrates with Claude Desktop, Cursor, Continue, and other MCP clients.
Included in the 'paper' category filter, restricting search results to arXiv (along with other academic domains).
Allows web search using Baidu as an opt-in engine, but may face intermittent challenges for headless clients.
Allows web search using Brave Search as an opt-in engine, though may require browser fallback to handle captchas.
Allows web search using DuckDuckGo, one of the default multi-engine search sources without API key.
Allows web search using Mojeek, one of the default multi-engine search sources without API key.
Allows web search using Startpage, one of the default multi-engine search sources without API key.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@free-search-mcpresearch Quantum computing breakthroughs, depth=2"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
free-search-mcp
A local-first, no-API-key Model Context Protocol server that gives any LLM (Claude, GPT, local Ollama, …) the ability to search the web, fetch and clean up pages, and read documents — without you signing up for a single search API.
It bundles together the best ideas from a handful of open-source MCPs into one Python package, and adds the LLM-ergonomics and reliability work they were each missing.
research("how does reciprocal rank fusion work", depth=3)
↓
# Research brief: how does reciprocal rank fusion work
_engines: duckduckgo, mojeek, googlenews · sources: 3 · ~3,400 tokens_
## Sources
- [1] Reciprocal rank fusion | Elasticsearch Reference — <https://…>
- [2] Hybrid Search Scoring (RRF) | Microsoft Learn — <https://…>
- [3] RRF explained in 4 mins — Medium — <https://…>
## Documents
…full Markdown bodies of each page, ready for the LLM to read…One tool call. Three sources. No API key. No OPENAI_API_KEY-but-for-search
shakedown.
🚀 One-click deploy
One command — the keyless engines work immediately, no signup, no key, no checkout (needs uv):
claude mcp add search -- uvx free-search-mcp # Claude Code
codex mcp add search -- uvx free-search-mcp # CodexClaude Code can install it as a plugin instead — same server, no
claude mcp add, and /plugin update handles version bumps:
/plugin marketplace add sweetcornna/free-search-mcp
/plugin install free-search@free-search-mcpAny other MCP client: point it at the command uvx free-search-mcp (stdio). The
first run downloads the package from PyPI; every HTTP engine works with no
further setup.
Optional — browser-rendered engines (startpage, zhihu, …) and JS-heavy
page fetches need Chromium once:
uvx --from free-search-mcp playwright install chromiumWithout it, HTTP search/fetch keep working, and any call that needs the browser returns that exact install command instead of a cryptic failure.
Configuration (all optional) lives in ~/.config/search-mcp/.env — see
Configuration.
Full install (source checkout + client registration)
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client claude-codeIt clones or updates the project under ~/.local/share/free-search-mcp,
installs uv, syncs dependencies, installs Chromium for rendered engines
(with OS deps on Linux), smoke-tests the server, and registers the search
MCP server in Claude Code user scope.
Other client targets:
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client claude-desktop
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client codex
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client generic
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client add-mcp
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client allCodex, Cursor, Cline, Continue, Zed, and generic agent guidance: docs/AGENT_USAGE.md.
Optional extras, any time (the defaults already work without them):
uv run search-mcp-admin # bilingual browser UI / 中英双语配置页: http://127.0.0.1:8765
uv run search-mcp-login zhihu # one-time Zhihu login (persists cookies)Prefer containers? docker compose run --rm search-mcp. Claude Desktop and
other clients: see Install below.
Related MCP server: web-basics-mcp
Why this exists
Existing search MCPs each do one thing well, but you usually want all of it:
Multi-engine | No API key | Smart fallback | PDF/DOCX | FTS5 cache | Filters | Trafilatura | LLM-tuned | |
| ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ~ |
| ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ~ |
| ✓ | ✓ | ~ | ✗ | ✗ | ✗ | ✗ | ~ |
| ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ~ |
free-search-mcp | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
"LLM-tuned" here means: Markdown-first output, token estimates, smart
truncation at paragraph boundaries, "Best for / Not for / Returns / Common
mistakes" docstrings the model uses to pick the right tool, actionable
error hints, MCP prompts and resource templates, and a one-shot
research() that collapses search→fetch→fetch→fetch into a single turn.
"Trafilatura" means we extract main content using
trafilatura — winner of the
Bevendorff 2023 ROUGE benchmark (~0.85 vs ~0.55 for naive boilerplate
stripping). Each fetched page also returns author, published_date, and
sitename for free.
"Filters" means search/research accept freshness, include_domains,
exclude_domains, category, include_text, exclude_text. category also
routes the query to sources that natively index it: a bare group
(news/pdf/github/paper/forum/blog/image/dataset/finance)
widens, and a dotted sub-group (paper.biomed, paper.math,
finance.filings, dataset.repository, dataset.ml, dataset.gov, …) narrows
to just the sources that index it — see
Vertical sources.
Anti-detection & resilience
HTTP fast path uses
curl_cffiwith a real Chrome 131 JA3/JA4 + HTTP/2 fingerprint, fixing the DDG "anomaly 202" rate-limit response that vanilla httpx triggered.Playwright fallback uses
launch_persistent_context(cookies survive restarts on disk), prefers a real installed Chrome (channel="chrome"), drops the--no-sandboxfingerprint marker on macOS, and randomizes the viewport per session.Result dedup is title-fuzzy + host-canonical (rapidfuzz
token_set_ratio >= 92, host normalized forwww./m./amp.and country-TLD collapse), catchingbbc.co.ukvsbbc.comduplicates that URL-only dedup misses.searchincludes an honest extractivelead_snippet— picks the top-3 result whose snippet contains ≥2 query terms and is ≥80 chars; rendered as> **Lead:** According to {host}: …. No LLM call. Returns nothing if no snippet qualifies (no fake answer).
⚠️ We deliberately do not attempt to defeat proof-of-work captchas on Bing or Brave — that crosses the ToS line. When those engines challenge us, we fall back to other engines instead.
Tools (11)
Tool | Description |
| Parallel multi-engine search, RRF-merged, title-fuzzy + host-canonical deduped, with optional extractive |
| One-shot: search + fetch top N + return Markdown brief |
| Walk one paper's citation graph — references, citing works ranked by influence, and Crossref retraction/correction notices |
| Concurrent fetch of 2-5 URLs, side-by-side excerpts keyed by question |
| Fetch any resource: reader-mode Markdown for pages, parsed text for documents, or a description (type/size/dimensions/sha256) for images and binaries. |
| Concurrent multi-URL fetch (max 20 per call) |
| Parse PDF / DOCX / XLSX / PPTX / EPUB / CSV / code / zip-tar / HTML / TXT / MD with pagination |
| Pull JSON-LD / OpenGraph / Twitter cards / microdata via extruct |
| FTS5 search across previously fetched pages |
| The source tree — group → sub-group → engine, one line each |
| Save a file to |
Plus 4 MCP prompts (Research thoroughly, Fact-check claim,
Compare sources, News brief) and 2 resource templates
(cache://page/{url}, cache://search/{query_hash}).
Filters (search / research)
Param | Values | Effect |
|
| Only results from the last N |
|
| Restrict to these domains |
|
| Remove these |
| a group ( | Routes to the sources that natively index that kind of thing; the enum in the tool schema lists every value |
|
| Substring required in title/snippet |
|
| Substring forbidden |
|
| Override the 7-day default cache TTL on this call |
All tools default to format="markdown" — readable, ~40% fewer tokens than
JSON, with provenance and a token-budget header. Pass format="json" for
structured access.
Tool annotations
Every tool ships correct readOnlyHint, idempotentHint, and
openWorldHint annotations so MCP clients can label them and gate
elevated actions.
Engines
The registry now contains 49 engines in total; the default pool remains the four all-HTTP engines below.
Default set (all-HTTP, no browser):
duckduckgo, mojeek, googlenews, bing.
When a search comes back empty (or nearly empty with gated/erroring
engines), the aggregator automatically runs one bounded rescue pass
via searx → bing and reports it as rescued_via — so a CAPTCHA wall
on the defaults degrades to slower results instead of no results.
Opt-in:
startpage— browser-rendered (~5-10s/query); good for hard-to-reach results that the HTTP defaults miss.brave,baidu— intermittent challenges to headless clients.searx— meta-search proxy via public SearXNG instances; included for completeness but most public instances are slow/unreliable in 2026.google— keyless Google web SERP scrape (HTTP first, Playwright fallback when Google serves a JS/consent shell).serpsearchis a pure alias ofgoogle(all dedicated "SERP APIs" require a key, so the only keyless SERP is a direct scrape).anysearch— AnySearch unified-search REST API, anonymous (keyless) tier; one HTTP call returns fused, re-ranked results. IP rate-limited; 429/5xx degrade to empty.bilibili— keyless Bilibili (哔哩哔哩) video search via the publicweb-interface/search/all/v2JSON API (syntheticbuvid3cookie, no login). Returns video results only.zhihu— best-effort keyless Zhihu (知乎) search. Zhihu'sapi/v4/search_v3needs login cookies +x-zse-96signing, so the only no-key path is browser-rendering the public search page. Zhihu hard-gates headless clients, so a login wall / empty result is common and honest — treat it likebaidu/brave.sogou,so360— Chinese web indexes, HTML scrapes, best-effort likezhihu. Notesogoureturns redirect URLs (sogou.com/link?url=…) rather than target URLs; the blob is only resolvable by following it.fetchhandles that, but host-basedcategoryfiltering will discard them.baiduandso360return direct URLs.wikipedia— encyclopedia search; language followsSEARCH_MCP_REGION.openlibrary— book search over the Internet Archive catalogue.
Scholarly and financial sources are keyless too, and are normally reached via
category= rather than by name: arxiv, openalex, crossref, pubmed,
europepmc, dblp, doaj, clinicaltrials, zbmath — and sec_edgar,
yahoofinance, cninfo, worldbank, imf. Dataset sources are
dryad, dataverse, figshare, huggingface, dataeuropa, and zenodo;
the image sources are openverse and wikimedia. semanticscholar is
registered alongside the scholarly sources but its anonymous pool answers 429
in practice, so it stays out of automatic routing until a (free) key is
configured.
All keyless engines stay opt-in — they're not in the fast default pool, so the ~2x latency win of the all-HTTP defaults is preserved. Enable per call with
engines=["google","bilibili", ...], or globally viaSEARCH_MCP_DEFAULT_ENGINES.
Vertical sources (selected automatically by category)
These index something a general web engine can't. You normally don't name
them — passing category= to search/research routes to them. Sources are
organised as group → sub-group: a bare group widens (one specialist per
sub-group joins the web pool until the engine cap), while a dotted sub-group
narrows to that branch; the same cap still applies.
| Engines | Why it matters |
| one per sub-group, below | Actually searches the literature instead of filtering web results by hostname |
|
| Cross-discipline DOI indexes with citation counts |
|
| Work that has not been peer reviewed yet, flagged as such |
|
| MEDLINE and its 40M-record superset, with open-access full text |
|
| Curated CS bibliography: exact venues, authors, DOIs |
|
| Every hit is free to read in full, so |
|
| Registered human trials — evidence the literature has not caught up with |
|
| Mathematics literature with reviews and classification |
| one per sub-group, below | No general engine indexes filings, quotes or macro series |
|
| Regulatory filings in full text (US, and A-share/HK) |
|
| Ticker resolution plus market news for the resolved instrument |
|
| World Bank research and IMF DataMapper series, WEO forecasts included |
|
| Real repository metadata, stars, last push |
|
| Accepted-answer and score signals |
|
| GDELT covers 100+ languages Google News never surfaces |
|
| Openly-licensed images; results are direct file URLs, with Wikimedia attribution and source metadata |
| one per sub-group, below | Datasets, software and open-data catalogues |
|
| Research datasets in public repositories, with DOI or landing-page metadata |
|
| Machine-learning dataset repositories and dataset cards |
|
| EU and member-state open-data catalogues in one index |
At the default category-engine limit, category="dataset" selects
dryad, huggingface, and dataeuropa—one source from each dataset
sub-group. Use dataset.repository, dataset.ml, or dataset.gov when the
sub-group is known; category="paper" remains arxiv, openalex, and
europepmc, while paper.math reaches zbmath explicitly.
image and dataset replace the default pool rather than adding to it —
a web engine can't return an image file or a dataset record, so mixing it in
only crowds out the specialist sources that can. Before 0.11.0, each exclusive
category had exactly one specialist (openverse or zenodo); now image has
two sources and dataset has five across three sub-groups, so an outage, rate
limit, or missed hit no longer leaves the category without a specialist
fallback. The other groups augment the web pool, capped by
SEARCH_MCP_CATEGORY_ENGINE_LIMIT (default 3). Because that cap is smaller
than most groups, a bare group round-robins across its sub-groups before
truncating: category="paper" spends its three slots on three different
corpora rather than on three overlapping DOI indexes.
Results from an engine that natively indexes the requested category count
double in the rank fusion. Without that, category= barely affected the
order: a specialist is usually the only source returning a given document, so
its hit lost to three general engines agreeing on a blog post about the topic.
engines() prints this tree live from the registry — it is derived from what
each engine declares, so it cannot drift from what actually runs.
Naming engines explicitly (engines=[...]) turns the routing off.
Sources that publish a stricter rate limit than our default declare it themselves, and are skipped rather than queued when their bucket is empty — search is a parallel fan-out, so waiting on one slow source would add that delay to every other engine's results.
API-key engines & the admin backend
Keyless is the default, but you can also plug in keyed providers for higher reliability/quality. These engines stay dormant (and return a clear "not configured" hint) until you add a key:
Engine | Provider | Free tier |
| 2,000 queries/mo | |
| Serper (Google) | 2,500 queries |
| Tavily (AI search) | 1,000 credits/mo |
| 100 queries/day | |
| AnySearch (key optional) | keyless works; key lifts limits |
| code search is auth-only; the keyless | |
| Stack Apps (key optional) | 300 req/day keyless; a key lifts the quota |
| Semantic Scholar (key optional) | the anonymous pool answers 429 in practice; a free key makes it usable |
Simplest setup — the admin UI:
uv run search-mcp-admin # opens a local config page on 127.0.0.1:8765It serves one bilingual page (English + 中文, bound to localhost only) with,
per provider: a how-to-get-a-key / 如何获取密钥 guide + signup/docs links,
masked key fields, Save / 保存 (applies live — no server restart),
Test / 测试, and Clear / 清除. The same page also includes
Network / Proxy / 网络 / 代理 settings. Keys are written to
~/.config/search-mcp/config.json (0600); they're never echoed back to the
page. Prefer env vars? Set SEARCH_MCP_<PROVIDER>_API_KEY instead (these
override the saved file). Full walkthrough for each provider:
docs/API_KEYS.md.
search("…", engines=["brave_api"]) # once a key is saved
search("…", engines=["tavily", "serper"]) # mix keyed + keyless freelyWhen an engine is gated (proxy · fallback · login)
Some engines get blocked by the provider — Google/Bing serve a CAPTCHA to datacenter IPs, Zhihu needs a login. We don't defeat CAPTCHAs (ToS); instead:
Proxy (the real fix for IP gating): set a proxy in the admin UI "Network / Proxy" card or
SEARCH_MCP_PROXY(http/https/socks5, optionaluser:pass@). It routes the HTTP engines, the browser, andfetchthrough a non-blocked IP. Scope it withSEARCH_MCP_PROXY_ENGINES="google bing zhihu".SearXNG auto-fallback: when
google/serpsearch/bingare CAPTCHA-gated, they transparently retry via the workingsearxmeta-search — you still get results, honestly attributed tosearx.Gate diagnostics: the response includes
gated_engines+gated_hinttelling you which engine was gated (captcha/consent/login) and how it was handled.Zhihu login: run
uv run search-mcp-login zhihu(or the admin "Login" button) once — a browser opens, you log in, cookies persist, andzhihusearch then works. Requires a desktop session.
Full guide: docs/PROXY_AND_GATES.md.
Brave/Bing/Baidu all gate headless browsers after a handful of calls (PoW CAPTCHAs, "something went wrong" pages, redirect wrappers). Pass
engines=["brave"]etc. only when the defaults can't find what you need.
Sparse-result diagnostics
When filters drop results so aggressively that ≤3 are returned, the
response includes filter_diagnostics so the LLM knows which knob to
relax. Example for category="forum" + exclude_text="beginner":
⚠️ **Filter diagnostics** (results were sparse)
Raw results: 20 across 3 engines → 0 after filters.
Top drops: category_forum (20).
Hint: Filters dropped 20 of 20 raw results. Most were excluded by
category=forum. Try widening or removing one filter.Install
Zero-checkout (uvx, recommended)
claude mcp add search -- uvx free-search-mcp # Claude Code
codex mcp add search -- uvx free-search-mcp # Codex
uvx free-search-mcp # or run the stdio server directlyOptional extras, any time:
uvx --from free-search-mcp playwright install chromium # browser-rendered engines
uvx --from free-search-mcp search-mcp-admin # bilingual config UI (opens browser)Claude Code plugin
/plugin marketplace add sweetcornna/free-search-mcp
/plugin install free-search@free-search-mcpThe marketplace is this repo (.claude-plugin/marketplace.json); the plugin is
plugins/free-search. Installing it registers exactly one thing — the stdio
MCP server search, started as uvx free-search-mcp==<plugin version>. No
skills, no hooks, no always-on prompt tokens. The pin is why the plugin version
matches the package version: plugin 0.10.0 runs package 0.10.0, and
/plugin update free-search is what moves you to a newer server.
Outside the TUI the same two steps are:
claude plugin marketplace add sweetcornna/free-search-mcp
claude plugin install free-search@free-search-mcp -s userConfiguration is unchanged (~/.config/search-mcp/.env), and Chromium for the
browser-rendered engines is still the one optional follow-up:
uvx --from free-search-mcp playwright install chromium.
One-click setup (source checkout)
curl -LsSf https://raw.githubusercontent.com/sweetcornna/free-search-mcp/main/scripts/install.sh | bash -s -- --client claude-codeThe remote installer clones or updates
~/.local/share/free-search-mcp, installs uv if needed, syncs dependencies,
installs Chromium, smoke-tests the MCP server, then registers it with the
requested client:
--client claude-code # Claude Code user-scope config
--client claude-desktop # claude_desktop_config.json
--client codex # Codex config
--client generic # print portable MCP JSON for other agents
--client add-mcp # delegate to npx add-mcp
--client both # Claude Code + Claude Desktop
--client all # Claude Code + Claude Desktop + Codex
--client none # install only, no client config changesPrefer a local checkout?
git clone https://github.com/sweetcornna/free-search-mcp.git
cd free-search-mcp
./scripts/install.sh --client nonePrefer to do it by hand?
uv sync
uv run playwright install chromium
cp .env.example .env # optional: customize engines/limitsRun as a stand-alone server (stdio transport):
uv run search-mcpOr serve it over HTTP. MCP revision 2026-07-28 removed protocol-level
sessions, so the HTTP endpoint is stateless — no sticky routing, any replica
can answer any request:
uv run search-mcp --transport streamable-http --port 8000 # → http://127.0.0.1:8000/mcpThe HTTP endpoint has no authentication and will fetch arbitrary URLs on behalf of whoever reaches it. It binds
127.0.0.1by default; if you change--host, put an authenticating reverse proxy in front. A DNS-rebinding guard rejects unknownOrigin/Hostheaders — add trusted browser origins withSEARCH_MCP_HTTP_ALLOWED_ORIGINS. This is a separate process and port from the admin UI (search-mcp-admin, port 8765), which stays loopback-only because it reads and writes your API keys.
Docker (one-click, containerized)
docker compose build
docker compose run --rm search-mcp # attaches stdio for MCPConfig & env vars
All settings are env vars prefixed with SEARCH_MCP_. Copy .env.example →
.env and edit — it documents every knob, including how to enable the new
engines via SEARCH_MCP_DEFAULT_ENGINES. See the full table under
Configuration and the usage guide.
Tests
uv run pytest -q # offline (default, no network)
SEARCH_MCP_TEST_NETWORK=1 uv run pytest -v # live tests, hit the real webReleasing
Cutting a version — the four files the version lives in (including the plugin's pinned package), the tag, PyPI, the GitHub Release, and what to verify afterwards: docs/RELEASING.md.
Wire into Claude Code
Three paths. They are not interchangeable — the first two run a published release, the third runs your working tree, so pick by what you are doing.
Using it — the plugin. One install, updates handled by Claude Code, and the server is pinned to the version you installed:
/plugin marketplace add sweetcornna/free-search-mcp
/plugin install free-search@free-search-mcpUsing it without the plugin — a plain MCP registration. The same published package, but it resolves to whatever PyPI holds at first launch, and nothing tells you when a newer version exists:
claude mcp add search -s user -- uvx free-search-mcpWorking on it — the project-scoped .mcp.json this repo ships. Running
claude inside the checkout auto-detects a search server started with
uv run search-mcp, i.e. the code in front of you. That is the whole reason
the file exists: neither the plugin nor the uvx line can ever show you your
own edits. To reach a checkout from outside it:
claude mcp add search -s user -- uv --directory /absolute/path/to/free-search-mcp run search-mcpThe plugin and the checkout coexist without a name clash — Claude Code exposes
the plugin's server as plugin:free-search:search — so inside this repo you can
have both and still tell which one answered.
Wire into Codex
codex mcp add search -- uvx free-search-mcp
codex mcp list(Source checkout: swap the command for
uv --directory /absolute/path/to/free-search-mcp run search-mcp.)
Wire into Claude Desktop
Add this to ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or the equivalent on your platform:
{
"mcpServers": {
"search": {
"command": "uvx",
"args": ["search-mcp"]
}
}
}(Source checkout: "command": "uv", "args": ["--directory", "/absolute/path/to/free-search-mcp", "run", "search-mcp"].)
Restart Claude Desktop. The eleven tools above will appear in the tool drawer.
Wire into other clients
The server speaks plain MCP over stdio (or streamable-http, see above). It
implements protocol revision 2026-07-28 and serves every earlier revision
from the same process, so both new and older clients work unchanged:
Codex (
codex mcp add search -- uvx free-search-mcp)Claude Code (
claude mcp add search -s user -- uvx free-search-mcp)Cursor / Continue / Cline (use the JSON snippet above)
Custom Python / TypeScript clients via the official MCP SDK
For agent-specific operating rules, tool-selection guidance, and a reusable system-prompt snippet, see docs/AGENT_USAGE.md.
Installer choice
uvx free-search-mcp (PyPI) is the fastest path and needs no checkout — HTTP
engines work immediately and Chromium is a single optional follow-up command.
The Claude Code plugin wraps that same command with a pinned version, so what
you installed and what you run cannot drift, and upgrading is /plugin update
rather than re-running claude mcp add.
scripts/install.sh remains the full bootstrap for people who want a source
checkout, Chromium with OS deps, a smoke test, and client registration in one
shot. Generic MCP installers still have their place: add-mcp can write
config for many clients at once, Smithery is strongest for registry/remote
MCP connections, and MCPB is the right future format for clickable desktop
bundles.
Configuration
All settings can be overridden by environment variables prefixed with
SEARCH_MCP_. They can live in three places (highest precedence first):
real environment variables → ./.env in the launch directory (source
checkouts) → ~/.config/search-mcp/.env (the stable location for uvx/PyPI
installs; directory overridable via SEARCH_MCP_CONFIG_DIR).
API keys and the proxy are easiest to manage in the admin UI
(search-mcp-admin, opens your browser automatically; set
SEARCH_MCP_ADMIN_NO_BROWSER=1 to suppress).
Available knobs:
Var | Default | Meaning |
|
| JSON list |
| (empty) | pin known-good SearXNG instance URL(s), comma/space separated; overrides the built-in shortlist |
|
| auto-rescue empty searches via rescue engines |
|
| rescue order (JSON list) |
|
| seconds; cap on the whole rescue pass |
|
| |
|
| per engine |
|
| shared |
|
| |
|
| 7 days |
|
| size cap on the cache file; |
|
|
|
|
| |
|
| concurrent pages |
|
| per result truncation |
| desktop Chrome UA | used by the httpx (documents) and Playwright paths; the curl_cffi path derives its UA from browser impersonation |
|
| set |
|
| optional directory override; unset or blank uses the dynamic default ( |
|
| downloaded files are deleted after this; |
|
| second-layer save cap; effective remote cap is the smaller of this and |
|
| how many category-native engines |
| (empty) | optional; routes OpenAlex/Crossref/NCBI into their faster identified-caller pools |
|
|
|
|
| streamable-http bind address |
|
| streamable-http port |
|
| streamable-http endpoint path |
| (empty) | extra |
Architecture
┌─────────────────────────────────────────────────────┐
│ MCP server (stdio | streamable-http) │
│ tools: search / research / fetch / fetch_batch / │
│ read_doc / cache_search / engines │
└────────────┬────────────────────────────────────────┘
│
┌────────────▼────────────┐ ┌────────────────────────┐
│ aggregator │ │ fetcher │
│ - parallel engines │ │ - httpx fast path │
│ - reciprocal rank │ │ - playwright fallback │
│ fusion │ │ - markdownify │
│ - search cache (FTS5) │ │ - page cache (FTS5) │
└────┬────────────────────┘ └────────────┬───────────┘
│ │
┌────▼─────────────────┐ ┌──────────────▼─────────────┐
│ engines/ │ │ browser pool │
│ duckduckgo.py │ │ - persistent context │
│ mojeek.py │ │ - stealth init script │
│ searx.py │ │ - shared cookies │
│ startpage.py (opt) │ │ - semaphore-bounded pages│
│ brave.py (opt) │ └────────────────────────────┘
│ bing.py (opt) │
│ baidu.py (opt) │
│ google.py (opt) │
│ serpsearch.py(opt) │
│ anysearch.py (opt) │
│ bilibili.py (opt) │
│ zhihu.py (opt) │
│ sogou.py (opt) │
│ so360.py (opt) │
│ arxiv/openalex/ │
│ crossref/pubmed │
│ github/stackexch. │
│ hackernews/gdelt │
│ wikipedia/openlib. │
│ openverse/zenodo │
│ dryad/dataverse │
│ figshare │
│ huggingface │
│ dataeuropa │
│ wikimedia/zbmath │
└──────────────────────┘
┌────────────────────────────┐ ┌──────────────────┐
│ documents/ │ │ ratelimit │
│ pypdf, python-docx, │ │ token bucket │
│ markdownify │ │ per engine │
└────────────────────────────┘ └──────────────────┘
┌────────────────────────────┐ ┌──────────────────┐
│ formatting │ │ research │
│ token estimate │ │ composed │
│ smart truncation │ │ workflow │
│ markdown renderers │ │ │
└────────────────────────────┘ └──────────────────┘Engine adapter pattern
Each engine in src/search_mcp/engines/ implements:
class Engine:
name: str
needs_browser: bool # Force Playwright?
wait_selector: str | None # CSS to wait for in browser mode
def build_url(self, query: str, max_results: int) -> str: ...
def parse(self, html: str) -> list[SearchResult]: ...The base class handles transport (httpx → Playwright fallback), rate limiting, and the case where HTTP returns a captcha shell instead of results (auto-retries via the browser).
Credits
This project stands on the shoulders of:
mrkrsl/web-search-mcp— smart httpx-then-Playwright fetch strategy, multi-engine fallback chainAas-ee/open-webSearch— multi-engine breadth (Bing/DDG/Baidu/Brave/Startpage)VincentKaufmann/noapi-google-search-mcp— anti-detection patterns (navigator.webdriver, UA, cookies), SQLite FTS5 cache idea, multi-formatread_documentnickclyde/duckduckgo-mcp-server— per-engine rate limiting, LLM-friendly content cleanupMojeek — independent search index that doesn't gate on User-Agent
License
MIT — see LICENSE.
Available Tools
10 toolscache_searchSearch local cache (FTS5)ARead-onlyIdempotent
Full-text search over pages already fetched into the local SQLite FTS5 index.
Best for:
- Recalling something the user/agent fetched earlier in the conversation
("what did that Wikipedia page say about X").
- Avoiding re-fetching content already in the local cache.
- Quick keyword grep across the corpus you've built up.
Not recommended for:
- Discovering new pages on the open web -> use `search` or `research`.
- When the cache is empty (fresh install) -> `search`/`research` first to
populate it.
Returns:
- markdown (default): a per-hit list of title, URL, and a `[bracket]`-
highlighted snippet around the matched terms.
- json: list of {url, title, snippet, author, date, sitename}. The last
three are "" when the cached row predates metadata capture.
Common mistakes:
- Treating this like web search — it ONLY hits pages already in the local
cache. If the user hasn't fetched anything, you'll get zero hits.
- Using natural-language phrases without quoting them; FTS5 splits on
whitespace as AND. For an exact phrase use `"like this"`.
Args:
query: FTS5 query. Bare terms = AND. Supports OR / NOT, prefix
(`term*`), and phrase (`"exact phrase"`).
limit: Max hits to return.
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| format | No | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint, openWorldHint=false, and idempotentHint, but the description adds substantial context: it explains cache-only behavior, FTS5 query semantics, default format, snippet highlighting, empty cache behavior, and empty metadata fields for old rows. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear section headers (Best for, Not recommended for, Returns, Common mistakes, Args). It is longer than average but every sentence contributes functional guidance; no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite an output schema, the description explains return value details (per-hit list, bracket highlighting, JSON fields, empty strings for old data). It also covers edge cases like empty cache and common usage mistakes, making it fully contextual for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but the description explains all three parameters clearly: query syntax (AND, OR, NOT, prefix, phrase), limit as max hits, and format with 'markdown' or 'json' values and their output differences. This fully compensates for the lack of schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Full-text search over pages already fetched into the local SQLite FTS5 index.' It clearly distinguishes the tool from siblings like 'search' and 'research' by emphasizing 'local cache only' and explicitly contrasting with web search.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'Best for' and 'Not recommended for' sections provide explicit usage criteria, including when to use alternatives ('use `search` or `research`'). This directly answers when to use vs. avoid the tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compareCompare URLs side-by-sideARead-onlyIdempotent
Fetch 2-5 URLs concurrently and return per-URL excerpts so the LLM can compare them against a single question in one round trip.
Best for:
- Side-by-side product/feature/article comparisons.
- "Compare X to Y" or "How does A differ from B" queries.
- Triangulating a fact across multiple sources.
Not recommended for:
- >5 URLs -> use `fetch_batch`.
- 1 URL -> use `fetch`.
- Don't have URLs yet -> use `search` or `research` first.
Returns:
- markdown (default): a comparison brief with per-URL sections, each
containing title, sitename, published date, and a smart-truncated excerpt.
- json: {question, urls, excerpts:[{url, title, excerpt, ...}],
tokens_estimated}.
Common mistakes:
- Asking `compare` to actually answer the question — it returns material,
the LLM does the comparison.
- Passing >5 URLs and expecting them all to fit in context — use
`fetch_batch` for bulk reads.
Args:
question: The comparison question the LLM will answer using the
returned excerpts.
urls: 2-5 absolute http(s) URLs.
format: "markdown" (default) or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| urls | Yes | ||
| format | No | markdown | |
| question | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Even with annotations declaring readOnlyHint and idempotentHint, the description adds valuable behavioral context: concurrent fetching, smart truncation, returned formats (markdown/json), tokens_estimated, and the common mistake that `compare` does not itself answer the question. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every sentence earns its place. It is structured with clear sections (Best for, Not recommended for, Returns, Common mistakes, Args), making it scannable and informative without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 3 parameters and an output schema, the description covers all necessary context: use cases, limits, return formats, and common pitfalls. It is fully self-contained and an agent can invoke it correctly without needing external information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description fully compensates with an 'Args' section explaining each parameter, including the default for `format`, the 2-5 URL constraint, and that `question` is the comparison question. This adds meaning well beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description leads with a specific verb+resource+scope: 'Fetch 2-5 URLs concurrently and return per-URL excerpts'. It clearly differentiates from siblings by explicitly naming use cases and not-recommended cases, such as using fetch_batch for >5 URLs and fetch for 1 URL.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage guidance is exemplary: it lists exact scenarios for use ('Compare X to Y', 'Triangulating a fact') and explicitly names alternatives (fetch_batch, fetch, search, research) with conditions. This leaves no ambiguity about when to invoke this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
downloadDownload a file to diskAIdempotent
Save a file from a URL to a local, auto-expiring download directory.
Downloads are enabled by default and saved under
`SEARCH_MCP_CACHE_DIR/downloads`. Set `SEARCH_MCP_DOWNLOAD_ENABLED=false`
to disable them or `SEARCH_MCP_DOWNLOAD_DIR` to override the destination.
Best for:
- Keeping an actual file (installer, dataset, archive, image) rather than
its text.
- Handing a path to another tool that needs a real file on disk.
Not recommended for:
- Reading a document's contents -> use `read_doc`, which parses it without
touching the filesystem.
- Looking at a web page -> use `fetch`.
- Viewing an image -> use `fetch(inline=True)`.
Returns:
- markdown (default): where the file was saved, its size and type.
- json: {url, saved_path, media_type, bytes_size, sha256, expires_in_hours}.
An expires_in_hours value of 0 means TTL cleanup is disabled.
Retention: files older than SEARCH_MCP_DOWNLOAD_TTL_HOURS (default 24) are
deleted before the next download and at startup. A value of 0 disables TTL
cleanup. Otherwise, treat the path as short-lived and copy it elsewhere if
you need to keep it.
Args:
url: Absolute http(s) URL of the file to save.
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| format | No | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses important behavioral details beyond annotations: auto-expiring directory, TTL cleanup, environment variable overrides, and the JSON return format including sha256 and expires_in_hours. It clearly states files are short-lived and advises copying if persistence is needed, which is valuable context for agents.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with a clear opening sentence, bullet-like 'Best for' and 'Not recommended for' sections, and a concise 'Args' list. Every section adds value without redundancy, and the most critical information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers purpose, configuration, retention behavior, return formats, and alternative tools. It also addresses the open-world aspect (external URLs) and idempotency implicitly. Given the output schema exists, the description still enriches context with environment variables and cleanup schedules, making it complete for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although the schema has no descriptions for parameters, the description compensates fully by documenting 'url: Absolute http(s) URL' and 'format: markdown or json'. It also explains the default format and the different return structures for each, adding semantics beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Save a file from a URL to a local, auto-expiring download directory.' It distinguishes itself from siblings by listing alternatives like read_doc and fetch for other use cases.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Best for' and 'Not recommended for' sections, naming specific alternatives (read_doc, fetch, fetch(inline=True)) and explaining when to use each. This leaves no ambiguity about when to select this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
enginesList available search enginesARead-onlyIdempotent
List engine names accepted by the engines= parameter of search / research.
Best for:
- Discovering what's installable before passing a non-default engine.
- Building user-facing UIs that let humans pick engines.
Not recommended for:
- Calling on every search — the list is static; cache it.
Returns:
- The live, complete list of engine name strings. The buckets below are
illustrative; always trust the returned list over this doc.
Common mistakes:
- Passing one of these names as a query to `search` — they go in the
`engines=` argument, not `query`.
- Passing a key-only engine (brave_api/serper/tavily/google_cse) with no key
configured — it returns an actionable error, not results.
Defaults: duckduckgo + mojeek + googlenews + bing (reliable, all-HTTP,
low-latency; googlenews is an RSS index with structured publish
dates and its URLs resolve to the real publisher on
fetch/research; bing's www4 edge answers in ~0.3s).
Keyless opt-in: google + serpsearch (Google SERP scrapers, HTTP-first),
anysearch (JSON aggregator), startpage (browser-rendered, slower),
brave (PoW captcha after a few calls), baidu
(CN index), bilibili (CN video), zhihu (CN Q&A, often login-gated),
sogou + so360 (CN indexes; sogou returns redirect URLs),
wikipedia (encyclopedia, follows SEARCH_MCP_REGION language),
openlibrary (books),
searx (public-instance meta-search; set SEARCH_MCP_SEARX_INSTANCES
if it returns nothing).
Vertical (auto-selected by `category`, see below): arxiv, openalex,
crossref, pubmed (papers); github, stackexchange, hackernews
(code and developer discussion); gdelt (worldwide news).
Key-required (configure via admin UI / SEARCH_MCP_*_API_KEY): brave_api,
serper, tavily, google_cse, github_code (GitHub rejects anonymous
code search).
You usually do NOT need to pass `engines=` for these. Passing `category=`
to `search`/`research` routes the query to the sources that natively index
it — `category="paper"` actually queries arXiv/OpenAlex/Crossref instead of
filtering general web results by hostname. Naming engines explicitly turns
that routing off.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Building on the readOnly and idempotent annotations, the description adds substantial behavioral detail: the list is live and complete, buckets are illustrative, the tool may return an actionable error for key-only engines with no key, and engine results are static/cacheable. It also discloses operational nuances like HTTP-first behavior and captcha issues, which go far beyond the annotation hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured with clear section labels ('Best for', 'Not recommended for', 'Returns', 'Common mistakes', and engine category groupings). The core purpose is front-loaded, and while the engine-by-engine details are extensive, they are substantive and directly useful for selecting the right engine. A minor deduction for slightly verbose enumeration that could be partly externalized.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple listing tool, the description is remarkably complete: it explains the return type, provides defaults, keyless opt-ins, verticals, key-required engines, common mistakes, and sibling relationships. The output schema exists, but the description still covers behavior and integration context, making it fully self-sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the input schema is empty and the description needs no parameter explanation. It earns the baseline 4 by clearly explaining the meaning and usage of the returned values (engine name strings) in the context of other tools, even though an output schema exists. It adds value by clarifying how these names are consumed by `search` and `research`.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a crisp, specific statement: 'List engine names accepted by the `engines=` parameter of `search` / `research`.' This uses a clear verb+resource and immediately distinguishes this tool from sibling search/fetch tools by focusing on engine name discovery. It answers both what and why.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use ('Best for: Discovering what's installable before passing a non-default engine', 'Building user-facing UIs') and when not (not for calling on every search, cache it). It also details the alternative of using `category=` with `search`/`research` instead of explicit engines, giving clear, actionable guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
extract_structuredExtract structured data from a URLARead-onlyIdempotent
Pull JSON-LD, OpenGraph, Twitter cards, and microdata from a web page.
Best for:
- Product pages (price, currency, availability, brand, rating).
- Article pages (author, publish date, image, headline).
- Recipe / event / video pages where rich metadata IS the answer.
- Cases where `fetch` returns prose but you need fields.
Not recommended for:
- Just reading a page -> use `fetch`.
- PDFs / DOCX -> use `read_doc`.
- Pages that don't publish schema.org metadata (most blogs) — you'll get
empty lists; fall back to `fetch`.
Returns:
- json: {url, json_ld:[], microdata:[], opengraph:[], rdfa:[]}. Twitter
card meta tags are surfaced inside the `opengraph` list.
- markdown (default): a flattened key/value view with each block printed
as a JSON code block under its syntax heading.
Common mistakes:
- Calling on every URL "just in case" — most sites have no structured
data, and `fetch` is what you actually want.
Args:
url: Absolute http(s) URL.
format: "markdown" (default) or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| format | No | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description provides rich behavioral context beyond the annotations: it details the exact return shape (`json` and `markdown` views), notes that Twitter cards are surfaced inside the `opengraph` list, and warns that pages without schema.org metadata will yield empty lists. This is far more than the read-only/idempotent hints already convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear section headers, bullet lists, and a compact Args section. Every sentence earns its place — the length is justified by the need to convey use cases, return formats, and common pitfalls without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite the moderate complexity, the description covers purpose, usage guidelines, return formats, behavior on empty results, and parameter semantics. It also cross-references sibling tools appropriately. With an output schema present, the description doesn't need to repeat return type details, but it still explains the two output formats clearly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description fully compensates: it specifies that `url` must be an absolute http(s) URL, and explains `format` options (markdown default vs json) with context from the Returns section. This adds meaning that the raw schema (type + enum) does not provide.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description starts with 'Pull JSON-LD, OpenGraph, Twitter cards, and microdata from a web page' — a specific verb and resource that clearly states what the tool does. It also explicitly contrasts with sibling tools, noting when to use `fetch` or `read_doc` instead, which fully distinguishes it from alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'Best for' and 'Not recommended for' sections give explicit use cases with concrete page types and explicit fallback alternatives. The 'Common mistakes' section further clarifies when not to use the tool, providing strong usage guidance beyond mere capability.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetchFetch a URL: page text, document, or resourceARead-onlyIdempotent
Fetch one URL: page text, or a description of a non-text resource.
Handles any http(s) resource, not just HTML:
- HTML pages -> reader-mode Markdown (nav/footer/scripts stripped).
- PDF/DOCX/XLSX/PPTX/EPUB/CSV/code/archives -> parsed text (same engine as
`read_doc`, which you should prefer when you need pagination).
- Images, video, audio, fonts, opaque binaries -> a description
(media type, byte size, dimensions, sha256), NOT the bytes.
Best for:
- You already have a URL (from `search`, the user, or your own knowledge)
and need the actual page text.
- Verifying a single claim by reading the source.
- Checking what a resource IS before deciding to spend tokens on it.
Not recommended for:
- Multiple URLs at once -> use `fetch_batch` (concurrent, one round-trip).
- "Search then read top N" -> use `research` (one call, not two).
- Long documents you need to page through -> use `read_doc` (start/length).
- You don't have a URL yet -> use `search` first.
Returns:
- markdown (default): a small header (URL, render method, token count)
plus the cleaned page body.
- json: {url, title, content, method, truncated, tokens_estimated,
author, published_date, sitename}, plus {media_type, bytes_size, sha256,
width, height} for non-text resources.
- With `inline=True` on an image: the image itself, viewable by a
vision-capable model.
Common mistakes:
- Passing a search query instead of a URL.
- Using `render="http"` on a JS-only SPA — it returns near-empty content;
use "auto" (default) or "browser".
- Setting `inline=True` on a large image out of habit. A 1MB image costs
well over a thousand tokens; fetch it plainly first and inline only if
the description says it's worth looking at.
- Forgetting that results are cached 7 days — use `force_refresh=True`
or `max_age_hours=0` for a fresh pull.
Args:
url: Absolute http(s) URL.
render: "auto" (try HTTP, fall back to stealth Chromium), "http"
(fast, fails on JS), "browser" (slow, robust).
force_refresh: Bypass the page cache entirely.
max_age_hours: Treat cached pages older than this as a miss. 0 = same
as force_refresh. None = server default TTL (7 days).
inline: For images only — return the image itself instead of a
description, so a vision-capable model can see it. Ignored for
text resources.
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| format | No | markdown | |
| inline | No | ||
| render | No | auto | |
| force_refresh | No | ||
| max_age_hours | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotations, the description discloses significant behavioral details: rendering modes (auto/http/browser) and their tradeoffs, parsing behavior for different file types, a 7-day cache with force_refresh/max_age_hours controls, token cost implications of inline images, and common mistakes like using render='http' on SPAs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but excellently structured with headers, bullet lists, and short paragraphs. Every sentence adds functional value—no filler—and the front-loaded summary plus examples make it easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite lacking an output schema, the description fully specifies return formats (markdown/json), non-text resource details, error-prone scenarios, and parameter semantics. It covers all necessary context for correct invocation, making it complete for a complex tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Since the input schema has zero description coverage, the description fully compensates with an 'Args' section that explains each of the 6 parameters, including defaults, allowed values, and specific behavior (e.g., inline ignored for text resources, max_age_hours semantics). This goes far beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Fetch one URL: page text, or a description of a non-text resource,' providing a specific verb and resource. It also distinguishes itself by covering multiple resource types and hinting at its relationship with siblings like fetch_batch and read_doc.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'Best for' and 'Not recommended for' sections name direct alternatives (fetch_batch, research, read_doc, search) and outline precise scenarios, such as verifying a single claim versus searching and reading top N.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch_batchFetch many URLs concurrentlyARead-onlyIdempotent
Fetch a list of URLs in parallel. Per-URL failures do not raise.
Best for:
- 2+ URLs you want to read in one round-trip.
- Reading the top N results of a previous `search` call.
Not recommended for:
- A single URL -> `fetch` (no list-wrapping overhead).
- "Search and then read" -> `research` collapses both into one tool call.
- PDFs/DOCX -> `read_doc` per file.
Returns:
- markdown (default): each page rendered as a Markdown section, separated
by horizontal rules; failed URLs become inline error notes.
- json: list[dict], one entry per URL, with `error` set on failures.
Common mistakes:
- Passing a single URL inside a 1-element list — use `fetch` directly.
- Assuming an exception means the whole batch failed; check each item's
`error` field instead.
Args:
urls: List of absolute http(s) URLs (max 20 per call).
render: Same as `fetch`.
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| urls | Yes | ||
| format | No | markdown | |
| render | No | auto |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (readOnlyHint, idempotentHint), the description discloses key behaviors: per-URL failures do not raise, failed URLs appear as inline error notes in markdown or error fields in JSON, and the max batch size of 20. This enriches the agent's understanding of edge cases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Though moderately long, the description is well-structured with clear sections (Best for, Not recommended for, Returns, Common mistakes, Args). Every sentence provides actionable information, and there is no redundant repetition of schema or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a complex tool with multiple parameters, error semantics, and alternatives. The description covers batch limits, error handling, return formats, render behavior, and sibling distinctions. With an output schema already present, the extra return-value detail is a bonus, making the description complete for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage, but the description fully compensates with an Args section explaining each parameter: `urls` (absolute http(s), max 20), `render` (same as fetch), and `format` (markdown or json). It also clarifies the output behavior tied to format.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'Fetch a list of URLs in parallel' with a specific verb and resource. It distinguishes itself from siblings by explicitly saying when to use `fetch` (single URL), `research` (search+read), and `read_doc` (PDFs/DOCX).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Best for' and 'Not recommended for' sections that name alternatives (`fetch`, `research`, `read_doc`). This gives the agent clear decision rules for tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_docRead a remote (or sandboxed local) documentARead-onlyIdempotent
Read an http(s) document (or a sandboxed local file) into Markdown.
Best for:
- Remote PDFs and DOCX from an http(s) URL (parsed locally, no remote API).
- Local PDF/DOCX/text/Markdown files — ONLY when local reads are enabled
(see Security below).
- Paginating through a long document via `start` / `length`.
Not recommended for:
- Arbitrary HTML web pages -> `fetch` does reader-mode cleanup that this
tool does not.
- Pages discovered through search -> `fetch` or `research`.
Security (local files are sandboxed and OFF by default):
- Local-file reads are DISABLED unless the server operator sets the
SEARCH_MCP_DOCUMENT_ROOT env var to a directory. With it unset, a local
path raises a "local file reads are disabled" error — pass an http(s)
URL instead, or ask the operator to enable the sandbox.
- When enabled, `source` must resolve INSIDE that root; relative paths
resolve against the root (not the process CWD) and any `..` traversal
that escapes the root is rejected. `file://` URLs are always rejected.
- Remote http(s) sources are unaffected by this setting.
Returns:
- markdown (default): rendered document text with a small header.
- json: {content, title, format, total_chars, start, returned_chars,
truncated}. Use `total_chars` and `returned_chars` to drive pagination.
Common mistakes:
- Calling this on a normal article URL — you'll get raw HTML noise; use
`fetch` instead.
- Forgetting to advance `start` when paginating: next call should pass
`start = previous_start + returned_chars`.
- Passing a negative `length` (raises an error) or a `start` past the end
(clamped to EOF: you'll get `returned_chars == 0`, `start == total_chars`,
and `truncated == False` — that's the signal you've paged off the end).
Args:
source: http(s) URL, or a local path UNDER SEARCH_MCP_DOCUMENT_ROOT when
local reads are enabled (disabled by default — see Security).
start: Character offset to begin reading from. Default 0. Clamped into
[0, total_chars]; a negative value is treated as 0.
length: Max characters to return; None = read to end (still capped by
the per-call max content size). Must be >= 0 — a negative length
is rejected with a ValueError.
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| start | No | ||
| format | No | markdown | |
| length | No | ||
| source | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, and open-world hints, but the description adds substantial behavioral detail: local-file sandboxing, environment variable gating, file:// rejection, path traversal protection, clamping behavior for start/length, and the exact signals for paging off the end. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place: Best-for/Not-recommended, Security, Returns, Common mistakes, and Args. Information is front-loaded with the core verb+resource first, and the structure makes it easy to scan. No redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having an output schema, the description still explains the json format fields and pagination signals. It fully covers the tool's complexity: security sandbox, env var dependency, error conditions, sibling differentiation, and parameter semantics. Nothing important is left ambiguous for a tool with this many nuances.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has 0% description coverage, so the description must fully explain parameters. The 'Args' section does this thoroughly: source (URL vs local path, security constraints), start (offset, clamping, negative handled as 0), length (None means to end, must be >= 0, ValueError on negative), and format (markdown vs json). Goes far beyond the schema's bare types and defaults.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool reads http(s) documents or sandboxed local files into Markdown. It distinguishes itself from siblings by explicitly noting it is not for arbitrary HTML pages (use fetch) or search-discovered pages (use fetch/research).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Best for' and 'Not recommended for' sections with named alternatives (fetch, research). Also includes critical usage context like when local reads are enabled and how pagination should work.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
researchSearch and read in one callARead-only
One-shot research: search the web, fetch the top results, return both.
Best for:
- Open-ended questions that need finding sources AND reading them
("what's new with X", "summarize the controversy around Y").
- Replacing a `search` + N x `fetch` chain with one call.
- Producing a citable brief with [n]-style source references.
Not recommended for:
- You only need links -> `search` (cheaper, no fetching).
- You only need to read one URL you already have -> `fetch`.
- You want to query previously-fetched cached pages -> `cache_search`.
Returns:
- markdown (default): a "Research brief" with a Sources index then the
full Markdown body of each fetched document, separated by horizontal
rules; includes a token estimate.
- json: {question, engines, sources:[{rank,title,url,snippet,...}],
documents:[...], tokens_estimated, errors}.
Common mistakes:
- Using `depth=8` for a quick lookup — that's 8 page fetches; 2-3 is
almost always enough.
- Calling `research` for a known URL — that's `fetch` territory.
- Forgetting that `fetch=False` returns sources only (much cheaper if
the LLM only needs to pick which one to read).
Args:
question: What you want to know, in natural language.
depth: How many top results to fetch (1-8). 3 is a good default.
engines: Override the engine set (see `engines()` for names).
fetch: If False, return source list without reading them.
use_cache: Reuse cached search/page data within TTL.
max_age_hours: Treat cached search results AND cached page bodies older
than this as a read miss; fresh data is always written back. 0 =
force-refresh both the engine search and every fetched page body;
None = server default TTL (7 days). A non-zero value is honored for
both halves (it used to be ignored for anything but 0).
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| depth | No | ||
| fetch | No | ||
| format | No | markdown | |
| engines | No | ||
| category | No | ||
| question | Yes | ||
| freshness | No | ||
| use_cache | No | ||
| exclude_text | No | ||
| include_text | No | ||
| max_age_hours | No | ||
| exclude_domains | No | ||
| include_domains | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the spartan annotations, the description discloses cache write-back behavior, TTL semantics for `max_age_hours`, return format details, token estimation, and failure-prone usage patterns. It even notes a historically surprising behavior (non-zero `max_age_hours` now applies to both search and pages), adding real transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but it is highly structured with scannable headings, bullets, and a clear linear flow. Every section adds distinct value: purpose, use cases, exclusions, return format, common mistakes, and parameter details. The length is justified by the tool's complexity and parameter count.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the description covers purpose, usage boundaries, output formats, and parameter behavior well, and the output schema exists to formalize return values. Still, the description omits several parameters (e.g., `category`, `freshness`, domain filters), which leaves some operational gaps for an agent trying to use the full feature set.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the burden. It explains `question`, `depth`, `engines`, `fetch`, `use_cache`, `max_age_hours`, and `format` with actionable detail, but leaves `category`, `freshness`, `exclude_text`, `include_text`, `exclude_domains`, and `include_domains` unexplained. The covered parameters are handled very well, but the six omitted ones are a clear gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb+resource: 'search the web, fetch the top results, return both.' It clearly distinguishes this tool from siblings by framing it as a combined search-and-fetch operation, with the title 'Search and read in one call' reinforcing the purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Best for' and 'Not recommended for' sections, naming concrete alternatives: `search`, `fetch`, and `cache_search`. It also includes common mistakes and specific scenarios, giving an agent clear decision criteria for when to use this tool versus siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchWeb search (multi-engine, no API key)ARead-only
Run a multi-engine web search and return a ranked, deduplicated link list.
Best for:
- Discovery queries ("what is X", "find me X", "who is X").
- Getting a list of URLs you can hand to `fetch` / `fetch_batch` next.
- Topics likely to be after your knowledge cutoff (use `freshness="week"`).
- Filtering to specific domains (`include_domains=["python.org"]`) or
content types (`category="paper"|"pdf"|"github"|"news"|"forum"|"blog"`).
Not recommended for:
- You already know the URL -> use `fetch` instead.
- You want both links AND their full text in one call -> use `research`.
- You want to query pages already in the local cache -> use `cache_search`.
- Reading PDFs/DOCX from a known URL -> use `read_doc`.
Returns:
- markdown (default): numbered list of `n. title`, `<url>`, snippet — ~40%
fewer tokens than json.
- json: dict with `results` (list of {title,url,snippet,engines,score}),
`engines`, `cached`, optional `errors` map, optional `hint` string.
Common mistakes:
- Passing a URL as `query` — that's `fetch`'s job.
- Cranking `max_results` to 50 hoping for better recall; engines cap around
10-20 each, anything beyond is duplicate noise.
- Adding `engines=["startpage","brave","bing","baidu"]` by default — those
need browser rendering or captcha-friendly conditions; stick with the
defaults unless they returned 0. If the defaults DO return 0, the keyless
HTTP extras `engines=["google"]` or `engines=["anysearch"]` (no key, no
browser) are the best recovery before reaching for the browser-gated ones.
- Using `category="news"` for breaking news without also setting
`freshness="day"` — the index lag is days, not minutes.
Args:
query: Natural-language query (the same string a human would type).
engines: Subset of `engines()`. None = duckduckgo+mojeek+googlenews+bing.
(startpage is opt-in and browser-rendered.)
max_results: Merged result count after dedup. 5-20 is the useful range.
use_cache: Reuse the last result for this exact (query, engines,
max_results, AND all active filters — freshness, include/exclude
domains, category, include/exclude text) within the cache TTL.
Changing any filter is a different cache entry. False forces a
re-fetch.
max_age_hours: Treat cached results older than this as a read miss; a
fresh result is ALWAYS written back to the cache regardless of this
value, so caching is never disabled. Use 0 to force-refresh while
keeping cache writes; None = use server default TTL (7 days).
freshness: "day"|"week"|"month"|"year" — restrict to recent results.
Best-effort: applied as an engine time-window param AND a client-side
date check, but most HTML-engine results carry no parseable date, so
undated results are kept rather than dropped (unknown != old). Treat
it as a strong hint, not a hard filter; googlenews dates are exact.
include_domains: List of domains to restrict to (e.g. ["python.org"]).
exclude_domains: List of domains to exclude.
category: "news"|"pdf"|"github"|"paper"|"forum"|"blog" — content-type
shortcut. "paper" => arxiv/acm/springer/ieee/etc; "forum" =>
reddit/HN/stackexchange; "github" => code forges (github/gitlab/
codeberg/bitbucket/sourceforge/...). "news" keeps only ~33 major
outlets (client-side whitelist), so most DDG/Mojeek hits are dropped
— pair it with the default engines (googlenews is auto-added) and
note googlenews URLs resolve to the publisher on fetch/research.
include_text: Substring required in title or snippet (case-insensitive).
exclude_text: Substring forbidden in title or snippet.
format: "markdown" (default) or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| format | No | markdown | |
| engines | No | ||
| category | No | ||
| freshness | No | ||
| use_cache | No | ||
| max_results | No | ||
| exclude_text | No | ||
| include_text | No | ||
| max_age_hours | No | ||
| exclude_domains | No | ||
| include_domains | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations provide readOnlyHint and openWorldHint, and the description adds substantial behavioral context beyond that: cache read/write semantics, freshness best-effort behavior and exactness for googlenews, category whitelist client-side filtering, engine limitations (browser-rendered, captcha), and deduplication behavior. It also discloses the return format and potential 'hint' string, all of which go beyond the structured annotation fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but extremely well-structured: it opens with a one-sentence summary, uses bullet lists for best/not-recommended use, separates return format details, and includes a 'Common mistakes' section. Every section adds actionable content for a 12-parameter tool, so the length is justified and the information is front-loaded with the core purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex tool with 12 parameters, no required schema descriptions, and rich annotations, this description covers all critical aspects: purpose, return formats, cache behavior, engine caveats, filter semantics, and failure recovery. The presence of an output schema is reinforced by an explicit explanation of markdown vs. JSON return structures, making the description fully self-sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite 0% schema description coverage in the structured fields, the description thoroughly documents every parameter: query (natural-language), engines (with default set and opt-in caveat), max_results (useful range and cap behavior), use_cache (cache key specificity), max_age_hours (write-back behavior), freshness (semantics and limitations), include/exclude_domains, category (with concrete examples like 'paper' => arxiv/acm/springer), include/exclude_text, and format. This fully compensates for the schema's lack of descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description opens with a specific verb+resource+scope: 'Run a multi-engine web search and return a ranked, deduplicated link list.' It distinguishes itself from siblings by explicitly naming alternatives like fetch, research, and cache_search in the 'Not recommended for' section, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is clearly segmented into 'Best for' (discovery queries, URL list generation, post-cutoff topics, domain/filtered searches) and 'Not recommended for' with explicit sibling alternatives (fetch, research, cache_search, read_doc). It also includes common mistakes that steer agents away from misuse, offering strong when-to-use vs. when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
4 tool updates
v0.9.1- Added
download - Changed
fetch2 fields changed- added
Input schema / properties / inlineAdded value: +{ + "default": false, + "title": "Inline", + "type": "boolean" +} - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "fetchOutput", - "type": "object" -}New value: +null
- Changed
research1 field changed- changed
Input schema / properties / category / anyOfPrevious value: -[ - { - "enum": [ - "news", - "pdf", - "github", - "paper", - "forum", - "blog" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "news", + "pdf", + "github", + "paper", + "forum", + "blog", + "image", + "dataset" + ], + "type": "string" + }, + { + "type": "null" + } +]
- Changed
search1 field changed- changed
Input schema / properties / category / anyOfPrevious value: -[ - { - "enum": [ - "news", - "pdf", - "github", - "paper", - "forum", - "blog" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "news", + "pdf", + "github", + "paper", + "forum", + "blog", + "image", + "dataset" + ], + "type": "string" + }, + { + "type": "null" + } +]
2 tool updates
v0.2.0- Added
compare - Added
extract_structured
7 tool updates
v0.1.0- First observed
cache_search - First observed
engines - First observed
fetch - First observed
fetch_batch - First observed
read_doc - First observed
research - First observed
search
TDQS
Each tool has a distinct primary role: engines lists valid engine names, search discovers URLs, fetch and read_doc both retrieve single resources but are clearly separated (web pages vs. documents), and fetch_batch vs. compare both handle multiple URLs but compare is question-driven with per-URL excerpts. The 'Not recommended for' sections in the descriptions sharply delineate boundaries, though fetch vs. read_doc and fetch_batch vs. compare could still cause brief hesitation.
Names mix single verbs (search, fetch, compare, download), verb_noun compounds (fetch_batch, read_doc, cache_search, extract_structured), and bare nouns (engines, research). All use snake_case, but the verb/noun ordering is inconsistent—e.g., fetch_batch vs. read_doc, and cache_search vs. search. The pattern is readable but not uniform.
10 tools is well-scoped for a search/retrieval server: engines for discovery, search for web discovery, fetch/fetch_batch for reading, read_doc for documents, research for search-and-read, cache_search for local recall, compare for multi-source comparison, extract_structured for metadata, and download for files. Each tool earns its place without redundancy or bloat.
The tool surface covers the entire search-read-retrieve pipeline: discover engines, search the web, fetch single or multiple pages, read PDFs/DOCX with pagination, run a full research workflow, query the local cache, compare sources, extract structured metadata, and download files. There are no obvious dead ends or missing operations for the stated purpose of web search and content retrieval.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Docs: https://docs.keenable.ai/mcp-server Keenable is a free, remote MCP server that gives agents access to the web index. Search the web with ranked results and date/site filters, then fetch any indexed page as clean markdown. Works out of the box with no account or API key.
Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.
Scrape, crawl and search the web for AI agents via MCP.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA lightweight MCP server that enables LLMs to search the web via DuckDuckGo, search GitHub code repositories, and extract clean content from web pages in LLM-friendly formats.8-
- AlicenseAqualityAmaintenanceA self-hosted MCP server providing web search and URL fetching tools, running locally without external API keys or accounts.2539MIT
- AlicenseNot gradedqualityBmaintenanceA zero-config web search and fetch MCP server for LLM agents, featuring multi-backend metasearch, persistent rolling cache, and structured error envelopes for retry-friendly interactions.MIT
- AlicenseNot gradedqualityBmaintenanceMCP server enabling local-first web search, fetch, extract, and caching with citeable excerpts, no API key required. Supports research workflows for agents and apps.18MIT
Appeared in Searches
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sweetcornna/free-search-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server