feat(mcp): add vector_ids to search for stored-content retrieval [mcp, bridge, docs, tests] - #243
Conversation
…, bridge, docs, tests] `search` に `vector_ids` パラメータを追加し、索引が既に保持している本文を 返せるようにした。従来は `doc` / `wiki_doc` 以外の型に本文を返す手段が無く、 `search` であたりを付けたあと `gh` や grep で取り直す一往復が必要だった。 設計は既存の導出作法に合わせた。モードは列挙パラメータで選ばせず、 パラメータ集合から導く。優先順位は `vector_ids` -> `query` 空 -> hybrid search。 専用 tool は追加しない。#104 / #105 で 4 tool を `search` へ統合した判断を維持する (本文取得の専用 tool は削除された `get_doc_content` の復活そのものになる)。 - `src/fetch.ts` (new): D1 `search_docs` の保存済み content を 1 回のバッチ クエリで読み出す。GitHub API は呼ばない。`scan.ts` と同じ理由でモジュールを 分けている(MCP server を立てずに実 D1 に対して検証できる)。 - 返す文字列が索引由来かつ 8000 文字 truncate 済みであることを応答が示す: top-level に `content_source: "index"` / `content_max_chars`、各行に `content_chars` / `content_truncated`。フラグは長さから導くため、自然長が ちょうど上限の本文も truncate 済みとして報告する(安全側)。 - 未知・失効 id は `not_found` に載せ、残りは返す。`vector_id` は永続識別子 として約束しない(`legacy-vector-id.ts` の移行履歴があるため)。 - `search` の全 result 行と `same_entity.others` の各要素に `vector_id` を追加。 others に載せるのは、実体集約が comment / review / 旧 diff をそこへ畳むため、 無いと本モードが本来返すべき行だけ到達不能になるからである。 - `include_content` の既存挙動(contents API から完全なファイル、5 件上限)は 変更しない。取得元も上限の根拠も違うため統合しない。 - bridge 側の静的 schema mirror (`mcp-server/server/tools.js`) と manifest も 同期。drift-check が CI gate として効いている。 Closes #239
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
github-rag-mcp | edb9c09 | Aug 14 2026, 12:30 PM |
smileygames
left a comment
There was a problem hiding this comment.
AI self-review (auto mode, parent)
検証したもの
tool 数 — branch の src/mcp.ts で tool 登録は 1 件。#104 / #105 の統合判断は維持されている。これが本 issue の load-bearing な制約だったので、最初に確認した。
モードの導出 — vector_ids の有無で分岐しており、列挙パラメータを新設していない。query が空なら scan という既存の作法と同じ形。commit 6acdbd3 の "three modes via its parameter set" の並びに 4 つめとして入っている。
上限の由来 — FETCH_CONTENT_MAX_CHARS = MAX_EMBEDDING_INPUT_CHARS。リテラルの複製ではなく導出。ingest 側の truncate 値が変われば追随する。
D1 失敗と not_found の切り分け — ここが最も評価できる判断。読み取り失敗を「全件 not_found」で返さず error にしている。コメントの理由づけがそのまま正しい。
an empty
resultswith every id innot_foundwould assert the rows do not exist, which a failed read gives no ground to claim.
証拠の不在と不在の証拠を混同しないという区別が実装に落ちている。issue の制約は「部分成功」としか書いていなかったので、これは仕様を超えて正した側。
provenance — content_source: "index"、content_truncated、content_max_chars が wire に載る。truncated 判定を長さから導いているため、実長がちょうど 8000 の本文は truncated と報告される。誤る向きが「余計な再読み込み」側であり、prefix を全体と誤認する側ではない。向きが正しい。
same_entity.others への vector_id 付与 — issue の受け入れ条件は「結果各行」までしか書いていない。ただし entity 集約が comment / review / 旧 diff を others に畳む以上、それらを付けなければ本文取得の対象になる行だけが引けないことになる。範囲外だが、範囲の書き漏らしを正した側と判断する。受け入れる。
受け入れ条件
| criterion | 結果 |
|---|---|
search 結果各行に vector_id |
pass(others にも) |
vector_ids で各型の本文が返る |
pass |
| GitHub API 呼び出しが増えていない | pass — D1 のみ |
| 索引由来・truncate 済みが判別できる | pass |
| 未知 id が混ざっても残りが返る | pass |
include_content の既存挙動が不変 |
pass |
| MCP tool 数が 1 のまま | pass |
scope 逸脱
mcp-server/server/tools.js と manifest / README の同期。bridge が tools/list を手書きの静的ミラーから返しており、scripts/check-schema-drift.mjs が CI ゲートになっているため、Worker 側だけ足すとクライアントからパラメータが消える。同一 PR で直すのが正しい。受け入れる。
申し送り
scan モードの行は vector_id を持たない(構造化ストア由来で索引キーが無い)。実装せず、tool description と要件仕様の両方に記載する形で処理されている。制限として明示されているので可。
next step
auto mode のため human check なし。self-review pass をもって AI が直接 merge する。
概要
searchにvector_idsパラメータを追加し、索引が既に保持している本文を返せるようにした。従来はdoc/wiki_doc以外の型に本文を返す手段が無く、searchであたりを付けたあとghや grep で取り直す一往復が必要だった。Closes #239
設計判断
専用 tool は追加していない。 issue の「過去の判断」節どおり、#104 / #105 で 4 tool を
search1 本へ統合した判断を維持する。本文取得の専用 tool は削除されたget_doc_contentの復活そのものになるため、統合時の設計意図(モードはパラメータ集合で表現する)に乗せた。優先順位は
vector_ids->query空 -> hybrid search。fetch 呼び出しは query を持たないため、空クエリ判定より前に分岐しないと scan mode に落ちる。fetch mode では metadata filter を適用しない(行はサーバが選ぶのではなく呼び出し側が名指しするため)。include_contentとは統合していない。取得元(GitHub contents API / D1)も上限の根拠(API fan-out / 呼び出し側が挙げた id 数)も違うため、1 つのフラグに載せると 1 つの保証で 2 つの挙動を覆うことになる。既存挙動は無変更。実装
src/fetch.ts(新規) — D1search_docsの保存済み content を 1 回のバッチクエリ(既存のgetDocsByVectorIds)で読む。GitHub API は呼ばず subrequest も増えない。scan.tsと同じ理由でモジュールを分けた(MCP server を立てずに実 D1 に対して検証できる)。content_source: "index"とcontent_max_chars、各行にcontent_charsとcontent_truncated。フラグは取り込み時の記録ではなく長さから導いているため、自然長がちょうど 8000 文字の本文も truncate 済みとして報告する。この向きの誤りは不要な読み直し 1 回で済み、逆向きは断片を完全な本文として通してしまう。not_foundに載せ、残りの行は返す。vector_idは永続識別子として約束しない(pipeline/legacy-vector-id.tsに採番移行の履歴がある)。D1 read 自体が失敗した場合は「全件 not_found」ではなくisErrorを返す。読み切れていない read に「行が存在しない」と主張させないため。vector_idの露出 —searchの全 result 行に加え、same_entity.othersの各要素にも載せた。others まで載せたのは、実体集約が comment / review / 旧 diff をそこへ畳むため、無いと本モードが本来返すべき行だけが到達不能になるからである(issue の acceptance は result 行のみを挙げているが、機能の目的に対して穴になる)。mcp-server/server/tools.jsの静的 schema mirror とmanifest.jsonを同期。scripts/check-schema-drift.mjsが CI gate として効いている。テスト
src/fetch.test.ts(node pool)— truncate 判定の境界、文字数(バイトではない)、型ごとの identity カラム付与。src/fetch.workers.test.ts(workers pool / 実 D1)— 実 ingest writerupsertFtsRowで 9 surface を書き、実 read で本文が返ることを型ごとに確認。truncate フラグ、部分成功、重複除去、空リスト。src/mcp-stateless-contract.test.ts— 配信される schema にvector_idsが載ること、description が index 由来 / 8000 /content_truncated/not_found/ 非永続識別子を述べていること。mcp-server/test/search-tool-schema.test.js— client が実際に読む proxy 側 schema に同じ記述があること。npm test(270 + 70 pass)/mcp-serverのnpm test(12 pass)/ drift-check /tsc --noEmit/wrangler deploy --dry-runいずれもローカルで green。acceptance 対応
searchの結果各行にvector_idが載るsame_entity.othersにも)vector_idsで 7 型の本文が返るdoc/wiki_docを含む 9 surface を D1 テストで確認)inlineDocContentは無変更)vector_idが混ざっても残りは返るinclude_contentの既存挙動が変わっていないtools/listが["search"]であることをテストで確認)非スコープ
vector_idが載らない。tool description と要件記述の双方に明記した。version type
minor と判断。client が読む schema(proxy mirror)に新パラメータが載り、全 result 行に新フィールドが増えるため user/system observable であり、Worker と bridge が揃って出る必要がある。内部構造だけの変更ではない。
🤖 Generated with Claude Code