Skip to main content
Glama
mmorrisj
by mmorrisj

corpus-mcp

Un servidor MCP que ofrece a un agente búsqueda por palabras clave sobre un directorio de documentos. Apunta a una carpeta y funciona: sin descarga de modelos, sin clave de API, sin GPU, sin base de datos vectorial ejecutándose a la vez. Una única dependencia: el MCP SDK.

pip install -e .
corpus-mcp --root ./docs serve

La parte interesante no es la recuperación. Es el diseño de las herramientas: qué puede hacer realmente un agente con una herramienta de búsqueda y qué hace que una sea utilizable en lugar de una hoguera de ventana de contexto.


Pruébalo en diez segundos

$ make demo
1. reference/glossary.md  (score 1.973, f700ededcfdd:0)
   # Glossary

   **Extraction** — the process of dissolving soluble compounds out of ground
   coffee. Under-extraction tastes sour and thin; over-extraction tastes bitter …

2. guides/brewing.md  (score 1.774, 71c6f092dbcb:0)
   # Pour-over brewing
   …

Esa consulta fue "por qué mi café sabe agrio". El documento dice tastes, la consulta decía taste, y la entrada del glosario que realmente la responde aparece en primer lugar. Ambas cosas son deliberadas; ver más abajo.

Las herramientas

Herramienta

Propósito

search(query, limit, snippet_chars)

Pasajes clasificados como fragmentos cortos centrados en la coincidencia, cada uno con un chunk_id

fetch(chunk_id, context_chunks)

Texto completo de un pasaje más sus vecinos

list_sources(limit)

Qué está indexado, con tamaños por documento

Los documentos también se exponen como recursos de MCP en corpus://<relative-path>.

Decisiones de diseño que merece la pena discutir

Search y fetch son herramientas separadas. Un único search que devuelve bloques completos es más sencillo de escribir y mucho peor de usar: diez resultados de 1.200 caracteres cada uno consumen la mayor parte de la ventana de contexto antes de que el agente decida cuál quiere. Así que search devuelve fragmentos — suficientes para el triaje — y fetch amplía un resultado elegido bajo demanda. El agente paga por el detalle solo donde ha decidido que el detalle merecía la pena.

Los fragmentos se centran en la coincidencia, no en el principio del bloque. Devolver los primeros N caracteres falla constantemente, porque la frase que coincide suele estar en medio: el agente ve un preámbulo irrelevante y o bien descarta un buen resultado o bien lo recupera todo para averiguarlo. La ventana del fragmento se elige para cubrir tantas apariciones de los términos de la consulta como sea posible.

Todo límite se aplica en el servidor. La salida de las herramientas va a parar directamente a una ventana de contexto, así que una herramienta sin límites es una denegación de servicio para quien la llama. Un llamador que pida 10.000 resultados es exactamente el caso para el que existe el tope, así que los límites se imponen en lugar de confiarse en que se respeten. Cuando la salida se trunca, la respuesta lo dice, para que el agente pueda acotar su consulta en lugar de asumir que lo ha visto todo.

Los resultados vacíos se explican por sí mismos. Una lista vacía sin más es un callejón sin salida. La respuesta informa de cuántos bloques y documentos existen, lo que distingue "tu consulta no ha coincidido" de "no hay nada indexado" — dos situaciones con siguientes pasos distintos.

Los identificadores obsoletos son un resultado esperado, no un error. Los IDs de los bloques cambian cuando se edita un documento, así que un ID de antes en una sesión larga puede volverse inválido. fetch lo dice exactamente e indica al agente que vuelva a buscar.

La superposición se elimina al unir los bloques. Los bloques se superponen para que ningún pasaje quede partido en un límite, pero devolver esa superposición significa que el agente lee las mismas frases dos veces y puede interpretar la repetición como énfasis. Los bloques llevan desplazamientos absolutos, por lo que la superposición se elimina por posición en lugar de por coincidencia de cadenas.

BM25, no embeddings. Para las consultas tipo palabra clave que un agente emite al navegar por un corpus que ya conoce, la recuperación léxica es potente y tiene la propiedad que más importa en un bucle de agente: es rápida y nunca cuesta dinero en silencio. La búsqueda semántica es una adición que vale la pena, no un requisito previo para que el sistema sea útil.

Derivación ligera, no un stemmer de verdad. Los plurales y las terminaciones verbales comunes se pliegan para que tastes coincida con taste. Una implementación completa de Porter son cien líneas y una superficie de mantenimiento, y su cola larga (operationaloper) tiene tantas probabilidades de perjudicar como de ayudar en consultas cortas. La indexación y la consulta comparten un único tokenizador, ya que cualquier divergencia entre ambos cuesta exhaustividad en silencio.

Seguridad

El servidor apunta a un directorio raíz y nunca lee fuera de él. Esto importa más de lo que parece: los argumentos de las herramientas provienen de la salida del modelo, así que un identificador de documento es una entrada no fiable, y ../../.ssh/id_rsa es algo que un agente confundido o adversario acabará pidiendo.

Toda ruta que cruza el límite pasa por una única comprobación de contención que resuelve los enlaces simbólicos antes de comparar: un enlace simbólico dentro de la raíz que apunte fuera de ella anula una comprobación de prefijo hecha sobre la ruta sin resolver. Los argumentos que parecen absolutos se interpretan como relativos a la raíz en lugar de como rutas absolutas reales. Los URI de recursos reciben el mismo tratamiento que los argumentos de las herramientas.

Los archivos que no son UTF-8, los archivos demasiado grandes y los directorios vendor (.git, node_modules, …) se omiten en lugar de indexarse como ruido.

Cómo conectarlo a un cliente

Claude Desktop, o cualquier host de MCP, lanza el servidor como un subproceso:

{
  "mcpServers": {
    "my-docs": {
      "command": "corpus-mcp",
      "args": ["--root", "/absolute/path/to/docs", "serve"]
    }
  }
}

El corpus se vuelve a leer cuando cambia en el disco, de modo que los archivos editados durante una sesión se vuelven buscables sin reiniciar: la reindexación es incremental según la hora de modificación, en lugar de reconstruirse con cada llamada.

Desarrollo

make install   # server plus dev tools
make demo      # one query against the example corpus
make test      # 89 tests, no network required
make smoke     # launch the installed server as a subprocess and exercise it
make lint

Dos capas de pruebas, porque detectan fallos distintos:

  • tests/test_server.py ejecuta un cliente MCP real contra un servidor real en el mismo proceso. Lo que se ejercita es el comportamiento de red — esquemas de herramientas, resultados estructurados, formas de error —, no las funciones de Python subyacentes. Un servidor cuyas funciones son correctas pero cuya superficie de herramientas es incorrecta sigue estando roto, y solo este nivel lo detecta.

  • scripts/stdio_smoke.py lanza el script de consola instalado como subproceso y se comunica con él mediante JSON-RPC a través de stdio, como hace un host. Eso cubre el empaquetado, el punto de entrada y el transporte — incluido el fallo clásico en el que algo escribe en stdout y corrompe el flujo del protocolo.

Limitaciones

  • Solo recuperación léxica. Una consulta que no comparte vocabulario con el documento no lo encontrará. Añadir un backend de embeddings detrás de la misma superficie de herramientas es el siguiente paso obvio.

  • Solo formatos de texto.md, .txt, .rst, .csv, .json, .yaml y similares. Sin extracción de PDF ni DOCX.

  • Todo el índice vive en memoria y se reconstruye por completo cuando el corpus cambia. Adecuado para el caso de miles de documentos para el que está hecho; un corpus de millones quiere un índice real que se actualice por archivo.

  • Solo inglés. La lista de stopwords y el plegado de sufijos asumen el inglés.

  • Sin control de acceso más allá de la raíz. Cualquier archivo bajo la raíz es visible para cualquier cosa a la que el servidor esté conectado.

Licencia

MIT. Creado por Aion Innovations.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.

  • Agentic search over your Dewey document collections from any MCP-compatible client.

  • Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mmorrisj/corpus_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server