GrooveSeek

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

View the Project on GitHub alphabet-h/grooveseek

4. resource の read はファイルシステムではなく索引で縛る

背景と課題

v0.22.0 で kb-mcp に MCP の resources capability が入った。クライアントは kb://doc/<path> を要求して文書のテキストを受け取れる。

ここで、本プロジェクトが別の機構について既に一度、意図的に答えた問いが再び立つ: read は何で縛られるのか。

数日前に受理した ADR-0003 は、 .kb-mcpignore が縛るのは索引であってアクセスではないと決めた。 get_documentkb_path 配下で拡張子が registry にあるファイルなら、 索引に入っていようがいまいが返す (document_in_excluded_dir_is_still_readable が pin)。 理由は「木の中に置いたルールはその木を守れない」— KB に書ける者はそれを消せるから。

resources/read はこの契約をそのまま継ぐこともできるし、より狭くすることもできる。 これは机上の問いではない: resource はサーバが提示したものなので、 それを要求するクライアントは「パスを既に知っていて get_document を呼ぶ側」とは 違うことをしている

決定要因

検討した選択肢

  1. get_document と同じ契約 — 索引の有無に関わらず、kb_path 配下で 拡張子が registry にあれば返す
  2. 索引 membership を先に見て、その後は get_document と同一の guard
  3. get_document の guard + read のたびに .kb-mcpignore を見る

決定

選択肢 2: 文書が resource として提供されるのは索引に入っている場合のみ。 そのうえで get_document と同一の guard を通す。

選択肢 3 は即却下する。ADR-0003 が明示的に退けた境界を、数日で変わっていない 同じ論理の上に再建することになり、しかも「読めるか」の答えが 「KB に書ける者なら誰でも消せるファイル」に依存する。

選択肢 2 は選択肢 1 より狭いので、到達範囲を広げようがない。そして その根拠は ADR-0003 が却下したものと質的に違う: 木の中のファイルに木を 守らせるのではなく、kb-mcp 自身の DB — サーバが構築し所有する状態 — を信じている。

安全なだけでなく正しいと言える理由は、resource が何かにある。 get_document は「パスを既に知っている呼び出し元」に答えるもので、そこでの契約は 「kb_path 配下は読める前提、秘密は外に置け」。resources/read が答える相手は このサーバが渡した URI を持っている呼び出し元である。提示していない URI を 提供するのは別の操作であり、提示物で縛るのは後付けの制限ではなく自然な契約

これは resources/list を正直にもする — ただし listing を読みが実際に受け付ける 集合から作った場合に限る。生の索引ではそうならない: [parsers].enabled を狭めて 再 index しないと、外した拡張子の行は意図的に残り、共有 guard の拡張子検査が それを拒否する。索引 membership だけで listing を作ると、次の呼び出しが拒否する kb://doc/… を渡してしまう。そこで listing も read も servable_document_paths() という 1 つのクエリ (= 索引のパスから、現在の registry で開けないものを除いたもの) を通す。「listing が出すものは read が受け付ける」は 1 つのリストの性質か、どちらのものでもないかのどちらかしかない。

帰結