local-pdf-rag-mcp
Un servidor MCP completamente local para responder preguntas sobre tus PDFs. Pregunta en lenguaje natural; Claude recupera solo los pasajes relevantes con citas de página. Embeddings en el dispositivo (sentence-transformers) + ChromaDB — sin claves API, nada sale de tu máquina.
Documentación
local-pdf-rag-mcp
Un servidor MCP totalmente local que permite a Claude (o cualquier cliente MCP) responder preguntas sobre tus PDFs. Apúntalo a un PDF, haz preguntas en lenguaje natural, y el modelo obtiene solo los pasajes relevantes — con citas a nivel de página — en lugar de procesar el documento completo.
- Totalmente local por defecto. Los embeddings se ejecutan en el dispositivo (sentence-transformers) y los vectores se almacenan en disco (ChromaDB). Sin claves API, nada sale de tu máquina.
- Económico en tokens. Solo un puñado de fragmentos relevantes se envían al modelo por pregunta, no el documento completo.
- Respuestas citadas. Cada fragmento recuperado lleva su nombre de archivo de origen y número de página.
- Cualquier PDF, muchos PDFs. Indexa un solo archivo o una carpeta completa, organizados en colecciones con nombre.
Cómo funciona
La ingesta (una vez por documento) extrae el texto página por página, lo divide en fragmentos superpuestos de ~250 tokens que respetan los límites de párrafo, genera el embedding de cada fragmento localmente y los almacena en ChromaDB. En el momento de la consulta, el servidor genera el embedding de tu pregunta, obtiene los ~20 fragmentos principales de la búsqueda vectorial y luego los reordena con un cross-encoder local para que los más relevantes aparezcan primero. El modelo lee esos fragmentos y escribe la respuesta — el servidor deliberadamente no genera respuestas por sí mismo, lo que lo mantiene simple y agnóstico al modelo.
Requisitos
- Python 3.10+
- ~170 MB de disco para los dos modelos predeterminados, descargados automáticamente y
almacenados en caché: el modelo de embeddings (~80 MB, descargado en la primera ingesta/búsqueda) y
el cross-encoder de reordenamiento (~80 MB, descargado en la primera búsqueda). El reordenamiento
se puede desactivar con
PDF_RAG_RERANK=0si prefieres omitir la segunda descarga.
Instalación
git clone https://github.com/arjun7965/local-pdf-rag-mcp.git
cd local-pdf-rag-mcp
pip install -e .
Nix
El repositorio incluye un flake uv2nix con un conjunto de dependencias de Python bloqueado:
nix run github:arjun7965/local-pdf-rag-mcp
Para desarrollo local, entra en el entorno editable con nix develop.
Proporciona las dependencias de la aplicación y uv sin modificar el
entorno del proyecto; usa uv lock al cambiar dependencias.
Registrar con Codex
Si lo instalaste con pip install -e ., registra el comando de consola:
codex mcp add pdf-rag -- local-pdf-rag-mcp
O ejecútalo directamente desde GitHub sin clonar, usando
uv's uvx:
codex mcp add pdf-rag -- uvx --from git+https://github.com/arjun7965/local-pdf-rag-mcp.git local-pdf-rag-mcp
Verifica el registro:
codex mcp get pdf-rag
La CLI de Codex y la extensión IDE de Codex comparten la configuración de MCP. Para configurar
el servidor manualmente, añade esto a ~/.codex/config.toml, o a
.codex/config.toml para un proyecto de confianza:
[mcp_servers.pdf-rag]
command = "local-pdf-rag-mcp"
Inicia una nueva sesión de Codex después de registrar el servidor. En la interfaz
de terminal de Codex, usa /mcp para comprobar que está activo. Consulta la
documentación de MCP de Codex.
Registrar con Claude Code
Si lo instalaste (el pip install -e . anterior), apunta Claude Code al
comando de consola:
claude mcp add pdf-rag -- local-pdf-rag-mcp
O ejecútalo directamente desde GitHub sin clonar, usando
uv's uvx — obtiene y almacena en caché el paquete
en el primer lanzamiento:
claude mcp add pdf-rag -- uvx --from git+https://github.com/arjun7965/local-pdf-rag-mcp.git local-pdf-rag-mcp
O añádelo manualmente a tu configuración de MCP de Claude:
{
"mcpServers": {
"pdf-rag": {
"command": "local-pdf-rag-mcp"
}
}
}
Reinicia Claude Code para que detecte el nuevo servidor.
Uso
El servidor expone cuatro herramientas. En la práctica, solo hablas con Claude y él las llama por ti:
Tú: Ingiere la especificación en ~/docs/pcie-5.0.pdf en una colección llamada "pcie".
Claude llama a
ingest_pdf→ "Ingerido en la colección 'pcie': pcie-5.0.pdf: 712 páginas, 2{,}480 fragmentos"Tú: ¿Cómo funciona la ecualización de enlaces durante el entrenamiento?
Claude llama a
searchcon tu pregunta, lee los pasajes devueltos y responde — citando, por ejemplo,pcie-5.0.pdf, p.412.
Herramientas
| Herramienta | Qué hace |
|---|---|
ingest_pdf(path, collection="default") | Divide en fragmentos + genera embeddings de un archivo PDF, o de cada PDF en una carpeta, en una colección. |
list_collections() | Muestra las colecciones indexadas y sus recuentos de fragmentos. |
search(query, collection="default", top_k=8) | Devuelve los fragmentos más relevantes con citas. |
delete_collection(collection) | Elimina una colección y todos sus fragmentos (irreversible). |
Configuración
Variables de entorno:
| Variable | Predeterminado | Propósito |
|---|---|---|
PDF_RAG_EMBED_MODEL | all-MiniLM-L6-v2 | Cualquier nombre de modelo sentence-transformers. |
PDF_RAG_RERANK_MODEL | cross-encoder/ms-marco-MiniLM-L-6-v2 | Cross-encoder utilizado para reordenar los resultados vectoriales. |
PDF_RAG_RERANK | 1 | Establécelo en 0 para omitir el reordenamiento y usar la clasificación vectorial bruta. |
PDF_RAG_TABLES | 0 | Establécelo en 1 para habilitar la extracción consciente de tablas (ver más abajo). |
PDF_RAG_DB_PATH | ~/.local_pdf_rag_mcp/chroma | Dónde vive el almacén vectorial en disco. |
Extracción consciente de tablas (opt-in)
Por defecto, el texto se extrae linealmente — las tablas se aplanan en prosa,
lo que dispersa las celdas de una fila y perjudica la recuperación en documentos técnicos densos.
Establece PDF_RAG_TABLES=1 para detectar tablas con líneas y serializarlas con un registro
por fila (Field: Foo; Bits: 0-3; Description: ...), de modo que una consulta sobre una
sola fila coincida directamente con el registro de esa fila. La detección es conservadora
(depende de líneas de regla, por lo que la prosa alineada por espacios no se malinterpreta como
tabla), y cualquier página sin tabla detectada vuelve a la ruta de prosa normal.
Vuelve a ingerir después de habilitarlo, ya que el cambio solo afecta a futuras
ingestas.
Solo se detectan tablas con líneas. Debido a que la detección requiere líneas de cuadrícula visibles,
las tablas sin bordes — columnas alineadas por espacios sin líneas de regla —
no se reconocen y vuelven a la ruta de prosa, donde sus
celdas se aplanan en texto lineal. Esta es una compensación deliberada:
la detección basada en alineación capturaría tablas sin bordes pero también malinterpretaría
diseños de prosa ordinarios como tablas, destrozándolos en celdas basura. Si tus
documentos dependen de tablas sin bordes, PDF_RAG_TABLES no ayudará con ellas.
Modelo de embeddings
El predeterminado es all-MiniLM-L6-v2 y el resto del proyecto está ajustado
en torno a él:
- Por qué este modelo. Pequeño (~80 MB), rápido en CPU, sin necesidad de GPU, calidad decente de recuperación en inglés general, licencia Apache-2.0. Predeterminado estándar en el ecosistema sentence-transformers.
- Límite de entrada: 256 tokens. sentence-transformers trunca silenciosamente
las entradas por encima del
max_seq_lengthdel modelo. Los fragmentos se dimensionan para permanecer dentro de esta ventana, de modo que el embedding refleje todo el fragmento, no solo su inicio. Si cambias a un modelo con un límite diferente (por ejemplo,BAAI/bge-large-en-v1.5en 512), considera aumentartarget_tokensenchunk_pagespara que coincida — de lo contrario, pagas por capacidad que no usas. - Salida: un vector de 384 dimensiones. Cada fragmento se incrusta en un vector fijo de 384 dimensiones independientemente de su longitud (el modelo produce un vector, no texto, por lo que no hay límite de tokens de salida). ChromaDB infiere esta dimensionalidad automáticamente; un modelo diferente con un tamaño diferente simplemente funciona, pero mezclar vectores de diferentes tamaños en una colección no — vuelve a ingerir después de cambiar de modelo.
- Cambio. Cualquier modelo sentence-transformers de HuggingFace funciona:
Los modelos más grandes (BGE-large, E5-large) mejoran la calidad de recuperación a costa de más disco, más RAM y embeddings más lentos. Cambiar de modelo invalida cualquier vector existente — eliminaPDF_RAG_EMBED_MODEL=BAAI/bge-large-en-v1.5 local-pdf-rag-mcp~/.local_pdf_rag_mcp/chromay vuelve a ingerir. - Operación sin conexión / silenciosa. Ambos modelos se almacenan en caché después del primer uso (en
~/.cache/huggingface). Para ejecutar completamente sin conexión después — y silenciar el mensaje deWarning: You are sending unauthenticated requests to the HF Hub— estableceHF_HUB_OFFLINE=1. Desactívalo temporalmente si cambias a un modelo que aún no has descargado, ya que el modo sin conexión bloquea nuevas descargas.
Limitaciones
- Sin OCR. Los PDFs escaneados o solo de imagen no tienen texto extraíble; el servidor detecta esto y devuelve un error claro en lugar de indexar nada.
- PDFs cifrados solo se abren si usan una contraseña vacía.
- Tablas sin bordes. Incluso con
PDF_RAG_TABLES=1, solo se detectan tablas con líneas de cuadrícula/borde visibles. Las tablas alineadas por espacios se aplanan en prosa como cualquier otro texto (ver Configuración → Extracción consciente de tablas). - Límite de entrada de embeddings. Los fragmentos más largos que el
max_seq_lengthdel modelo de embeddings (256 tokens para el MiniLM predeterminado) se truncan silenciosamente por sentence-transformers — el texto completo aún se almacena y se devuelve, pero el vector refleja solo el inicio. Manténtarget_tokensalineado con el modelo que uses. - Ajustado para un flujo de trabajo de una sola máquina y un solo usuario. Para implementaciones multiusuario o de muy gran escala, cambiarías ChromaDB por un almacén vectorial alojado.
Licencia
MIT — ver LICENSE.