Forge
csharpdeeb25a2
1using System.Text.Json;
2using AgentClientProtocol;
3
4namespace CascadeIDE.Services.CursorAcp;
5
6/// <summary>
7/// Собирает <see cref="McpServer"/> для <see cref="ClientSideConnection.NewSessionAsync"/> из JSON настроек IDE
8/// (<see cref="Models.McpSettings.ExternalServersJson"/>): тот же массив, что для автономного режима, плюс опционально элементы в формате ACP (поле <c>type</c>).
9/// </summary>
10public static class CascadeAcpMcpServerCatalog
11{
12 /// <summary>Стабильное имя для автоподмешивания IDE MCP (ADR 0048 §7); при совпадении с записью пользователя — приоритет у пользователя.</summary>
13 public const string AutoInjectIdeMcpServerName = "cascade-ide";
14
15 private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web)
16 {
17 ReadCommentHandling = JsonCommentHandling.Skip,
18 AllowTrailingCommas = true,
19 };
20
21 /// <summary>
22 /// Пустой или битый JSON → пустой список (как раньше с <c>McpServers = []</c>).
23 /// </summary>
24 public static McpServer[] FromExternalServersJson(string? externalServersJson)
25 {
26 if (string.IsNullOrWhiteSpace(externalServersJson))
27 return [];
28
29 try
30 {
31 using var doc = JsonDocument.Parse(externalServersJson);
32 if (doc.RootElement.ValueKind != JsonValueKind.Array)
33 return [];
34
35 var list = new List<McpServer>();
36 foreach (var el in doc.RootElement.EnumerateArray())
37 {
38 if (el.ValueKind != JsonValueKind.Object)
39 continue;
40
41 if (el.TryGetProperty("enabled", out var enSkip) && enSkip.ValueKind == JsonValueKind.False)
42 continue;
43
44 if (el.TryGetProperty("type", out var typeEl)
45 && typeEl.ValueKind == JsonValueKind.String
46 && !string.IsNullOrWhiteSpace(typeEl.GetString()))
47 {
48 var server = JsonSerializer.Deserialize<McpServer>(el.GetRawText(), Json);
49 if (server is not null)
50 list.Add(server);
51 continue;
52 }
53
54 var built = TryBuildFromAutonomousSpec(el);
55 if (built is not null)
56 list.Add(built);
57 }
58
59 return list.ToArray();
60 }
61 catch
62 {
63 return [];
64 }
65 }
66
67 /// <summary>
68 /// Список для <c>session/new</c>: <paramref name="externalServersJson"/> плюс при необходимости stdio на текущий процесс IDE.
69 /// Если сервер с именем <see cref="AutoInjectIdeMcpServerName"/> уже есть у пользователя — автозапись не добавляется.
70 /// </summary>
71 public static McpServer[] MergeForAcpNewSession(string? externalServersJson, bool acpAutoInjectIdeMcp)
72 {
73 var user = FromExternalServersJson(externalServersJson);
74 if (!acpAutoInjectIdeMcp)
75 return user;
76
77 if (HasServerNamed(user, AutoInjectIdeMcpServerName))
78 return user;
79
80 var auto = TryCreateAutoIdeMcpStdioServer();
81 if (auto is null)
82 return user;
83
84 var merged = new McpServer[user.Length + 1];
85 merged[0] = auto;
86 Array.Copy(user, 0, merged, 1, user.Length);
87 return merged;
88 }
89
90 /// <summary>Текущий исполняемый файл + <c>--mcp-stdio</c> (как в MCP-PROTOCOL для Cursor).</summary>
91 public static StdioMcpServer? TryCreateAutoIdeMcpStdioServer()
92 {
93 var exe = Environment.ProcessPath;
94 if (string.IsNullOrWhiteSpace(exe))
95 return null;
96
97 return new StdioMcpServer
98 {
99 Name = AutoInjectIdeMcpServerName,
100 Command = exe,
101 Args = ["--mcp-stdio"],
102 Env = [],
103 };
104 }
105
106 private static bool HasServerNamed(McpServer[] servers, string name)
107 {
108 foreach (var s in servers)
109 {
110 if (string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase))
111 return true;
112 }
113
114 return false;
115 }
116
117 private static StdioMcpServer? TryBuildFromAutonomousSpec(JsonElement el)
118 {
119 var name = el.TryGetProperty("name", out var n) ? n.GetString() : null;
120 var command = el.TryGetProperty("command", out var c) ? c.GetString() : null;
121 if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(command))
122 return null;
123
124 if (!el.TryGetProperty("enabled", out var en) || en.ValueKind != JsonValueKind.True)
125 return null;
126
127 var args = new List<string>();
128 if (el.TryGetProperty("arguments", out var a) && a.ValueKind == JsonValueKind.Array)
129 {
130 foreach (var arg in a.EnumerateArray())
131 {
132 if (arg.ValueKind == JsonValueKind.String)
133 {
134 var s = arg.GetString();
135 if (s is not null)
136 args.Add(s);
137 }
138 }
139 }
140
141 var env = new List<EnvVariable>();
142 if (el.TryGetProperty("env", out var envEl) && envEl.ValueKind == JsonValueKind.Object)
143 {
144 foreach (var p in envEl.EnumerateObject())
145 {
146 if (p.Value.ValueKind != JsonValueKind.String)
147 continue;
148 var v = p.Value.GetString() ?? "";
149 env.Add(new EnvVariable { Name = p.Name, Value = v });
150 }
151 }
152
153 return new StdioMcpServer
154 {
155 Name = name,
156 Command = command,
157 Args = args.ToArray(),
158 Env = env.ToArray(),
159 };
160 }
161}
162
View only · write via MCP/CIDE