Skip to main content
Glama
adborroto

semantic-search-mcp

by adborroto

semantic-search-mcp

CI Security npm node License: MIT

Миниатюрный, самодостаточный движок извлечения в стиле RAG-lite: индексирует файлы на диске и отвечает на вопрос «что семантически релевантно этому запросу» — и ничего больше. Он не вызывает LLM и не генерирует ответы. Он возвращает наиболее релевантные текстовые фрагменты (файл, строка, оценка), чтобы тот, кто их потребляет — человек, скрипт или LLM через MCP — сам решал, что с ними делать.

Всё работает локально и офлайн после первого запуска:

  • Эмбеддинги: @huggingface/transformers с моделью Xenova/all-MiniLM-L6-v2 в int8-квантованных весах на CPU. Ни GPU, ни API-ключа, ни сетевых вызовов во время запроса.

  • Векторное хранилище: @lancedb/lancedb — встраиваемая, файловая векторная база данных. Ни серверного процесса, ни Docker.

  • Интерфейсы: CLI и stdio MCP-сервер, так что любой MCP-совместимый агент (Claude Code, Cursor, Zed, …) может напрямую искать в вашем корпусе.

Быстрый старт

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"

Вот и вся настройка. Никаких конфигурационных файлов, которые нужно писать вручную — add создаёт и управляет им за вас. Чтобы попробовать без установки чего-либо:

npx @adborroto/semantic-search-mcp add ~/code/my-project

Важно о размере установки: ~950 МБ зависимостей, плюс модель эмбеддингов (~25 МБ), загружаемая при первом использовании. Почти всё это — нативные бинарные файлы, которых на этом уровне не избежать — @lancedb/lancedb (~430 МБ, включая платформенный бинарник) и среда выполнения ONNX (~300 МБ, которая поставляется со сборками для всех платформ в одном пакете). Оба кэшируются однократно; всё после первого запуска работает офлайн.

Требования

  • Node.js >= 22 (node:sqlite, используемый запасным бэкендом, стабилен только с версии 22).

  • ~950 МБ на диске для зависимостей и ~25 МБ для модели эмбеддингов, плюс примерно 1–3 КБ на каждый проиндексированный фрагмент.

  • Ни GPU, ни внешних сервисов, ни сервера баз данных.

Почему «RAG-lite»

Полный пайплайн RAG выглядит так: извлечение фрагментов → передача их LLM → LLM пишет ответ. Этот проект останавливается на первом шаге. Это делает его простым, быстрым, дешёвым в работе и лёгким для понимания — и он чисто компонуется с любым LLM или фреймворком агентов, который вы уже используете, вместо того чтобы включать собственный, жёстко заданный слой генерации.

Управление корпусом

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 live

add проверяет, что каждый путь является реальной директорией, преобразует его в абсолютный путь и пропускает дубликаты (включая одну и ту же директорию, достигнутую через символическую ссылку). remove также удаляет фрагменты этой папки из индекса, так что её содержимое перестаёт появляться в результатах — передайте --keep-index, если хотите убрать её из корпуса, но оставить доступной для поиска.

Где хранятся данные

Конфигурация и индекс следуют спецификации базовых директорий XDG, поэтому они переживают обновления и являются общими для всех способов установки:

Что

Расположение

Конфигурация

~/.config/semantic-search/config.json

Индекс + кэш модели

~/.local/share/semantic-search/

Любой из этих путей можно переопределить с помощью SS_CONFIG_PATH, SS_INDEX_DIR, SS_MODEL_CACHE_DIR или стандартных XDG_CONFIG_HOME / XDG_DATA_HOME. SS_STORE_BACKEND=sqlite принудительно включает запасной бэкенд.

Индекс содержит дословный текст всего, что вы проиндексировали. Если вы укажете его на приватный код, ~/.local/share/semantic-search/ будет хранить этот контент в открытом тексте. Никогда не помещайте его в систему контроля версий и не прикрепляйте к отчёту об ошибке.

Каждая опция документирована в src/config.js — размер фрагментов, шаблоны игнорирования, имя модели, top-k, конкурентность. Редактирование config.json напрямую по-прежнему работает для них; add/remove сохраняют любые ключи, которые им не принадлежат.

Использование

Индексация

semantic-search index                      # all configured folders
semantic-search index ~/code/one-project   # just this folder, ignoring config
semantic-search index --force              # reprocess everything

Индексация является инкрементальной: неизменённые файлы пропускаются по времени модификации, файлы, чьё содержимое на самом деле не изменилось (только время доступа), пропускают повторное эмбеддинг, а файлы, удалённые с диска, удаляются из индекса. Обрабатывается только то, что действительно изменилось.

Если настроено несколько папок, index обходит их последовательно с заголовком для каждой папки и общим итогом:

[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.3s

Каждый вызов index <path> удаляет устаревшие записи только для файлов внутри этого пути, поэтому индексация папки B никогда не затрагивает записи папки A.

Полезные флаги: --max-files <n> останавливается после N новых файлов (ограничивает память для огромных корпусов), --concurrency <n> задаёт параллелизм, --verbose выводит каждый файл в stderr.

Поиск

semantic-search search "how does the retry logic work" -k 5

Выводит таблицу с путём к файлу, номером строки, оценкой и текстовым превью. Под капотом: эмбеддинг запроса, извлечение пула ближайших векторных совпадений, небольшое лексическое повышение для фрагментов, которые также содержат буквальные термины запроса, и возврат верхних k результатов.

Исключение файлов

Индексация по умолчанию пропускает node_modules/, .git/, результаты сборки и файлы блокировок, а также любые файлы размером более 500 000 байт. Чтобы исключить больше, поместите файл .indexignore в стиле gitignore в одном из следующих мест:

  • внутри индексируемой папки — шаблоны относительны к этой папке, поэтому репозиторий может исключать свои собственные сгенерированные выходные данные;

  • рядом с вашей конфигурацией (~/.config/semantic-search/.indexignore) — применяется везде.

Смотрите .indexignore.example для отправной точки, охватывающей артефакты сборки iOS, Android, Flutter, Ruby и JVM.

MCP-сервер

semantic-search mcp

Запускает stdio MCP-сервер, предоставляющий шесть инструментов.

search(query, k?) — семантический поиск, возвращает сырой JSON:

[{ filePath, text, score, offset, startLine }, ...]

gather(query, k?, contextLines?) — тот же поиск, возвращаемый в виде одного отформатированного Markdown-блока, готового к вставке в контекстное окно:

### [1/5]  my-api  ·  src/auth/session.js  ·  line 42  ·  score 0.923
```
...chunk text...
```

contextLines (по умолчанию 0) читает N дополнительных строк вокруг каждого фрагмента из исходного файла — полезно, когда граница фрагмента обрезает необходимый контекст.

list_folders() — каждая настроенная папка с её именем и абсолютным путём. Хороший первый вызов, чтобы агент знал, какой корпус существует.

cat_file(filePath, startLine?, endLine?) — чтение файла по абсолютному пути, как возвращается search/gather. Ограничено настроенными папками (см. Безопасность).

grep(pattern, folder?, fileGlob?, caseSensitive?, maxResults?) — буквальный или регекс-поиск по корпусу для случаев, когда нужны точные совпадения, а не сходство. Соблюдает те же правила .indexignore, что и индексация.

my-api  ·  src/auth/session.js:42  export function createSession(user) {

index(root?, force?, maxFiles?, concurrency?) — запуск инкрементальной переиндексации, чтобы агент мог обновить корпус без вызова внешней команды.

Все инструменты поиска используют тот же код ранжирования и разрешения файлов, что и CLI; ни один из них не перереализует его.

Регистрация в MCP-клиенте

Claude Code:

claude mcp add --scope user semantic-search -- semantic-search mcp
claude mcp list   # should show "✔ Connected"

Любой клиент, принимающий JSON-определение сервера:

{
  "mcpServers": {
    "semantic-search": {
      "command": "semantic-search",
      "args": ["mcp"]
    }
  }
}

Здесь предпочтительнее глобальная установка, а не npx: простой npx заново разрешает пакет при каждом запуске сервера, добавляя задержку при запуске и незаметно подхватывая обновления. Если вы используете npx, зафиксируйте версию — npx -y @adborroto/semantic-search-mcp@0.1.0 mcp.

Новые MCP-серверы обычно подхватываются только при запуске сессии, поэтому после регистрации начните новую сессию.

Безопасность

Это локальный, однопользовательский инструмент с простой моделью доверия: всё, что находится внутри настроенной папки, доступно для чтения любому MCP-клиенту, который может подключиться к серверу.

  • cat_file отказывает в доступе к путям за пределами настроенных папок, сначала разрешая символические ссылки, чтобы ссылка, помещённая внутрь папки, не могла быть использована для выхода за её пределы.

  • grep применяет ваши правила .indexignore, поэтому файлы, намеренно исключённые из индексации, не просачиваются через поиск точных совпадений.

  • Дочерние процессы запускаются с массивами argv (никогда не через оболочку), поэтому шаблоны не могут внедрять команды.

Учитывая это, не указывайте его на корпус, который вы не передали бы своему LLM-провайдеру — фрагменты возвращаются тому клиенту, который их запросил. Смотрите SECURITY.md.

Как это работает

Разбиение на фрагменты

Текст разбивается на абзацы, затем жадно упаковывается в фрагменты примерно по 200 токенов с ~35 токенами перекрытия, подсчитанными с помощью реального токенизатора модели эмбеддингов, а не приближения по количеству символов. Это не произвольно: all-MiniLM-L6-v2 имеет окно в 256 токенов и молча обрезает всё, что длиннее, поэтому фрагменты имеют размер, чтобы поместиться в него с запасом для токенов [CLS]/[SEP]. Перекрытие дополнительно ограничено так, чтобы перекрытие плюс следующий абзац никогда не могли превысить этот лимит — иначе хвост фрагмента был бы отброшен во время эмбеддинга, но всё равно возвращался бы search.

Один абзац, превышающий жёсткий лимит (минифицированный бандл, одна гигантская строка лога), возвращается к упаковке на уровне слов с той же логикой перекрытия, и любое отдельное «слово» длиннее 500 символов сначала разрезается, так что токенизатору никогда не передаётся ничего огромного за один раз.

Подсчёт токенов выполняется один раз на абзац/слово и кэшируется для повторного использования при расчёте перекрытия. Более ранняя версия повторно токенизировала при каждом поиске перекрытия, что было нормально для небольших входных данных, но вызывало неконтролируемый рост загрузки CPU и многогигабайтный рост памяти на больших репозиториях. Если вы расширяете модуль разбиения, сохраните это свойство.

Инкрементальная переиндексация

Нет отдельного манифеста — векторное хранилище является манифестом. Каждый сохранённый фрагмент несёт mtimeMs исходного файла и хеш содержимого sha256. При каждом запуске:

  1. Если mtime файла на диске совпадает с сохранённым, пропустить его без чтения файла.

  2. Если mtime изменилось, но хеш содержимого идентичен (команда touch), пропустить повторный эмбеддинг.

  3. В противном случае удалить старые фрагменты этого файла и вставить заново эмбеддированные.

  4. После обхода любые индексированные пути, которых больше нет на диске (и которые находятся в корне индексируемой папки), удаляются.

Бэкенды хранения

По умолчанию используется LanceDB: встраиваемое, файловое, настоящее векторное хранилище. Запасной вариант на node:sqlite + полный перебор с косинусным сходством (src/store/sqliteFallbackStore.js) реализует тот же интерфейс (src/store/vectorStore.js) для сред, где нативная привязка LanceDB не загружается — изолированные контейнеры, необычные архитектуры. Переключение с помощью SS_STORE_BACKEND=sqlite.

Запасной вариант выполняет полное сканирование таблицы при каждом поиске: нормально для десятков тысяч фрагментов, не более. Метрика LanceDB по умолчанию — L2, а не косинусная, поэтому этот проект явно устанавливает .distanceType('cosine') для каждого запроса, так как эмбеддинги сравниваются как нормализованные векторы.

Структура проекта

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)

Разработка

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 lint

config.json в корне рабочей копии имеет приоритет над расположением XDG, поэтому вы можете разрабатывать, используя тестовый корпус, не затрагивая вашу реальную настройку. Тесты всегда пишут во временные директории. Смотрите CONTRIBUTING.md.

Вне области видимости (преднамеренно)

  • Генерация ответов. Этот инструмент возвращает фрагменты, а не ответы. Передайте их в LLM самостоятельно.

  • Переранжирование с помощью второй модели. Лексическое повышение — это дешёвая, не требующая зависимостей аппроксимация, а не замена настоящего кросс-энкодерного переранжировщика.

  • Веб-интерфейс. Только CLI и MCP.

  • Корпуса огромного масштаба. Создан для корпуса документов и кода размером с личный или командный — десятки тысяч фрагментов, а не миллионы. Оба бэкенда рассчитаны на такой масштаб.

Лицензия

MIT

-
license - not tested
-
quality - not tested
B
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.

  • Hosted MCP memory: save sessions/decisions once, search from Claude, Cursor, ChatGPT. EU-hosted FTS.

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/adborroto/semantic-search-mcp'

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