Skip to main content
Glama
adborroto

semantic-search-mcp

by adborroto

semantic-search-mcp

CI Security npm node License: MIT

小さな、自己完結型のRAG-lite検索エンジンです。ディスク上のファイルをインデックス化し、「このクエリに意味的に関連するものは何か」という問いに答えます。それ以上は何もしません。LLMを呼び出さず、回答を生成しません。最も関連性の高いテキストチャンク(ファイル、行、スコア)を返すので、それを消費するもの(人間、スクリプト、MCPを介したLLM)が自由に判断できます。

初回実行後は、すべてローカルかつオフラインで動作します。

  • 埋め込み: @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

インストールサイズに関する注意: 約950MBの依存関係に加え、初回使用時に約25MBの埋め込みモデルがダウンロードされます。そのほとんどは、このレイヤーでは避けられないネイティブバイナリです — @lancedb/lancedb(プラットフォームバイナリを含めて約430MB)とONNXランタイム(約300MB、すべてのプラットフォーム用のビルドを1つのパッケージに同梱)です。どちらも一度キャッシュされれば、初回実行以降はオフラインで動作します。

Related MCP server: rag-retriever-mcp

必要条件

  • Node.js >= 22(フォールバックバックエンドで使用する node:sqlite は22から安定しています)。

  • 依存関係に約950MB、埋め込みモデルに約25MBのディスク容量。さらに、インデックス化されたチャンクあたり約1~3KB。

  • 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

ファイルパス、行番号、スコア、テキストプレビューの表を出力します。

検索はハイブリッドです。クエリは2つの独立したアームに送られます — 埋め込みに対するベクトル検索と、同じチャンクに対するBM25全文検索 — そして2つのランキングはReciprocal Rank Fusionで融合されます。アームは異なる方法で失敗します: ベクトルアームは意味的な手がかりがない正確な識別子、エラーメッセージ、設定キーを見逃します。語彙アームは言い換えを見逃します。両方を実行することは再現率の修正であり、スコアではなくランクで融合することで、無制限のBM25スコアがコサイン類似度を圧倒するのを防ぎます。

config.json で "hybridSearch": false を設定するとベクトルのみの検索になり、"rrfK" でRRFのランク平滑化定数(デフォルト60、論文より)を調整できます。

インデックス化されるもの

フォルダを指定すると、その中のすべてが再帰的にインデックス化されます。「サポートされる」ファイル拡張子の許可リストはありません — .dart、.kt、.java、.tsx、.sql、.erb など、テキスト形式のものはすべてそのままインデックス化され、.pdf や .docx は事前にパーサーを通します。

以下の4つは除外されます:

  1. gitが無視するもの(フォルダがgitリポジトリの場合)。.gitignore は任意の深さで尊重され、.git/info/exclude、グローバル除外ファイル、否定パターン(!keep.this)も同様です。これは再実装ではなく git ls-files に委譲されるため、gitと完全に一致します。つまり、プロジェクトがすでに無視している生成物やベンダー出力は、別のリストを管理しなくてもインデックスから除外されます。

  2. .indexignore ルール(下記参照)。コミットされているが検索可能にすべきでないコンテンツ — フィクスチャ、スナップショット、チェックインされたシークレットテンプレートなど。

  3. バイナリファイル。拡張子(画像、アーカイブ、フォント、コンパイル済みオブジェクト、モデル重み)および内容(最初の4KBにNULバイトがある場合、grep -I と同じヒューリスティック)で判定。これはトークナイザーに非テキストバイトが入らないようにする安全策であり、何をインデックスする価値があるかという判断ではありません。

  4. 500,000バイトを超えるファイル(maxFileSizeBytes)。これは、生成された1行メガバイトファイルがメモリを枯渇させるのを防ぐ主要なガードです。

シンボリックリンクはスキップされ(たどられない)、フォルダ内に仕掛けられたリンクが外部のコンテンツをインデックスに引き込むことはありません。

gitリポジトリではないフォルダの場合、.gitignore に頼ることはできません。そのため、小さな組み込みリスト(node_modules/、.git/、dist/、build/、coverage/、vendor/ など)が引き続き適用されます。

さらに除外するには、gitignore形式の .indexignore を以下のいずれかの場所に配置します:

  • インデックス化するフォルダ内 — パターンはそのフォルダからの相対パス。

  • 設定ファイルの隣(~/.config/semantic-search/.indexignore) — すべての場所に適用。

iOS、Android、Flutter、Ruby、JVMのビルドアーティファクトをカバーする出発点として、.indexignore.example を参照してください。

MCPサーバー

semantic-search mcp

6つのツールを公開するstdio MCPサーバーを起動します。

search(query, k?) — セマンティック検索。生のJSONを返します:

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

gather(query, k?, contextLines?) — 同じ検索ですが、コンテキストウィンドウにそのまま貼り付け可能な、1つの整形された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?) — リテラルまたは正規表現によるコーパス全体の検索。類似性ではなく完全一致が必要な場合に使用します。インデクサーがインデックス化するのとまったく同じファイルリストでフィルタリングされるため、gitignoredや.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 はインデクサーが構築するのと同じファイルリストでフィルタリングされます — gitの無視ルールと .indexignore — そのため、インデックスから意図的に除外されたファイルが完全一致検索で漏れることはありません。

  • サブプロセスはargv配列で生成されるため(シェルは使用しない)、パターンによるコマンドインジェクションはできません。

以上の点から、LLMプロバイダーに渡したくないコーパスをこのツールに向けないでください — チャンクは要求したクライアントにそのまま返されます。SECURITY.md を参照してください。

仕組み

ファイルの発見

ルールは「フォルダ内のすべてをインデックス化する」であり、唯一の興味深い部分は何をインデックス化しないかです。gitの無視セマンティクス(ネストされた .gitignore ファイル、否定、info/exclude、グローバル除外ファイル)を再実装する代わりに、gitルートは次のように列挙されます:

git ls-files -z --cached --others --exclude-standard

追跡ファイルと、無視されていないが追跡されていないファイルを、実行ディレクトリにスコープして取得します。gitが無視するものは構造上存在しません。git以外のフォルダは、組み込みのパターンリストを使用した単純な再帰的ウォークにフォールバックします。

同じ関数がインデクサーとMCPの grep ツール(src/ignoreRules.js)の両方を支えています。これは意図的です: grep は実際の grep -r をシェルアウトしますが、これはgitignoredなビルド出力内のヒットも喜んで報告するため、結果をインデクサー自身のファイルリストでフィルタリングします。もし両者が別々にルールを導出すると、それらは乖離し、無視リストは境界として機能しなくなります。

ハイブリッド検索

クエリは2つのアームで並行して実行されます:

  • ベクトル — クエリを埋め込み、コサイン距離で最近傍を取得し、そのリストをクエリのリテラル用語を含むチャンクに対して小さな語彙ブーストで並べ替えます。

  • 語彙 — 同じチャンクテキストに対するBM25。LanceDBの全文インデックスを介して実行されます(sqlite フォールバックはJSでBM25を計算します。node:sqlite がFTS5を確実に提供するとは限らないため)。

2つのランキングはRRF(Reciprocal Rank Fusion)で統合されます。各リストは、自身が返すすべてのチャンクに対して 1 / (60 + rank) を寄与し、その寄与が合算されます。スコアではなく順位で統合するのがポイントです。コサイン類似度は[-1, 1]の範囲に収まるのに対し、BM25には上限がないため、生のスコアを加算または平均すると、コーパスのサイズによって一方のアームがもう一方を黙って圧倒してしまう可能性があります。

なぜ2つのアームが必要かというと、ベクトルアームの出力に適用される語彙ブーストは、ベクトルクエリがすでに返した結果を並べ替えることしかできません。エラーコード、シンボル名、意味的近傍を持たない設定キーなど、正確な用語一致のみがシグナルとなるチャンクは、ベクトルプールの外にあるため、到達不可能でした。語彙アームはそれを独立に取得します。これは再ランキングではなく、再現率の修正であり、検索スコアが0.9ではなく0.03のように見える理由でもあります。これらはRRFの総和であり、コサイン類似度ではありません。順序だけが意味を持ちます。

全文インデックスは各インデックス実行の最後に再構築されます。FTSインデックスは構築後に追加された行をカバーしないため、実行によって書き込まれたばかりのチャンクが語彙アームから見えなくなってしまうのを防ぐためです。

チャンク分割

テキストは段落に分割され、その後、約200トークン、約35トークンのオーバーラップを目安に貪欲にチャンクに詰められます。トークン数は文字数近似ではなく、埋め込みモデルの実際のトークナイザーでカウントされます。これは恣意的なものではありません。all-MiniLM-L6-v2には256トークンのウィンドウがあり、それ以上のものは黙って切り捨てられます。そのため、チャンクは[CLS]/[SEP]トークンの余裕も含めて、その範囲内に収まるようにサイズ調整されています。また、オーバーラップは、オーバーラップ部分と次の段落を合わせてもその制限を超えないようにさらに制限されています。そうしないと、searchで返されるチャンクの先頭部分が埋め込み時には削除されてしまうからです。

1つの段落がハードリミット(縮小されたバンドル、巨大なログ行など)より大きい場合は、同じオーバーラップロジックで単語レベルのパッキングにフォールバックします。また、500文字を超える単一の「単語」は最初にスライスされるため、巨大なものがそのままトークナイザーに渡されることはありません。

トークン数は段落/単語ごとに一度だけ計算され、オーバーラップ計算時に再利用するためにキャッシュされます。以前のバージョンではオーバーラップのたびに再トークナイズしていましたが、小さな入力では問題ないものの、大きなリポジトリではCPUが暴走し、メモリが数GBに膨れ上がる原因となっていました。チャンカーを拡張する場合は、その性質を維持してください。

インクリメンタルな再インデックス

別個のマニフェストはありません。ベクトルストア自体がマニフェストです。保存されたすべてのチャンクは、そのソースファイルのmtimeMsとsha256コンテンツハッシュを持ちます。実行ごとに:

  1. ファイルのディスク上のmtimeが保存されているものと一致する場合、ファイルを読み取らずにスキップします。

  2. mtimeが変更されていても、コンテンツハッシュが同一(touchによる変更)の場合は、再埋め込みをスキップします。

  3. それ以外の場合は、そのファイルの古いチャンクを削除し、新しく埋め込んだチャンクを挿入します。

  4. リスト作成後、ディスク上に存在しなくなった(かつインデックス対象のルート以下にある)インデックス済みパスは削除されます。

ストレージバックエンド

デフォルトはLanceDBです。組み込み、ファイルベース、本物のベクトル検索。node:sqlite + 力技のコサイン類似度フォールバック(src/store/sqliteFallbackStore.js)は、LanceDBのネイティブバインディングが読み込めない環境(サンドボックス化されたコンテナ、特殊なアーキテクチャ)向けに、同じインターフェース(src/store/vectorStore.js)を実装しています。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       What is indexable: git ignore rules + .indexignore + binary filter,
                       shared by the indexer and grep so they can't drift apart
  safePath.js          Path confinement for the MCP file-reading tools
  version.js           Version read from package.json
  extractors/          text (anything not binary), 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           List + extract + chunk + embed + incremental upsert/prune
  search.js            Hybrid retrieval: vector + BM25 arms fused with RRF — 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に渡して自分で処理してください。

  • 第2モデルによる再ランキング。 ハイブリッド検索とRRFは依存関係がなく、ほとんどのケースで十分な性能を発揮します。ただし、クロスエンコーダ再ランカーではありません。

  • Web UI。 CLIとMCPのみです。

  • 大規模コーパス。 個人またはチーム規模のドキュメントやコード(数万チャンク程度、数百万ではありません)向けに構築されています。両方のバックエンドがその規模を想定しています。

ライセンス

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Local-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.
    3
    13 npm
    MIT
  • F
    license
    A
    quality
    D
    maintenance
    A local-first document retrieval engine that mounts as an MCP tool for agents to index files, search for relevant passages, and let the agent's own LLM answer.
    4
    -
  • A
    license
    A
    quality
    B
    maintenance
    Indexes local Markdown/text files into a SQLite database with vector embeddings and provides MCP tools for semantic search without cloud dependencies.
    3
    AGPL 3.0