semantic-search-mcp
semantic-search-mcp
Un motor de recuperación RAG-lite pequeño y autónomo: indexa archivos en disco y responde "qué es semánticamente relevante para esta consulta" — nada más. No llama a un LLM y no genera respuestas. Devuelve los fragmentos de texto más relevantes (archivo, línea, puntuación) para que quien lo consuma — un humano, un script o un LLM a través de MCP — decida qué hacer con ellos.
Todo se ejecuta localmente y sin conexión después de la primera ejecución:
Embeddings:
@huggingface/transformersejecutandoXenova/all-MiniLM-L6-v2con pesos cuantizados int8 en CPU. Sin GPU, sin clave API, sin llamadas de red en el momento de la consulta.Almacén de vectores:
@lancedb/lancedb— una base de datos vectorial incrustada y respaldada por archivos. Sin proceso de servidor, sin Docker.Interfaces: una CLI y un servidor MCP stdio, para que cualquier agente compatible con MCP (Claude Code, Cursor, Zed, …) pueda buscar directamente en tu corpus.
Inicio rápido
npm install -g @adborroto/semantic-search-mcp
semantic-search add ~/code/my-project # add a folder to the corpus
semantic-search index # embed it (incremental on later runs)
semantic-search search "how does the retry logic work"Esa es toda la configuración. No hay ningún archivo de configuración que escribir a mano — add lo crea y lo gestiona
por ti. Para probarlo sin instalar nada:
npx @adborroto/semantic-search-mcp add ~/code/my-projectAviso sobre el tamaño de instalación: ~950MB de dependencias, más un modelo de embeddings de ~25MB descargado en el primer uso. Casi todo son binarios nativos que no se pueden evitar en esta capa —
@lancedb/lancedb(~430MB incluyendo su binario de plataforma) y el runtime ONNX (~300MB, que incluye compilaciones para cada plataforma en un solo paquete). Ambos se almacenan en caché una vez; todo después de la primera ejecución es sin conexión.
Requisitos
Node.js >= 22 (
node:sqlite, usado por el backend alternativo, solo es estable desde 22).~950MB de disco para dependencias y ~25MB para el modelo de embeddings, más aproximadamente 1–3 KB por fragmento indexado.
Sin GPU, sin servicios externos, sin servidor de base de datos.
Por qué "RAG-lite"
Un pipeline RAG completo es: recuperar fragmentos → alimentarlos a un LLM → el LLM escribe una respuesta. Este proyecto se detiene en el primer paso. Esto lo mantiene simple, rápido, económico de ejecutar y fácil de razonar — y se compone limpiamente con cualquier LLM o framework de agentes que ya estés usando, en lugar de incluir su propia capa de generación dogmática.
Gestión del corpus
semantic-search add ~/code/api ~/notes # add one or more folders
semantic-search list # show what's configured
semantic-search remove api # by folder name...
semantic-search remove ~/notes # ...or by path
semantic-search config # where config + index actually liveadd valida que cada ruta sea un directorio real, la resuelve a una ruta absoluta y omite
duplicados (incluyendo el mismo directorio alcanzado a través de un enlace simbólico). remove también elimina
los fragmentos de esa carpeta del índice, por lo que su contenido deja de aparecer en los resultados — pasa
--keep-index si quieres eliminarlo del corpus pero mantenerlo buscable.
Dónde se almacenan las cosas
La configuración y el índice siguen la especificación del directorio base XDG, por lo que sobreviven a las actualizaciones y son compartidos por cada método de instalación:
Qué | Ubicación |
Configuración |
|
Índice + caché del modelo |
|
Anula cualquiera de ellos con SS_CONFIG_PATH, SS_INDEX_DIR, SS_MODEL_CACHE_DIR, o los estándar
XDG_CONFIG_HOME / XDG_DATA_HOME. SS_STORE_BACKEND=sqlite fuerza el backend alternativo.
El índice contiene el texto literal de todo lo que indexaste. Si apuntas esto a código privado,
~/.local/share/semantic-search/contiene ese contenido en texto plano. Nunca lo confirmes en un repositorio y no lo adjuntes a un informe de error.
Cada opción está documentada en src/config.js — tamaño de fragmentos, patrones de
ignorado, nombre del modelo, top-k, concurrencia. Editar config.json directamente sigue funcionando para
esas opciones; add/remove conservan cualquier clave que no les pertenezca.
Uso
Índice
semantic-search index # all configured folders
semantic-search index ~/code/one-project # just this folder, ignoring config
semantic-search index --force # reprocess everythingLa indexación es incremental: los archivos sin cambios se omiten por la hora de modificación, los archivos cuyo contenido realmente no cambió (solo tocados) omiten la re-embedding, y los archivos eliminados del disco se eliminan del índice. Solo se reprocesa lo que realmente cambió.
Con varias carpetas configuradas, index las recorre en secuencia con un encabezado por carpeta y un
total combinado:
[1/3] my-api /home/me/code/my-api ─────────────────────────────
↺ indexed src/auth/middleware.js (8 chunks)
2 indexed 1,203 skipped 16 chunks 4.1s
[2/3] my-app /home/me/code/my-app ─────────────────────────────
...
──────────────────────────────────────────────────────────────
total 5 indexed 3,891 skipped 0 deleted 41 chunks 12.3sCada llamada a index <ruta> solo elimina las entradas obsoletas de los archivos bajo esa ruta,
por lo que indexar la carpeta B nunca toca las entradas de la carpeta A.
Banderas útiles: --max-files <n> se detiene después de N archivos nuevos (limita la memoria en corpus enormes),
--concurrency <n> establece el paralelismo, --verbose registra cada archivo en stderr.
Búsqueda
semantic-search search "how does the retry logic work" -k 5Imprime una tabla con la ruta del archivo, el número de línea, la puntuación y una vista previa del texto. Internamente: incrusta la
consulta, obtiene un grupo de las coincidencias vectoriales más cercanas, aplica un pequeño impulso léxico para fragmentos que también
contienen los términos literales de la consulta y devuelve los primeros k.
Exclusión de archivos
La indexación omite node_modules/, .git/, los resultados de compilación y los archivos de bloqueo de forma predeterminada, junto con
cualquier archivo de más de 500.000 bytes. Para excluir más, coloca un .indexignore de estilo gitignore en cualquiera de estos lugares:
dentro de una carpeta que indexas — los patrones son relativos a esa carpeta, por lo que un repositorio puede excluir su propia salida generada;
junto a tu configuración (
~/.config/semantic-search/.indexignore) — se aplica en todas partes.
Consulta .indexignore.example para un punto de partida que cubre artefactos de compilación de iOS,
Android, Flutter, Ruby y JVM.
Servidor MCP
semantic-search mcpInicia un servidor MCP stdio que expone seis herramientas.
search(query, k?) — búsqueda semántica, devuelve JSON sin procesar:
[{ filePath, text, score, offset, startLine }, ...]gather(query, k?, contextLines?) — misma búsqueda, devuelta como un solo bloque de markdown formateado
listo para colocar en una ventana de contexto:
### [1/5] my-api · src/auth/session.js · line 42 · score 0.923
```
...chunk text...
```contextLines (por defecto 0) lee N líneas adicionales alrededor de cada fragmento del archivo fuente — útil
cuando un límite de fragmento corta el contexto que necesitas.
list_folders() — cada carpeta configurada con su nombre y ruta absoluta. Una buena primera llamada
para que el agente sepa qué corpus existe.
cat_file(filePath, startLine?, endLine?) — lee un archivo por ruta absoluta, tal como lo devuelven
search/gather. Limitado a las carpetas configuradas (consulta Seguridad).
grep(pattern, folder?, fileGlob?, caseSensitive?, maxResults?) — búsqueda literal o regex
en todo el corpus, para cuando necesitas coincidencias exactas en lugar de similitud. Respeta las mismas
reglas de .indexignore que la indexación.
my-api · src/auth/session.js:42 export function createSession(user) {index(root?, force?, maxFiles?, concurrency?) — desencadena una reindexación incremental, para que un
agente pueda actualizar el corpus sin necesidad de ejecutar un comando externo.
Todas las herramientas de búsqueda comparten el mismo código de clasificación y resolución de archivos que la CLI; ninguna lo reimplementa.
Registro con un cliente MCP
Claude Code:
claude mcp add --scope user semantic-search -- semantic-search mcp
claude mcp list # should show "✔ Connected"Cualquier cliente que acepte una definición de servidor JSON:
{
"mcpServers": {
"semantic-search": {
"command": "semantic-search",
"args": ["mcp"]
}
}
}Prefiere una instalación global sobre npx aquí: un npx simple vuelve a resolver el paquete cada vez que el
servidor se inicia, añadiendo latencia de inicio y recogiendo actualizaciones sin previo aviso. Si usas
npx, fija la versión — npx -y @adborroto/semantic-search-mcp@0.1.0 mcp.
Los nuevos servidores MCP normalmente solo se detectan cuando se inicia una sesión, así que inicia una sesión nueva después de registrarlo.
Seguridad
Esta es una herramienta local para un solo usuario con un modelo de confianza simple: cualquier cosa dentro de una carpeta configurada es legible por cualquier cliente MCP que pueda alcanzar el servidor.
cat_filerechaza rutas fuera de las carpetas configuradas, resolviendo primero los enlaces simbólicos para que un enlace colocado dentro de una carpeta no pueda usarse para escapar de ella.grepaplica tus reglas de.indexignore, por lo que los archivos excluidos deliberadamente de la indexación no se filtran a través de la búsqueda de coincidencias exactas.Los subprocesos se generan con arrays argv (nunca un shell), por lo que los patrones no pueden inyectar comandos.
Dado esto, no lo apuntes a un corpus que no le entregarías a tu proveedor de LLM — los fragmentos se devuelven al cliente que los solicitó. Consulta SECURITY.md.
Cómo funciona
Fragmentación
El texto se divide en párrafos y luego se empaqueta de forma codiciosa en fragmentos de aproximadamente 200 tokens con
~35 tokens de superposición, contados con el verdadero tokenizador del modelo de embeddings en lugar de una
aproximación por conteo de caracteres. Esto no es arbitrario: all-MiniLM-L6-v2 tiene una ventana de 256 tokens
y trunca silenciosamente cualquier cosa más larga, por lo que los fragmentos se dimensionan para caber dentro de ella con margen
para los tokens [CLS]/[SEP]. La superposición también está limitada para que la superposición más el siguiente
párrafo nunca puedan superar ese límite; de lo contrario, la cola de un fragmento se descartaría en el momento de la incrustación
mientras aún se devuelve mediante search.
Un solo párrafo más grande que el límite estricto (un paquete minimizado, una línea de registro gigante) recurre al empaquetado a nivel de palabra con la misma lógica de superposición, y cualquier "palabra" única de más de 500 caracteres se corta primero, por lo que nunca se entrega nada enorme al tokenizador de una sola vez.
Los recuentos de tokens se calculan una vez por párrafo/palabra y se almacenan en caché para su reutilización durante el cálculo de la superposición. Una versión anterior volvía a tokenizar en cada búsqueda de superposición, lo que estaba bien en entradas pequeñas pero causaba una CPU descontrolada y un crecimiento de memoria de varios GB en repositorios grandes. Si extiendes el fragmentador, conserva esa propiedad.
Reindexación incremental
No hay un manifiesto separado — el almacén de vectores es el manifiesto. Cada fragmento almacenado lleva
el mtimeMs de su archivo fuente y un hash de contenido sha256. En cada ejecución:
Si el
mtimedel archivo en disco coincide con lo almacenado, omítelo sin leer el archivo.Si
mtimecambió pero el hash de contenido es idéntico (untouch), omite la re-embedding.De lo contrario, elimina los fragmentos antiguos de ese archivo e inserta los recién incrustados.
Después del recorrido, cualquier ruta indexada que ya no esté en disco (y bajo la raíz que se está indexando) se elimina.
Backends de almacenamiento
El predeterminado es LanceDB: incrustado, respaldado por archivos, búsqueda vectorial real. Un node:sqlite +
coseno por fuerza bruta alternativo (src/store/sqliteFallbackStore.js)
implementa la misma interfaz (src/store/vectorStore.js) para
entornos donde el enlace nativo de LanceDB no se carga — contenedores en caja de arena, arquitecturas
inusuales. Cambia con SS_STORE_BACKEND=sqlite.
El alternativo realiza un escaneo completo de la tabla por búsqueda: bien para decenas de miles de fragmentos, no
para más. La métrica predeterminada de LanceDB es L2, no coseno, por lo que este proyecto establece explícitamente
.distanceType('cosine') en cada consulta, ya que los embeddings se comparan como vectores normalizados.
Estructura del proyecto
src/
config.js Defaults + config file resolution (XDG) — the only source of tunables
configFile.js Read/modify/write the config file (backs add/remove/list)
embeddings.js transformers.js pipeline + tokenizer (lazy singletons)
chunker.js Token-aware paragraph packing with overlap
ignoreRules.js .indexignore layering, shared by the indexer and grep
safePath.js Path confinement for the MCP file-reading tools
version.js Version read from package.json
extractors/ text (.txt .md .js .ts .py .rb .json), pdf (pdf-parse), docx (mammoth)
store/
vectorStore.js Storage interface + backend selector
lancedbStore.js LanceDB implementation (default)
sqliteFallbackStore.js node:sqlite + manual cosine fallback
indexer.js Walk + extract + chunk + embed + incremental upsert/prune
search.js Embed query + vector search + lexical boost — shared by CLI and MCP
mcp-server.js MCP stdio server: the six tools above
index.js CLI entrypoint (commander)
scripts/index-all.sh Batched indexing for very large corpora on constrained hosts (Linux)Desarrollo
git clone https://github.com/adborroto/semantic-search-mcp.git
cd semantic-search-mcp
npm install
npm test # unit + end-to-end (node:test, no framework)
npm run test:unit # skip the slow end-to-end test
npm run lintUn config.json en la raíz del checkout tiene prioridad sobre la ubicación XDG, por lo que puedes desarrollar
con un corpus de prueba sin tocar tu configuración real. Las pruebas siempre escriben en directorios
temporales. Consulta CONTRIBUTING.md.
Fuera del alcance (por diseño)
Generación de respuestas. Esto devuelve fragmentos, no respuestas. Aliméntalos a un LLM tú mismo.
Reordenación con un segundo modelo. El impulso léxico es una aproximación económica y sin dependencias — no un sustituto de un reordenador de codificador cruzado real.
Una interfaz web. Solo CLI y MCP.
Corpus a escala masiva. Construido para un corpus personal o de equipo de documentos y código — decenas de miles de fragmentos, no millones. Ambos backends asumen esa escala.
Licencia
This server cannot be installed
Maintenance
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.
Hosted MCP memory: save sessions/decisions once, search from Claude, Cursor, ChatGPT. EU-hosted FTS.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/adborroto/semantic-search-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server