GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

Claude Code / Cursor への接続

MCP クライアントを GrooveSeek に向ける方法 — stdio の .mcp.json、 複数クライアント同時接続のための HTTP トランスポート、その周辺。

English version: clients.md

デプロイ用の完全なレシピは grooveseek/examples/deployments/ を参照。3 パターン (個人 stdio / NAS 共有 = 1 writer + 多 read-only / 社内 HTTP サーバ = 1 サーバ + 多クライアント) で groove.toml / .mcp.json / systemd unit までセットで揃えてある。1 マシン上で複数 Claude Code を並行させる loopback daemon が要る場合は groove service install を使う (v0.8.0 で旧 personal-http レシピを置き換えた)。下のスニペットはそれらのレシピの中核を成す stdio エントリポイント。

プロジェクトルート (またはクライアント対応の MCP 設定場所) の .mcp.json に以下を追加:

{
  "mcpServers": {
    "ai-knowledge": {
      "command": "/path/to/groove",
      "args": ["serve", "--kb-path", "/path/to/knowledge-base"],
      "type": "stdio"
    }
  }
}

groove.toml がプロジェクト内にある場合は、argsserve の前に "--config", "/abs/path/to/groove.toml" を足すこと。groove は config を置き場所でどこまで信頼するか決めるので、見つけただけのファイルは特権的なキーが既定へ戻される — [parsers] もその 1 つで、Markdown 以外の KB が Markdown だけとして提供されることになる。同じ config を読む他の groove 実行にも同様に効き、特に index で効く信頼する置き場所 / しない置き場所 を参照。バイナリの隣にある config や groove service install が置いた config はそのまま信頼される。

多言語モデル + 再ランクを有効化する場合:

{
  "mcpServers": {
    "ai-knowledge": {
      "command": "/path/to/groove",
      "args": [
        "serve",
        "--kb-path", "/path/to/knowledge-base",
        "--model", "bge-m3",
        "--reranker", "bge-v2-m3"
      ],
      "env": {
        "FASTEMBED_CACHE_DIR": "/path/to/.cache/huggingface/hub"
      },
      "type": "stdio"
    }
  }
}

エージェントワークフロー向けの保守的な案: reranker はロードするが既定はオフにしておき、呼び出し側が個別 searchrerank: true を指定してオプトインする:

{
  "mcpServers": {
    "ai-knowledge": {
      "command": "/path/to/groove",
      "args": [
        "serve",
        "--kb-path", "/path/to/knowledge-base",
        "--model", "bge-m3",
        "--reranker", "bge-v2-m3",
        "--rerank-by-default=false"
      ],
      "env": { "FASTEMBED_CACHE_DIR": "/path/to/.cache/huggingface/hub" },
      "type": "stdio"
    }
  }
}

あるいは、探索パス のいずれかに groove.toml を置いて同じ項目を設定しているなら、.mcp.json はここまで縮められる:

{
  "mcpServers": {
    "ai-knowledge": {
      "command": "/path/to/groove",
      "args": ["serve"],
      "type": "stdio"
    }
  }
}

クライアント接続時にサーバが自動起動する。

PostToolUse hook による index 鮮度保守

Claude Code セッション内部からナレッジベースを編集する (または Markdown を書く skill を実行する) 場合、MCP サーバは再構築されるまで古い結果を返し続ける。.claude/settings.jsonPostToolUse hook で書込み後に自動再 index できる。最小形:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit|Skill",
        "hooks": [
          { "type": "command", "command": "groove index" }
        ]
      }
    ]
  }
}

groove.toml がバイナリの隣ではなくプロジェクトの隣にあるなら、ここでも名指しすること: groove --config /abs/path/groove.toml index。見つけただけの config は [parsers] が Markdown のみへ戻され (信頼する置き場所 / しない置き場所)、groove index訪れなかった document を削除する — つまり既定の parser 集合で再構築する hook は、アップグレード後の最初の編集で、索引済みの .txt / PDF / Office 文書 / ソースコードをすべて索引から消す。バイナリの隣にある config や groove service install が置いた config は信頼されるので何も足さなくてよい。

groove index の SHA-256 差分検出により 2 回目以降は高速 (小さな KB なら大抵 1 秒未満)。ツールペイロードを精査して編集ファイルが $KB_PATH 配下のときだけ再構築する、より精密なシェルスクリプトがリポジトリ同梱 — grooveseek/examples/hooks/ 参照 (まさにこのために GROOVE_CONFIG を受け取る)。SQLite は WAL モードで動作するため、MCP サーバ起動中に hook が走っても安全。

Frontmatter スキーマ検証

ナレッジベースで frontmatter 規約を運用しているなら (例: title 必須、date は YYYY-MM-DD、topic は enum)、以下でファイル毎の違反をチェックできる:

groove validate --kb-path /path/to/knowledge-base

--kb-path 直下に groove-schema.toml を置く (テンプレート: groove-schema.toml.example):

[fields.title]
required = true
type = "string"
min_length = 1

[fields.date]
required = true
type = "string"
pattern = '^\d{4}-\d{2}-\d{2}$'

[fields.topic]
required = true
type = "string"
enum = ["mcp", "rag", "ai", "tooling", "ops"]

[fields.tags]
required = true
type = "array"
min_length = 1

HTTP トランスポート (複数クライアント同時接続)

既定の groove serve は stdio で MCP を話す — 1 クライアント / サーバプロセス。複数クライアント同時接続 (例: 複数の Claude Code セッション、または外部スクリプトが同じ index を叩く) には Streamable HTTP に切替:

groove serve --kb-path /path/to/knowledge-base --transport http --port 3100
# または、このマシン以外からの接続を受ける場合: --bind 0.0.0.0:3100 --i-know

サーバは /mcp に MCP エンドポイントをマウントし、/healthz をヘルスプローブ用に公開する。HTTP 対応クライアントの .mcp.json:

{
  "mcpServers": {
    "ai-knowledge": {
      "type": "http",
      "url": "http://127.0.0.1:3100/mcp"
    }
  }
}

セキュリティ注意:

Web UI と admin API (HTTP transport のみ)

serve--transport http で動かすと、/mcp/healthz に加えて 2 つの route が生える。有効化の設定は無く HTTP transport があれば常に存在し、どちらも loopback 限定: middleware が peer アドレスが loopback でないリクエストを 拒否し、その後 Host ヘッダを loopback の別名 (127.0.0.1 / ::1 / localhost) と照合する。bind アドレスが追加されるのは それ自体が loopback の 場合だけ で、0.0.0.0 に bind した時の Host: 0.0.0.0 は意図的に拒否される (LAN のブラウザが bind アドレス経由でこれらの route に到達しないため)。 /mcp 用に Host を allow-list していても、ネットワーク上の別マシンからは 403。 OriginHostに、[transport.http].allowed_origins と照合する。

両 route は X-Content-Type-Options: nosniffContent-Security-Policy を 付けて応答する。ポリシーは default-src 'none'このページが実際に使うもの だけを戻した形 — 自前の inline <script> / <style>data: の favicon、 同一 origin への fetch/ui は外部リソースを一切読まないが、それを保つのが このポリシーである。

Route 中身
/ui 運用者向けの画面。状態帯 (version / 文書数 / チャンク数 / モデル / watcher / uptime / pid) の下に検索。検索は /mcp を呼んで行うので、このページ自体が「Streamable HTTP 上の MCP クライアントの最小の実例」になっている
/api/admin/status daemon / indexing / watcher / KB の状態を JSON で返す。Windows tray が 5 秒間隔で polling しているのはこれで、上の状態帯もこれを読む

/api/search は v0.27.0 で削除した。 search tool が取る 17 パラメータのうち 2 つしか受け取っておらず、プロセスの外から使う口としては /mcp の方が既に優れていたため。/ui/mcp を使うようになった。docs/stability.ja.md 参照

curl http://127.0.0.1:3100/api/admin/status
{
  "daemon":   { "version": "0.13.1", "pid": 36400, "uptime_secs": 4210, "started_at": "2026-07-26T09:12:03Z" },
  "indexing": { "active": false, "started_at": null, "progress": null },
  "watcher":  { "active": true, "debounce_ms": 500 },
  "kb":       { "documents": 596, "chunks": 8878, "model": "bge-m3" },
  "config_source": "Cwd"
}

kb.path は削除した。 ナレッジベースの絶対パスを持っていたので、Windows では payload も、それを表示していた状態帯も C:\Users\<名前>\... と読めていた。 読んでいた消費者はいない (tray が読むのは daemon.pidindexing.active)。 docs/stability.ja.md 参照

/ui は Windows tray の Open Web UI が開くページだが、Windows 専用ではない。 Linux / macOS では daemon が動いているマシン上でブラウザから開くか、ポートを forward する:

ssh -L 3100:127.0.0.1:3100 kb-server.lan   # → http://127.0.0.1:3100/ui

これらの route を reverse proxy に map しないこと: proxy 自身が loopback peer で、既定の Host も allow-list に載るため、/ui を proxy すると proxy に 到達できる相手全員にページが渡る。転送するのは /mcp/healthz だけにする。

ライブ同期 (file watcher)

groove serve は既定で notify ベースのファイルウォッチャを走らせる。--kb-path 配下の任意の変更 (create / modify / delete / rename) が検知され、debounce ののち該当ファイルのみが再インデックスされる。手動の editor save・git pull・外部スクリプトといった、PostToolUse hook では捕まえられないケースをカバーする。

grammar plugin の置き方 (v1.3.0+)

groove はソースコードを定義 1 つ = chunk 1 つで parse するが、バイナリに焼き込んであるのは Rust だけ。他の言語はすべて、自分で DL して置く小さなライブラリになる。この非対称と「なぜ feature flag ではないのか」は ADR-0013 にある。

  1. releases ページで、使っている groove の版groove-grammar-<言語>-<target> アーカイブを探す。plugin とバイナリは ABI 版を共有するので、別の release のものは拒否されうる。
  2. 展開する前に checksum を検証する。 どのアーカイブにも .sha256 が並んで公開されている。ライブラリを開くと、groove が symbol を 1 つも見ないうちにそのライブラリ自身の初期化が走るので、すり替えられた / 壊れたアーカイブは「自分の権限で動くネイティブコード」そのものであり、あとから groove が何を検査しても変わらない。ファイルを開く前に済ませる必要があるのはこの手順だけ。

    アーカイブと .sha256 を両方置いたディレクトリで実行すること: .sha256 は自分が属するアーカイブのファイル名を持っていて、検証はその名前で探す。target は実際に落としたものに置き換える — 公開されているのは x86_64-unknown-linux-gnu / aarch64-unknown-linux-gnu / aarch64-apple-darwin / x86_64-pc-windows-msvc

    # Linux
    sha256sum -c groove-grammar-python-x86_64-unknown-linux-gnu.tar.xz.sha256
    
    # macOS — 素の macOS に sha256sum は無い。Mac 向けに公開しているのは Apple Silicon だけ
    shasum -a 256 -c groove-grammar-python-aarch64-apple-darwin.tar.xz.sha256
    
    # Windows
    (Get-FileHash groove-grammar-python-x86_64-pc-windows-msvc.zip -Algorithm SHA256).Hash -eq `
        (Get-Content groove-grammar-python-x86_64-pc-windows-msvc.zip.sha256).Split()[0].ToUpper()
    
  3. 展開して、ライブラリを grammar ディレクトリに置く。既定は Windows なら %LOCALAPPDATA%\groove\grammars、Linux なら ~/.local/share/groove/grammars、macOS なら ~/Library/Application Support/groove/grammars。別の場所にするなら groove.tomlgrammar_dir か、環境変数 GROOVE_GRAMMAR_DIR を使う — こちらは絶対パス必須で、相対値だと「クライアントがたまたま groove を起動したディレクトリ」に対して解決されてしまうため。
  4. [parsers].enabled にその言語を足す (例: enabled = ["md", "py"])。ただし groove が信頼する config に書くこと。プロジェクトの隣で見つけただけの groove.toml[parsers] が無視されるので、言語が有効にならず plugin も開かれない。--config で名指しするか、バイナリの隣に置くか、groove service install に置かせること。信頼する置き場所 / しない置き場所 を参照。
  5. service に任せる前に、groove index を 1 回手で走らせる — 手順 4 で --config を使ったなら、ここでも同じように名指しすること。 登録した Windows service は stdio を捨てるので、plugin が無い / 拒否された場合のメッセージがどこにも出ず、daemon がただ動かないという状態になる。groove index は DB を開くよりもモデルを読むよりも先に有効化した言語をすべて解決するので、壊れた plugin はその場で、何も作らずに、画面の上で止まる。ここで --config を落とすと派手に失敗はしない — 言語が有効にならないので誰も plugin を探さず、置いたはずの plugin を一度も検査しないまま run が成功する。(groove doctor既にある索引を検査するコマンドで、索引がまだ無い状態では plugin に触れる前に「No index found」と答える。この手順には使えない。)

自動 DL は一切しないし、enabled に書いた言語以外は開かない — そのディレクトリに有効化していない言語のファイルがあっても触らない。有効化した言語の plugin が使えない場合、コマンドは「どのファイルをどこに置くべきか」を告げて止まる。ソースを plain text として索引するフォールバックはしない。

plugin を差し替えたら groove index --force で索引し直すこと。 grammar を作り直すと同じファイルでも chunk の切れ目が動きうるが、索引は内容が変わっていないファイルを飛ばすので、普通に index し直してもそのファイルは古い grammar が切った chunk のまま残り、新しい grammar はその後編集したファイルにだけ効く。結果として索引が 2 世代を同時に抱える。現時点では groove がこれを検出して警告することはないので、--force は利用者の側の判断になる。groove 本体を上げた時も同様。[parsers.code].max_chunk_chars を変えた時だけは groove が報告する: 索引はコード chunk を切った時の budget を記録しているので、違う値で次を走らせると --force を名指しする warning が出る。

grammar plugin は groove が自分のプロセスへ読み込むネイティブコード。 インストールする他のバイナリと同じ扱いにすること — 使っている版の release ページから取り、それ以外の場所からは取らない。groove が見つけただけgroove.toml (--config で名指ししていないもの) が、このディレクトリも、そもそもライブラリを開かせる言語の指定も選べないのは同じ理由: 信頼する置き場所 / しない置き場所 を参照。

HuggingFace の TLS 失敗への対処 (初回 DL 時)

環境によっては (企業プロキシ、TLS inspection を行うファイアウォール) fastembed の native TLS 接続が huggingface.co に対して os error 10054 / “Connection was reset” で失敗する。その場合は Python の HuggingFace CLI で事前にモデルを DL し、FASTEMBED_CACHE_DIR で HF Hub キャッシュを指す:

# 一度インストール
pip install --user huggingface_hub

# BGE-M3 を事前 DL (必要な ONNX ファイルのみ)
hf download BAAI/bge-m3 \
    --include 'onnx/*' 'tokenizer*' 'config.json' 'special_tokens_map.json'

# BGE-reranker-v2-m3 を事前 DL (`--reranker bge-v2-m3` 用)
hf download BAAI/bge-reranker-v2-m3

# HF cache を指して groove を起動 (HF Hub cache は fastembed と互換)
FASTEMBED_CACHE_DIR=~/.cache/huggingface/hub \
    groove index --kb-path ./knowledge-base --model bge-m3 --force