Skip to main content
Glama

EnriWeb

EnriWeb is a Model Context Protocol (MCP) server over stdio that exposes web search and URL fetching tools by delegating execution to EnriProxy.

If your MCP client can call MCP tools, it can do web search / fetch in a consistent way without implementing provider-specific scraping logic.

What this project is

  • An MCP server process your MCP host launches (OpenCode, Claude Code, Codex, etc.)

  • A thin client for EnriProxy (input validation + structured output)

Related MCP server: url-context-mcp

Requirements

  • Node.js >= 24 (Node 24 LTS)

  • A reachable EnriProxy server with:

    • POST /v1/tools/web_search

    • POST /v1/tools/web_fetch

  • An EnriProxy API key (configured on the EnriProxy side)

Install

# Global install
npm install -g @bedolla/enriweb

# Or run without installing
npx -y @bedolla/enriweb@latest --help

Build

npm install
npm run typecheck
npm run build

Usage

1) Configure your MCP host

EnriWeb runs as an MCP server over stdio. Your MCP host is responsible for launching the process.

Example: global install

{
  "EnriWeb": {
    "type": "stdio",
    "command": "enriweb",
    "args": [],
    "env": {
      "ENRIPROXY_URL": "http://127.0.0.1:8888",
      "ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY"
    }
  }
}

Example: no install (always uses whatever npm currently tags as latest)

{
  "EnriWeb": {
    "type": "stdio",
    "command": "npx",
    "args": ["-y", "@bedolla/enriweb@latest"],
    "env": {
      "ENRIPROXY_URL": "http://127.0.0.1:8888",
      "ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY"
    }
  }
}
{
  "EnriWeb": {
    "type": "stdio",
    "command": "node",
    "args": ["C:\\\\Users\\\\Administrator\\\\Projects\\\\EnriWeb\\\\dist\\\\index.js"],
    "env": {
      "ENRIPROXY_URL": "http://127.0.0.1:8888",
      "ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY"
    }
  }
}

Configuration

EnriWeb is configured via environment variables:

  • ENRIPROXY_URL (string, optional, default: http://127.0.0.1:8888)

  • ENRIPROXY_API_KEY (string, required)

  • ENRIWEB_TIMEOUT_MS (string, optional, default: 300000)

    • Parsed as an integer (milliseconds); fetch budget at double the proxy's total fetch budget (150 s) plus margin. Operator policy is a uniform 5-minute tool budget for both tools; when slow SearXNG engines are kept (server budget up to ~310 s), raise ENRIWEB_SEARCH_TIMEOUT_MS beyond the 300 s default.

  • ENRIWEB_SEARCH_TIMEOUT_MS (string, optional, default: 300000)

    • Parsed as an integer (milliseconds); uniform 5-minute tool budget. Residual: the SearXNG server budget alone can reach 310 s on slow-engine days, so searches slower than 300 s still end in the retryable timeout; raise the env var to extend it.

  • ENRIWEB_WEB_FETCH_DEFAULT_MAX_CHARS (string, optional, default: 200000, max 4000000)

    • Parsed as an integer.

  • ENRIWEB_GITHUB_TOKEN (string, optional)

    • Used for GitHub API enrichment to improve rate limits.

  • ENRIWEB_SCREENSHOT_MODE (string, optional, one of auto | force | none | analyze)

    • Installation-level default for web_fetch screenshots, applied when the host omits the screenshot parameter (explicit host values always win). Designed for clients whose provider rejects image blocks inside tool results (OpenAI-compatible Chat Completions APIs accept images in user messages but not in tool messages — e.g. OpenCode surfaces "this model does not support image input" even for vision models). Set analyze on such installs: the server captures the page and returns a TEXT description per segment (screenshot_analyses) instead of image blocks. Invalid values warn on stderr and are ignored.

  • ENRIWEB_SEARCH_ENGINES (string, optional, e.g. google or google,bing)

    • Operator SearXNG engine selector applied to every web_search call. Overrides the EnriProxy server default without reconfiguring the server; unset uses the server configuration. This is operator configuration on purpose — the model-facing web_search schema exposes no engine option so models cannot narrow their own results. Invalid values warn on stderr and are ignored.

MCP tools

EnriWeb exposes these MCP tools:

  • web_search

  • web_fetch

General notes:

  • All tools accept a single JSON object as their input (the MCP arguments for that tool).

  • EnriWeb returns both:

    • a short human-readable preview (content)

    • the full result payload (structuredContent)


Search the web via EnriProxy.

Inputs:

  • query (string or string[], required unless queries is provided): the search query. As an array it accepts a batch of 1 to 4 non-blank queries (each entry trimmed; duplicates collapse) — equivalent to sending queries.

  • queries (string[], optional): batch of 1 to 4 queries. Takes precedence over query. EnriProxy runs every query in parallel, merges results by relevance rank, and deduplicates by URL.

  • max_results (integer, optional; alias maxResults)

    • Must be >= 1.

    • If omitted, EnriProxy uses its configured default.

    • The upper limit is enforced server-side (values above the limit are clamped to it).

  • recency (string, optional, default: noLimit)

    • One of: oneDay | oneWeek | oneMonth | oneYear | noLimit

  • allowed_domains (string[], optional; alias allowedDomains): allowlist of domains to include.

  • blocked_domains (string[], optional; alias blockedDomains): blocklist of domains to exclude.

  • search_prompt (string, optional; alias searchPrompt): extra context to refine the search intent. Capped at 2000 characters server-side (the excess is trimmed).

Outputs (structuredContent):

  • query / queries: the executed query (or batch).

  • results[]: entries with url, title, snippet, and published_at when available.

  • count: number of returned results.

  • perQuery[]: for batched searches, { query, urls } attribution groups so each result can be traced back to the query that found it.

  • failedQueries[]: queries that failed while at least one other succeeded (their section is absent from perQuery).

  • fetchedContents[] / fetchedCount: when server-side auto-fetch is enabled, the verified content of the top result pages (url, title, content, truncated). Read these before concluding information is missing.

  • verified[]: registry verification rows for npm / PyPI / crates.io / NuGet / GitHub URLs found in the results (kind, name, latest_stable, latest_prerelease, status, error).

Example arguments object:

{
  "queries": ["qdrant docker compose autostart", "qdrant container restart policy"],
  "max_results": 10,
  "recency": "oneMonth"
}

web_fetch

Fetch and read content from a URL via EnriProxy.

Inputs:

  • url (string, required unless cursor is provided): full URL (http:// or https://).

  • cursor (string, optional): opaque cursor returned by a previous web_fetch call. A valid cursor always wins over a coexisting url. Send url together with cursor whenever you know it: if the cursor expired server-side (TTL ~10 minutes), EnriWeb transparently re-fetches the url with the same parameters and returns fresh content with a new cursor (recovered_from_expired_cursor) instead of an error.

  • action ("delete", optional): releases the server-side capture owned by cursor. Send with cursor; other parameters are ignored. Responds { deleted, cursor }.

  • ranges (array, 1-10 items, optional): grouped { offset_chars, limit_chars } windows read in one call. With cursor: each range is read server-side in parallel and the response is a grouped object (range_applied, range_count, ranges[], range_hint). With url: the document is fetched first; if it arrives truncated with a cursor the ranges read that capture in parallel, otherwise they are sliced locally from the returned content.

  • offset_chars (integer, optional, default: 0; aliases offsetChars and legacy offset): read offset in characters. With cursor: server-side window over the capture. With url (first read): local slice over the returned content, like EnriCode.

  • limit_chars (integer, optional, default: max_chars; aliases limitChars and legacy limit): read limit in characters. A value of 0 is ignored.

  • prompt (string, optional): extraction hint (what to focus on).

  • max_chars (integer, optional, default: ENRIWEB_WEB_FETCH_DEFAULT_MAX_CHARS; alias maxChars): maximum content length.

  • format (string, optional): content flavor for HTML pages — "text" (default, lightweight structured text), "markdown" (full markdown with links, emphasis, code fences, images, and tables), or "html" (sanitized markup for DOM inspection — scripts/styles stripped, tags intact). Use markdown only when the exact page structure matters; text is cheaper for factual lookups.

  • content (string, optional): HTML scope — "main" (default, article/main container only; drops nav, sidebars, cookie banners, and footers, typically saving 60-80% of tokens) or "full" (whole page).

  • include_links (boolean, optional, default: true; alias includeLinks): append the ENLACES DE LA PÁGINA inventory with every unique link (label + URL, up to 200) — useful for informed crawling or handing image URLs to URL-capable media analysis tools. Send false to omit it.

  • include_metadata (boolean, optional, default false; alias includeMetadata): append the METADATOS DE LA PÁGINA block with language, author, published date, and og:image.

  • anchor (string, optional): section selector — element id (with or without #) or exact heading text; returns only that section up to the next same-or-higher heading. When the section is missing, the response says so and returns the full document.

  • screenshot ("auto" | "force" | "none" | "analyze", optional, per-call): page capture request. "auto" captures when the page looks visual, "force" always captures, "none" disables capture for this call, and "analyze" returns a TEXT description per screenshot segment (screenshot_analyses, server-side vision) instead of image blocks — MANDATORY for models without vision input (any other mode delivers image blocks such a model cannot process, so the visual material is lost). Explicit per-call values always win over the installation-level ENRIWEB_SCREENSHOT_MODE default.

Outputs (structuredContent):

  • Single read: content, status, content_type, truncated, url, and pagination fields when present (cursor, offset_chars, limit_chars, total_chars, has_more, next_offset_chars, reduced, fetched_truncated, applied_max_chars on the npm path).

  • Delete: deleted (whether the cursor existed and was released) plus the addressed cursor.

  • Grouped ranges: range_applied: true, range_count, ranges[] (per-range index, offsets, content, truncated, Spanish error/note rows), range_hint, and the backing cursor/total_chars when a capture exists.

Notes:

  • If the response is truncated and includes a cursor, page through the captured content by calling web_fetch again with cursor + offset_chars + limit_chars (or a ranges batch for non-contiguous windows) — no re-download needed. Always echo the url on cursor calls: an expired cursor then recovers automatically (recovered_from_expired_cursor: true, fresh offsets, new cursor) instead of failing with HTTP 400.

  • Exhausted captures are reclaimed automatically: when a read reports has_more: false, EnriWeb releases the server-side cursor best-effort, omits it from the result, and tells you the capture was fully read (a 10-minute TTL backstops anything left behind).

  • npm package pages (npmjs.com/package/<name>, including /v/<version> pins and scoped packages) get a structured projection: registry metadata for the requested version plus the repository README, with pagination fields propagated when the README sub-fetch is truncated.

  • EnriProxy-side URL controls travel glued to the URL and are documented in the tool description: ?enri_find=TEXT (find text with offsets), ?enri_parts= (select page sections), ?enri_body_offset=N&enri_body_limit=M (body window), ?enri_section= for YouTube (manifest/transcript/comments/description), and Drive/OneDrive folder listings.

Example arguments object:

{
  "url": "https://example.com/docs",
  "max_chars": 200000,
  "ranges": [
    { "offset_chars": 0, "limit_chars": 5000 },
    { "offset_chars": 120000, "limit_chars": 5000 }
  ]
}

Available Tools

2 tools
web_fetchLectura de URL EnriProxyA
Read-onlyIdempotent

Obtiene y lee el contenido de una URL mediante el servicio multi-nivel de EnriProxy.

Cuándo usarla:

  • Cuando necesite leer el contenido completo de una página web.

  • Cuando necesite acceder a documentación, artículos o archivos de código.

  • Cuando métodos de fetch más simples fallen por protección anti-bot.

Características:

  • Detección de APIs de registros de paquetes (npm, PyPI)

  • Fetch de archivos raw (GitHub raw, HuggingFace)

  • Fetch robusto para sitios estáticos, dinámicos y protegidos (best-effort)

  • Respaldo automático entre múltiples estrategias de recuperación (detalles omitidos intencionalmente)

  • Proyección controlable: format ('text' ligero por defecto, 'markdown' estructura completa, 'html' DOM saneado), content ('main' por defecto elimina navegación/banners y conserva el artículo; use 'full' para todo), anchor (lee sólo una sección por id o título de encabezado), include_links (inventario de enlaces de la página, ACTIVO por defecto; envíe false para omitirlo) e include_metadata (idioma/autor/fecha/imagen destacada)

  • Render de páginas con JavaScript: cuando la página devuelve un cascarón sin contenido renderizado, el servidor reintenta automáticamente con tiers que sí renderizan antes de responder

  • Sitios con JavaScript pesado (Steam, Reddit, X, Instagram, tiendas) se renderizan con navegador real: entregan texto, reseñas, comentarios, imágenes y archivos descargables completos, organizados en secciones (DATOS, MEDIOS, ENLACES, ARCHIVOS PARA DESCARGAR, COMENTARIOS)

  • Controles enri_* (sufijos que se agregan a la URL): ?enri_find=TEXTO busca dentro de toda la captura y devuelve las líneas con offsets (ÚSELO PRIMERO en páginas grandes); ?enri_parts= elige partes: sections,post,ld,imagenes,variantes,media,links,drive,nota,archivos,body (ej: ?enri_parts=links solo enlaces, omita body para respuestas pequeñas); ?enri_body_offset=N&enri_body_limit=M ventana del cuerpo en caracteres

  • YouTube: ?enri_section= manifest (por defecto: inventario con instrucciones) | transcripcion | comentarios | descripcion | todo, con enri_transcript_offset/enri_transcript_limit (caracteres) y enri_comments_offset/enri_comments_limit (cantidad). Cada corte trae su URL de continuación ya construida

  • Carpetas de Google Drive/OneDrive: inventario de archivos con URL de descarga directa por elemento

  • PDFs: cualquier URL de PDF (incluso bitstreams de repositorios tras muros anti-bot) se devuelve como TEXTO EXTRAÍDO (hasta 40 páginas por pasada, con avisos de truncado); los PDFs ESCANEADOS sin capa de texto se transcriben renderizando sus páginas con visión del lado del servidor en la misma respuesta; cuando la transcripción no es posible, la respuesta lo declara y sugiere pasar la misma URL a la herramienta de análisis de media para el análisis completo (multipass: páginas, tablas, diagramas)

  • Documentos de Office: URLs o descargas de Word (.docx), Excel (.xlsx) y PowerPoint (.pptx) — incluso tras Content-Disposition o tipos genéricos application/octet-stream — se extraen a TEXTO PLANO en la misma respuesta (párrafos, textos compartidos y valores de celdas, diapositivas en orden); los archivos de texto plano (txt, csv) adjuntos se decodifican directo; un zip sin documento de Office reconocible se declara honestamente

  • Decodificación de páginas con encoding legado (windows-1252/ISO-8859-1) sin mojibake

Notas:

  • Proporcione la URL completa incluyendo protocolo (https://).

  • El contenido se limita con el parámetro max_chars (por defecto: 200000).

  • Si el resultado viene truncado e incluye un cursor, vuelva a llamar web_fetch con cursor + offset_chars + limit_chars para leer más sin volver a descargar.

  • Envíe url junto con cursor siempre que la conozca: si el cursor expiró en el servidor (TTL ~10 minutos), la herramienta re-obtiene la url con los mismos parámetros y devuelve contenido fresco con cursor nuevo (campo recovered_from_expired_cursor) en vez de un error; sin url el cursor expirado sigue devolviendo error.

  • Los controles enri_* van pegados a la URL: web_fetch(url="https://ejemplo.com/pagina?enri_find=precio") — no son parámetros aparte de la herramienta.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNoURL completa a obtener (http:// o https://).
limitNoAlias legado de limit_chars. Límite de lectura en caracteres (por defecto: max_chars; con `url` recorta localmente el contenido devuelto). Un valor 0 se ignora.
actionNoAcción especial sobre un cursor: 'delete' libera en el servidor la captura asociada al cursor (envíelo junto con `cursor`; los demás parámetros se ignoran). La respuesta es {deleted, cursor}: true si existía y se liberó, false si ya no existía. Se recomienda liberar cursores que ya no usará (si no, expiran solos tras ~10 minutos).
anchorNoSelector de sección: id de un elemento (con o sin '#', ej. 'installation') o texto exacto de un encabezado (ej. 'Instalación'). Devuelve sólo esa sección hasta el siguiente encabezado del mismo nivel o superior. Mucho más barato que paginar con offset_chars a ciegas en documentos largos. Máximo 300 caracteres; el exceso se recorta. Si la sección no existe, la respuesta lo indica y devuelve el documento completo.
cursorNoCursor opaco devuelto por una llamada previa de `web_fetch` para paginación. Nunca invente este valor. Envíe también `url` cuando la conozca para activar la recuperación automática si el cursor expiró.
formatNoFormato del contenido para páginas HTML. 'text' (por defecto) devuelve texto estructurado ligero y gasta menos tokens. 'markdown' reproduce la estructura exacta de la página: enlaces con URL, énfasis, bloques de código, listas anidadas, imágenes y tablas. 'html' devuelve el marcado HTML saneado (sin scripts/estilos) para inspeccionar el DOM: formularios, atributos data-*, estructura de componentes. Para preguntas puntuales (versiones, precios, datos sueltos) deje el formato por defecto. Los valores inválidos se degradan a 'text'.
offsetNoAlias legado de offset_chars. Offset de lectura en caracteres (por defecto: 0; con `url` aplica un rango local sobre el contenido devuelto).
promptNoPista opcional de extracción. Cuando el documento excede max_chars y el servidor reduce la respuesta (reduced=true), la pista guía la selección de extractos del paquete devuelto; en documentos que caben en el presupuesto no cambia el contenido devuelto. Nunca se envía al sitio de destino.
rangesNoHasta 10 rangos {offset_chars, limit_chars} leídos en una sola llamada, para leer tramos no contiguos de un documento grande. Con `cursor`: cada rango se lee del servidor en paralelo y la respuesta es un objeto agrupado {range_applied, range_count, ranges[], range_hint}. Con `url`: primero se descarga el documento; si viene truncado con cursor, cada rango se lee por cursor en paralelo; si no, los rangos se recortan localmente del contenido devuelto. Ejemplo: [{"offset_chars": 0, "limit_chars": 5000}, {"offset_chars": 120000, "limit_chars": 5000}].
contentNoAlcance del contenido HTML. 'main' (por defecto) devuelve sólo el contenido principal (contenedor article/main, sin menús, barras laterales, banners de cookies ni pies): ahorra típicamente 60-80% de tokens en artículos, documentación y blogs. Use 'full' cuando necesite la estructura completa de la página. Combine content='main' con format='markdown' para la lectura óptima de artículos largos. Los valores inválidos se degradan a 'main'.
max_charsNoLongitud máxima del contenido (por defecto: 200000).
screenshotNoCaptura de pantalla renderizada de la página (juegos en canvas, dashboards, mapas, splash pages donde el texto no describe lo que se ve). ES OBLIGATORIO elegirla según TU modelo: (1) Si tu modelo NO puede ver imágenes (sin visión): es OBLIGATORIO usar 'analyze' — el servidor captura la página (hasta 3 segmentos de scroll) y te devuelve un TEXTO que describe lo que se ve ('Análisis visual: ...'), sin imágenes; cualquier otro modo te entrega bloques de imagen que tu modelo NO puede procesar y el material visual se pierde. (2) Si tu modelo SÍ puede ver imágenes: omite el parámetro o usa 'auto' (captura sólo cuando el texto es escaso, <1,500 caracteres) o 'force' (captura siempre); las imágenes llegan como bloques de imagen MCP (~1,400 tokens de visión por segmento). (3) Si no necesitas nada visual y quieres ahorrar tokens: 'none'. Solo aplica a la lectura única completa por url (no cursor/ranges). Cuando no se captura, la respuesta lo indica con screenshot_status='skipped' y su razón.
limit_charsNoLímite de lectura en caracteres. Con `cursor`: límite del servidor (por defecto: max_chars). Con `url` (primera lectura): recorta localmente el contenido devuelto. Un valor 0 se ignora. Prefiera este nombre actual de campo de EnriProxy sobre limit.
offset_charsNoOffset de lectura en caracteres (por defecto: 0). Con `cursor`: ventana del servidor sobre la captura. Con `url` (primera lectura): rango local sobre el contenido devuelto; la primera lectura amplía automáticamente su presupuesto hasta alcanzar la ventana solicitada, así que los offsets más allá de max_chars SÍ devuelven contenido. Prefiera este nombre actual de campo de EnriProxy sobre offset.
include_linksNoPor defecto es true: agrega al final un inventario ENLACES DE LA PÁGINA con los enlaces únicos (etiqueta y URL, hasta 200). Úselo para decidir a dónde navegar después (crawling informado), descargar documentos enlazados o pasar URLs de imágenes a una herramienta de análisis de media que acepte URLs http(s) directas. Envíe false para omitir el inventario y ahorrar tokens. También se acepta el alias camelCase `includeLinks`.
include_metadataNoPor defecto es false. Si es true, agrega al final un bloque METADATOS DE LA PÁGINA con idioma, autor, fecha de publicación e imagen destacada (og:image). Útil para citar fuentes o decidir frescura del contenido antes de gastar tokens en el fetch completo. También se acepta el alias camelCase `includeMetadata`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
urlNoURL que se obtuvo.
cursorNoCursor de paginación, cuando existe.
rangesNoCortes por rango en orden de petición.
statusNoCódigo HTTP de la lectura.
contentNoContenido obtenido (lectura única).
deletedNoResultado de action 'delete': si el cursor existía y se liberó.
reducedNoSi el contenido se redujo a un paquete de extractos.
has_moreNoSi existe más contenido tras este corte.
truncatedNoSi el contenido quedó truncado.
page_charsNoLongitud de la página sin decoraciones dentro de `content` (ventanas de rangos direccionan esta base).
range_hintNoGuía de continuación para lecturas por rangos.
limit_charsNoLímite de lectura por cursor.
range_countNoNúmero de rangos devueltos.
total_charsNoTotal de caracteres capturados.
content_typeNoTipo de contenido de la respuesta.
offset_charsNoOffset de lectura por cursor.
range_appliedNoMarca de resultado por rangos agrupados.
recovery_noteNoNota en español describiendo la recuperación automática de cursor expirado.
applied_max_charsNoPresupuesto aplicado en el camino npm.
fetched_truncatedNoSi el fetch aguas arriba se truncó.
next_offset_charsNoOffset exacto donde empieza la página siguiente (lecturas por cursor), cuando el servidor lo reporta.
page_offset_charsNoOffset base-cero dentro de `content` donde empieza la página sin decoraciones (lecturas url con encabezado de estado).
screenshot_reasonNoRazón de captura o omisión: auto_thin_text, forced, analyze_requested, image_target (la URL apuntaba a una imagen y va adjunta inline), auto_rich_text, background_verification, http_error_status, capture_failed, lane_unsupported.
screenshot_statusNo'captured' cuando el proxy adjuntó capturas como bloques de imagen; 'analyzed' cuando las convirtió en texto del lado del servidor (modo 'analyze'); 'skipped' cuando no (solo cuando se pidió screenshot).
screenshot_analysesNoDescripciones en TEXTO de cada segmento de captura, generadas del lado del servidor con el modo screenshot='analyze' (para modelos que no pueden ver imágenes). Un elemento null significa que ese segmento falló el análisis.
screenshot_segmentsNoNúmero de segmentos de captura entregados como bloques de imagen MCP (el payload base64 no viaja en structuredContent).
recovered_from_expired_cursorNoTrue cuando el cursor enviado había expirado y la herramienta re-obtuvo la url con los mismos parámetros; los offsets aplican a la captura nueva.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive/openWorld, yet the description adds rich behavior: cursor TTL ~10 minutes, automatic recovery via re-fetch when url is supplied, max_chars default, truncation+cursor continuation, silent degradation of invalid enum values, and honest declaration when extraction fails (scanned PDFs, unrecognized zips). This goes well beyond the safety profile annotations provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and usage, but the 'Características' section is an unusually long bullet list that repeats format/content/anchor guidance already covered in the schema and buries key operational notes (cursor TTL, enri_* controls) below extensive feature enumeration.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite an output schema existing, the description supplies everything needed for correct invocation on a 16-parameter, enum-heavy tool: pagination recovery, the URL-suffix control scheme, per-model screenshot behavior, and fallback/truncation semantics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

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 contributes meaning the schema does not: the enri_* URL-suffix controls (find, parts, section, offsets) are a whole invocation vocabulary absent from the parameter list, plus practical guidance on combining format/content and on cursor+url recovery. It mostly duplicates field descriptions rather than exceeding them, keeping it at 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('obtiene y lee el contenido de una URL') plus the mechanism (multi-tier EnriProxy service). An agent can immediately distinguish this fetch tool from the web_search sibling 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.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'Cuándo usarla' block gives three concrete triggering conditions (full page content, docs/articles/code, fallback when simpler fetches hit anti-bot protection). However, it never names web_search as the alternative or states when NOT to fetch, so routing between siblings is left partly implicit.

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.

  1. 1 tool updatev0.1.22
    • Changedweb_search1 field changed
      • changedInput schema / properties / query / anyOf
        Previous value: -[
        -  {
        -    "description": "Consulta de búsqueda. Sea específico para obtener mejores resultados. También acepta un arreglo de 1 a 4 consultas (equivalente a `queries`). Use `queries` en su lugar cuando convenga lanzar varias formulaciones a la vez.",
        -    "type": "string"
        -  },
        -  {
        -    "description": "Lote de 1 a 4 consultas no vacías (equivalente a `queries`).",
        -    "items": {
        -      "type": "string"
        -    },
        -    "maxItems": 4,
        -    "minItems": 1,
        -    "type": "array"
        -  }
        -]New value: +[
        +  {
        +    "description": "Consulta de búsqueda. Operadores que el proxy hace cumplir: site:dominio, -site:dominio, filetype:pdf o ext:pdf (cualquier extensión: docx, xlsx, csv…), \"frase exacta\", -palabra. En lenguaje natural, escribir \"en pdf\"/\"formato pdf\"/\"pdfs\" también filtra PDFs. Ejemplo: ley imss site:gob.mx filetype:pdf. También acepta un arreglo de 1 a 4 consultas (equivalente a `queries`).",
        +    "type": "string"
        +  },
        +  {
        +    "description": "Lote de 1 a 4 consultas no vacías (equivalente a `queries`).",
        +    "items": {
        +      "type": "string"
        +    },
        +    "maxItems": 4,
        +    "minItems": 1,
        +    "type": "array"
        +  }
        +]
  2. 1 tool updatev0.1.18
    • Changedweb_fetch1 field changed
      • changedInput schema / properties / screenshot / description
        Previous value: -"Captura de pantalla renderizada de la página (juegos en canvas, dashboards, mapas, splash pages donde el texto no describe lo que se ve). ELIGE según TU modelo: (1) Si NO puedes ver imágenes: usa 'analyze' — el servidor captura la página (hasta 3 segmentos de scroll) y te devuelve un TEXTO que describe lo que se ve ('Análisis visual: ...'), sin imágenes que rompan tu request. (2) Si SÍ puedes ver imágenes: omite el parámetro o usa 'auto' (captura sólo cuando el texto es escaso, <1,500 caracteres) o 'force' (captura siempre); las imágenes llegan como bloques de imagen MCP (~1,400 tokens de visión por segmento). (3) Si no necesitas nada visual y quieres ahorrar tokens: 'none'. Solo aplica a la lectura única completa por url (no cursor/ranges). Cuando no se captura, la respuesta lo indica con screenshot_status='skipped' y su razón."New value: +"Captura de pantalla renderizada de la página (juegos en canvas, dashboards, mapas, splash pages donde el texto no describe lo que se ve). ES OBLIGATORIO elegirla según TU modelo: (1) Si tu modelo NO puede ver imágenes (sin visión): es OBLIGATORIO usar 'analyze' — el servidor captura la página (hasta 3 segmentos de scroll) y te devuelve un TEXTO que describe lo que se ve ('Análisis visual: ...'), sin imágenes; cualquier otro modo te entrega bloques de imagen que tu modelo NO puede procesar y el material visual se pierde. (2) Si tu modelo SÍ puede ver imágenes: omite el parámetro o usa 'auto' (captura sólo cuando el texto es escaso, <1,500 caracteres) o 'force' (captura siempre); las imágenes llegan como bloques de imagen MCP (~1,400 tokens de visión por segmento). (3) Si no necesitas nada visual y quieres ahorrar tokens: 'none'. Solo aplica a la lectura única completa por url (no cursor/ranges). Cuando no se captura, la respuesta lo indica con screenshot_status='skipped' y su razón."
  3. 2 tool updatesv0.1.11
    • Changedweb_fetch10 fields changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Cursor opaco devuelto por una llamada previa de `web_fetch` para paginación. Nunca invente este valor."New value: +"Cursor opaco devuelto por una llamada previa de `web_fetch` para paginación. Nunca invente este valor. Envíe también `url` cuando la conozca para activar la recuperación automática si el cursor expiró."
      • addedInput schema / properties / screenshot
        Added value: +{
        +  "description": "Captura de pantalla renderizada de la página (juegos en canvas, dashboards, mapas, splash pages donde el texto no describe lo que se ve). ELIGE según TU modelo: (1) Si NO puedes ver imágenes: usa 'analyze' — el servidor captura la página (hasta 3 segmentos de scroll) y te devuelve un TEXTO que describe lo que se ve ('Análisis visual: ...'), sin imágenes que rompan tu request. (2) Si SÍ puedes ver imágenes: omite el parámetro o usa 'auto' (captura sólo cuando el texto es escaso, <1,500 caracteres) o 'force' (captura siempre); las imágenes llegan como bloques de imagen MCP (~1,400 tokens de visión por segmento). (3) Si no necesitas nada visual y quieres ahorrar tokens: 'none'. Solo aplica a la lectura única completa por url (no cursor/ranges). Cuando no se captura, la respuesta lo indica con screenshot_status='skipped' y su razón.",
        +  "enum": [
        +    "auto",
        +    "force",
        +    "none",
        +    "analyze"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / page_chars
        Added value: +{
        +  "description": "Longitud de la página sin decoraciones dentro de `content` (ventanas de rangos direccionan esta base).",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / page_offset_chars
        Added value: +{
        +  "description": "Offset base-cero dentro de `content` donde empieza la página sin decoraciones (lecturas url con encabezado de estado).",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / recovered_from_expired_cursor
        Added value: +{
        +  "description": "True cuando el cursor enviado había expirado y la herramienta re-obtuvo la url con los mismos parámetros; los offsets aplican a la captura nueva.",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / recovery_note
        Added value: +{
        +  "description": "Nota en español describiendo la recuperación automática de cursor expirado.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / screenshot_analyses
        Added value: +{
        +  "description": "Descripciones en TEXTO de cada segmento de captura, generadas del lado del servidor con el modo screenshot='analyze' (para modelos que no pueden ver imágenes). Un elemento null significa que ese segmento falló el análisis.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / screenshot_reason
        Added value: +{
        +  "description": "Razón de captura o omisión: auto_thin_text, forced, analyze_requested, image_target (la URL apuntaba a una imagen y va adjunta inline), auto_rich_text, background_verification, http_error_status, capture_failed, lane_unsupported.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / screenshot_segments
        Added value: +{
        +  "description": "Número de segmentos de captura entregados como bloques de imagen MCP (el payload base64 no viaja en structuredContent).",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / screenshot_status
        Added value: +{
        +  "description": "'captured' cuando el proxy adjuntó capturas como bloques de imagen; 'analyzed' cuando las convirtió en texto del lado del servidor (modo 'analyze'); 'skipped' cuando no (solo cuando se pidió screenshot).",
        +  "type": "string"
        +}
    • Changedweb_search3 fields changed
      • addedOutput schema / properties / fetchNote
        Added value: +{
        +  "description": "Nota en español del servidor explicando el resultado del auto-fetch (p.ej. por qué fetchedContents está vacío o parcial).",
        +  "type": "string"
        +}
      • addedOutput schema / properties / searchPromptNotice
        Added value: +{
        +  "description": "Aviso en español describiendo el recorte del search_prompt, cuando se recortó.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / searchPromptTruncated
        Added value: +{
        +  "description": "Si EnriProxy recortó el search_prompt al tope del servidor.",
        +  "type": "boolean"
        +}
  4. 2 tool updatesv0.1.8
    • Changedweb_fetch15 fields changed
      • addedInput schema / examples
        Added value: +[
        +  {
        +    "url": "https://example.com/docs"
        +  },
        +  {
        +    "limit_chars": 4000,
        +    "offset_chars": 0,
        +    "url": "https://example.com/docs"
        +  },
        +  {
        +    "cursor": "123e4567-e89b-12d3-a456-426614174000",
        +    "limit_chars": 20000,
        +    "offset_chars": 20000
        +  }
        +]
      • addedInput schema / properties / action
        Added value: +{
        +  "description": "Acción especial sobre un cursor: 'delete' libera en el servidor la captura asociada al cursor (envíelo junto con `cursor`; los demás parámetros se ignoran). La respuesta es {deleted, cursor}: true si existía y se liberó, false si ya no existía. Se recomienda liberar cursores que ya no usará (si no, expiran solos tras ~10 minutos).",
        +  "enum": [
        +    "delete"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / anchor / description
        Previous value: -"Selector de sección: id de un elemento (con o sin '#', ej. 'installation') o texto exacto de un encabezado (ej. 'Instalación'). Devuelve sólo esa sección hasta el siguiente encabezado del mismo nivel o superior. Mucho más barato que paginar con offset_chars a ciegas en documentos largos. Si la sección no existe, la respuesta lo indica y devuelve el documento completo."New value: +"Selector de sección: id de un elemento (con o sin '#', ej. 'installation') o texto exacto de un encabezado (ej. 'Instalación'). Devuelve sólo esa sección hasta el siguiente encabezado del mismo nivel o superior. Mucho más barato que paginar con offset_chars a ciegas en documentos largos. Máximo 300 caracteres; el exceso se recorta. Si la sección no existe, la respuesta lo indica y devuelve el documento completo."
      • addedInput schema / properties / anchor / maxLength
        Added value: +300
      • changedInput schema / properties / content / description
        Previous value: -"Alcance del contenido HTML. 'full' (por defecto) devuelve toda la página, incluida navegación, encabezados y pie. Use 'main' para quedarse sólo con el contenido principal (contenedor article/main, sin menús, barras laterales, banners de cookies ni pies): ahorra típicamente 60-80% de tokens en artículos, documentación y blogs. Combine content='main' con format='markdown' para la lectura óptima de artículos largos."New value: +"Alcance del contenido HTML. 'main' (por defecto) devuelve sólo el contenido principal (contenedor article/main, sin menús, barras laterales, banners de cookies ni pies): ahorra típicamente 60-80% de tokens en artículos, documentación y blogs. Use 'full' cuando necesite la estructura completa de la página. Combine content='main' con format='markdown' para la lectura óptima de artículos largos. Los valores inválidos se degradan a 'main'."
      • changedInput schema / properties / format / description
        Previous value: -"Formato del contenido para páginas HTML. 'text' (por defecto) devuelve texto estructurado ligero y gasta menos tokens. 'markdown' reproduce la estructura exacta de la página: enlaces con URL, énfasis, bloques de código, listas anidadas, imágenes y tablas. 'html' devuelve el marcado HTML saneado (sin scripts/estilos) para inspeccionar el DOM: formularios, atributos data-*, estructura de componentes. Para preguntas puntuales (versiones, precios, datos sueltos) deje el formato por defecto."New value: +"Formato del contenido para páginas HTML. 'text' (por defecto) devuelve texto estructurado ligero y gasta menos tokens. 'markdown' reproduce la estructura exacta de la página: enlaces con URL, énfasis, bloques de código, listas anidadas, imágenes y tablas. 'html' devuelve el marcado HTML saneado (sin scripts/estilos) para inspeccionar el DOM: formularios, atributos data-*, estructura de componentes. Para preguntas puntuales (versiones, precios, datos sueltos) deje el formato por defecto. Los valores inválidos se degradan a 'text'."
      • changedInput schema / properties / include_links / description
        Previous value: -"Si es true, agrega al final un inventario ENLACES DE LA PÁGINA con todos los enlaces únicos (etiqueta y URL, hasta 200). Úselo para decidir a dónde navegar después (crawling informado), descargar documentos enlazados o pasar URLs de imágenes a una herramienta de análisis de media que acepte URLs http(s) directas."New value: +"Por defecto es true: agrega al final un inventario ENLACES DE LA PÁGINA con los enlaces únicos (etiqueta y URL, hasta 200). Úselo para decidir a dónde navegar después (crawling informado), descargar documentos enlazados o pasar URLs de imágenes a una herramienta de análisis de media que acepte URLs http(s) directas. Envíe false para omitir el inventario y ahorrar tokens. También se acepta el alias camelCase `includeLinks`."
      • changedInput schema / properties / include_metadata / description
        Previous value: -"Si es true, agrega al final un bloque METADATOS DE LA PÁGINA con idioma, autor, fecha de publicación e imagen destacada (og:image). Útil para citar fuentes o decidir frescura del contenido antes de gastar tokens en el fetch completo."New value: +"Por defecto es false. Si es true, agrega al final un bloque METADATOS DE LA PÁGINA con idioma, autor, fecha de publicación e imagen destacada (og:image). Útil para citar fuentes o decidir frescura del contenido antes de gastar tokens en el fetch completo. También se acepta el alias camelCase `includeMetadata`."
      • changedInput schema / properties / limit / description
        Previous value: -"Alias legado de limit_chars. Límite de lectura por cursor en caracteres (por defecto: max_chars)."New value: +"Alias legado de limit_chars. Límite de lectura en caracteres (por defecto: max_chars; con `url` recorta localmente el contenido devuelto). Un valor 0 se ignora."
      • changedInput schema / properties / limit_chars / description
        Previous value: -"Límite de lectura por cursor en caracteres (por defecto: max_chars). Prefiera este nombre actual de campo de EnriProxy sobre limit."New value: +"Límite de lectura en caracteres. Con `cursor`: límite del servidor (por defecto: max_chars). Con `url` (primera lectura): recorta localmente el contenido devuelto. Un valor 0 se ignora. Prefiera este nombre actual de campo de EnriProxy sobre limit."
      • changedInput schema / properties / offset / description
        Previous value: -"Alias legado de offset_chars. Offset de lectura por cursor en caracteres (por defecto: 0)."New value: +"Alias legado de offset_chars. Offset de lectura en caracteres (por defecto: 0; con `url` aplica un rango local sobre el contenido devuelto)."
      • changedInput schema / properties / offset_chars / description
        Previous value: -"Offset de lectura por cursor en caracteres (por defecto: 0). Prefiera este nombre actual de campo de EnriProxy sobre offset."New value: +"Offset de lectura en caracteres (por defecto: 0). Con `cursor`: ventana del servidor sobre la captura. Con `url` (primera lectura): rango local sobre el contenido devuelto; la primera lectura amplía automáticamente su presupuesto hasta alcanzar la ventana solicitada, así que los offsets más allá de max_chars SÍ devuelven contenido. Prefiera este nombre actual de campo de EnriProxy sobre offset."
      • changedInput schema / properties / prompt / description
        Previous value: -"Pista opcional que describe qué desea extraer (la herramienta devuelve el contenido obtenido; no genera un resumen con IA)."New value: +"Pista opcional de extracción. Cuando el documento excede max_chars y el servidor reduce la respuesta (reduced=true), la pista guía la selección de extractos del paquete devuelto; en documentos que caben en el presupuesto no cambia el contenido devuelto. Nunca se envía al sitio de destino."
      • addedInput schema / properties / ranges
        Added value: +{
        +  "description": "Hasta 10 rangos {offset_chars, limit_chars} leídos en una sola llamada, para leer tramos no contiguos de un documento grande. Con `cursor`: cada rango se lee del servidor en paralelo y la respuesta es un objeto agrupado {range_applied, range_count, ranges[], range_hint}. Con `url`: primero se descarga el documento; si viene truncado con cursor, cada rango se lee por cursor en paralelo; si no, los rangos se recortan localmente del contenido devuelto. Ejemplo: [{\"offset_chars\": 0, \"limit_chars\": 5000}, {\"offset_chars\": 120000, \"limit_chars\": 5000}].",
        +  "items": {
        +    "properties": {
        +      "limit_chars": {
        +        "description": "Longitud del rango en caracteres; omitido usa max_chars.",
        +        "type": "integer"
        +      },
        +      "offset_chars": {
        +        "description": "Offset inicial del rango en caracteres (>=0).",
        +        "type": "integer"
        +      }
        +    },
        +    "type": "object"
        +  },
        +  "maxItems": 10,
        +  "minItems": 1,
        +  "type": "array"
        +}
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "description": "Contenido obtenido con metadatos de paginación; variantes: lectura única, borrado de cursor o rangos agrupados.",
        +  "properties": {
        +    "applied_max_chars": {
        +      "description": "Presupuesto aplicado en el camino npm.",
        +      "type": "integer"
        +    },
        +    "content": {
        +      "description": "Contenido obtenido (lectura única).",
        +      "type": "string"
        +    },
        +    "content_type": {
        +      "description": "Tipo de contenido de la respuesta.",
        +      "type": "string"
        +    },
        +    "cursor": {
        +      "description": "Cursor de paginación, cuando existe.",
        +      "type": "string"
        +    },
        +    "deleted": {
        +      "description": "Resultado de action 'delete': si el cursor existía y se liberó.",
        +      "type": "boolean"
        +    },
        +    "fetched_truncated": {
        +      "description": "Si el fetch aguas arriba se truncó.",
        +      "type": "boolean"
        +    },
        +    "has_more": {
        +      "description": "Si existe más contenido tras este corte.",
        +      "type": "boolean"
        +    },
        +    "limit_chars": {
        +      "description": "Límite de lectura por cursor.",
        +      "type": "integer"
        +    },
        +    "next_offset_chars": {
        +      "description": "Offset exacto donde empieza la página siguiente (lecturas por cursor), cuando el servidor lo reporta.",
        +      "type": "integer"
        +    },
        +    "offset_chars": {
        +      "description": "Offset de lectura por cursor.",
        +      "type": "integer"
        +    },
        +    "range_applied": {
        +      "description": "Marca de resultado por rangos agrupados.",
        +      "type": "boolean"
        +    },
        +    "range_count": {
        +      "description": "Número de rangos devueltos.",
        +      "type": "integer"
        +    },
        +    "range_hint": {
        +      "description": "Guía de continuación para lecturas por rangos.",
        +      "type": "string"
        +    },
        +    "ranges": {
        +      "description": "Cortes por rango en orden de petición.",
        +      "items": {
        +        "properties": {
        +          "content": {
        +            "description": "Contenido del corte.",
        +            "type": "string"
        +          },
        +          "content_type": {
        +            "description": "Tipo de contenido.",
        +            "type": "string"
        +          },
        +          "cursor": {
        +            "description": "Cursor de continuación.",
        +            "type": "string"
        +          },
        +          "error": {
        +            "description": "Error en español cuando la lectura de este rango falló.",
        +            "type": "string"
        +          },
        +          "has_more": {
        +            "description": "Si hay más contenido tras el corte.",
        +            "type": "boolean"
        +          },
        +          "index": {
        +            "description": "Índice del rango (base 1).",
        +            "type": "integer"
        +          },
        +          "limit_chars": {
        +            "description": "Límite solicitado.",
        +            "type": "integer"
        +          },
        +          "note": {
        +            "description": "Nota en español cuando el offset quedó fuera del contenido devuelto.",
        +            "type": "string"
        +          },
        +          "offset_chars": {
        +            "description": "Offset solicitado.",
        +            "type": "integer"
        +          },
        +          "status": {
        +            "description": "Código HTTP de la lectura.",
        +            "type": "integer"
        +          },
        +          "total_chars": {
        +            "description": "Total capturado para el cursor.",
        +            "type": "integer"
        +          },
        +          "truncated": {
        +            "description": "Si el corte quedó truncado.",
        +            "type": "boolean"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "reduced": {
        +      "description": "Si el contenido se redujo a un paquete de extractos.",
        +      "type": "boolean"
        +    },
        +    "status": {
        +      "description": "Código HTTP de la lectura.",
        +      "type": "integer"
        +    },
        +    "total_chars": {
        +      "description": "Total de caracteres capturados.",
        +      "type": "integer"
        +    },
        +    "truncated": {
        +      "description": "Si el contenido quedó truncado.",
        +      "type": "boolean"
        +    },
        +    "url": {
        +      "description": "URL que se obtuvo.",
        +      "type": "string"
        +    }
        +  },
        +  "type": "object"
        +}
    • Changedweb_search13 fields changed
      • addedInput schema / anyOf
        Added value: +[
        +  {
        +    "required": [
        +      "query"
        +    ]
        +  },
        +  {
        +    "required": [
        +      "queries"
        +    ]
        +  }
        +]
      • addedInput schema / examples
        Added value: +[
        +  {
        +    "query": "bun runtime documentation"
        +  },
        +  {
        +    "max_results": 8,
        +    "query": [
        +      "rust async tokio spawn",
        +      "tokio::spawn vs block_on"
        +    ],
        +    "recency": "oneMonth"
        +  }
        +]
      • changedInput schema / properties / allowed_domains / description
        Previous value: -"Devuelve sólo resultados de estos dominios."New value: +"Devuelve sólo resultados de estos dominios. También se acepta el alias camelCase `allowedDomains`."
      • changedInput schema / properties / blocked_domains / description
        Previous value: -"Excluye resultados de estos dominios."New value: +"Excluye resultados de estos dominios. También se acepta el alias camelCase `blockedDomains`."
      • changedInput schema / properties / max_results / description
        Previous value: -"Máximo de resultados (>= 1). Si se omite, EnriProxy usa su valor configurado por defecto. El límite superior se aplica en el servidor."New value: +"Máximo de resultados deseados (1 hasta el límite del servidor; los valores mayores se recortan al límite). Omitido usa el default configurado. También se acepta el alias camelCase `maxResults`."
      • changedInput schema / properties / queries / description
        Previous value: -"Lote de 1 a 4 consultas no vacías; se ejecutan en paralelo y sus resultados se combinan y deduplican por URL. Ejemplo: [\"rust async tokio spawn\", \"tokio::spawn vs block_on\"]. No combine con `query`."New value: +"Lote de 1 a 4 consultas no vacías; se ejecutan en paralelo y sus resultados se combinan y deduplican por URL. Ejemplo: [\"rust async tokio spawn\", \"tokio::spawn vs block_on\"]. Si también envía `query`, se ignora y se usan `queries`."
      • addedInput schema / properties / query / anyOf
        Added value: +[
        +  {
        +    "description": "Consulta de búsqueda. Sea específico para obtener mejores resultados. También acepta un arreglo de 1 a 4 consultas (equivalente a `queries`). Use `queries` en su lugar cuando convenga lanzar varias formulaciones a la vez.",
        +    "type": "string"
        +  },
        +  {
        +    "description": "Lote de 1 a 4 consultas no vacías (equivalente a `queries`).",
        +    "items": {
        +      "type": "string"
        +    },
        +    "maxItems": 4,
        +    "minItems": 1,
        +    "type": "array"
        +  }
        +]
      • removedInput schema / properties / query / description
        Removed value: -"Consulta de búsqueda. Sea específico para obtener mejores resultados. Use `queries` en su lugar cuando convenga lanzar varias formulaciones a la vez."
      • removedInput schema / properties / query / type
        Removed value: -"string"
      • changedInput schema / properties / search_prompt / description
        Previous value: -"Contexto opcional para refinar la intención de búsqueda."New value: +"Contexto opcional para refinar la intención de búsqueda. Máximo 2000 caracteres; el exceso se recorta en EnriProxy. También se acepta el alias camelCase `searchPrompt`."
      • addedInput schema / properties / search_prompt / maxLength
        Added value: +2000
      • removedInput schema / required
        Removed value: -[
        -  "query"
        -]
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "description": "Resultados de búsqueda con contenidos verificados y verificación de registros opcionales.",
        +  "properties": {
        +    "count": {
        +      "description": "Número de resultados.",
        +      "type": "integer"
        +    },
        +    "failedQueries": {
        +      "description": "Consultas que fallaron mientras otras tuvieron éxito.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "fetchedContents": {
        +      "description": "Contenidos de páginas verificados.",
        +      "items": {
        +        "properties": {
        +          "content": {
        +            "description": "Contenido extraído.",
        +            "type": "string"
        +          },
        +          "title": {
        +            "description": "Título al momento del fetch.",
        +            "type": "string"
        +          },
        +          "truncated": {
        +            "description": "Si el contenido fue recortado al presupuesto.",
        +            "type": "boolean"
        +          },
        +          "url": {
        +            "description": "URL de la página.",
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "fetchedCount": {
        +      "description": "Número de contenidos verificados adjuntos.",
        +      "type": "integer"
        +    },
        +    "perQuery": {
        +      "description": "Grupos de URLs por consulta, con búsquedas por lote.",
        +      "items": {
        +        "properties": {
        +          "query": {
        +            "description": "Consulta ejecutada.",
        +            "type": "string"
        +          },
        +          "urls": {
        +            "description": "URLs atribuidas.",
        +            "items": {
        +              "type": "string"
        +            },
        +            "type": "array"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "queries": {
        +      "description": "Consultas ejecutadas.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "query": {
        +      "description": "Consulta que se ejecutó.",
        +      "type": "string"
        +    },
        +    "results": {
        +      "description": "Lista de resultados.",
        +      "items": {
        +        "properties": {
        +          "published_at": {
        +            "description": "Fecha de publicación, cuando existe.",
        +            "type": "string"
        +          },
        +          "snippet": {
        +            "description": "Extracto del resultado.",
        +            "type": "string"
        +          },
        +          "title": {
        +            "description": "Título del resultado.",
        +            "type": "string"
        +          },
        +          "url": {
        +            "description": "URL del resultado.",
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "unresponsiveEngines": {
        +      "description": "Motores SearXNG que no respondieron, cuando el servidor reportó alguno.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "verified": {
        +      "description": "Entidades de registro verificadas.",
        +      "items": {
        +        "properties": {
        +          "error": {
        +            "description": "Mensaje cuando status es error.",
        +            "type": "string"
        +          },
        +          "kind": {
        +            "description": "Ecosistema (npm, pypi, crates, nuget, github).",
        +            "type": "string"
        +          },
        +          "latest_prerelease": {
        +            "description": "Última versión prerelease.",
        +            "properties": {
        +              "published_at": {
        +                "type": "string"
        +              },
        +              "source_url": {
        +                "type": "string"
        +              },
        +              "version": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "latest_stable": {
        +            "description": "Última versión estable.",
        +            "properties": {
        +              "published_at": {
        +                "type": "string"
        +              },
        +              "source_url": {
        +                "type": "string"
        +              },
        +              "version": {
        +                "type": "string"
        +              }
        +            },
        +            "type": "object"
        +          },
        +          "name": {
        +            "description": "Nombre del paquete o repo.",
        +            "type": "string"
        +          },
        +          "status": {
        +            "description": "ok o error.",
        +            "type": "string"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "type": "object"
        +}
  5. 1 tool updatev0.1.5
    • Changedweb_search2 fields changed
      • addedInput schema / properties / queries
        Added value: +{
        +  "description": "Lote de 1 a 4 consultas no vacías; se ejecutan en paralelo y sus resultados se combinan y deduplican por URL. Ejemplo: [\"rust async tokio spawn\", \"tokio::spawn vs block_on\"]. No combine con `query`.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "maxItems": 4,
        +  "minItems": 1,
        +  "type": "array"
        +}
      • changedInput schema / properties / query / description
        Previous value: -"Consulta de búsqueda. Sea específico para obtener mejores resultados."New value: +"Consulta de búsqueda. Sea específico para obtener mejores resultados. Use `queries` en su lugar cuando convenga lanzar varias formulaciones a la vez."
  6. 1 tool updatev0.1.4
    • Changedweb_fetch6 fields changed
      • addedInput schema / properties / anchor
        Added value: +{
        +  "description": "Selector de sección: id de un elemento (con o sin '#', ej. 'installation') o texto exacto de un encabezado (ej. 'Instalación'). Devuelve sólo esa sección hasta el siguiente encabezado del mismo nivel o superior. Mucho más barato que paginar con offset_chars a ciegas en documentos largos. Si la sección no existe, la respuesta lo indica y devuelve el documento completo.",
        +  "type": "string"
        +}
      • addedInput schema / properties / content
        Added value: +{
        +  "description": "Alcance del contenido HTML. 'full' (por defecto) devuelve toda la página, incluida navegación, encabezados y pie. Use 'main' para quedarse sólo con el contenido principal (contenedor article/main, sin menús, barras laterales, banners de cookies ni pies): ahorra típicamente 60-80% de tokens en artículos, documentación y blogs. Combine content='main' con format='markdown' para la lectura óptima de artículos largos.",
        +  "enum": [
        +    "main",
        +    "full"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / format / description
        Previous value: -"Formato del contenido para páginas HTML. 'text' (por defecto) devuelve texto estructurado ligero y gasta menos tokens. Use 'markdown' cuando necesite reproducir la estructura exacta de la página: enlaces con URL, énfasis, bloques de código, listas anidadas o imágenes. Para preguntas puntuales (versiones, precios, datos sueltos) deje el formato por defecto."New value: +"Formato del contenido para páginas HTML. 'text' (por defecto) devuelve texto estructurado ligero y gasta menos tokens. 'markdown' reproduce la estructura exacta de la página: enlaces con URL, énfasis, bloques de código, listas anidadas, imágenes y tablas. 'html' devuelve el marcado HTML saneado (sin scripts/estilos) para inspeccionar el DOM: formularios, atributos data-*, estructura de componentes. Para preguntas puntuales (versiones, precios, datos sueltos) deje el formato por defecto."
      • changedInput schema / properties / format / enum
        Previous value: -[
        -  "text",
        -  "markdown"
        -]New value: +[
        +  "text",
        +  "markdown",
        +  "html"
        +]
      • addedInput schema / properties / include_links
        Added value: +{
        +  "description": "Si es true, agrega al final un inventario ENLACES DE LA PÁGINA con todos los enlaces únicos (etiqueta y URL, hasta 200). Úselo para decidir a dónde navegar después (crawling informado), descargar documentos enlazados o pasar URLs de imágenes a una herramienta de análisis de media que acepte URLs http(s) directas.",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / include_metadata
        Added value: +{
        +  "description": "Si es true, agrega al final un bloque METADATOS DE LA PÁGINA con idioma, autor, fecha de publicación e imagen destacada (og:image). Útil para citar fuentes o decidir frescura del contenido antes de gastar tokens en el fetch completo.",
        +  "type": "boolean"
        +}
  7. 2 tool updatesv0.1.2
    • Changedweb_fetch9 fields changed
      • changedInput schema / properties / cursor / description
        Previous value: -"Opaque cursor returned by a previous `web_fetch` call for pagination."New value: +"Cursor opaco devuelto por una llamada previa de `web_fetch` para paginación. Nunca invente este valor."
      • addedInput schema / properties / format
        Added value: +{
        +  "description": "Formato del contenido para páginas HTML. 'text' (por defecto) devuelve texto estructurado ligero y gasta menos tokens. Use 'markdown' cuando necesite reproducir la estructura exacta de la página: enlaces con URL, énfasis, bloques de código, listas anidadas o imágenes. Para preguntas puntuales (versiones, precios, datos sueltos) deje el formato por defecto.",
        +  "enum": [
        +    "text",
        +    "markdown"
        +  ],
        +  "type": "string"
        +}
      • changedInput schema / properties / limit / description
        Previous value: -"Legacy alias for limit_chars. Cursor read limit in characters (default: max_chars)."New value: +"Alias legado de limit_chars. Límite de lectura por cursor en caracteres (por defecto: max_chars)."
      • changedInput schema / properties / limit_chars / description
        Previous value: -"Cursor read limit in characters (default: max_chars). Prefer this current EnriProxy field name over limit."New value: +"Límite de lectura por cursor en caracteres (por defecto: max_chars). Prefiera este nombre actual de campo de EnriProxy sobre limit."
      • changedInput schema / properties / max_chars / description
        Previous value: -"Maximum content length (default: 200000)."New value: +"Longitud máxima del contenido (por defecto: 200000)."
      • changedInput schema / properties / offset / description
        Previous value: -"Legacy alias for offset_chars. Cursor read offset in characters (default: 0)."New value: +"Alias legado de offset_chars. Offset de lectura por cursor en caracteres (por defecto: 0)."
      • changedInput schema / properties / offset_chars / description
        Previous value: -"Cursor read offset in characters (default: 0). Prefer this current EnriProxy field name over offset."New value: +"Offset de lectura por cursor en caracteres (por defecto: 0). Prefiera este nombre actual de campo de EnriProxy sobre offset."
      • changedInput schema / properties / prompt / description
        Previous value: -"Optional hint describing what you want to extract (the tool returns fetched content; it does not generate an AI summary)."New value: +"Pista opcional que describe qué desea extraer (la herramienta devuelve el contenido obtenido; no genera un resumen con IA)."
      • changedInput schema / properties / url / description
        Previous value: -"Full URL to fetch (http:// or https://)."New value: +"URL completa a obtener (http:// o https://)."
    • Changedweb_search6 fields changed
      • changedInput schema / properties / allowed_domains / description
        Previous value: -"Only return results from these domains."New value: +"Devuelve sólo resultados de estos dominios."
      • changedInput schema / properties / blocked_domains / description
        Previous value: -"Exclude results from these domains."New value: +"Excluye resultados de estos dominios."
      • changedInput schema / properties / max_results / description
        Previous value: -"Maximum results (>= 1). If omitted, EnriProxy uses its configured default. The upper limit is enforced server-side."New value: +"Máximo de resultados (>= 1). Si se omite, EnriProxy usa su valor configurado por defecto. El límite superior se aplica en el servidor."
      • changedInput schema / properties / query / description
        Previous value: -"Search query. Be specific for better results."New value: +"Consulta de búsqueda. Sea específico para obtener mejores resultados."
      • changedInput schema / properties / recency / description
        Previous value: -"Filter by recency (default: noLimit)."New value: +"Filtra por recencia (por defecto: noLimit)."
      • changedInput schema / properties / search_prompt / description
        Previous value: -"Optional context to refine search intent."New value: +"Contexto opcional para refinar la intención de búsqueda."
  8. 1 tool updatev0.1.1
    • Changedweb_fetch4 fields changed
      • changedInput schema / properties / limit / description
        Previous value: -"Cursor read limit in characters (default: max_chars)."New value: +"Legacy alias for limit_chars. Cursor read limit in characters (default: max_chars)."
      • addedInput schema / properties / limit_chars
        Added value: +{
        +  "description": "Cursor read limit in characters (default: max_chars). Prefer this current EnriProxy field name over limit.",
        +  "type": "integer"
        +}
      • changedInput schema / properties / offset / description
        Previous value: -"Cursor read offset in characters (default: 0)."New value: +"Legacy alias for offset_chars. Cursor read offset in characters (default: 0)."
      • addedInput schema / properties / offset_chars
        Added value: +{
        +  "description": "Cursor read offset in characters (default: 0). Prefer this current EnriProxy field name over offset.",
        +  "type": "integer"
        +}
  9. 2 tool updatesv0.1.0
    • First observedweb_fetch
    • First observedweb_search

TDQS

A4.4/5.0

Scored across 2 tools

Disambiguation5/5

web_search discovers URLs/excerpts while web_fetch retrieves and parses a specific URL's content. The two purposes are clearly disjoint, and an agent can easily tell when to use each.

Naming Consistency5/5

Both tools follow the identical `web_` + verb pattern (web_search, web_fetch), which is predictable and easy to remember.

Tool Count4/5

Two tools fully cover the search-then-fetch primitive for this server, but the count falls below the typical well-scoped range of 3-15. The loaded feature set makes web_fetch do a lot of work, though each tool still clearly earns its place.

Completeness3/5

The core discover-and-retrieve lifecycle is covered, and web_fetch handles many formats. However, web_fetch explicitly references a separate 'herramienta de análisis de media' that is not present in this two-tool set, creating a dead end for OCR/media analysis scenarios.

Maintenance

ActivityNo data
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    An MCP server that provides AI assistants with web search and intelligence capabilities via the ihyee API. It allows users to search the web, fetch extracted content from URLs, and perform full browser rendering for JavaScript-heavy websites.
    3
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    An MCP server that fetches web pages and extracts clean, AI-usable context from them, enabling tools for link discovery, content search, and integrated fetch-and-search operations.
    5
    9 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server for multi-engine web search and web page fetching, supporting parallel search, content extraction, and optional LLM-powered search summarization and deep search.
    5
    MIT