|
4 | 4 | using System.IO; |
5 | 5 | using System.Linq; |
6 | 6 | using System.Runtime.InteropServices; |
| 7 | +using System.Threading.Tasks; |
7 | 8 | using MCPForUnity.Editor.Clients; |
8 | 9 | using MCPForUnity.Editor.Helpers; |
9 | 10 | using MCPForUnity.Editor.Models; |
@@ -38,6 +39,9 @@ public class McpClientConfigSection |
38 | 39 |
|
39 | 40 | // Data |
40 | 41 | private readonly List<IMcpClientConfigurator> configurators; |
| 42 | + private readonly Dictionary<IMcpClientConfigurator, DateTime> lastStatusChecks = new(); |
| 43 | + private readonly HashSet<IMcpClientConfigurator> statusRefreshInFlight = new(); |
| 44 | + private static readonly TimeSpan StatusRefreshInterval = TimeSpan.FromSeconds(45); |
41 | 45 | private int selectedClientIndex = 0; |
42 | 46 |
|
43 | 47 | public VisualElement Root { get; private set; } |
@@ -105,33 +109,7 @@ public void UpdateClientStatus() |
105 | 109 | return; |
106 | 110 |
|
107 | 111 | var client = configurators[selectedClientIndex]; |
108 | | - MCPServiceLocator.Client.CheckClientStatus(client); |
109 | | - |
110 | | - clientStatusLabel.text = GetStatusDisplayString(client.Status); |
111 | | - clientStatusLabel.style.color = StyleKeyword.Null; |
112 | | - |
113 | | - clientStatusIndicator.RemoveFromClassList("configured"); |
114 | | - clientStatusIndicator.RemoveFromClassList("not-configured"); |
115 | | - clientStatusIndicator.RemoveFromClassList("warning"); |
116 | | - |
117 | | - switch (client.Status) |
118 | | - { |
119 | | - case McpStatus.Configured: |
120 | | - case McpStatus.Running: |
121 | | - case McpStatus.Connected: |
122 | | - clientStatusIndicator.AddToClassList("configured"); |
123 | | - break; |
124 | | - case McpStatus.IncorrectPath: |
125 | | - case McpStatus.CommunicationError: |
126 | | - case McpStatus.NoResponse: |
127 | | - clientStatusIndicator.AddToClassList("warning"); |
128 | | - break; |
129 | | - default: |
130 | | - clientStatusIndicator.AddToClassList("not-configured"); |
131 | | - break; |
132 | | - } |
133 | | - |
134 | | - configureButton.text = client.GetConfigureActionLabel(); |
| 112 | + RefreshClientStatus(client); |
135 | 113 | } |
136 | 114 |
|
137 | 115 | private string GetStatusDisplayString(McpStatus status) |
@@ -240,7 +218,8 @@ private void OnConfigureClicked() |
240 | 218 | try |
241 | 219 | { |
242 | 220 | MCPServiceLocator.Client.ConfigureClient(client); |
243 | | - UpdateClientStatus(); |
| 221 | + lastStatusChecks.Remove(client); |
| 222 | + RefreshClientStatus(client, forceImmediate: true); |
244 | 223 | UpdateManualConfiguration(); |
245 | 224 | } |
246 | 225 | catch (Exception ex) |
@@ -314,11 +293,123 @@ public void RefreshSelectedClient() |
314 | 293 | if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count) |
315 | 294 | { |
316 | 295 | var client = configurators[selectedClientIndex]; |
317 | | - MCPServiceLocator.Client.CheckClientStatus(client); |
318 | | - UpdateClientStatus(); |
| 296 | + RefreshClientStatus(client, forceImmediate: true); |
319 | 297 | UpdateManualConfiguration(); |
320 | 298 | UpdateClaudeCliPathVisibility(); |
321 | 299 | } |
322 | 300 | } |
| 301 | + |
| 302 | + private void RefreshClientStatus(IMcpClientConfigurator client, bool forceImmediate = false) |
| 303 | + { |
| 304 | + if (client is ClaudeCliMcpConfigurator) |
| 305 | + { |
| 306 | + RefreshClaudeCliStatus(client, forceImmediate); |
| 307 | + return; |
| 308 | + } |
| 309 | + |
| 310 | + if (forceImmediate || ShouldRefreshClient(client)) |
| 311 | + { |
| 312 | + MCPServiceLocator.Client.CheckClientStatus(client); |
| 313 | + lastStatusChecks[client] = DateTime.UtcNow; |
| 314 | + } |
| 315 | + |
| 316 | + ApplyStatusToUi(client); |
| 317 | + } |
| 318 | + |
| 319 | + private void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImmediate) |
| 320 | + { |
| 321 | + if (forceImmediate) |
| 322 | + { |
| 323 | + MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); |
| 324 | + lastStatusChecks[client] = DateTime.UtcNow; |
| 325 | + ApplyStatusToUi(client); |
| 326 | + return; |
| 327 | + } |
| 328 | + |
| 329 | + bool hasStatus = lastStatusChecks.ContainsKey(client); |
| 330 | + bool needsRefresh = forceImmediate || !hasStatus || ShouldRefreshClient(client); |
| 331 | + |
| 332 | + if (!hasStatus) |
| 333 | + { |
| 334 | + ApplyStatusToUi(client, showChecking: true); |
| 335 | + } |
| 336 | + else |
| 337 | + { |
| 338 | + ApplyStatusToUi(client); |
| 339 | + } |
| 340 | + |
| 341 | + if (needsRefresh && !statusRefreshInFlight.Contains(client)) |
| 342 | + { |
| 343 | + statusRefreshInFlight.Add(client); |
| 344 | + ApplyStatusToUi(client, showChecking: true); |
| 345 | + |
| 346 | + Task.Run(() => |
| 347 | + { |
| 348 | + MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); |
| 349 | + }).ContinueWith(_ => |
| 350 | + { |
| 351 | + EditorApplication.delayCall += () => |
| 352 | + { |
| 353 | + statusRefreshInFlight.Remove(client); |
| 354 | + lastStatusChecks[client] = DateTime.UtcNow; |
| 355 | + ApplyStatusToUi(client); |
| 356 | + }; |
| 357 | + }); |
| 358 | + } |
| 359 | + } |
| 360 | + |
| 361 | + private bool ShouldRefreshClient(IMcpClientConfigurator client) |
| 362 | + { |
| 363 | + if (!lastStatusChecks.TryGetValue(client, out var last)) |
| 364 | + { |
| 365 | + return true; |
| 366 | + } |
| 367 | + |
| 368 | + return (DateTime.UtcNow - last) > StatusRefreshInterval; |
| 369 | + } |
| 370 | + |
| 371 | + private void ApplyStatusToUi(IMcpClientConfigurator client, bool showChecking = false) |
| 372 | + { |
| 373 | + if (selectedClientIndex < 0 || selectedClientIndex >= configurators.Count) |
| 374 | + return; |
| 375 | + |
| 376 | + if (!ReferenceEquals(configurators[selectedClientIndex], client)) |
| 377 | + return; |
| 378 | + |
| 379 | + clientStatusIndicator.RemoveFromClassList("configured"); |
| 380 | + clientStatusIndicator.RemoveFromClassList("not-configured"); |
| 381 | + clientStatusIndicator.RemoveFromClassList("warning"); |
| 382 | + |
| 383 | + if (showChecking) |
| 384 | + { |
| 385 | + clientStatusLabel.text = "Checking..."; |
| 386 | + clientStatusLabel.style.color = StyleKeyword.Null; |
| 387 | + clientStatusIndicator.AddToClassList("warning"); |
| 388 | + configureButton.text = client.GetConfigureActionLabel(); |
| 389 | + return; |
| 390 | + } |
| 391 | + |
| 392 | + clientStatusLabel.text = GetStatusDisplayString(client.Status); |
| 393 | + clientStatusLabel.style.color = StyleKeyword.Null; |
| 394 | + |
| 395 | + switch (client.Status) |
| 396 | + { |
| 397 | + case McpStatus.Configured: |
| 398 | + case McpStatus.Running: |
| 399 | + case McpStatus.Connected: |
| 400 | + clientStatusIndicator.AddToClassList("configured"); |
| 401 | + break; |
| 402 | + case McpStatus.IncorrectPath: |
| 403 | + case McpStatus.CommunicationError: |
| 404 | + case McpStatus.NoResponse: |
| 405 | + clientStatusIndicator.AddToClassList("warning"); |
| 406 | + break; |
| 407 | + default: |
| 408 | + clientStatusIndicator.AddToClassList("not-configured"); |
| 409 | + break; |
| 410 | + } |
| 411 | + |
| 412 | + configureButton.text = client.GetConfigureActionLabel(); |
| 413 | + } |
323 | 414 | } |
324 | 415 | } |
0 commit comments