11using System . Text . Json . Nodes ;
22using Microsoft . Extensions . Logging ;
33using Polly ;
4+ using Polly . CircuitBreaker ;
45using Polly . Registry ;
56
67namespace GsdOrchestrator . Mcp ;
78
89/// <summary>
9- /// Wraps IMcpClient with resilience (Polly retry), logging, and secondary rate limit
10- /// special-casing. Inject and use this instead of calling IMcpClient directly.
10+ /// Wraps IMcpClient with resilience (Polly retry + circuit breaker ), logging, and secondary
11+ /// rate limit special-casing. Inject and use this instead of calling IMcpClient directly.
1112/// </summary>
1213public sealed class McpToolDispatcher
1314{
@@ -28,21 +29,30 @@ public McpToolDispatcher(
2829 public async Task < McpToolResult > CallAsync (
2930 string tool , JsonObject args , CancellationToken ct = default )
3031 {
31- return await _pipeline . ExecuteAsync ( async token =>
32+ try
3233 {
33- try
34+ return await _pipeline . ExecuteAsync ( async token =>
3435 {
35- return await _client . CallToolAsync ( tool , args , token ) ;
36- }
37- catch ( McpException ex ) when ( ex . IsSecondaryRateLimit )
38- {
39- // Secondary rate limit: MUST wait at least 60 seconds — bypass Polly retry
40- _logger . LogWarning (
41- "Secondary rate limit on '{Tool}'. Waiting 65s before retry." , tool ) ;
42- await Task . Delay ( TimeSpan . FromSeconds ( 65 ) , token ) ;
43- return await _client . CallToolAsync ( tool , args , token ) ;
44- }
45- } , ct ) ;
36+ try
37+ {
38+ return await _client . CallToolAsync ( tool , args , token ) ;
39+ }
40+ catch ( McpException ex ) when ( ex . IsSecondaryRateLimit )
41+ {
42+ // Secondary rate limit: MUST wait at least 60 seconds — bypass Polly retry
43+ _logger . LogWarning (
44+ "Secondary rate limit on '{Tool}'. Waiting 65s before retry." , tool ) ;
45+ await Task . Delay ( TimeSpan . FromSeconds ( 65 ) , token ) ;
46+ return await _client . CallToolAsync ( tool , args , token ) ;
47+ }
48+ } , ct ) ;
49+ }
50+ catch ( BrokenCircuitException )
51+ {
52+ throw new McpException (
53+ "MCP circuit breaker open — too many consecutive failures" ,
54+ isTransient : false ) ;
55+ }
4656 }
4757
4858 public Task < IReadOnlyList < McpTool > > ListToolsAsync ( CancellationToken ct = default ) =>
0 commit comments