free-search-mcp
free-search-mcp
Сервер протокола Model Context Protocol, работающий преимущественно локально и не требующий API-ключей. Он дает любой LLM (Claude, GPT, локальная Ollama и т. д.) возможность искать в интернете, загружать и очищать страницы, а также читать документы — без необходимости регистрироваться в каком-либо поисковом API.
Он объединяет лучшие идеи из нескольких MCP с открытым исходным кодом в один пакет Python и добавляет эргономику для LLM и надежность, которых им не хватало.
research("how does reciprocal rank fusion work", depth=3)
↓
# Research brief: how does reciprocal rank fusion work
_engines: duckduckgo, mojeek, startpage · 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…Один вызов инструмента. Три источника. Никаких API-ключей. Никаких требований OPENAI_API_KEY для поиска.
Зачем это нужно
Существующие MCP для поиска хорошо справляются с чем-то одним, но обычно вам нужно всё сразу:
Несколько движков | Без API-ключа | Умный откат | PDF/DOCX | Кэш FTS5 | Фильтры | Trafilatura | Настройка под LLM | |
| ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ~ |
| ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ~ |
| ✓ | ✓ | ~ | ✗ | ✗ | ✗ | ✗ | ~ |
| ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ~ |
free-search-mcp | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
«Настройка под LLM» здесь означает: вывод в формате Markdown, оценка количества токенов, умное усечение на границах абзацев, строки документации «Best for / Not for / Returns / Common mistakes», которые модель использует для выбора правильного инструмента, полезные подсказки об ошибках, шаблоны промптов и ресурсов MCP, а также однократный вызов research(), который объединяет поиск→загрузку→загрузку→загрузку в один шаг.
«Trafilatura» означает, что мы извлекаем основной контент с помощью trafilatura — победителя теста ROUGE 2023 года (около 0,85 против 0,55 для простого удаления шаблонного кода). Каждая загруженная страница также бесплатно возвращает author, published_date и sitename.
«Фильтры» означают, что поиск/исследование принимают параметры freshness, include_domains, exclude_domains, category (news/pdf/github/paper/forum/blog), include_text, exclude_text.
Related MCP server: uvxwebsearchmcp
Инструменты
Инструмент | Описание |
| Параллельный поиск по нескольким движкам, объединенный через Reciprocal Rank Fusion |
| Однократный вызов: поиск + загрузка топ-N + возврат краткой сводки в Markdown |
| Загрузка страницы, возврат Markdown в режиме чтения (извлечено через trafilatura, с автором/датой/названием сайта) |
| Параллельная загрузка нескольких URL |
| Парсинг PDF / DOCX / HTML / TXT / MD с пагинацией |
| Поиск FTS5 по ранее загруженным страницам |
| Список имен движков, доступных для |
Плюс 2 промпта MCP (Research thoroughly, Fact-check claim) и шаблон ресурса (cache://page/{url}) для возврата кэшированных страниц в контекст без повторной загрузки.
Фильтры (search / research)
Параметр | Значения | Эффект |
|
| Только результаты за последние N |
|
| Ограничить этими доменами |
|
| Исключить эти домены |
|
| Ярлык типа контента (paper = arxiv/acm/ieee/…, forum = reddit/HN/SE и т. д.) |
|
| Подстрока, обязательная в заголовке/сниппете |
|
| Запрещенная подстрока |
|
| Переопределить стандартный TTL кэша в 7 дней для этого вызова |
Все инструменты по умолчанию используют format="markdown" — читабельно, примерно на 40% меньше токенов, чем JSON, с указанием источника и заголовком бюджета токенов. Передайте format="json" для структурированного доступа.
Аннотации инструментов
Каждый инструмент поставляется с правильными аннотациями readOnlyHint, idempotentHint и openWorldHint, чтобы клиенты MCP могли помечать их и ограничивать выполнение действий с повышенными привилегиями.
Движки
Набор по умолчанию (все надежные, без капчи при повторных вызовах):
duckduckgo, mojeek, startpage.
Опционально (периодические проверки для headless-клиентов):
brave, bing, baidu.
Brave/Bing/Baidu блокируют headless-браузеры после нескольких вызовов (PoW CAPTCHA, страницы «что-то пошло не так», редиректы). Передавайте
engines=["brave"]и т. д. только тогда, когда стандартные движки не могут найти то, что вам нужно.
Установка
git clone https://github.com/ymylive/free-search-mcp.git
cd free-search-mcp
uv sync
uv run playwright install chromiumЗапуск в качестве автономного сервера (транспорт stdio):
uv run search-mcpЗапуск живых тестов (обращаются к реальному интернету — установите переменную окружения):
SEARCH_MCP_TEST_NETWORK=1 uv run pytest -vОфлайн-тесты запускаются по умолчанию и не используют сеть.
Подключение к Claude Desktop
Добавьте это в ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) или эквивалентный файл на вашей платформе:
{
"mcpServers": {
"search": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/free-search-mcp", "run", "search-mcp"]
}
}
}Перезапустите Claude Desktop. Семь инструментов выше появятся в панели инструментов.
Подключение к другим клиентам
Сервер использует обычный MCP через stdio. Работает всё, что поддерживает MCP:
Claude Code (
claude mcp add search uv --directory /…/free-search-mcp run search-mcp)Cursor / Continue / Cline (используйте фрагмент JSON выше)
Пользовательские клиенты на Python / TypeScript через официальный SDK MCP
Конфигурация
Все настройки можно переопределить с помощью переменных окружения с префиксом SEARCH_MCP_:
Переменная | По умолчанию | Значение |
|
| Список JSON |
|
| |
|
| на движок |
|
| общий пул |
|
| |
|
| 7 дней |
|
|
|
|
| |
|
| параллельные страницы |
|
| усечение на результат |
Архитектура
┌─────────────────────────────────────────────────────┐
│ FastMCP server (stdio) │
│ 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 │
│ startpage.py │ │ - shared cookies │
│ brave.py (opt) │ │ - semaphore-bounded pages│
│ bing.py (opt) │ └────────────────────────────┘
│ baidu.py (opt) │
└──────────────────────┘
┌────────────────────────────┐ ┌──────────────────┐
│ documents/ │ │ ratelimit │
│ pypdf, python-docx, │ │ token bucket │
│ markdownify │ │ per engine │
└────────────────────────────┘ └──────────────────┘
┌────────────────────────────┐ ┌──────────────────┐
│ formatting │ │ research │
│ token estimate │ │ composed │
│ smart truncation │ │ workflow │
│ markdown renderers │ │ │
└────────────────────────────┘ └──────────────────┘Паттерн адаптера движка
Каждый движок в src/search_mcp/engines/ реализует:
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]: ...Базовый класс обрабатывает транспорт (httpx → откат к Playwright), ограничение частоты запросов и случай, когда HTTP возвращает капчу вместо результатов (автоматические повторные попытки через браузер).
Благодарности
Этот проект стоит на плечах гигантов:
mrkrsl/web-search-mcp— умная стратегия загрузки httpx-затем-Playwright, цепочка отката по нескольким движкамAas-ee/open-webSearch— широта охвата движков (Bing/DDG/Baidu/Brave/Startpage)VincentKaufmann/noapi-google-search-mcp— паттерны защиты от обнаружения (navigator.webdriver, UA, cookies), идея кэша SQLite FTS5, многоформатныйread_documentnickclyde/duckduckgo-mcp-server— ограничение частоты запросов на движок, очистка контента для LLMMojeek — независимый поисковый индекс, который не блокирует по User-Agent
Лицензия
MIT — см. LICENSE.
Available Tools
11 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so safety is covered. The description goes well beyond by disclosing FTS5 semantics (AND on whitespace, phrase quoting, prefix/OR/NOT), return format differences, and the common mistake of treating it like web search. It adds rich behavioral context without contradicting 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 structured with clear sections and front-loaded with the core purpose. It's longer than average but each section (best-for, not-recommended, returns, mistakes, args) adds distinct value. Slightly verbose, but nothing is redundant 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?
The tool has 3 params and no output schema, so the description must cover both invocation and return semantics. It does: it explains the return formats, metadata caveats, and pitfalls. An agent has everything needed to call this correctly, including edge cases like empty cache and phrase quoting.
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 full burden. It explains `query` with FTS5 syntax (AND, OR, NOT, prefix, phrase), `limit` as max hits, and `format` with output differences (markdown vs json, plus the metadata caveat). This is far more than the schema 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?
The description opens with a precise verb+resource: 'Full-text search over pages already fetched into the local SQLite FTS5 index.' It immediately distinguishes itself from siblings by explicitly naming `search`/`research` as the alternatives for discovering new pages, making the tool's scope 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?
The description has a 'Best for' section listing concrete use cases (recalling fetched content, avoiding re-fetches, keyword grep) and a 'Not recommended for' section that names alternatives (`search`/`research`) and even covers the empty-cache case. This is explicit routing with no inference needed.
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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint and idempotentHint annotations, the description discloses concurrency, smart truncation, return format specifics (markdown/json with tokens_estimated), and common mistakes (e.g., asking it to answer the question). No contradiction with annotations; it adds valuable behavioral context.
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 sections (intro, best for, not recommended, returns, mistakes, args). It front-loads the core purpose and each sentence earns its place, providing high information density without fluff.
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 is fully self-contained: it covers when to use, when not to use, parameter details, return formats, and common pitfalls. No output schema exists, but the return description is detailed enough for an agent to handle responses 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 0%, so the description must compensate. The 'Args' section explains every parameter: question (purpose), urls (count and format), format (enum with default). This is complete and adds meaning 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 states a specific action ('Fetch 2-5 URLs concurrently and return per-URL excerpts'), identifies the resource (URLs), and clarifies the outcome (enables comparison). It also distinguishes itself from siblings by naming fetch_batch and fetch as alternatives for different URL counts, leaving no ambiguity.
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 give concrete usage conditions: >5 URLs → fetch_batch, 1 URL → fetch, no URLs → search/research. This is direct, actionable guidance on when to use this tool versus alternatives.
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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations, the description discloses filesystem effects, default-enabled behavior, environment variable controls, auto-expiring paths, TTL cleanup, and return details. This is substantial additive transparency consistent with readOnlyHint=false and there is no contradiction with 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 organized into clear labeled sections (best for, not recommended, returns, retention, args) with no filler. The core action is front-loaded, and every section earns its place by providing actionable information.
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 lack of an output schema, the description provides both markdown and JSON return shapes, including the sha256 and expiry fields. It also covers retention, configuration, and use cases, making it complete enough for an agent to invoke safely and 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?
With 0% schema description coverage, the description compensates by specifying that url must be an absolute http(s) URL and by explaining format through the Returns section. It could go slightly deeper on format semantics, but the return value details largely close the 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 and resource: 'Save a file from a URL to a local, auto-expiring download directory.' It clearly differentiates from siblings like read_doc and fetch by saying it keeps an actual file rather than parsing content or viewing a page.
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 explicitly name alternatives: read_doc for document content, fetch for web pages, and fetch(inline=True) for images. This gives an agent unambiguous when-to-use versus 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.
enginesList available search enginesARead-onlyIdempotent
List the available sources, grouped by what they index.
Best for:
- Choosing a source deliberately: which one indexes filings, or preprints,
or Chinese-language pages.
- Checking a name before passing it to `engines=` on `search` / `research`.
Not recommended for:
- Calling on every search: the list is static, so read it once.
Returns (markdown): a `group -> sub-group -> engine` tree, one line of
description per engine. `group="paper"` restricts it to that group.
Returns (json): `{"engines": [...names...], "taxonomy": {...},
"descriptions": {...}}`.
Prefer `category=` over `engines=`. `category="paper"` WIDENS: it routes to
one specialist per sub-group. A dotted sub-group NARROWS: `"paper.biomed"`
queries only the biomedical indexes. Naming engines explicitly turns that
routing off entirely, so reach for it only to force a specific source.
Common mistakes:
- Passing one of these names as `query`: they belong in `engines=`.
- Passing a key-only engine with no key configured; it returns an
actionable error, not results.
| Name | Required | Description | Default |
|---|---|---|---|
| group | No | ||
| format | No | markdown |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool read-only and idempotent, and the description adds genuinely useful behavioral context: the list is static and should be read once, returns can be markdown or JSON, and key-only engines produce actionable errors. This goes well beyond what annotations 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?
Although long, the description is tightly organized with functional headers, scannable bullet-ish sections, and every sentence adds routing or error-avoidance value. The core purpose is front-loaded, and the detailed guidance is where an agent needs it.
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 list tool with two optional parameters and no output schema, this description is complete: it explains when to use it, what the parameters do, what output format to expect, and likely failure modes. No critical calling information 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 coverage is 0%, so the description carries the full burden of explaining parameters. It clarifies `group` values, how dotted sub-groups narrow, how `category=` differs from `engines=`, and what both markdown and JSON output shapes look like. This compensates fully for the schema's lack of parameter 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 clearly states the tool lists available sources/engines grouped by what they index, which is specific and immediately distinct from siblings like `search`, `research`, and `fetch`. It names the resource and the organizing principle without ambiguity.
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 gives explicit best-for scenarios, a not-recommended scenario, and detailed routing guidance comparing `category=` vs `engines=` and group/sub-group semantics. It also lists common mistakes, giving an agent both positive and negative selection criteria.
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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, and idempotentHint. The description adds value beyond these: it discloses the return format details (JSON key structure, markdown flattening behavior), the Twitter-card-inside-opengraph quirk, and the open-world caveat that most pages yield empty lists. 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?
Well-organized with clear section headers (Best for, Not recommended, Returns, Common mistakes, Args). It is longer than minimal, but each section earns its place and the core purpose is front-loaded. Slightly verbose but purposefully structured.
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?
Complete for a multi-format extraction tool with no output schema. The description carries the return-format burden itself, listing the JSON keys, the markdown default behavior, and parameter meanings. It also sets expectations about empty results. An agent has everything needed to call it 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 0%, so the description must compensate — and it does. The Args section clarifies that url must be an 'Absolute http(s) URL' (a constraint not in the schema) and explains format's options and default. This adds real meaning beyond the bare schema definitions.
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 pair: 'Pull JSON-LD, OpenGraph, Twitter cards, and microdata from a web page.' It lists the exact data formats extracted, and distinguishes itself from siblings by name (fetch, read_doc), making the tool's identity 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?
Exceptionally explicit. It provides 'Best for' scenarios (product, article, recipe/event/video pages), 'Not recommended for' cases with named alternatives (use fetch, use read_doc), and a 'Common mistakes' warning that most sites lack structured data and fetch is preferred. Nothing is left to inference.
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. The header says `cached N days ago`
when you are not looking at the live page; for deadlines, prices and
anything else that moves, that is the cue to refresh.
- Reading `no publication date found` as "recent". It means unknown.
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. Returns 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?
Annotations declare readOnlyHint, openWorldHint, and idempotentHint, and the description adds extensive context beyond them: cache behavior with 7-day TTL and force_refresh semantics, render mode differences including caveats for JS-only SPAs, inline image token costs, and the meaning of 'no publication date found'. 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 lengthy but densely structured with clear headers, bullet lists, and code-style naming. Every section adds actionable information: return formats, common mistakes, and parameter semantics. No redundant fluff; the structure makes the length digestible.
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 six parameters and no output schema, the description is remarkably complete. It covers input requirements, output shapes for markdown and json, non-text resource descriptions, caching, render strategies, and failure-prone usage patterns. An agent has everything needed to invoke this 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?
Schema description coverage is 0%, so the description carries the full burden, and it delivers a detailed Args section explaining every parameter, including defaults (render='auto', format='markdown') and nuanced behavior (max_age_hours=0 means force_refresh, None means server TTL). 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?
The description states a specific verb and resource: 'Fetch one URL: page text, or a description of a non-text resource.' It clearly identifies what the tool does and differentiates it from siblings by listing what it is not (batch fetch, research, read_doc). An agent can select it correctly.
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 alternatives like fetch_batch, research, read_doc, and search with precise conditions. This is exactly the level of guidance needed to route the agent to the right tool.
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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, and idempotentHint, so the safety profile is covered. The description adds crucial behavioral details: per-URL failures do not raise exceptions, the markdown output embeds inline error notes, and the JSON format includes an error field per entry. It also warns against common mistakes, exceeding what annotations alone 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 organized into labeled sections (Best for, Not recommended, Returns, Common mistakes, Args) that are easy to scan. Every sentence adds value—there is no filler. The core purpose is stated first, and the detailed subsections are appropriately sequenced.
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 there is no output schema, the description fully specifies the two return formats (markdown sections with inline errors, and JSON list with error fields). It covers edge cases (single URL, failed items, maximum batch size) and references sibling tools for alternatives. An agent has everything needed to invoke it 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 0%, so the description carries the full burden. It explains each parameter: urls as absolute http(s) with a max of 20, render as 'Same as fetch' (referencing the sibling), and format with its two allowed values. It also clarifies the failure semantics associated with the urls parameter, which the schema does not.
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 ('Fetch a list of URLs in parallel') and immediately differentiates itself from siblings by naming fetch, research, and read_doc with explicit conditions. The purpose is unambiguous and the scope is clearly bounded.
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 provides a 'Best for' section listing concrete scenarios (2+ URLs, top N results of a search) and a 'Not recommended for' section with explicit alternatives (single URL -> fetch, search-and-read -> research, PDFs/DOCX -> read_doc). This gives an agent clear decision criteria for tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
paper_graphWalk a paper's citation graphARead-onlyIdempotent
Follow the citations of ONE paper, and check whether it still stands.
`search` finds papers that MENTION your words. This follows the edges
instead: what a specific paper built on, and what has built on it since.
Best for:
- Checking a citation before repeating it: is the DOI real, and has the
paper been retracted or corrected?
- "What happened after this result": citing works come back ordered by how
much the field cited them, so a 2019 paper leads to the current state of
the art rather than to the most recent preprint about it.
- Building a reading list backwards from one good paper.
Not recommended for:
- Finding papers by topic -> `search(category="paper")`, or a sub-group
like `"paper.biomed"` / `"paper.cs"` / `"paper.preprint"`.
- Reading the paper itself -> `read_doc` on the returned URL.
Returns:
- markdown (default): the paper with its retraction/correction notices,
then "References" and "Cited by" sections.
- json: {paper, references, citations, notes}, where `paper.crossref`
carries `registered` and every post-publication `notices` entry.
Common mistakes:
- Passing a topic instead of a paper. A title resolves to its single best
match; a phrase that names no specific paper resolves to the wrong one.
- Reading an empty `citations` list as "uncited" when `notes` says the
lookup was truncated.
Args:
paper: DOI (`10.1145/1571941.1572114`, or a doi.org URL), an OpenAlex
ID (`W2148972377`), or the paper's exact title.
direction: "both", "references" (what it cites) or "citations" (what
cites it).
limit: Max neighbours per direction, 1-50.
format: "markdown" or "json".
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| paper | Yes | ||
| format | No | markdown | |
| direction | No | both |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations provide readOnlyHint, openWorldHint, and idempotentHint, but the description adds substantial behavioral context: retraction/correction notices, citation ordering by field impact, truncation behavior in citations list, and specific return format details. It also explains the 'notes' field for truncated lookups. 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?
Though lengthy, the description is meticulously structured with headers, bullet lists, and code blocks. The core purpose and differentiation are front-loaded, and every section (Best for, Not recommended, Returns, Common mistakes) provides non-redundant, actionable information.
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 there is no output schema, the description fully explains both markdown and json return formats, including nested fields like paper.crossref and notices. It covers edge cases like truncated citations and retraction notices, ensuring an agent has all necessary information to 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?
Schema coverage is 0%, so the description fully compensates. It explains the paper parameter with concrete examples (DOI, OpenAlex ID, exact title), the direction enum with meanings, the limit range (1-50), and format with return-type implications. This goes far beyond the schema's bare definitions.
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 the specific verb 'follow the citations' and the resource 'ONE paper', clearly differentiating from search which finds mentions. It also explains the distinction between edge-following and text-matching, 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?
Explicitly provides 'Best for' and 'Not recommended for' sections, naming alternatives like search(category=...), read_doc, and giving concrete scenarios. Also includes 'Common mistakes' that warn against passing topics instead of papers, which directly guides correct usage.
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`, which is 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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations provide readOnlyHint, openWorldHint, and idempotentHint, but the description goes far beyond them: it details sandboxing rules (env var requirement, path traversal rejection, file:// rejection), error behavior (disabled error, negative length ValueError), clamping semantics, and the exact pagination signal (`returned_chars == 0`). This is rich behavioral context that annotations alone could never 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 long but every section serves a purpose: 'Best for' and 'Not recommended for' give usage guidance, 'Security' explains the critical env var behavior, 'Returns' details output formats, 'Common mistakes' prevents misusage, and 'Args' is a compact parameter reference. It front-loads the core purpose in the first line and organizes details logically, earning its length.
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 genuinely complex tool (remote vs local, pagination, security, multiple output formats), yet the description covers every aspect needed to call it correctly: input expectations, edge cases (clamping, negative length), output structure (markdown header, json fields), and how to detect end-of-document. With no output schema present, the description fully substitutes that information. Nothing essential 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?
With schema description coverage at 0%, the description fully compensates. The Args section explains each parameter: source's format and security constraints, start's default and clamping behavior, length's range and rejection condition, and format's enum meaning. It also provides the json return fields to drive pagination, making each parameter's role crystal clear.
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 opening line states a specific verb and resource ('Read an http(s) document or sandboxed local file into Markdown'), and the 'Best for' / 'Not recommended for' sections explicitly differentiate it from siblings like fetch and research. An agent can immediately tell what this tool does and what it doesn't.
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?
There are explicit usage recommendations with named alternatives: 'Not recommended for: Arbitrary HTML web pages -> `fetch` does reader-mode cleanup...' and 'Pages discovered through search -> `fetch` or `research`.' It also explains the crucial security prerequisite for local files. No ambiguity remains.
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`.
- Checking or expanding one paper's citations -> `paper_graph`.
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, and 2-3 is
almost always enough.
- Calling `research` for a known URL: that is what `fetch` is for.
- 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). Prefer
`category=`. Naming engines turns category routing off.
fetch: If False, return source list without reading them.
freshness: "day"|"week"|"month"|"year". Restricts to recent results.
Best-effort; undated results are kept rather than dropped.
include_domains: Restrict to these domains (e.g. ["python.org"]).
exclude_domains: Drop results from these domains.
category: Which KIND of source to search; a bare group widens, a dotted
sub-group narrows. Same values as `search`; see `engines()`.
include_text: Substring required in title or snippet (case-insensitive).
exclude_text: Substring forbidden in title or snippet.
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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=true, but the description goes far beyond that: it discloses fetch behavior, cache semantics (use_cache, max_age_hours with 0 vs None), freshness handling, the difference between markdown and json return formats, the effect of fetch=False, and even behavioral nuances like 'Naming engines turns category routing off'. It also explains that non-zero max_age_hours is honored for both halves, correcting a past behavior. This is rich, accurate, and does not contradict 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 long but impeccably structured: a one-line summary, then 'Best for', 'Not recommended for', 'Returns', 'Common mistakes', and 'Args' sections. Every sentence adds value—even the 'Common mistakes' section reinforces the usage guidelines. It is front-loaded with the core purpose, and the Args section is efficiently organized with one-line-per-parameter explanations. Nothing is redundant.
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 (13 parameters, no output schema) and the absence of an output schema, the description covers everything an agent needs: return formats (markdown vs json) with descriptions, parameter semantics, usage guidance, and even error handling (json 'errors' field). It also provides token estimates and source structure, making it self-contained for correct invocation.
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 full responsibility for parameter meaning. It does this exceptionally: each of the 13 parameters is explained in natural language with defaults, effects, and interactions (e.g., depth=3 as good default, engines override category routing, include_text substring semantics). This far exceeds the schema's basic types and enums.
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 opening line 'One-shot research: search the web, fetch the top results, return both' states a specific verb+resource and a composite action. It distinguishes itself from siblings via the 'Not recommended for' section, naming 'search', 'fetch', and 'cache_search' with explicit conditions. This leaves no ambiguity about what the tool does.
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 are explicit, concrete, and name the exact sibling tools to use instead (search, fetch, cache_search, paper_graph) with the conditions that select them. It even gives example use cases and common mistakes (e.g., using depth=8 for quick lookups), leaving no inference to the agent.
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
a kind of source (`category="paper"` / `"finance"`, or a sub-group like
`category="paper.biomed"` / `"finance.filings"`; see `engines()` for the
full tree).
- Looking a fact up at its registry, with no category needed: a current
version or support end date ("latest fastapi version"), a CVE
("CVE-2024-3094": NVD record, OSV advisories, CISA exploited status),
a forecast ("上海明天天气"), an exchange rate ("100 usd to cny": ECB, and
the PBOC parity for 人民币), a coin price, a country indicator ("china
gdp", "japan population"), public holidays ("2026年放假安排"), the current
time in a city, a domain's expiry, a company's legal entity, an iOS
app's version, Wikidata facts ("Shanghai population"). The source that
fits the words joins the search and its record arrives as the first
result with the publisher's date in `dated …`, so it can be cited
without a fetch. Name the thing plainly. `category=` ("software",
"security", "weather", "finance.fx", "stats", "calendar", "reference",
"docs", "gov") asks the same sources explicitly.
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`.
- Following one paper's references or citations -> use `paper_graph`.
Returns:
- markdown (default): numbered list of `n. title`, `<url>`, snippet. About 40%
fewer tokens than json. When an answer agent is configured
(`SEARCH_MCP_AGENT_BACKEND`), a short sourced `## Answer` block sits above
the list; it starts beside the engines and answers from the same result
set.
- json: dict with `results` (list of {title,url,snippet,engines,score}),
`engines`, `cached`, optional `errors` map, optional `hint` string, and
`agent` ({answer, backend, model, sources, elapsed_seconds} or
{error}) when an answer agent is configured.
`engines` is what was actually ASKED for this answer; each result's own
`engines` is what found it. A default engine that failed recently is not
asked and appears under `benched_engines` instead; a rescue pass that
substituted a source appears as `rescued_via`. When the operator has
signed in to an agent engine (`codex`), it searches beside the keyless
engines and `agent_lane` reports it ({engines, answered, pending, errors,
rate_limited, benched, left_out}; `left_out` lists an answer dropped as
off-topic or unconfirmed);
one still searching when the links return is merged into the cached
result set, so the same call repeated later carries its results too.
Common mistakes:
- Answering from snippets. A snippet is an engine's summary of a page as it
looked when crawled: it drops qualifiers and is often years old. Use
`search` to find the URL, then read the page (`fetch`, `research`) for
any date, amount, rule, deadline or number you will state.
- Treating the `## Answer` block as verified. It is one small model's
reading of the few pages below; read the source page yourself before
acting on a deadline, price or rule.
- Treating a result as recent because `freshness=` was set. The filter keeps
undated results; the header says how many could be dated (`dated: 3/10`)
and each result is marked `dated …`, `… (from snippet text)` or `undated`.
- Passing a URL as `query`: that is `fetch`'s job.
- Cranking `max_results` up hoping for better recall; engines cap around
10-20 each, anything beyond is duplicate noise (and 50 is the ceiling).
- Naming engines by default. The default pool is already the set that works
keylessly over plain HTTP, it benches an engine that starts failing and
seats a reserve when it gets thin, and naming engines switches all of
that, and `category=` routing, off. Name engines when you want a
specific index: `engines=["so360","baidu"]` for Chinese-language sites,
`engines=["brave"]` (needs the browser) for a second opinion.
- Using `category="news"` for breaking news without also setting
`freshness="day"`: the index lags by days.
Args:
query: Natural-language query (the same string a human would type).
engines: Subset of `engines()`. None (recommended) = the health-aware
default pool: duckduckgo, bing, yahoojp (Google's index) and
anysearch, plus googlenews when `freshness` is "day"/"week",
so360 and quark for a Chinese query and naver for a Korean one.
max_results: Merged result count after dedup, clamped to 1-50. It is
also the PER-ENGINE budget, so it multiplies across the fan-out;
5-20 is the useful range and anything past that is duplicate noise
bought with real latency. Omit it for the configured default.
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". Restricts 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: Which KIND of source to search. The enum lists every value.
A bare group widens: "paper" adds one specialist per sub-group to the
default web pool. A dotted sub-group narrows to just the sources that
index it ("paper.biomed" => the biomedical indexes only). It also
RERANKS: engines that natively index the category count double in
the fusion, so the filing outranks the commentary about it, and a
record looked up by the query (a PyPI release, a CVE, an ECB rate,
a forecast) counts five times, so it leads the list. Call
`engines()` for the group -> sub-group -> engine tree with a line on
each source. Two behaviours worth knowing: "image"/"dataset" REPLACE
the web pool rather than augment it (a web engine cannot return an
image file), and "news"/"paper"/"forum"/"github"/"blog" also filter
general-web hits by hostname, so a strict category can thin those
engines out. The specialists it routes to are exempt.
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 |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only/open-world/idempotency, but the description adds substantial context beyond them: cache key composition and TTL semantics, engine benching and rescue passes, best-effort freshness with undated results kept, per-engine result caps, and the risk that an agent-sourced Answer block is unverified. It even flags the failure mode of trusting snippets for dates and numbers.
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?
Headers, bullet lists, and front-loaded scope make a long description navigable, and the 'Common mistakes' block earns its space by preventing real misuse. It is nonetheless verbose — repeated restatements of category behavior and category values appear in both the bullet list and the Args section.
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?
No output schema exists, and the description fully specifies both return formats including the json field set (results, engines, cached, errors, hint, agent, benched_engines, rescued_via, agent_lane). For a 12-parameter, multi-engine tool with conditional routing, nothing an agent needs to call 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 0%, so the description carries the full burden and does: every one of the 12 params gets semantics beyond type — max_results is both merged count and per-engine budget with a 1-50 clamp, use_cache keys on the full filter tuple, max_age_hours never disables writes, freshness is a hint not a filter, and category is explained as widen/narrow plus a reranking signal.
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 opening sentence names a specific verb, resource, and output shape ('multi-engine web search ... ranked, deduplicated link list'), and the surrounding sections make it unmistakable against siblings like fetch, research, and cache_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?
There is an explicit 'Best for' list, a 'Not recommended for' list that names the alternative for each excluded case (fetch, research, cache_search, read_doc, paper_graph), and per-category routing advice. An agent can decide between siblings without opening another schema.
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.
10 tool updates
v0.12.0- Changed
cache_search1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": "array" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "cache_searchOutput", - "type": "object" -}New value: +null
- Changed
compare1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "compareOutput", - "type": "object" -}New value: +null
- Changed
download1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "downloadOutput", - "type": "object" -}New value: +null
- Changed
engines3 fields changed- added
Input schema / properties / formatAdded value: +{ + "default": "markdown", + "enum": [ + "markdown", + "json" + ], + "title": "Format", + "type": "string" +} - added
Input schema / properties / groupAdded value: +{ + "anyOf": [ + { + "enum": [ + "web", + "news", + "paper", + "github", + "forum", + "image", + "dataset", + "finance", + "software", + "security", + "reference", + "weather", + "docs", + "gov", + "stats", + "calendar" + ], + "type": "string" + }, + { + "enum": [ + "news", + "pdf", + "github", + "paper", + "forum", + "blog", + "image", + "dataset", + "finance", + "software", + "security", + "reference", + "weather", + "docs", + "gov", + "stats", + "calendar", + "news.world", + "paper.index", + "paper.preprint", + "paper.biomed", + "paper.cs", + "paper.math", + "paper.openaccess", + "paper.trial", + "dataset.repository", + "dataset.ml", + "dataset.gov", + "finance.filings", + "finance.market", + "finance.macro", + "finance.fx", + "finance.entity", + "finance.crypto", + "software.lifecycle", + "software.github", + "software.python", + "software.node", + "software.rust", + "software.registry", + "software.app", + "security.cve", + "security.package", + "security.exploited", + "reference.domain", + "docs.web", + "docs.rfc", + "gov.us", + "gov.uk", + "stats.indicator", + "calendar.holidays", + "calendar.clock" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Group" +} - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "items": { - "type": "string" - }, - "title": "Result", - "type": "array" - } - }, - "required": [ - "result" - ], - "title": "enginesOutput", - "type": "object" -}New value: +null
- Changed
extract_structured1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "extract_structuredOutput", - "type": "object" -}New value: +null
- Changed
fetch_batch1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "additionalProperties": true, - "type": "object" - }, - "type": "array" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "fetch_batchOutput", - "type": "object" -}New value: +null
- Added
paper_graph - Changed
read_doc1 field changed- changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "read_docOutput", - "type": "object" -}New value: +null
- Changed
research2 fields changed- changed
Input schema / properties / category / anyOfPrevious value: -[ - { - "enum": [ - "news", - "pdf", - "github", - "paper", - "forum", - "blog", - "image", - "dataset" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "news", + "pdf", + "github", + "paper", + "forum", + "blog", + "image", + "dataset", + "finance", + "software", + "security", + "reference", + "weather", + "docs", + "gov", + "stats", + "calendar", + "news.world", + "paper.index", + "paper.preprint", + "paper.biomed", + "paper.cs", + "paper.math", + "paper.openaccess", + "paper.trial", + "dataset.repository", + "dataset.ml", + "dataset.gov", + "finance.filings", + "finance.market", + "finance.macro", + "finance.fx", + "finance.entity", + "finance.crypto", + "software.lifecycle", + "software.github", + "software.python", + "software.node", + "software.rust", + "software.registry", + "software.app", + "security.cve", + "security.package", + "security.exploited", + "reference.domain", + "docs.web", + "docs.rfc", + "gov.us", + "gov.uk", + "stats.indicator", + "calendar.holidays", + "calendar.clock" + ], + "type": "string" + }, + { + "type": "null" + } +] - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "researchOutput", - "type": "object" -}New value: +null
- Changed
search2 fields changed- changed
Input schema / properties / category / anyOfPrevious value: -[ - { - "enum": [ - "news", - "pdf", - "github", - "paper", - "forum", - "blog", - "image", - "dataset" - ], - "type": "string" - }, - { - "type": "null" - } -]New value: +[ + { + "enum": [ + "news", + "pdf", + "github", + "paper", + "forum", + "blog", + "image", + "dataset", + "finance", + "software", + "security", + "reference", + "weather", + "docs", + "gov", + "stats", + "calendar", + "news.world", + "paper.index", + "paper.preprint", + "paper.biomed", + "paper.cs", + "paper.math", + "paper.openaccess", + "paper.trial", + "dataset.repository", + "dataset.ml", + "dataset.gov", + "finance.filings", + "finance.market", + "finance.macro", + "finance.fx", + "finance.entity", + "finance.crypto", + "software.lifecycle", + "software.github", + "software.python", + "software.node", + "software.rust", + "software.registry", + "software.app", + "security.cve", + "security.package", + "security.exploited", + "reference.domain", + "docs.web", + "docs.rfc", + "gov.us", + "gov.uk", + "stats.indicator", + "calendar.holidays", + "calendar.clock" + ], + "type": "string" + }, + { + "type": "null" + } +] - changed
Output schema / (root)Previous value: -{ - "properties": { - "result": { - "anyOf": [ - { - "type": "string" - }, - { - "additionalProperties": true, - "type": "object" - } - ], - "title": "Result" - } - }, - "required": [ - "result" - ], - "title": "searchOutput", - "type": "object" -}New value: +null
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
Scored across 11 tools
Each tool has a sharply bounded purpose, and the descriptions actively preempt confusion with explicit 'Not recommended for' cross-references (fetch vs fetch_batch vs compare vs research, read_doc vs fetch on documents, search vs cache_search). The one genuinely thin boundary — compare vs fetch_batch for multi-URL reads — is spelled out by URL-count and output-shape rules.
All names are lowercase snake_case with a consistent style, and most follow verb_noun or verb_object (fetch_batch, cache_search, read_doc, extract_structured). A few are bare verbs or nouns (search, fetch, compare, download, engines, research) and paper_graph is noun_noun, but the set still reads predictably.
Eleven tools is squarely in the sweet spot for a search-and-retrieval server. Every tool maps to a distinct workflow primitive (discover, read, batch, compare, extract, cache, cite, save) with no filler.
The surface covers the full lifecycle: discovery (search/research/engines), retrieval (fetch/fetch_batch/read_doc with pagination), inspection (extract_structured), local corpus reuse (cache_search), citation tracing (paper_graph), and file persistence (download). Only very minor cache-management gaps exist (no listing or eviction of cached pages), which agents can work around.
Maintenance
Related MCP Connectors
Web search API and MCP server for developers building AI agents.
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.
Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.
Related MCP Servers
- AlicenseNot 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.9MIT
- 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 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.65 npm1MIT
- AlicenseAqualityAmaintenanceAn MCP server providing web search, image search, and page scraping tools to LLMs without requiring API keys.12323MIT