Forge
csharpc0b81713
1using System.Text.Json;
2using AgentNotesMcp.Status;
3using ModelContextProtocol.Protocol;
4using Tool = ModelContextProtocol.Protocol.Tool;
5
6/// <summary>Каталог MCP-тулов. Согласован с <c>mcp-tools.manifest.json</c> и <c>docs/MCP-TOOLS.md</c> (генерация: <c>tools/ExportMcpManifest</c>, тесты <c>McpToolManifestTests</c>, <c>McpToolsDocTests</c>).</summary>
7internal static class ToolCatalog
8{
9 private static JsonElement Schema(object schema) => JsonSerializer.SerializeToElement(schema);
10
11 internal static List<Tool> Build() =>
12 [
13 new()
14 {
15 Name = "memory_health",
16 Description = "Быстрый health-check памяти: размер hot-context, обязательные секции, предупреждения по бюджету и рекомендации по compaction. Резолв scope: active_scope (если передан) → workspace-scope-map-v1 (по workspace_path) → опционально current: в секции active-scope (легаси) → иначе встроенный fallback (door-to-singularity).",
17 InputSchema = Schema(new
18 {
19 type = "object",
20 properties = new
21 {
22 workspace_path = new { type = "string", description = "Каталог workspace." },
23 active_scope = new { type = "string", description = "Опционально: door-to-singularity | portal | harvester | imc | mixed (алиасы: dts, cp; ptl→portal; hrv→harvester; legacy: current-projects)." }
24 },
25 required = new[] { "workspace_path" }
26 })
27 },
28 new()
29 {
30 Name = "route_context",
31 Description = "Подобрать релевантные секции из agent-notes.md по запросу и собрать компактный context-пакет (router-first). Не индексирует файлы knowledge/ — длинные playbook/kb подгружать отдельно через read_knowledge_file (напр. playbook-multi-project-context-v1.md, index-knowledge-router-v1.md). Резолв scope: active_scope (если передан) → workspace-scope-map-v1 (по workspace_path) → опционально current: в секции active-scope (легаси) → иначе встроенный fallback (door-to-singularity).",
32 InputSchema = Schema(new
33 {
34 type = "object",
35 properties = new
36 {
37 workspace_path = new { type = "string", description = "Каталог workspace." },
38 query = new { type = "string", description = "Поисковый запрос или задача для маршрутизации контекста." },
39 active_scope = new { type = "string", description = "Опционально: door-to-singularity | portal | harvester | imc | mixed (алиасы: dts, cp; ptl→portal; hrv→harvester; legacy: current-projects)." },
40 max_sections = new { type = "integer", description = "Максимум секций в ответе (по умолчанию 5)." },
41 max_chars = new { type = "integer", description = "Бюджет символов для assembled_context (по умолчанию 12000)." }
42 },
43 required = new[] { "workspace_path", "query" }
44 })
45 },
46 new()
47 {
48 Name = "write_agent_notes",
49 Description = "Записать заметки агента (полная замена файла). Путь hot-файла: primary knowledge root из --config → {корень}/agent-notes.md; иначе workspace_path/.cascade-ide/agent-notes.md. ВНИМАНИЕ: перезаписывает файл целиком; для добавления блока без риска стереть остальное используйте append_agent_notes.",
50 InputSchema = Schema(new
51 {
52 type = "object",
53 properties = new
54 {
55 workspace_path = new { type = "string", description = "Каталог workspace (например корень проекта в Cursor). Нужен для резолва scope; hot-файл — из --config (primary root) или workspace_path/.cascade-ide/agent-notes.md." },
56 content = new { type = "string", description = "Полное содержимое заметок (перезаписывает файл целиком)." }
57 },
58 required = new[] { "workspace_path", "content" }
59 })
60 },
61 new()
62 {
63 Name = "append_agent_notes",
64 Description = "Добавить блок в конец заметок агента без перезаписи файла. Путь hot-файла: primary knowledge root из --config → {корень}/agent-notes.md; иначе workspace_path/.cascade-ide/agent-notes.md.",
65 InputSchema = Schema(new
66 {
67 type = "object",
68 properties = new
69 {
70 workspace_path = new { type = "string", description = "Каталог workspace (тот же, что при read/write)." },
71 content = new { type = "string", description = "Текст блока для добавления в конец файла (перед ним добавляется перевод строки, если нужно)." }
72 },
73 required = new[] { "workspace_path", "content" }
74 })
75 },
76 new()
77 {
78 Name = "read_agent_notes",
79 Description = "Прочитать заметки агента. Путь hot-файла: primary knowledge root из --config → {корень}/agent-notes.md; иначе workspace_path/.cascade-ide/agent-notes.md. Возвращает содержимое или пустую строку.",
80 InputSchema = Schema(new
81 {
82 type = "object",
83 properties = new
84 {
85 workspace_path = new { type = "string", description = "Каталог workspace (тот же, что при записи)." }
86 },
87 required = new[] { "workspace_path" }
88 })
89 },
90 new()
91 {
92 Name = "read_hot_context",
93 Description = "Прочитать только горячий контекст (L0/L1) без загрузки архивного хвоста. Резолв scope: active_scope (если передан) → workspace-scope-map-v1 (по workspace_path) → опционально current: в секции active-scope (легаси) → иначе встроенный fallback (door-to-singularity).",
94 InputSchema = Schema(new
95 {
96 type = "object",
97 properties = new
98 {
99 workspace_path = new { type = "string", description = "Каталог workspace." },
100 active_scope = new { type = "string", description = "Опционально: door-to-singularity | portal | harvester | imc | mixed (алиасы: dts, cp; ptl→portal; hrv→harvester; legacy: current-projects)." }
101 },
102 required = new[] { "workspace_path" }
103 })
104 },
105 new()
106 {
107 Name = "upsert_agent_notes_section",
108 Description = "Точечно вставить/обновить секцию заметок по section_id без полной перезаписи файла. Секция оформляется маркерами <!-- section:ID --> ... <!-- /section:ID -->. Путь hot-файла — как у read_agent_notes.",
109 InputSchema = Schema(new
110 {
111 type = "object",
112 properties = new
113 {
114 workspace_path = new { type = "string", description = "Каталог workspace (тот же, что при read/write)." },
115 section_id = new { type = "string", description = "Стабильный ID секции (латиница/цифры/._-)." },
116 content = new { type = "string", description = "Новое содержимое секции." }
117 },
118 required = new[] { "workspace_path", "section_id", "content" }
119 })
120 },
121 new()
122 {
123 Name = "list_agent_notes_revisions",
124 Description = "Список ревизий заметок для rollback. Ревизии хранятся рядом с файлом заметок в подпапке .revisions.",
125 InputSchema = Schema(new
126 {
127 type = "object",
128 properties = new
129 {
130 workspace_path = new { type = "string", description = "Каталог workspace (тот же, что при read/write)." },
131 limit = new { type = "integer", description = "Максимум ревизий в ответе (по умолчанию 20)." }
132 },
133 required = new[] { "workspace_path" }
134 })
135 },
136 new()
137 {
138 Name = "rollback_agent_notes",
139 Description = "Откатить заметки к выбранной ревизии (или к последней, если revision_file не задан). Текущее содержимое перед откатом тоже сохраняется как ревизия.",
140 InputSchema = Schema(new
141 {
142 type = "object",
143 properties = new
144 {
145 workspace_path = new { type = "string", description = "Каталог workspace (тот же, что при read/write)." },
146 revision_file = new { type = "string", description = "Имя файла ревизии из list_agent_notes_revisions (опционально)." }
147 },
148 required = new[] { "workspace_path" }
149 })
150 },
151 new()
152 {
153 Name = "search_agent_notes",
154 Description = "Поиск по заметкам с возвратом совпавших строк и номеров строк.",
155 InputSchema = Schema(new
156 {
157 type = "object",
158 properties = new
159 {
160 workspace_path = new { type = "string", description = "Каталог workspace (тот же, что при read/write)." },
161 query = new { type = "string", description = "Подстрока для поиска (case-insensitive)." },
162 head_limit = new { type = "integer", description = "Сколько совпадений вернуть (по умолчанию 20)." }
163 },
164 required = new[] { "workspace_path", "query" }
165 })
166 },
167 new()
168 {
169 Name = "extract_from_archive",
170 Description = "Точечное извлечение фактов из архивной ревизии без чтения всего файла.",
171 InputSchema = Schema(new
172 {
173 type = "object",
174 properties = new
175 {
176 workspace_path = new { type = "string", description = "Каталог workspace." },
177 query = new { type = "string", description = "Подстрока для поиска в архивной ревизии." },
178 revision_file = new { type = "string", description = "Имя ревизии. Если не задано — берется последняя." },
179 head_limit = new { type = "integer", description = "Сколько совпадений вернуть (по умолчанию 10)." },
180 context_lines = new { type = "integer", description = "Контекст строк вокруг совпадения (по умолчанию 2)." }
181 },
182 required = new[] { "workspace_path", "query" }
183 })
184 },
185 new()
186 {
187 Name = "compact_hot_context",
188 Description = "Ужать hot-context: удалить дубли секций, нормализовать формат. По умолчанию preview, apply=true для записи.",
189 InputSchema = Schema(new
190 {
191 type = "object",
192 properties = new
193 {
194 workspace_path = new { type = "string", description = "Каталог workspace." },
195 apply = new { type = "boolean", description = "true — применить изменения, false — только превью." }
196 },
197 required = new[] { "workspace_path" }
198 })
199 },
200 new()
201 {
202 Name = "write_knowledge_file",
203 Description = "Записать файл в каталог knowledge/ (полная замена). Перед записью текущая версия сохраняется в knowledge/.revisions/ (если save_revision=true). Запись только в primary; read-only roots (knowledge_root_id=group) отклоняются.",
204 InputSchema = Schema(new
205 {
206 type = "object",
207 properties = new
208 {
209 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
210 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
211 file_path = new { type = "string", description = "Относительный путь внутри knowledge/, например kb-music-acoustics-v1.md (без '..' и без абсолютного пути)." },
212 content = new { type = "string", description = "Полное содержимое файла." },
213 save_revision = new { type = "boolean", description = "Сохранить текущую версию в knowledge/.revisions/ перед записью (по умолчанию true)." }
214 },
215 required = new[] { "file_path", "content" }
216 })
217 },
218 new()
219 {
220 Name = "append_knowledge_file",
221 Description = "Добавить блок в конец файла в knowledge/ без перезаписи. Перед добавлением текущая версия сохраняется в knowledge/.revisions/ (если save_revision=true).",
222 InputSchema = Schema(new
223 {
224 type = "object",
225 properties = new
226 {
227 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
228 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
229 file_path = new { type = "string", description = "Относительный путь внутри knowledge/." },
230 content = new { type = "string", description = "Текст для добавления в конец файла (перед ним при необходимости добавляется перевод строки)." },
231 save_revision = new { type = "boolean", description = "Сохранить текущую версию в knowledge/.revisions/ перед добавлением (по умолчанию true)." }
232 },
233 required = new[] { "file_path", "content" }
234 })
235 },
236 new()
237 {
238 Name = "upsert_knowledge_section",
239 Description = "Вставить или обновить секцию в файле knowledge/ по section_id (маркеры <!-- section:ID --> ... <!-- /section:ID -->). Перед изменением текущая версия сохраняется в knowledge/.revisions/ (если save_revision=true).",
240 InputSchema = Schema(new
241 {
242 type = "object",
243 properties = new
244 {
245 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
246 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
247 file_path = new { type = "string", description = "Относительный путь внутри knowledge/, например index-knowledge-router-v1.md." },
248 section_id = new { type = "string", description = "Стабильный ID секции (A-Za-z0-9._-)." },
249 content = new { type = "string", description = "Новое содержимое секции." },
250 save_revision = new { type = "boolean", description = "Сохранить текущую версию в knowledge/.revisions/ перед изменением (по умолчанию true)." }
251 },
252 required = new[] { "file_path", "section_id", "content" }
253 })
254 },
255 new()
256 {
257 Name = "delete_knowledge_file",
258 Description = "Удалить файл из каталога knowledge/. file_path — относительный путь (без '..'). Если файла нет — NO_CHANGES.",
259 InputSchema = Schema(new
260 {
261 type = "object",
262 properties = new
263 {
264 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
265 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
266 file_path = new { type = "string", description = "Относительный путь внутри knowledge/, например mcp-test-irl.md." }
267 },
268 required = new[] { "file_path" }
269 })
270 },
271 new()
272 {
273 Name = "delete_knowledge_section",
274 Description = "Удалить секцию из файла knowledge/ по section_id (блок между <!-- section:ID --> и <!-- /section:ID -->). Если секции нет — NO_CHANGES.",
275 InputSchema = Schema(new
276 {
277 type = "object",
278 properties = new
279 {
280 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
281 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
282 file_path = new { type = "string", description = "Относительный путь внутри knowledge/." },
283 section_id = new { type = "string", description = "ID секции для удаления (A-Za-z0-9._-)." }
284 },
285 required = new[] { "file_path", "section_id" }
286 })
287 },
288 new()
289 {
290 Name = "read_knowledge_file",
291 Description = "Прочитать файл из каталога knowledge/. Корень: knowledge_path, knowledge_root_id (group, …) или primary из --config. Возвращает содержимое или пустую строку. Опционально offset (1-based) и limit. Для протоколов: playbook-multi-project-context-v1.md, index-knowledge-router-v1.md (route_context их не подставляет автоматически).",
292 InputSchema = Schema(new
293 {
294 type = "object",
295 properties = new
296 {
297 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
298 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
299 file_path = new { type = "string", description = "Относительный путь внутри knowledge/, например kb-music-theory-fundamentals-v1.md." },
300 offset = new { type = "integer", description = "Опционально. Номер первой возвращаемой строки, нумерация с 1. Без offset и limit — весь файл." },
301 limit = new { type = "integer", description = "Опционально. Максимум строк в ответе (после offset). 0 = пусто. Без limit — до конца файла." }
302 },
303 required = new[] { "file_path" }
304 })
305 },
306 new()
307 {
308 Name = "list_knowledge_files",
309 Description = "Список файлов в каталоге knowledge/ (без .revisions). Опционально subdir — подкаталог (например work). Возвращает path, size_bytes, modified_utc.",
310 InputSchema = Schema(new
311 {
312 type = "object",
313 properties = new
314 {
315 knowledge_path = new { type = "string", description = "Корень репозитория knowledge (каталог с подпапкой knowledge/). Опционально: primary из --config. Не задавать вместе с knowledge_root_id." },
316 knowledge_root_id = new { type = "string", description = "Опционально. id из [knowledge.roots] или [[knowledge.read_only]] (напр. group). Чтение — любой корень; запись — только primary (user)." },
317 subdir = new { type = "string", description = "Подкаталог внутри knowledge/ (пусто = весь knowledge/). Например work." }
318 },
319 required = Array.Empty<string>()
320 })
321 }
322 ];
323
324 internal static IReadOnlyList<AgentNotesStatusSnapshot.ToolSummary> ListSummaries() =>
325 Build()
326 .Select(t => new AgentNotesStatusSnapshot.ToolSummary(t.Name, t.Description ?? ""))
327 .ToArray();
328}
329
View only · write via MCP/CIDE