Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,7 @@ Process exit codes are coarse (`0` success including valid zero-row queries, `1`
- **MCP resource-list cursor stability** — `resources/list` emits a fixed-size opaque keyset cursor that binds the last consumed file id to a persisted indexed-file generation and the canonical discovery filters. The reader resolves that id back to the existing source/test/docs bucket plus path ordering inside the same SQLite snapshot. Any file insertion, deletion, or update changes the generation; a later page then returns `-32011` / `index_stale` with `restart_required: true`. Changing `path`, `lang`, or `includeGenerated` between pages returns `-32602` / `resources_list_filters_changed` with the same restart requirement. In either case, the client must omit `params.cursor` to restart instead of continuing across mixed snapshots or filters. Writable legacy databases install the generation row and triggers through the normal read migration before a cursor is issued. A mutable read-only legacy database that cannot prove generation tracking returns `resources_list_generation_unavailable` with `migration_required: true`; a canonical, unambiguous `immutable=1` legacy URI (optionally paired with `mode=ro`) may safely use connection-local generation zero because it cannot change between pages. Encoded, case-variant, whitespace-padded, duplicated, conflicting, or extra query parameters are not trusted as that immutable guarantee. The legacy decimal zero remains a first-page upgrade input, but nonzero decimal offsets cannot prove their source generation and therefore return the same restart-required error; decimal cursors are never emitted. Version-1 opaque cursors remain valid only with the default unfiltered view.
- **MCP file resource discovery** — `resources/templates/list` advertises `cdidx://file-path/{path}` so a client that already knows an exact repository-relative path can construct a `resources/read` URI without paging the repository inventory. Simple URI-template expansion percent-encodes separators and reserved filename characters such as `?` and `#`; the template-only resolver decodes the value once, rejects absolute paths, traversal, backslashes, empty segments, queries, and fragments, then returns the canonical `cdidx://file/<path>` identity. Canonical resource URIs continue to reject encoded separators. `resources/list` accepts `path` as one string or at most 100 strings of at most 1024 characters and 128 wildcard operators each, using the same anchored directory/glob semantics as file queries, plus an exact normalized `lang` filter and `includeGenerated` (default `false`). Generated files also require `includeGenerated: true` for direct reads.
- **MCP resource-list response budget** — `resources/list.params.maxBytes` accepts 4,096 through 1,000,000 bytes and defaults to 1,000,000, matching the default HTTP response-body cap. The effective budget is the minimum of that request, the server-wide MCP envelope cap, and the active HTTP transport response-body cap (when applicable), so a lower configured HTTP cap shapes a valid page instead of rejecting it with HTTP 500. The server measures the complete JSON-RPC envelope, keeps 200 as the candidate ceiling, and stops before the next resource would cross the effective byte budget. For HTTP JSON-RPC batches, the active transport budget covers the complete response array, including brackets and commas, and is divided fairly among response-bearing items; notifications consume no response slot. Each `resources/list` item honors its current share and preserves its request ID in a canonical budget error if even a bounded page cannot fit. State-changing and other non-resource outcomes are never relabeled as retry-safe; an aggregate overflow after execution reports an unknown completion state and forbids automatic retry. `_meta.response_controls` reports the requested/effective budgets, consumed and returned counts, `omitted_resource_count`, bounded reason counts (`resource_uri_too_long` / `resource_exceeds_max_bytes`), `byte_budget_reached`, and `continuation_reason` (`byte_budget`, `item_limit`, or `completed`). A continuation cursor anchors the last consumed database row; a valid resource that did not fit remains unconsumed for the next page, while a resource that cannot fit even on an empty page is consumed and counted so pagination cannot livelock.
- **Typed MCP resource reads** — `read_resource` is the discoverable tools/call surface for bounded file reads. Its `tools/list` schema owns the typed `uri`, 1-based inclusive line range, 4-byte through 128-KiB UTF-8 text budget, generated-file opt-in, and opaque continuation cursor contract. The handler delegates to `HandleResourcesRead`, so URI validation, generated-file policy, snapshot lookup, response-aware budgeting, UTF-8 boundaries, stale-cursor checks, and legacy index compatibility stay in one reader. The adapter returns file text once in `content[0].text`, puts canonical identity in `structuredContent.resource`, and puts range/truncation/continuation data in `structuredContent._meta`; resource errors become MCP tool-result errors with the original classification and JSON-RPC code preserved. Keep `resources/read` and both URI forms backward compatible. Clients must feature-detect `read_resource` in `tools/list` so older servers continue through the protocol method.
- **MCP array argument bounds** — MCP string-array filters such as `path`, `project`, `excludePaths`, and mixed `names` arrays reject invalid entries instead of silently dropping them. Arrays are capped at 100 entries and each entry is capped at 4096 characters; `batch_query` reports these validation failures per slot with `request_index` and `ok: false`.
- **MCP schema lock-down** — Every tool `inputSchema` includes `additionalProperties: false`, and `tools/call` mirrors that contract by rejecting unknown argument names with `-32602` / `invalid_argument` instead of silently defaulting misspelled fields.
- **MCP stability markers and naming** — Every tool advertises `x-stability` (`stable`, `experimental`, or `deprecated`). MCP structured payload keys use snake_case, matching the CLI JSON contract; do not add camelCase aliases for new fields.
Expand Down Expand Up @@ -5498,6 +5499,7 @@ USER_GUIDEの[終了コード](USER_GUIDE.md#終了コード)セクションを
- **MCP ファイル resource discovery** — `resources/templates/list` は `cdidx://file-path/{path}` を公開し、正確なリポジトリ相対 path が既知の client は全 inventory をページングせず `resources/read` URI を構築できる。simple URI-template expansion は separator と `?` / `#` などの予約 filename 文字を percent encode する。template 専用 resolver は値を一度だけ decode し、absolute path、traversal、backslash、空 segment、query、fragment を拒否して canonical な `cdidx://file/<path>` identity を返す。canonical resource URI は encoded separator を引き続き拒否する。`resources/list` は `path` に 1 文字列または各 1024 文字・wildcard operator 128 個以内かつ最大 100 件の文字列を受け付け、file query と同じ anchored directory / glob semantics を使うほか、正規化した完全一致の `lang` と `includeGenerated`(既定 `false`)も受け付ける。cursor は generation と canonical filter の両方に結び付き、ページ間の filter 変更は `-32602` / `resources_list_filters_changed` と `restart_required: true` を返す。generated file は discovery と direct read のどちらでも `includeGenerated: true` が必要である。
- **MCP resource list カーソルの安定性** — `resources/list` は、最後に消費した file id と永続化されたインデックス済みファイル世代を結び付ける固定長の不透明 keyset cursor を返す。reader は同じ SQLite snapshot 内でその id を既存の source/test/docs bucket と path の並び順へ解決する。ファイルの追加・削除・更新で世代が変わると、後続ページは `restart_required: true` 付きの `-32011` / `index_stale` を返し、混在 snapshot を続行せず `params.cursor` を省略して再開する必要がある。書き込み可能な legacy DB は cursor 発行前に通常の read migration で世代 row と trigger を導入する。世代追跡を証明できない変更可能な read-only legacy DB は `migration_required: true` 付きの `resources_list_generation_unavailable` を返すが、canonical かつ曖昧性のない `immutable=1` legacy URI(任意で `mode=ro` を併記)はページ間で変化しないため connection-local な世代 0 を安全に利用できる。encoded、case variant、空白付き、重複、競合、または余分な query parameter はこの immutable 保証として信頼しない。旧 decimal の 0 は先頭ページ用の移行入力として残すが、0 以外の decimal offset は発行時世代を検証できないため同じ再開必須 error を返し、decimal cursor は出力に使わない。
- **MCP resource list の response budget** — `resources/list.params.maxBytes` は 4,096〜1,000,000 bytes を受け付け、既定値は HTTP response body の既定上限と同じ 1,000,000。有効 budget はこの要求値、server-wide MCP envelope 上限、および該当時の active HTTP transport response-body 上限の最小値とし、HTTP の設定上限が低い場合も HTTP 500 で拒否せず有効なページへ整形する。server は JSON-RPC envelope 全体を計測し、candidate 上限を 200 件に保ったまま、次の resource で有効 byte budget を超える直前に停止する。HTTP JSON-RPC batch では active transport budget を bracket と comma を含む response 配列全体へ適用し、応答対象 item へ公平に分配する。notification は response slot を消費しない。各 `resources/list` item は現在の割当を守り、有界なページさえ収まらない場合も canonical な budget error に request ID を保持する。state-changing item とその他の non-resource outcome を retry-safe として付け替えることはなく、実行後の aggregate overflow は completion state が unknown で自動再試行不可であることを報告する。`_meta.response_controls` は要求/有効 budget、消費/返却件数、`omitted_resource_count`、有界な理由別件数(`resource_uri_too_long` / `resource_exceeds_max_bytes`)、`byte_budget_reached`、`continuation_reason`(`byte_budget`、`item_limit`、`completed`)を返す。継続 cursor は最後に消費した DB row を anchor とし、収まらなかった有効 resource は次ページ用に未消費のまま残すが、空ページにも収まらない resource は消費して件数へ計上し、pagination の livelock を防ぐ。
- **型付き MCP resource read** — `read_resource` は bounded file read を発見可能にする tools/call surface である。`tools/list` schema が型付き `uri`、1-based inclusive の行範囲、4 byte〜128 KiB の UTF-8 text budget、generated file の opt-in、opaque な continuation cursor 契約を持つ。handler は `HandleResourcesRead` に委譲するため、URI validation、generated-file policy、snapshot lookup、response-aware budgeting、UTF-8 boundary、stale cursor check、legacy index 互換性は 1 つの reader に保たれる。adapter は file text を `content[0].text` に一度だけ返し、canonical identity を `structuredContent.resource`、範囲・切り詰め・継続 metadata を `structuredContent._meta` に置く。resource error は元の分類と JSON-RPC code を維持した MCP tool-result error へ変換する。`resources/read` と両 URI 形式の後方互換性を維持し、client は `tools/list` で `read_resource` を feature-detect して、古い server では protocol method を使い続けること。
- **MCP 配列引数の上限** — `path` / `project` / `excludePaths` / mixed `names` などの string-array filter は、不正要素を暗黙に落とさず拒否する。配列は 100 件、各要素は 4096 文字を上限とし、`batch_query` では `request_index` と `ok: false` 付きの slot 失敗として報告する。
- **MCP schema のロックダウン** — すべての tool `inputSchema` は `additionalProperties: false` を含み、`tools/call` も同じ契約として未知の引数名を黙って既定値にせず `-32602` / `invalid_argument` で拒否する。
- **MCP stability marker と命名** — すべての tool は `x-stability`(`stable`、`experimental`、`deprecated`)を公開する。MCP の構造化 payload key は CLI JSON 契約に合わせて snake_case を使う。新規 field に camelCase alias を追加しないこと。
Expand Down
27 changes: 27 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2795,6 +2795,20 @@ parameters and bounds under `_meta.discovery_contract`, so AI clients can discov
`path`, `lang`, `includeGenerated`, `maxBytes`, and cursor semantics without
guessing beyond the standard protocol.

Clients that need typed discovery should prefer the `read_resource` tool when
`tools/list` advertises it. Its `inputSchema` declares the required `uri` plus
optional 1-based inclusive `startLine` / `endLine`, UTF-8 `maxBytes`,
`includeGenerated`, and opaque continuation `cursor`, including their ranges and
mutual-exclusion rules. The file text is returned in `content[0].text`;
`structuredContent.resource` carries its canonical identity and
`structuredContent._meta` carries effective ranges, byte counts, truncation, and
`nextCursor`. `read_resource` and `resources/read` use the same validation and
bounded database reader. Existing `cdidx://file/...` and
`cdidx://file-path/...` URIs do not change. Clients should feature-detect
`read_resource` through `tools/list`; older servers and compatibility-oriented
clients can continue to use `resources/read`, whose extra range/budget fields
remain supported even though the standard MCP method only types `uri`.

MCP `resources/read` is bounded too. Pass optional inclusive `startLine` / `endLine` values and `maxBytes` (the UTF-8 resource-text budget); the minimum is 4 bytes, omitted budgets default to 64 KiB, and requests cannot exceed 128 KiB. Each page is also capped at 1,000 logical lines. `result._meta.truncationReason` is `maxLines` when that cap is reached and `maxBytes` when the requested text budget is reached. If the configured MCP or HTTP response ceiling is tighter, `_meta.maxBytes` retains the requested budget, `_meta.effectiveMaxBytes` reports the reduced budget, and truncation uses `maxResponseBytes`. Multiple `resources/read` calls in one JSON-RPC batch share the aggregate frame ceiling, so each item also yields to the space remaining in that batch. A non-pageable item that cannot fit its allocation returns a structured `batch_response_budget_too_small` error while preserving the request ID. When `result._meta.truncated` is true, send the returned `nextCursor` with the same resource URI to continue. Do not combine a cursor with new line boundaries; you may change `maxBytes` for the next page. A cursor becomes stale if the indexed resource changes. Read-only or immutable legacy databases without the dedicated range indexes use the existing `idx_chunks_file` index for a metadata-only compatibility lookup under a SQLite VM-step budget; exceeding that budget returns a structured index-unavailable error instead of performing an unbounded scan.

### MCP Server (for Claude Code, Cursor, Windsurf, etc.)
Expand Down Expand Up @@ -6057,6 +6071,19 @@ parameter と上限を `_meta.discovery_contract` に公開するため、AI cli
protocol の外側にある `path`、`lang`、`includeGenerated`、`maxBytes`、cursor
semantics を推測せず発見できます。

型付き discovery が必要な client は、`tools/list` に公開されている場合は
`read_resource` tool を優先してください。その `inputSchema` は必須の `uri` と、
任意の 1-based inclusive な `startLine` / `endLine`、UTF-8 `maxBytes`、
`includeGenerated`、opaque な継続 `cursor` を、範囲・排他規則とともに宣言します。
file text は `content[0].text`、canonical identity は
`structuredContent.resource`、有効範囲・byte 数・切り詰め・`nextCursor` は
`structuredContent._meta` に返ります。`read_resource` と `resources/read` は
同じ validation と bounded database reader を使用し、既存の
`cdidx://file/...` / `cdidx://file-path/...` URI は変わりません。client は
`tools/list` で `read_resource` を feature-detect してください。古い server や
互換性重視の client は引き続き `resources/read` を使用でき、標準 MCP method が
`uri` だけを型付けしていても追加の range / budget field は維持されます。

MCP `resources/read` にも上限があります。inclusive な `startLine` / `endLine` と、UTF-8 resource 本文の budget である `maxBytes` を任意指定できます。最小値は 4 byte、budget 省略時は 64 KiB、要求可能な最大値は 128 KiB です。各ページには論理行 1,000 行の上限もあります。この上限に達した場合、`result._meta.truncationReason` は `maxLines`、要求した本文 budget に達した場合は `maxBytes` になります。設定された MCP または HTTP のレスポンス上限の方が小さい場合、`_meta.maxBytes` は要求値を保持し、`_meta.effectiveMaxBytes` が縮小後の budget を示し、切り詰め理由は `maxResponseBytes` になります。1 つの JSON-RPC batch に複数の `resources/read` call がある場合は aggregate frame 上限を共有するため、各 item はその batch の残り領域にも従います。page 化できない item が割当内に収まらない場合は、request ID を保持した構造化 `batch_response_budget_too_small` error を返します。`result._meta.truncated` が true の場合、返された `nextCursor` を同じ resource URI とともに送って継続してください。cursor と新しい行境界は併用できませんが、次ページの `maxBytes` は変更できます。index 済み resource が変わると cursor は stale になります。専用の range index がない read-only または immutable な legacy database では、既存の `idx_chunks_file` index を使い、SQLite VM-step budget 内の metadata-only compatibility lookup を行います。この budget を超えた場合は無制限に scan せず、構造化された index-unavailable error を返します。

### MCP サーバー(Claude Code、Cursor、Windsurf 等に対応)
Expand Down
30 changes: 30 additions & 0 deletions changelog.d/unreleased/4900.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
category: added
issues:
- 4900
affected:
- src/CodeIndex/Mcp/McpServer.Resources.cs
- src/CodeIndex/Mcp/McpServer.Responses.cs
- src/CodeIndex/Mcp/McpServer.ToolDispatch.cs
- src/CodeIndex/Mcp/McpServer.cs
- src/CodeIndex/Mcp/McpToolArgumentContracts.cs
- src/CodeIndex/Mcp/McpToolCatalog.cs
- src/CodeIndex/Mcp/McpToolFilter.cs
- src/CodeIndex/Mcp/McpToolHandlers.ArgumentValidation.cs
- src/CodeIndex/Mcp/McpToolHandlers.BatchQuery.cs
- src/CodeIndex/Mcp/McpToolHandlers.Instructions.cs
- src/CodeIndex/Mcp/McpToolDefinitions.cs
- tests/CodeIndex.Tests/McpServerProtocolTests.cs
- tests/CodeIndex.Tests/McpServerTests.cs
- tests/CodeIndex.Tests/McpServerToolsListTests.cs
- USER_GUIDE.md
- DEVELOPER_GUIDE.md
---

## English

- **Bounded MCP resource reads are now discoverable as a typed tool (#4900)** — `tools/list` advertises `read_resource` with typed URI, inclusive line-range, UTF-8 byte-budget, generated-file, and continuation-cursor inputs while preserving the existing resource URIs and `resources/read` compatibility through the same bounded reader.

## 日本語

- **上限付き MCP resource read を型付き tool として発見できるようになりました (#4900)** — `tools/list` は URI、inclusive な行範囲、UTF-8 byte budget、generated file、continuation cursor を型付けした `read_resource` を公開し、同じ bounded reader を通じて既存の resource URI と `resources/read` の互換性を維持します。
Loading
Loading