free-search-mcp-ts
Subject index engine automatically selected for relevant academic queries, providing arXiv results.
Chinese-language search engine for CJK queries or explicit region: "cn", with local decoding of redirect wrappers.
Provides both a keyless Brave HTML search engine and an optional brave-api API-key engine that joins the first tier with higher RRF weight when a Brave API key is present.
Primary keyless web search engine using DuckDuckGo HTML and lite endpoints, with region, freshness, and safe-search parameters.
Subject index engine automatically selected for relevant queries, providing GitHub results.
Supports Google Custom Search (google-cse) as an optional API-key engine, allowing keyed Google search results to be included and given higher RRF weight.
Primary keyless news search engine using Google News RSS, returning articles with recency operators.
Primary keyless web search engine using Mojeek's independent index and crawler, useful for escaping the Bing/Google duopoly.
Subject index engine automatically selected for relevant queries, providing npm registry results.
Optional self-hosted SearXNG engine configured via SEARXNG_URL, accepting a comma-separated list and failing over between instances.
Chinese-language search engine for CJK queries or explicit region: "cn", with local decoding of redirect wrappers.
Subject index engine automatically selected for error messages and programming questions, providing Stack Exchange results.
Surfaces Stack Overflow results via the Stack Exchange engine for error messages and programming questions.
Optional keyless HTML search front end, with consent or anti-bot walls detected and reported as blocks.
Subject index engine automatically selected for relevant queries, providing Wikipedia results.
Click on "Deploy 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-mcp-tssearch for the latest on the Mars rover"
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-ts
Local-first web search, page fetching and document parsing for any MCP client — with no API key.
By lhswg (GitHub lhswgzy) · © 2025 · MIT licensed.
Origin. This repository is an independent, from-scratch TypeScript implementation. The idea, and the older and more mature Python project that came first, belong to
sweetcornna: sweetcornna/free-search-mcp — MIT, published on PyPI asfree-search-mcp. This is not a fork, none of its code was copied or read, and no parity is claimed. Full statement: AUTHORS.md.
free-search-mcp-ts is a Model Context Protocol server that gives Claude, GPT, Cursor, Codex, local Ollama front ends and any other MCP-capable client the ability to search the web, read pages and parse documents. It runs entirely on your machine, needs no account, and returns Markdown instead of JSON because Markdown costs a model roughly a third fewer tokens for the same information.
Not on npm yet. npx -y free-search-mcp-ts will install it once the package is published; until then
use the Quick start, which builds it locally in four commands.
Why this exists
Asking a model to "look it up" usually means one of three things: pay for a search API, hand the model a browser it cannot drive, or paste URLs manually. None of them work well for a local model, and none of them keep your queries on your machine.
This server takes a different position:
No keys by default. DuckDuckGo, Mojeek and Google News are queried directly over their public endpoints. Nothing to sign up for, nothing to leak.
Several engines, not one. Every engine's result list is merged with Reciprocal Rank Fusion, so a page that three engines independently surface outranks a page only one engine found. The result set is stable in a way a single provider's is not.
It degrades instead of failing. Engines that are blocked, rate-limited or unreachable are detected, benched, and replaced by the next tier. The server keeps working on networks that filter some providers — one of the engines in the default fallback tier is there precisely because it stays reachable where others do not.
Everything stays local. Queries go to the search engine; the pages you read are stored in a local SQLite index on your disk, and nothing else is uploaded anywhere. There is no telemetry.
Markdown first. The output is a heading-and-list document a model can read directly. Measured on a ten-result search, the Markdown form is 32 % fewer tokens than the equivalent JSON (see the measurements).
Related MCP server: webfetch
Quick start
This is the fastest path that works today. It needs no npm publication, because it builds the package
from source and links the free-search-mcp-ts binary into your PATH:
git clone https://github.com/lhswgzy/free-search-mcp-ts
cd free-search-mcp-ts
npm install && npm run build && npm link
free-search-mcp-ts installThat registers the server with the MCP clients installed on this machine. Then confirm the engines and network work from here, and try it without involving a client at all:
free-search-mcp-ts doctor
free-search-mcp-ts search "state of the art in retrieval augmented generation"
free-search-mcp-ts research "how does reciprocal rank fusion work" --depth 2Restart your client afterwards. If you would rather see what the installer would do before it touches
anything, free-search-mcp-ts install --dry-run prints the plan without writing.
Once the package is published to npm
npx -y free-search-mcp-ts is the intended path for anyone who does not want a local clone, but the
package is not on npm yet. Once it is, every command above becomes:
npx -y free-search-mcp-ts install
npx -y free-search-mcp-ts doctor
npx -y free-search-mcp-ts search "state of the art in retrieval augmented generation"
npx -y free-search-mcp-ts install --dry-runTools exposed to the model
Tool | What it does |
| Searches several engines at once, fuses the results with RRF, de-duplicates and re-ranks. Use it when you do not yet know which page holds the answer. |
| The whole loop in one call: search, derive follow-up queries from the vocabulary the first results actually use, fetch the most relevant and diverse sources, then extract the passages that answer the question. Returns a citable Markdown brief. |
| Fetches one page or document and returns clean Markdown. Handles HTML, PDF, DOCX, XLSX, PPTX, EPUB, ODT, CSV, JSON and plain text. Long documents are truncated with a continuation offset instead of being refused. |
| Fetches up to twelve URLs concurrently, reporting failures per URL. |
| Parses a local file or a remote document: spreadsheets become Markdown tables, presentations become one section per slide, DOCX keeps headings, lists and tables. |
| Full-text search over everything fetched so far, using a local SQLite FTS5 index with CJK bigram support. Costs no network requests. |
| Reports on, prunes, clears or compacts that local index. |
| Lists every engine with its tier, whether it needs a key, and its live health. |
Every tool accepts format: "json" for callers that want to post-process the output instead of reading it.
Engines
Twenty-four engines, in tiers. The tier decides when an engine runs; you can always override with engines: ["bing", "wikipedia"] or ask for everything with engines: ["all"].
Primary — the documented keyless defaults
Engine | Notes |
| HTML endpoint with an automatic fallback to the lite endpoint; region, freshness and safe-search parameters. |
| An independent index with its own crawler — useful for escaping the Bing/Google duopoly. |
| Google News RSS: articles rather than a general web index, with |
Fallback — used automatically when the primary tier returns too little
Engine | Notes |
| Keyless HTML. Reachable on networks where DuckDuckGo and Google are not, which is what makes it the global fallback. |
| Chinese-language engines, eligible for CJK queries or an explicit |
Subject indexes — selected automatically from the query
wikipedia, hackernews, github, stackexchange, arxiv, openalex, crossref, npm, crates
A question about a Rust crate quietly gains crates.io; a question containing an error message gains Stack Overflow. The trigger list lives in src/engines/registry.ts and is plain readable regex.
Optional and keyed
Engine | Notes |
| Keyless HTML front ends. Frequently behind a consent or anti-bot wall, which is detected and reported as a block rather than as "no results". |
| Point it at your own instance with |
| Optional API-key engines. When a key is present they join the first tier and get a higher RRF weight, because a documented JSON API beats a scraper. |
BRAVE_API_KEY=... npx -y free-search-mcp-ts search "..." # or SERPER_API_KEY, TAVILY_API_KEY, EXA_API_KEY
SEARXNG_URL=https://searx.example.org npx -y free-search-mcp-ts search "..."How it works
Reciprocal Rank Fusion. Each engine contributes 1 / (k + rank) for every document it returns, with per-engine weights and a small bonus when several engines agree. RRF needs no calibration between providers, which matters because a Bing relevance score, a Mojeek BM25 score and a Google News publish order are not comparable numbers. See src/rrf.ts.
Tiered escalation. The primary tier runs first. Only if it returns fewer than a threshold does the fallback tier run, and only then do the subject indexes that match the query. A query the primary tier answers costs two or three HTTP requests; a query on a filtered network still succeeds.
A circuit breaker that survives restarts. A provider that is blocked (403/429/captcha) is benched for ten minutes after a single occurrence; a provider whose host cannot be connected to at all is benched for five. That state is written to the local SQLite index, so a restarted server — or your next command-line search — does not pay the timeout again.
Redirect wrappers decoded offline. Baidu, Sogou, 360, DuckDuckGo and Bing all hand back /link?url= or /ck/a?u= wrappers. Those are decoded locally, which fixes both citation quality and cross-engine de-duplication; only wrappers that cannot be decoded cost a redirect-following request.
A local full-text index. Every fetched page is stored in SQLite with an FTS5 index over its title and body. CJK text is expanded into overlapping bigrams at index time, because unicode61 would otherwise treat a whole Chinese sentence as a single token and make it unsearchable.
Markdown extraction. linkedom parses, Mozilla Readability isolates the article, and Turndown renders GitHub-flavoured Markdown — headings, lists, tables, fenced code with language tags and absolute links. When Readability declines a page (a changelog, a spec, a table-heavy docs site) a conservative fallback strips the chrome from the body instead of giving up.
Measured, not claimed
Claim | Measurement |
Markdown costs fewer tokens than JSON | On a ten-result search: 3,752 chars / ~938 tokens as Markdown versus 5,550 chars / ~1,388 tokens as JSON — 32 % fewer tokens. |
Fetching a page returns far less than the raw HTML | MDN's Fetch API page: 150 kB of HTML → 5.4 kB of Markdown (96 % smaller, ~38,300 → ~1,378 estimated tokens). Bing's and Baidu's result pages measure 97 % and 99 %. |
The circuit breaker pays for itself | On a network where DuckDuckGo and Google News are unreachable: first search 11.9 s, subsequently 1.3 s, because the dead engines are benched in between. |
Reproduce the first two with npx tsx scripts/measure-savings.mts; the third with two consecutive free-search-mcp-ts search calls.
Clients
free-search-mcp-ts install detects and patches the following. Only clients whose config file already exists are touched, every write is preceded by a .bak copy, and only the one key the server owns is modified.
Client | Config file |
Claude Desktop |
|
Claude Code |
|
Cursor |
|
Windsurf |
|
VS Code (Copilot agent mode) |
|
Cline |
|
Roo Code |
|
Zed |
|
Codex CLI |
|
Gemini CLI |
|
opencode |
|
LM Studio |
|
Continue |
|
npx -y free-search-mcp-ts clients # every supported client id
npx -y free-search-mcp-ts install --client cursor --dry-run
npx -y free-search-mcp-ts install --client claude-desktop,codex
npx -y free-search-mcp-ts uninstall --client cursorOllama does not speak MCP. The tool-calling loop has to live in the client, so point an MCP-aware front end at this server instead — run it over HTTP and add it in Open WebUI, LibreChat or any other MCP-capable UI:
free-search-mcp-ts serve --transport http --port 8765
# then add http://127.0.0.1:8765/mcp as a streamable-HTTP MCP serverCommand line
free-search-mcp-ts # serve MCP over stdio (what a client runs)
free-search-mcp-ts install # register with the clients found here
free-search-mcp-ts uninstall # remove the registration
free-search-mcp-ts doctor # check the network, every engine and the local index
free-search-mcp-ts search "<query>" # multi-engine search
free-search-mcp-ts research "<query>" # search + read + extract, as a citable brief
free-search-mcp-ts fetch <url> # one page or document as Markdown
free-search-mcp-ts parse <file> # a local document as Markdown
free-search-mcp-ts engines [--check] # list engines, tiers and health
free-search-mcp-ts cache <stats|search|clear|prune|vacuum|path>
free-search-mcp-ts config # the effective configuration
free-search-mcp-ts tools [--json] # the MCP tool manifest
free-search-mcp-ts clients # supported client idsUseful flags: --json, --max <n>, --engines a,b, --freshness day|week|month|year, --lang, --region, --depth 1-3, --sources <n>, --data-dir <path>, --verbose.
The subcommands call the same handlers the MCP server registers, so free-search-mcp-ts search "…" is a genuine end-to-end test of what a model would receive.
Configuration
Everything is optional. Copy .env.example to ~/.free-search-mcp/.env, or set the variables in your client's env block.
Variable | Default | Purpose |
|
| Comma-separated engine ids, or |
|
| Default result count. |
|
| Per-request timeout in milliseconds. |
|
| Set to |
|
| How long a fetched page stays fresh. |
| — | HTTP(S) proxy, e.g. |
|
| Set to |
|
| Set to |
| — | Region and language hints, e.g. |
|
| Where the index and configuration live. |
|
|
|
A JSON config file at ~/.free-search-mcp/config.json accepts the same keys in camelCase.
Behind a proxy or on a filtered network
The server works out of the box on the open internet. If your network filters some providers, either point it at a proxy:
FREE_SEARCH_PROXY=http://127.0.0.1:7890 free-search-mcp-ts search "..."or leave it alone and let the tiered fallback do its job — the fallback engines were chosen because they are reachable from networks that block DuckDuckGo and Google. free-search-mcp-ts doctor prints a per-engine sweep so you can see exactly which engines work from where you are.
Security and privacy
Nothing leaves your machine except the search queries and the pages you ask for. There is no telemetry, no analytics, and no server component.
fetch_urlis model-driven, so it is guarded. Loopback, link-local and private-range addresses are refused by default; setFREE_SEARCH_ALLOW_PRIVATE=1if you deliberately want to read a local service.robots.txt is honoured for
fetch_urlby default, and a disallowed path is reported as such rather than silently skipped.Local file access is limited to regular files under 100 MB, and only through the explicit
parse_documenttool.Nothing is written to stdout except JSON-RPC. Diagnostics go to stderr, so a misbehaving log line can never corrupt the protocol stream.
Requirements
Node.js 20.19+ to run.
Node.js 22.5+ (24+ recommended) for the local SQLite index, which uses the built-in
node:sqlitemodule. On older runtimes the cache degrades to an in-process LRU and everything else keeps working —doctortells you which backend is active.No native modules, no compiler, no post-install build step.
Verification status
Honesty about what has been tested matters more than a feature list.
Verified against live traffic from this machine: Bing, Baidu, Sogou, 360, Hacker News (Algolia), GitHub, Stack Exchange, npm, crates.io, OpenAlex, Crossref, and the full fetch → Markdown → index → re-read pipeline.
doctor's engine sweep and the CLI subcommands were exercised end to end, and the MCP server completed a 17-check protocol handshake (initialize,tools/list,tools/call,ping) over stdio.Verified against captured real responses: arXiv, and every engine listed above.
Verified against fixtures only, because this network cannot reach them: DuckDuckGo, Mojeek, Google News, Startpage, Brave, SearXNG, Wikipedia. Their parsers, parameter mapping and error paths are covered by tests built from the documented markup and API shapes, but they have not been exercised against a live response. If one of them misbehaves for you, that is the most likely place, and an issue with the raw response attached is the fastest fix.
Not verified at all: the five keyed engines' live responses (no keys available here). Their request construction and response mapping are verified offline against fixtures.
Development
git clone https://github.com/lhswgzy/free-search-mcp-ts
cd free-search-mcp-ts
npm install
npm run build # tsc -> dist/
npm test # vitest
npm run typecheck
node dist/cli.js doctornpm run verify runs the whole acceptance sequence — typecheck, build, the test
suite, both MCP protocol probes, the CLI surface and a packaging leak check — and
prints one summary. npm run verify:online additionally performs a live search,
fetch and engine sweep. The two protocol probes are standalone scripts, so you
can also check a build directly:
node scripts/probe-stdio.mjs # speaks MCP over stdio like a client does
node scripts/probe-http.mjs # exercises the streamable-HTTP transportLayout:
src/
cli.ts command line and the stdio/HTTP entry point
server.ts MCP server, tool registration, both transports
search.ts tiered orchestration, RRF, wrapper decoding
research.ts search -> fetch -> passage extraction
rrf.ts Reciprocal Rank Fusion and de-duplication
cache.ts SQLite FTS5 page index and circuit-breaker state
http.ts proxy, retries, size caps, the SSRF guard
robots.ts robots.txt parsing and caching
config.ts defaults <- config.json <- .env <- environment
html/markdown.ts HTML -> Markdown pipeline
fetch/page.ts the fetch-and-convert service
fetch/documents.ts PDF, DOCX, XLSX, PPTX, EPUB, ODT, CSV, JSON
engines/ one file per engine, plus kit.ts and registry.ts
install/ client specs and the installer
tools/ tool schemas, handlers and Markdown renderers
tests/ vitest suites and engine fixturesAdding an engine is one file plus one line in src/engines/registry.ts. Every engine declares its own tier, RRF weight and, for subject indexes, the query patterns that make it eligible.
Limitations
research()quotes, it does not summarise. This server has no language model, so the brief is extractive by design: it selects and quotes the passages that match the query and labels every one with a citable source. The calling model writes the synthesis. Calling that a summary would be a lie.HTML scraping is inherently fragile. Engines change their markup. The structural fallback extractor adapts without selectors, and a broken engine degrades to zero results and gets benched rather than breaking the search — but a provider can still change its markup faster than a release cycle.
The subject indexes are narrow by design. Wikipedia, GitHub, crates.io and arXiv index specific corpora, not the web.
No OCR. A scanned PDF is reported as having no extractable text rather than silently returning nothing.
Origin and authorship
Author: lhswg — GitHub lhswgzy — © 2025, MIT.
Source of the idea: sweetcornna/free-search-mcp, a Python project (MIT, on PyPI as free-search-mcp).
This codebase was written from scratch. It is not a fork; it shares no commit history, no source file and no
engine parser with that project, and nothing from it was read while this was written. The npm package is named
free-search-mcp-ts rather than free-search-mcp deliberately, so that the original author keeps their own
project name on their own registry. Bugs in the Python project should be reported
there, not here. The full statement, including what is
and is not claimed, is in AUTHORS.md.
License
MIT — see LICENSE. Copyright © 2025 lhswg.
If this saves you a search API bill, a star is appreciated. Issues with a raw engine response attached are the most useful kind of bug report.
Available Tools
8 toolsfetch_urlFetch a URL as MarkdownARead-only
Fetch one web page or document and return its main content as clean Markdown, with the navigation, adverts and cookie banners removed. Handles HTML (via a readability pass), PDF, DOCX, XLSX, PPTX, EPUB, ODT, CSV, JSON and plain text. Results are stored in a local SQLite index, so reading the same URL again is instant and the page becomes searchable offline with search_index. Long pages are truncated rather than refused: the response reports a next offset you can pass back to continue. robots.txt is honoured by default and private/loopback addresses are refused as a safety measure.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Absolute http(s) URL to fetch. | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| offset | No | Skip this many characters into the content. Use the `next offset` from a previous call to page through a long document. | |
| refresh | No | Ignore the local cache and re-fetch (default false). | |
| max_chars | No | Maximum characters of content to return. Longer content is truncated and a `next offset` is reported so you can continue reading. | |
| use_cache | No | Use the cached copy when it is fresh (default true). | |
| include_links | No | Also return the outgoing links found in the body, for follow-up fetching. | |
| respect_robots | No | Honour robots.txt for this request (default: the server setting, which is true). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/openWorldHint annotations, it discloses substantial behavior: results cached in a local SQLite index, long pages truncated rather than refused, robots.txt honoured by default, and private/loopback addresses refused as a safety measure. These are exactly the operational traits an agent needs and are not derivable from the 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 front-loaded with the core behavior (fetch → clean Markdown) and then layers caching, truncation, and safety in tight successive sentences. It is dense but each sentence carries distinct information; only the long format enumeration is slightly list-like.
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 an 8-parameter tool with no output schema, the description covers return shape (Markdown main content, `next offset` for continuation), caching semantics, and safety constraints, which is everything needed to invoke it correctly. Nothing material is left unspecified.
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 100%, so all 8 parameters are already documented in the schema, and the description adds only the conceptual paging loop around `offset`/`max_chars` plus the supported file types. It reinforces but does not meaningfully extend the schema's parameter documentation, so the baseline 3 applies.
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 and resource ('Fetch one web page or document') and names the exact output ('clean Markdown' with navigation, adverts and cookie banners removed). It is clearly distinguishable from the plural sibling fetch_urls and from parse_document by emphasizing single-URL retrieval of the main content.
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?
It gives clear usage context: reading the same URL again is instant, the page becomes searchable offline with search_index, and the `next offset` is passed back to continue long documents. However, it never explicitly contrasts itself with fetch_urls for batch fetching or with parse_document, leaving the sibling routing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch_urlsFetch several URLsARead-only
Fetch up to 12 URLs concurrently and return each as Markdown under its own heading. Cheaper and faster than calling fetch_url repeatedly because the requests overlap. Failures are reported per URL instead of failing the whole call.
| Name | Required | Description | Default |
|---|---|---|---|
| urls | Yes | Absolute http(s) URLs to fetch. | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| refresh | No | Ignore the local cache and re-fetch (default false). | |
| max_chars | No | Maximum characters returned *per page*. | |
| concurrency | No | How many fetches to run at once (default 5). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, so the safety profile is covered. The description still adds meaningful behavior beyond that: the 12-URL batch cap, per-URL failure isolation ('Failures are reported per URL instead of failing the whole call'), and the concurrent overlap that makes it fast. Remaining gaps are minor, such as the caching behavior behind the refresh param.
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?
Three sentences, zero waste. The core capability and return shape are front-loaded, followed by the sibling comparison and then the failure-handling contract, in descending order of importance.
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?
With no output schema, the description carries the return-shape burden and does so adequately by describing per-URL Markdown sections and per-URL error reporting. Behavior is complete enough to invoke correctly, though ordering/pagination semantics and cache behavior are left to the schema.
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 100%, so the schema already documents urls, format, refresh, max_chars, and concurrency with enums and ranges. The description adds little parameter-level detail beyond echoing the 12-URL cap and Markdown default, so the baseline 3 for schema-driven parameters is appropriate.
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 gives a specific verb (Fetch), a resource (URLs), and a concrete scope: 'up to 12 URLs concurrently', returning 'each as Markdown under its own heading'. It also differentiates itself from the sibling fetch_url by naming it explicitly, so an agent can route between them without opening the schemas.
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?
It states a clear selection condition: 'Cheaper and faster than calling fetch_url repeatedly because the requests overlap', which tells the agent to prefer this tool over the single-URL sibling for batches. There is no explicit when-not guidance (e.g. use web_search when you have no URLs, or fetch_url when you need one page), so it falls short of the top score.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_enginesList search engines and their statusARead-only
Show every engine this server knows about: what it indexes, whether it needs an API key, which tier it belongs to, and its current health (a repeatedly blocked engine is benched for a while). Use it to pick explicit engines for web_search, or to diagnose an empty result list.
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds genuinely new behavioral context: engines can be temporarily 'benched' after repeated blocks, which explains otherwise confusing absence from results. It does not cover rate limits or output shape, so it lands just short of a 5.
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?
Two sentences, zero filler, with the content of the listing front-loaded and the actionable routing ('pick explicit engines for web_search') placed second. Every clause earns its place.
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?
There is no output schema, so the description carries the return-value burden and does it by enumerating the four fields returned. Combined with the annotation-backed safety profile and the schema-documented format parameter, an agent has everything needed to call this 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?
Schema description coverage is 100% and the single 'format' parameter is fully documented in the schema, so the description correctly stays out of it. Baseline 3 applies since the description adds no syntax or default-value detail beyond what the schema already provides.
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?
States a specific verb and resource ('Show every engine this server knows about') and enumerates exactly what the listing contains: indexed content, API-key requirement, tier, and current health. It also routes the agent toward web_search as the downstream consumer, so it is distinguishable from the other search/index siblings.
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?
Gives two concrete use cases: selecting explicit engines for web_search and diagnosing an empty result list. That is clear when-to-use guidance, but it stops short of an explicit exclusion or comparison against the other siblings (research, search_index, local_index).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
local_indexInspect or maintain the local indexADestructiveIdempotent
Report on, prune, clear or compact the local cache that stores fetched pages and recent engine responses. stats is safe and cheap; clear and prune delete data permanently.
| Name | Required | Description | Default |
|---|---|---|---|
| scope | No | For `clear`: what to delete (default all). | |
| action | No | stats (default) reports sizes; clear empties the cache; prune drops entries older than max_age_days or beyond max_pages; vacuum compacts the database file. | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| max_pages | No | For `prune`: keep only this many of the most recently fetched pages. | |
| max_age_days | No | For `prune`: drop pages fetched more than this many days ago. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and readOnlyHint=false, but they are tool-level and cannot say that the danger is confined to specific actions. The description adds per-action detail by naming `stats` as safe and `clear`/`prune` as permanently deleting data, which is genuinely useful disambiguation. It doesn't cover what `vacuum` does or what stats returns, so it stops short of a 5.
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?
Two sentences, front-loaded with the capability and immediately followed by the safety-relevant distinction. Every clause earns its place and nothing is padded.
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 an action-dispatch maintenance tool with 5 optional params and no output schema, the description covers purpose and the primary risk (permanent deletion). It could say more about what `stats` reports or how `vacuum` differs, but with 100% schema coverage and destructive annotations, the essentials are present.
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 100%, and the schema fully documents scope, action, format, max_pages, and max_age_days. The description only echoes the action names already enumerated in the schema, adding no syntax, defaults, or interaction detail beyond it, so the baseline 3 applies.
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 pairs concrete verbs (report, prune, clear, compact) with a specific resource: the local cache of fetched pages and recent engine responses. This is far more than a restatement of the name, though it never names or differentiates itself from siblings like search_index or list_engines.
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 line '`stats` is safe and cheap; `clear` and `prune` delete data permanently' hints at action selection, but there is no explicit 'use this when' guidance and no comparison to the sibling index/engine tools. The agent must infer that destructive maintenance is only warranted in specific situations.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
parse_documentParse a local file or document URLARead-only
Read a document from a local path or a URL and return its text as Markdown. Supports PDF, DOCX, XLSX, PPTX, EPUB, ODT, CSV, JSON, HTML and plain text. Spreadsheets become Markdown tables, presentations become one section per slide, DOCX keeps headings, lists and tables, and PDFs get their hyphenation and column artefacts repaired. Everything is parsed on this machine — no upload, no conversion service.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | Absolute http(s) URL of a document. Mutually exclusive with `path`. | |
| path | No | Local file path (absolute, or relative to the server working directory). | |
| sheet | No | For spreadsheets: only return the worksheet with this name. | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| max_chars | No | Maximum characters of content to return. Longer content is truncated and a `next offset` is reported so you can continue reading. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnlyHint, openWorldHint), so the bar is lower, and the description adds genuine context beyond them: local-only parsing with no upload or conversion service, plus per-format conversion behavior (spreadsheets to tables, presentations to one section per slide, PDF hyphenation/column repair). It does not discuss cost, size limits, or failure modes on remote fetches, but the added context is substantive.
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?
Three sentences, front-loaded with what the tool does, then supported formats, then conversion specifics, then the privacy guarantee. Each sentence carries distinct information and nothing is repeated from the schema.
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?
With no output schema, the description usefully explains the return shape (text as Markdown, with format-specific structuring), and the schema handles offsets and mutual exclusivity. What is missing is any guidance on large documents or when a remote URL fetch would fail, but nothing an agent needs to invoke it correctly is absent.
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 100%, so all five parameters — including the url/path mutual exclusivity, sheet selection, format enum, and max_chars truncation with next offset — are already fully documented in the schema. The description adds only the format list and conversion outcomes, not parameter-level meaning, so the baseline 3 applies.
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?
States a specific verb (read/parse) and resource (document from a local path or URL) and names the exact output (text as Markdown), plus an enumerated list of supported formats. It never names a sibling such as fetch_url or web_search, so the agent must infer the boundary between 'parse a document' and 'fetch a URL' on its own.
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 implies its niche — documents (PDF, DOCX, XLSX, etc.) rather than arbitrary web pages — but gives no explicit when-to-use or when-not-to-use statement and no alternative tool named. The presence of fetch_url/fetch_urls among siblings makes that omission a real gap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
researchResearch a question (search + read + organise)ARead-only
One call that does the whole mechanical research loop: search several engines, derive extra queries from the vocabulary of the results, fetch the most relevant and diverse sources, then extract the passages from each that actually answer the question. Returns a Markdown brief with numbered, citable sources and verbatim quoted passages — it does NOT paraphrase, because this server has no language model; you write the synthesis from the evidence it gathers. Use this instead of chaining web_search and several fetch_url calls whenever you need more than a snippet. depth 1 = the query as written. depth 2 (default) = the query plus expansions derived from what the first page of results is actually about. depth 3 = broader expansion and more sources.
| Name | Required | Description | Default |
|---|---|---|---|
| depth | No | How hard to dig. 1 is fastest, 3 is most thorough (default 2). | |
| query | Yes | The research question, in natural language. | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| region | No | Region hint such as "us" or "cn". | |
| engines | No | Engines to use, in priority order. Omit for automatic tiered selection (keyless defaults first, then fallbacks, then matching subject indexes). Use ["all"] for every configured engine, or see the list_engines tool for ids. | |
| refresh | No | Ignore cached pages and re-fetch every source (default false). | |
| language | No | Language hint such as "en" or "zh". | |
| freshness | No | Only return results newer than this. Honoured by engines that support it (Google News, Bing, Serper, Tavily, Brave, Google CSE) and ignored by the rest. | |
| max_sources | No | How many sources to fetch and quote (default 8 at depth 2+). | |
| safe_search | No | Safe-search level. Defaults to the server configuration ("moderate"). | |
| exclude_domains | No | Exclude these domains from the research. | |
| include_domains | No | Restrict the research to these domains. | |
| passages_per_source | No | Passages to quote per source (default 4). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint/openWorldHint, but the description adds behavior annotations cannot: the server has no language model, so it does NOT paraphrase and the agent must write the synthesis itself. It also describes the actual return shape (verbatim passages, numbered citable sources), which is critical given there is no output schema.
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?
Front-loaded with the pipeline and clear value; every clause carries signal. Slightly long, and the depth elaboration partly overlaps the schema, but nothing is wasted 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?
With no output schema, the description carries the full burden of explaining return values and does so (Markdown brief, numbered sources, verbatim quotes, markdown vs json formats). It also flags the key limitation (no LLM synthesis server-side), so an agent knows exactly what it must do next.
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 100% so the baseline is 3, but the description earns above baseline by elaborating what depth actually does ('depth 2 = the query plus expansions derived from what the first page of results is actually about'), which the terse schema ('How hard to dig') does not convey.
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?
States a specific compound action (search several engines, expand queries, fetch sources, extract answering passages) and names the exact output artifact (a Markdown brief with numbered citable sources and verbatim quotes). An agent can distinguish this from web_search or fetch_url without opening any schema.
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?
Explicitly routes the agent: 'Use this instead of chaining web_search and several fetch_url calls whenever you need more than a snippet' — naming the alternatives and the condition ('more than a snippet') that selects this tool. It also gives depth-tier guidance so the agent knows which intensity to pick.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_indexSearch the local page indexARead-only
Full-text search over every page this server has already fetched on this machine, using a local SQLite FTS5 index (with CJK bigram support). Use it to recall something you read earlier in the session, to avoid re-fetching, or to work offline. Returns cached page URLs, titles and highlighted snippets — and it costs no network requests at all.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Full-text query. Latin terms match by prefix; CJK phrases match by bigram, so "上下文" finds "模型上下文协议". | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| max_results | No | Maximum matches to return (default 10). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered; the description still adds real context beyond them — the FTS5 backend, CJK bigram matching, and the fact that it issues no network requests. It does not cover ranking, result ordering, or pagination behavior.
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?
Three sentences, front-loaded with what the tool is, then usage, then return values and cost. Every sentence carries information; only the closing 'costs no network requests at all' slightly restates the earlier 'local/on this machine' framing.
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 read-only search tool with full schema coverage and annotations covering the safety profile, the description supplies the remaining essentials: what corpus is searched, when to use it, and what it returns (cached URLs, titles, highlighted snippets). No output schema is needed given that the return shape is described.
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 100%, so the schema already documents query, format, and max_results in detail, including the default and bounds. The description only reinforces the CJK bigram behavior, adding marginal meaning over the schema — baseline 3 applies.
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?
States a specific verb+resource+scope: full-text search over pages already fetched on this machine, backed by a local SQLite FTS5 index. It implicitly separates itself from the web_search/fetch_url family by stressing locality and zero network cost, but it never differentiates from the sibling 'local_index', which is a plausible source of confusion.
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?
Gives three concrete situations to reach for it: recalling something read earlier in the session, avoiding a re-fetch, and working offline. There is no explicit when-not or named alternative (e.g. 'for new content use web_search'), so routing is clear but not fully resolved.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
web_searchWeb search (multi-engine)ARead-only
Search the web across several independent engines at once and get back one de-duplicated, re-ranked result list. Results from every engine are merged with Reciprocal Rank Fusion, so a page several engines agree on outranks a page only one engine found — this is materially more stable than querying a single provider. Runs with no API key by default; engines that are blocked or failing are skipped automatically and reported. Prefer this over fetch_url when you do not yet know which page has the answer. Use research() when you want the pages read for you as well.
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Restrict results to one domain (host suffix match), e.g. "docs.python.org". Equivalent to adding site: to the query but applied after fusion, so it cannot be ignored by an engine. | |
| query | Yes | The search query. Natural language works; operators such as "site:example.com", quoted phrases and "filetype:pdf" are passed through to engines that support them. | |
| format | No | Output format. "markdown" (default) is compact and readable; "json" returns structured data for post-processing. | |
| region | No | Region hint such as "us", "cn", "de". Selects region-appropriate engines and result sets. | |
| engines | No | Engines to use, in priority order. Omit for automatic tiered selection (keyless defaults first, then fallbacks, then matching subject indexes). Use ["all"] for every configured engine, or see the list_engines tool for ids. | |
| language | No | Language hint such as "en", "zh", "ja". Also picks language-appropriate default engines. | |
| freshness | No | Only return results newer than this. Honoured by engines that support it (Google News, Bing, Serper, Tavily, Brave, Google CSE) and ignored by the rest. | |
| max_results | No | Maximum fused results to return (default 12). | |
| safe_search | No | Safe-search level. Defaults to the server configuration ("moderate"). | |
| exclude_domains | No | Drop results from these domains. | |
| include_domains | No | Only keep results from these domains. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover readOnly and openWorld, so the description carries the real behavioral burden and delivers: no API key by default, blocked/failing engines skipped and reported, and RRF fusion semantics explaining why results are stable. It stops short of describing latency, rate limits, or what the 'reported' failures look like, so a 5 is not warranted.
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?
Four sentences, each doing distinct work: capability, mechanism/justification, operational constraints, and sibling routing. Front-loaded with the core action and zero 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?
For an 11-parameter search tool with no output schema, the description covers capability, ranking behavior, failure handling, key requirements, and sibling routing, while the schema handles every parameter including output format. Nothing an agent needs to select or invoke it correctly is missing.
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 100%, so all 11 parameters are already documented, including site, engines, freshness, and max_results. The description adds no parameter-level detail beyond the schema (its site/fusion note duplicates the schema text), so the baseline 3 applies.
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?
States a specific verb and resource (search the web), names the multi-engine scope, and describes the output shape ('one de-duplicated, re-ranked result list'). This distinguishes it cleanly from fetch_url, research, and list_engines without opening any schema.
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?
Explicitly routes between siblings: 'Prefer this over fetch_url when you do not yet know which page has the answer' and 'Use research() when you want the pages read for you as well.' Both the when and the when-not are stated with a selecting condition.
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.
8 tool updates
v0.1.0- First observed
fetch_url - First observed
fetch_urls - First observed
list_engines - First observed
local_index - First observed
parse_document - First observed
research - First observed
search_index - First observed
web_search
TDQS
Scored across 8 tools
Most tools have clearly distinct roles, and descriptions explicitly route between them (web_search vs research, fetch_url vs fetch_urls). The main overlap is fetch_url and parse_document, which both convert remote documents/HTML to Markdown from a URL, leaving some ambiguity about which to pick for a remote document.
The dominant pattern is snake_case verb_noun (fetch_url, fetch_urls, parse_document, search_index, list_engines), which is predictable. Deviations are research (a bare verb/noun) and local_index (a noun phrase rather than an action), but these are minor.
Eight tools is well-scoped for a search/fetch/research server, with each tool earning its place across search, fetch, parse, index recall and engine introspection. No redundancy or padding.
The surface covers the full lifecycle: multi-engine search, orchestrated deep research, single and batch fetching, document parsing, offline index recall, cache management and engine introspection. No obvious gaps or dead ends for the stated domain.
Maintenance
Related MCP Connectors
Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.
Free web search for AI agents. No API key required. Hosted MCP in active development.
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.
MCP server for Firecrawl — web search, scraping, and biomedical/arXiv paper search.
Related MCP Servers
- AlicenseAqualityAmaintenanceA local-first, no-API-key MCP server that enables LLMs to search the web, fetch pages, and read documents using multiple engines and smart fallbacks.1193MIT
- AlicenseNot gradedqualityAmaintenanceLocal web search MCP server that fuses multiple search engines, fetches and compresses pages to reduce tokens and cost, with caching for repeated and paraphrased queries.58MIT
- AlicenseNot gradedqualityFmaintenanceMCP server enabling local-first web search, fetch, extract, and caching with citeable excerpts, no API key required. Supports research workflows for agents and apps.301 npm1MIT
- AlicenseAqualityAmaintenanceLocal MCP server for web search and page extraction, providing clean markdown from URLs, search results, site mapping, and research endpoints without API keys or accounts.5Apache 2.0