| 1 | using System.Text.Json; |
| 2 | |
| 3 | namespace RoslynMcp.Mcp; |
| 4 | |
| 5 | /// <summary>JSON-схемы параметров инструментов MCP.</summary> |
| 6 | public static class ToolSchemas |
| 7 | { |
| 8 | private static readonly string[] RequiredFilePath = ["file_path"]; |
| 9 | private static readonly string[] RequiredFilePathLineColumn = ["file_path", "line", "column"]; |
| 10 | private static readonly string[] RequiredSolutionFilePathLineColumn = ["solution_or_project_path", "file_path", "line", "column"]; |
| 11 | private static readonly string[] RequiredSolutionFilePathLineColumnNewName = ["solution_or_project_path", "file_path", "line", "column", "new_name"]; |
| 12 | private static readonly string[] RequiredSolutionFilePathLineColumnActionIndex = ["solution_or_project_path", "file_path", "line", "column", "action_index"]; |
| 13 | private static readonly string[] RequiredSolutionOrProjectPath = ["solution_or_project_path"]; |
| 14 | private static readonly string[] RequiredSolutionFilePathLineColumnMemberNamesOutPath = ["solution_or_project_path", "file_path", "line", "column", "member_names", "output_file_path"]; |
| 15 | private static readonly string[] RequiredSolutionFilePathMode = ["solution_or_project_path", "file_path", "mode"]; |
| 16 | private static readonly string[] RequiredSolutionFilePathSymbolName = ["solution_or_project_path", "file_path", "symbol_name"]; |
| 17 | |
| 18 | private static JsonElement ToElement(object schema) => |
| 19 | JsonSerializer.SerializeToElement(schema); |
| 20 | |
| 21 | public static JsonElement Ping() => |
| 22 | ToElement(new { type = "object", properties = new { } }); |
| 23 | |
| 24 | public static JsonElement GetDocumentSymbols() => |
| 25 | ToElement(new |
| 26 | { |
| 27 | type = "object", |
| 28 | properties = new |
| 29 | { |
| 30 | file_path = new { type = "string", description = "Путь к .cs файлу." } |
| 31 | }, |
| 32 | required = RequiredFilePath |
| 33 | }); |
| 34 | |
| 35 | public static JsonElement GetSymbolAtPosition() => |
| 36 | ToElement(new |
| 37 | { |
| 38 | type = "object", |
| 39 | properties = new |
| 40 | { |
| 41 | file_path = new { type = "string", description = "Путь к .cs файлу." }, |
| 42 | line = new { type = "integer", description = "Номер строки (1-based)." }, |
| 43 | column = new { type = "integer", description = "Номер столбца (1-based)." }, |
| 44 | solution_or_project_path = new { type = "string", description = "Опционально: путь к .sln или .csproj — тогда в ответе будет Qualified (полное имя символа)." } |
| 45 | }, |
| 46 | required = RequiredFilePathLineColumn |
| 47 | }); |
| 48 | |
| 49 | public static JsonElement FindUsages() => |
| 50 | ToElement(new |
| 51 | { |
| 52 | type = "object", |
| 53 | properties = new |
| 54 | { |
| 55 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj (.slnx не поддерживается)." }, |
| 56 | file_path = new { type = "string", description = "Путь к .cs файлу." }, |
| 57 | line = new { type = "integer", description = "Строка (1-based). Позиция на идентификаторе символа (имя метода/класса/свойства), не на типе — иначе найдёт ссылки на тип." }, |
| 58 | column = new { type = "integer", description = "Столбец (1-based)." } |
| 59 | }, |
| 60 | required = RequiredSolutionFilePathLineColumn |
| 61 | }); |
| 62 | |
| 63 | public static JsonElement GoToDefinition() => |
| 64 | ToElement(new |
| 65 | { |
| 66 | type = "object", |
| 67 | properties = new |
| 68 | { |
| 69 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 70 | file_path = new { type = "string", description = "Путь к .cs файлу." }, |
| 71 | line = new { type = "integer", description = "Строка (1-based), позиция на идентификаторе символа." }, |
| 72 | column = new { type = "integer", description = "Столбец (1-based)." } |
| 73 | }, |
| 74 | required = RequiredSolutionFilePathLineColumn |
| 75 | }); |
| 76 | |
| 77 | public static JsonElement Rename() => |
| 78 | ToElement(new |
| 79 | { |
| 80 | type = "object", |
| 81 | properties = new |
| 82 | { |
| 83 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 84 | file_path = new { type = "string", description = "Путь к .cs файлу." }, |
| 85 | line = new { type = "integer", description = "Строка (1-based), позиция на идентификаторе символа." }, |
| 86 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 87 | new_name = new { type = "string", description = "Новое имя символа." }, |
| 88 | apply = new { type = "boolean", description = "true — записать изменения в файлы; false (по умолчанию) — только превью." }, |
| 89 | rename_in_comments = new { type = "boolean", description = "Переименовывать вхождения в комментариях (как в VS «Include comments»)." }, |
| 90 | rename_in_strings = new { type = "boolean", description = "Переименовывать вхождения в строковых литералах." }, |
| 91 | rename_overloads = new { type = "boolean", description = "Для метода — переименовать и перегрузки." }, |
| 92 | rename_file = new { type = "boolean", description = "Для типа — переименовать файл с объявлением (как в VS)." }, |
| 93 | rename_partial_type_files = new { type = "boolean", description = "После переименования топ-уровневого class/struct/interface: переименовать на диске все TypeName.cs и TypeName.*.cs в проекте в NewName.cs / NewName.*.cs (partial). После записи текста Roslyn." } |
| 94 | }, |
| 95 | required = RequiredSolutionFilePathLineColumnNewName |
| 96 | }); |
| 97 | |
| 98 | public static JsonElement GetCodeActions() => |
| 99 | ToElement(new |
| 100 | { |
| 101 | type = "object", |
| 102 | properties = new |
| 103 | { |
| 104 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 105 | file_path = new { type = "string", description = "Путь к .cs файлу." }, |
| 106 | line = new { type = "integer", description = "Строка начала (1-based)." }, |
| 107 | column = new { type = "integer", description = "Столбец начала (1-based)." }, |
| 108 | end_line = new { type = "integer", description = "Опционально. Строка конца выделения (1-based). Если заданы end_line и end_column — refactoring получает диапазон (например Extract method / Extract local function)." }, |
| 109 | end_column = new { type = "integer", description = "Опционально. Столбец конца выделения (1-based). Задавать вместе с end_line." } |
| 110 | }, |
| 111 | required = RequiredSolutionFilePathLineColumn |
| 112 | }); |
| 113 | |
| 114 | public static JsonElement ApplyCodeAction() => |
| 115 | ToElement(new |
| 116 | { |
| 117 | type = "object", |
| 118 | properties = new |
| 119 | { |
| 120 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 121 | file_path = new { type = "string", description = "Путь к .cs файлу." }, |
| 122 | line = new { type = "integer", description = "Строка (1-based), та же позиция, что при get_code_actions." }, |
| 123 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 124 | end_line = new { type = "integer", description = "Опционально. Та же end_line, что при get_code_actions, если использовался диапазон." }, |
| 125 | end_column = new { type = "integer", description = "Опционально. Та же end_column, что при get_code_actions." }, |
| 126 | action_index = new { type = "integer", description = "Индекс действия (0-based) из списка roslyn_get_code_actions." }, |
| 127 | fix_all_scope = new { type = "string", description = "Опционально. Scope применения: \"document\" | \"project\" | \"solution\" — Fix all в файле/проекте/решении. Только для code fixes с поддержкой Fix All." }, |
| 128 | constant_name = new { type = "string", description = "Опционально. Имя константы для действий с опциями (например Introduce constant). Если не задано, провайдер использует значение по умолчанию (например V)." }, |
| 129 | action_options = new { type = "object", description = "Опционально. JSON-объект опций для действий с диалогом (Extract interface, Extract base class и т.д.): ключи — имена свойств типа опций, значения — строки, числа, bool или массив строк (например member_names). См. REFACTORINGS.md." } |
| 130 | }, |
| 131 | required = RequiredSolutionFilePathLineColumnActionIndex |
| 132 | }); |
| 133 | |
| 134 | public static JsonElement GetDiagnostics() => |
| 135 | ToElement(new |
| 136 | { |
| 137 | type = "object", |
| 138 | properties = new |
| 139 | { |
| 140 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj. Предпочтительный способ проверки C# кода (неиспользуемые переменные, предупреждения) — вызывать этот тул вместо опоры только на ReadLints." }, |
| 141 | file_path = new { type = "string", description = "Опционально. Путь к .cs файлу — тогда только диагностики этого файла. Иначе — по всему решению/проекту." } |
| 142 | }, |
| 143 | required = RequiredSolutionOrProjectPath |
| 144 | }); |
| 145 | |
| 146 | public static JsonElement GetSolutionStructure() => |
| 147 | ToElement(new |
| 148 | { |
| 149 | type = "object", |
| 150 | properties = new |
| 151 | { |
| 152 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj. Если в репо только .slnx (формат не поддерживается): передай путь к главному .csproj (например UI или стартовый проект) — вернётся список всех подгруженных проектов (включая ссылки). Иначе: список проектов (имя и путь к .csproj) для передачи в остальные тулы." } |
| 153 | }, |
| 154 | required = RequiredSolutionOrProjectPath |
| 155 | }); |
| 156 | |
| 157 | public static JsonElement WorkspaceNavigationContext() => |
| 158 | ToElement(new |
| 159 | { |
| 160 | type = "object", |
| 161 | properties = new |
| 162 | { |
| 163 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj — загрузка через MSBuildWorkspace; список файлов берётся из документов solution (не из дерева Cascade IDE)." }, |
| 164 | file_path = new { type = "string", description = "Якорный файл (полный путь): должен входить в загруженное solution/project." }, |
| 165 | mode = new { type = "string", description = "related — список связанных файлов; subgraph — узлы и рёбра от якоря." }, |
| 166 | line = new { type = "integer", description = "Опционально. Строка (1-based), для подписи контекста в JSON." }, |
| 167 | column = new { type = "integer", description = "Опционально. Столбец (1-based)." }, |
| 168 | max_related = new { type = "integer", description = "Опционально. Лимит элементов в related (по умолчанию 32)." }, |
| 169 | max_nodes = new { type = "integer", description = "Опционально. Лимит узлов в subgraph (по умолчанию 12)." }, |
| 170 | max_edges = new { type = "integer", description = "Опционально. Лимит рёбер в subgraph (по умолчанию 24)." }, |
| 171 | include_kinds = new { type = "array", items = new { type = "string" }, description = "Опционально. Белый список видов связей (partial_peer, project_peer, …); перезаписывает include пресета." }, |
| 172 | exclude_kinds = new { type = "array", items = new { type = "string" }, description = "Опционально. Исключаемые виды; с пресетом объединяется по правилам merge." }, |
| 173 | preset = new { type = "string", description = "Опционально. Встроенный пресет (имена как в Cascade IDE: peers_only, no_namespace_noise, tests_and_peers, structure_only). Без .cascade/workspace.toml — только эти id." } |
| 174 | }, |
| 175 | required = RequiredSolutionFilePathMode |
| 176 | }); |
| 177 | |
| 178 | public static JsonElement SyncNamespaces() => |
| 179 | ToElement(new |
| 180 | { |
| 181 | type = "object", |
| 182 | properties = new |
| 183 | { |
| 184 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj (.slnx не поддерживается). После переименования/перемещения папок вызвать сначала с dry_run для превью." }, |
| 185 | project_path = new { type = "string", description = "Опционально. Для solution с несколькими проектами — путь к .csproj, чтобы синхронизировать только этот проект." }, |
| 186 | dry_run = new { type = "boolean", description = "Опционально. true — только отчёт о планируемых изменениях, без записи. Рекомендуется сначала dry_run, затем без dry_run для применения." } |
| 187 | }, |
| 188 | required = RequiredSolutionOrProjectPath |
| 189 | }); |
| 190 | |
| 191 | public static JsonElement ResolveBreakpoint() => |
| 192 | ToElement(new |
| 193 | { |
| 194 | type = "object", |
| 195 | properties = new |
| 196 | { |
| 197 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 198 | file_path = new { type = "string", description = "Путь к .cs файлу, в котором искать символ." }, |
| 199 | symbol_name = new { type = "string", description = "Имя символа: метод, конструктор, свойство. Для индексатора передать \"this\". Возвращается file:line первой исполняемой строки (место для брейкпоинта)." } |
| 200 | }, |
| 201 | required = RequiredSolutionFilePathSymbolName |
| 202 | }); |
| 203 | |
| 204 | public static JsonElement GenerateInterfaceFromClass() => |
| 205 | ToElement(new |
| 206 | { |
| 207 | type = "object", |
| 208 | properties = new |
| 209 | { |
| 210 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 211 | file_path = new { type = "string", description = "Путь к .cs файлу с классом." }, |
| 212 | line = new { type = "integer", description = "Строка (1-based): курсор на имени класса или внутри тела." }, |
| 213 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 214 | interface_name = new { type = "string", description = "Опционально. Имя интерфейса (по умолчанию I + имя класса)." }, |
| 215 | output_file_path = new { type = "string", description = "Опционально. Путь к .cs файлу для записи интерфейса. Иначе возвращается только текст." }, |
| 216 | member_names = new { type = "array", items = new { type = "string" }, description = "Опционально. Массив имён методов/свойств для включения в интерфейс. Если не задан — все public instance методы и свойства." } |
| 217 | }, |
| 218 | required = RequiredSolutionFilePathLineColumn |
| 219 | }); |
| 220 | |
| 221 | public static JsonElement GenerateBaseClassFromClass() => |
| 222 | ToElement(new |
| 223 | { |
| 224 | type = "object", |
| 225 | properties = new |
| 226 | { |
| 227 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 228 | file_path = new { type = "string", description = "Путь к .cs файлу с классом." }, |
| 229 | line = new { type = "integer", description = "Строка (1-based): курсор на имени класса или внутри тела." }, |
| 230 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 231 | base_class_name = new { type = "string", description = "Опционально. Имя абстрактного базового класса (по умолчанию ИмяКлассаBase)." }, |
| 232 | output_file_path = new { type = "string", description = "Опционально. Путь к .cs файлу для записи. Иначе возвращается только текст." }, |
| 233 | member_names = new { type = "array", items = new { type = "string" }, description = "Опционально. Массив имён методов/свойств/событий для переноса в базовый класс как abstract. Если не задан — все public instance." } |
| 234 | }, |
| 235 | required = RequiredSolutionFilePathLineColumn |
| 236 | }); |
| 237 | |
| 238 | public static JsonElement GenerateOverrides() => |
| 239 | ToElement(new |
| 240 | { |
| 241 | type = "object", |
| 242 | properties = new |
| 243 | { |
| 244 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 245 | file_path = new { type = "string", description = "Путь к .cs файлу с классом (с базовым типом)." }, |
| 246 | line = new { type = "integer", description = "Строка (1-based): курсор на имени класса или внутри тела." }, |
| 247 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 248 | member_names = new { type = "array", items = new { type = "string" }, description = "Опционально. Массив имён методов/свойств/событий базового типа для генерации override. Если не задан — все виртуальные/абстрактные, ещё не переопределённые." }, |
| 249 | insert_into_file = new { type = "boolean", description = "Опционально. true — вставить сгенерированные override в тело класса перед закрывающей скобкой; false (по умолчанию) — только вернуть текст для вставки вручную." } |
| 250 | }, |
| 251 | required = RequiredSolutionFilePathLineColumn |
| 252 | }); |
| 253 | |
| 254 | public static JsonElement GenerateConstructorFromMembers() => |
| 255 | ToElement(new |
| 256 | { |
| 257 | type = "object", |
| 258 | properties = new |
| 259 | { |
| 260 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 261 | file_path = new { type = "string", description = "Путь к .cs файлу с классом." }, |
| 262 | line = new { type = "integer", description = "Строка (1-based): курсор на имени класса или внутри тела." }, |
| 263 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 264 | member_names = new { type = "array", items = new { type = "string" }, description = "Опционально. Массив имён полей/свойств (с сеттером) для параметров конструктора и присвоений. Если не задан — все instance поля и свойства с set." }, |
| 265 | insert_into_file = new { type = "boolean", description = "Опционально. true — вставить конструктор в тело класса; false (по умолчанию) — только вернуть текст." } |
| 266 | }, |
| 267 | required = RequiredSolutionFilePathLineColumn |
| 268 | }); |
| 269 | |
| 270 | public static JsonElement GenerateEqualsGetHashCode() => |
| 271 | ToElement(new |
| 272 | { |
| 273 | type = "object", |
| 274 | properties = new |
| 275 | { |
| 276 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 277 | file_path = new { type = "string", description = "Путь к .cs файлу с классом." }, |
| 278 | line = new { type = "integer", description = "Строка (1-based): курсор на имени класса или внутри тела." }, |
| 279 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 280 | member_names = new { type = "array", items = new { type = "string" }, description = "Опционально. Массив имён полей/свойств для сравнения в Equals и GetHashCode. Если не задан — все instance поля и свойства." }, |
| 281 | insert_into_file = new { type = "boolean", description = "Опционально. true — вставить Equals/GetHashCode в тело класса; false (по умолчанию) — только вернуть текст." } |
| 282 | }, |
| 283 | required = RequiredSolutionFilePathLineColumn |
| 284 | }); |
| 285 | |
| 286 | public static JsonElement MoveMembersToPartialFile() => |
| 287 | ToElement(new |
| 288 | { |
| 289 | type = "object", |
| 290 | properties = new |
| 291 | { |
| 292 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 293 | file_path = new { type = "string", description = "Путь к исходному .cs файлу с типом." }, |
| 294 | line = new { type = "integer", description = "Строка (1-based): курсор на имени типа или внутри тела." }, |
| 295 | column = new { type = "integer", description = "Столбец (1-based)." }, |
| 296 | member_names = new { type = "array", items = new { type = "string" }, description = "Имена переносимых членов (методы, поля, свойства, вложенные типы). Конструктор: имя типа или .ctor; индексатор: this; все перегрузки с одним именем переносятся вместе." }, |
| 297 | output_file_path = new { type = "string", description = "Полный путь к новому .cs файлу (partial-часть). Не должен существовать при apply: true." }, |
| 298 | apply = new { type = "boolean", description = "false — только превью текста; true — записать изменения и добавить файл в проект (TryApplyChanges)." }, |
| 299 | add_dependent_upon = new { type = "boolean", description = "Опционально. После apply: true (по умолчанию) — добавить в .csproj Compile Update с DependentUpon на исходный файл; false — не трогать .csproj." } |
| 300 | }, |
| 301 | required = RequiredSolutionFilePathLineColumnMemberNamesOutPath |
| 302 | }); |
| 303 | |
| 304 | public static JsonElement SyncDependentUponPartials() => |
| 305 | ToElement(new |
| 306 | { |
| 307 | type = "object", |
| 308 | properties = new |
| 309 | { |
| 310 | solution_or_project_path = new { type = "string", description = "Путь к .sln или .csproj." }, |
| 311 | project_path = new { type = "string", description = "Опционально. Полный путь к одному .csproj — только этот проект." }, |
| 312 | dry_run = new { type = "boolean", description = "true — только отчёт; false — записать .csproj." } |
| 313 | }, |
| 314 | required = RequiredSolutionOrProjectPath |
| 315 | }); |
| 316 | } |
| 317 | |