| 1 | using System.Text.Json; |
| 2 | using ModelContextProtocol.Protocol; |
| 3 | using Tool = ModelContextProtocol.Protocol.Tool; |
| 4 | |
| 5 | namespace HybridCodebaseIndex.Mcp; |
| 6 | |
| 7 | internal static class ToolCatalog |
| 8 | { |
| 9 | private static JsonElement Schema(object schema) => JsonSerializer.SerializeToElement(schema); |
| 10 | |
| 11 | private static readonly string[] RequiredWorkspace = ["workspace_path"]; |
| 12 | |
| 13 | internal static List<Tool> Build() |
| 14 | { |
| 15 | return |
| 16 | [ |
| 17 | new Tool |
| 18 | { |
| 19 | Name = "codebase_index_version", |
| 20 | Description = "Версия MCP сервера: assembly version + informational version (commit), runtime.", |
| 21 | InputSchema = Schema(new |
| 22 | { |
| 23 | type = "object", |
| 24 | properties = new { }, |
| 25 | }), |
| 26 | }, |
| 27 | new Tool |
| 28 | { |
| 29 | Name = "codebase_index_search", |
| 30 | Description = |
| 31 | "Гибридный полнотекстовый поиск по индексу workspace (SQLite FTS5; архитектурное решение — ADR 0105 в CascadeIDE: docs/adr/0105-hybrid-codebase-index-for-csharp-web.md). hit_kind=text_fts. До reindex база может отсутствовать.", |
| 32 | InputSchema = Schema(new |
| 33 | { |
| 34 | type = "object", |
| 35 | properties = new |
| 36 | { |
| 37 | workspace_path = new { type = "string", description = "Корень workspace (решение/репозиторий)." }, |
| 38 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Используется для области индекса: одна БД на (workspace_root, solution_path)." }, |
| 39 | query = new { type = "string", description = "Поисковая строка (ключевые слова, AND по токенам)." }, |
| 40 | top_n = new { type = "integer", description = "Максимум попаданий (по умолчанию 15)." }, |
| 41 | semantic = new { type = "boolean", description = "Опционально: включить semantic (vec) канал, если он проиндексирован и semantic_enabled=true." }, |
| 42 | alpha = new { type = "number", description = "Опционально: вес FTS канала при фьюжне (по умолчанию 0.65)." }, |
| 43 | beta = new { type = "number", description = "Опционально: вес vec канала при фьюжне (по умолчанию 0.35)." }, |
| 44 | vec_top_k = new { type = "integer", description = "Опционально: внутренний top-K по vec (по умолчанию 30)." }, |
| 45 | path_prefix = new { type = "string", description = "Опционально: ограничить попадания путями, начинающимися с префикса (unix-style, например 'src/')." }, |
| 46 | exclude_path_prefixes = new { type = "array", items = new { type = "string" }, description = "Опционально: исключить пути по префиксам (unix-style)." }, |
| 47 | extensions = new { type = "array", items = new { type = "string" }, description = "Опционально: ограничить расширениями (например ['.md','.csproj'] или ['md','csproj'])." }, |
| 48 | }, |
| 49 | required = new[] { "workspace_path", "query" }, |
| 50 | }), |
| 51 | }, |
| 52 | new Tool |
| 53 | { |
| 54 | Name = "codebase_index_explain", |
| 55 | Description = "Explain для одного попадания (по hit_id из search): вернуть контекст чанка и метаданные.", |
| 56 | InputSchema = Schema(new |
| 57 | { |
| 58 | type = "object", |
| 59 | properties = new |
| 60 | { |
| 61 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 62 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Должен совпадать со scope при search, иначе hit_id может не найтись." }, |
| 63 | hit_id = new { type = "integer", description = "Идентификатор попадания (hitId из search)." }, |
| 64 | }, |
| 65 | required = new[] { "workspace_path", "hit_id" }, |
| 66 | }), |
| 67 | }, |
| 68 | new Tool |
| 69 | { |
| 70 | Name = "codebase_index_status", |
| 71 | Description = "Статус локального индекса: путь к SQLite, число документов, версия формата.", |
| 72 | InputSchema = Schema(new |
| 73 | { |
| 74 | type = "object", |
| 75 | properties = new |
| 76 | { |
| 77 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 78 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Используется для области индекса: одна БД на (workspace_root, solution_path)." }, |
| 79 | }, |
| 80 | required = RequiredWorkspace, |
| 81 | }), |
| 82 | }, |
| 83 | new Tool |
| 84 | { |
| 85 | Name = "codebase_index_reindex", |
| 86 | Description = "Перестройка индекса (FTS5). По умолчанию инкрементальная по файлам; full_rebuild=true — полная.", |
| 87 | InputSchema = Schema(new |
| 88 | { |
| 89 | type = "object", |
| 90 | properties = new |
| 91 | { |
| 92 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 93 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Используется для области индекса: одна БД на (workspace_root, solution_path)." }, |
| 94 | full_rebuild = new { type = "boolean", description = "Если true — полный rebuild вместо инкремента." }, |
| 95 | }, |
| 96 | required = RequiredWorkspace, |
| 97 | }), |
| 98 | }, |
| 99 | new Tool |
| 100 | { |
| 101 | Name = "codebase_index_watch", |
| 102 | Description = |
| 103 | "Включить/выключить watcher для авто-инкрементальной индексации (debounced). Важно: это best-effort фоновая синхронизация, не заменяет явный reindex.", |
| 104 | InputSchema = Schema(new |
| 105 | { |
| 106 | type = "object", |
| 107 | properties = new |
| 108 | { |
| 109 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 110 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Определяет область watcher (одна БД на (workspace_root, solution_path))." }, |
| 111 | enabled = new { type = "boolean", description = "true — включить watcher, false — выключить." }, |
| 112 | debounce_ms = new { type = "integer", description = "Опционально: debounce в миллисекундах (по умолчанию 750)." }, |
| 113 | }, |
| 114 | required = new[] { "workspace_path", "enabled" }, |
| 115 | }), |
| 116 | }, |
| 117 | new Tool |
| 118 | { |
| 119 | Name = "codebase_index_verify", |
| 120 | Description = |
| 121 | "Анти-галлюцинации: проверить список идентификаторов через индекс (FTS) и вернуть exists/missing + подсказки похожих (prefix search).", |
| 122 | InputSchema = Schema(new |
| 123 | { |
| 124 | type = "object", |
| 125 | properties = new |
| 126 | { |
| 127 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 128 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Scope: одна БД на (workspace_root, solution_path)." }, |
| 129 | identifiers = new { type = "array", items = new { type = "string" }, description = "Список идентификаторов для проверки (методы/типы/свойства; можно с точками/дженериками)." }, |
| 130 | top_n = new { type = "integer", description = "Максимум попаданий на идентификатор (по умолчанию 5)." }, |
| 131 | suggestions = new { type = "integer", description = "Максимум подсказок на missing-идентификатор (по умолчанию 8)." }, |
| 132 | extensions = new { type = "array", items = new { type = "string" }, description = "Опционально: ограничить расширениями (по умолчанию ['.cs'])." }, |
| 133 | path_prefix = new { type = "string", description = "Опционально: ограничить попадания путями, начинающимися с префикса (unix-style)." }, |
| 134 | exclude_path_prefixes = new { type = "array", items = new { type = "string" }, description = "Опционально: исключить пути по префиксам (unix-style)." }, |
| 135 | }, |
| 136 | required = new[] { "workspace_path", "identifiers" }, |
| 137 | }), |
| 138 | }, |
| 139 | new Tool |
| 140 | { |
| 141 | Name = "codebase_index_draft_doc", |
| 142 | Description = |
| 143 | "Синтез документации (черновик): собрать markdown-скелет с выдержками из изменённых файлов (по индексу).", |
| 144 | InputSchema = Schema(new |
| 145 | { |
| 146 | type = "object", |
| 147 | properties = new |
| 148 | { |
| 149 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 150 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Scope: одна БД на (workspace_root, solution_path)." }, |
| 151 | title = new { type = "string", description = "Заголовок документа (например 'ADR: ...' или 'Design notes: ...')." }, |
| 152 | changed_paths = new { type = "array", items = new { type = "string" }, description = "Список путей (relative to workspace) для включения в черновик." }, |
| 153 | }, |
| 154 | required = new[] { "workspace_path", "title", "changed_paths" }, |
| 155 | }), |
| 156 | }, |
| 157 | new Tool |
| 158 | { |
| 159 | Name = "codebase_index_vec_reindex", |
| 160 | Description = |
| 161 | "Построить/обновить vec-индекс (эмбеддинги) для текущей SQLite базы. Требует semantic_enabled=true.", |
| 162 | InputSchema = Schema(new |
| 163 | { |
| 164 | type = "object", |
| 165 | properties = new |
| 166 | { |
| 167 | workspace_path = new { type = "string", description = "Корень workspace." }, |
| 168 | solution_path = new { type = "string", description = "Опционально: путь к .sln/.slnx/.csproj (relative to workspace или absolute). Scope: одна БД на (workspace_root, solution_path)." }, |
| 169 | }, |
| 170 | required = new[] { "workspace_path" }, |
| 171 | }), |
| 172 | }, |
| 173 | ]; |
| 174 | } |
| 175 | } |
| 176 | |