Skip to content

Commit 85c2e0f

Browse files
Dumbrisclaude
andauthored
fix(cli): read real upstream_stats keys in mcpproxy status server counts (#815)
'mcpproxy status' always printed 'Servers: 0 connected, 0 quarantined' because extractServerCounts read connected/quarantined/total while the daemon's GetStats builders emit connected_servers/total_servers (and no quarantined count at all). - extractServerCounts now reads connected_servers/quarantined_servers/ total_servers, falling back to the bare legacy keys for older daemons - both GetStats builders (stateview path in internal/server, manager fallback in internal/upstream) now emit quarantined_servers Found in v0.47.0-rc.4 release QA: live daemon had 14 connected + 2 quarantined; CLI showed 0/0. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent e6190f6 commit 85c2e0f

4 files changed

Lines changed: 70 additions & 20 deletions

File tree

cmd/mcpproxy/status_cmd.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,21 +269,38 @@ func collectStatusFromConfig(cfg *config.Config, socketPath, configPath string)
269269
func extractServerCounts(stats map[string]interface{}) *ServerCounts {
270270
counts := &ServerCounts{}
271271

272-
if v, ok := stats["connected"].(float64); ok {
273-
counts.Connected = int(v)
274-
}
275-
if v, ok := stats["quarantined"].(float64); ok {
276-
counts.Quarantined = int(v)
277-
}
278-
if v, ok := stats["total"].(float64); ok {
279-
counts.Total = int(v)
272+
// The daemon emits connected_servers/quarantined_servers/total_servers
273+
// (see the GetStats builders in internal/server and internal/upstream);
274+
// bare connected/quarantined/total are accepted for older daemons.
275+
counts.Connected = statsInt(stats, "connected_servers", "connected")
276+
counts.Quarantined = statsInt(stats, "quarantined_servers", "quarantined")
277+
if v, ok := statsIntOK(stats, "total_servers", "total"); ok {
278+
counts.Total = v
280279
} else {
281280
counts.Total = counts.Connected + counts.Quarantined
282281
}
283282

284283
return counts
285284
}
286285

286+
// statsInt returns the first of the given keys present in stats as an int.
287+
func statsInt(stats map[string]interface{}, keys ...string) int {
288+
v, _ := statsIntOK(stats, keys...)
289+
return v
290+
}
291+
292+
func statsIntOK(stats map[string]interface{}, keys ...string) (int, bool) {
293+
for _, key := range keys {
294+
switch v := stats[key].(type) {
295+
case float64:
296+
return int(v), true
297+
case int:
298+
return v, true
299+
}
300+
}
301+
return 0, false
302+
}
303+
287304
// extractStatusUpdate pulls the `update` object out of the /api/v1/info
288305
// payload. Returns nil when the daemon did not report update state.
289306
func extractStatusUpdate(infoData map[string]interface{}) *StatusUpdateInfo {

cmd/mcpproxy/status_cmd_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,29 @@ func TestFormatDuration(t *testing.T) {
374374
}
375375

376376
func TestExtractServerCounts(t *testing.T) {
377+
// Real /api/v1/status upstream_stats shape (see internal/server/server.go
378+
// and internal/upstream/manager.go GetStats builders).
379+
stats := map[string]interface{}{
380+
"connected_servers": float64(14),
381+
"quarantined_servers": float64(2),
382+
"total_servers": float64(28),
383+
}
384+
385+
counts := extractServerCounts(stats)
386+
387+
if counts.Connected != 14 {
388+
t.Errorf("expected Connected=14, got %d", counts.Connected)
389+
}
390+
if counts.Quarantined != 2 {
391+
t.Errorf("expected Quarantined=2, got %d", counts.Quarantined)
392+
}
393+
if counts.Total != 28 {
394+
t.Errorf("expected Total=28, got %d", counts.Total)
395+
}
396+
}
397+
398+
func TestExtractServerCountsLegacyKeys(t *testing.T) {
399+
// Older daemons emitted bare connected/quarantined/total keys.
377400
stats := map[string]interface{}{
378401
"connected": float64(5),
379402
"quarantined": float64(2),
@@ -395,8 +418,8 @@ func TestExtractServerCounts(t *testing.T) {
395418

396419
func TestExtractServerCountsNoTotal(t *testing.T) {
397420
stats := map[string]interface{}{
398-
"connected": float64(3),
399-
"quarantined": float64(1),
421+
"connected_servers": float64(3),
422+
"quarantined_servers": float64(1),
400423
}
401424

402425
counts := extractServerCounts(stats)

internal/server/server.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ func (s *Server) GetUpstreamStats() map[string]interface{} {
733733

734734
connectedCount := 0
735735
connectingCount := 0
736+
quarantinedCount := 0
736737
totalTools := 0
737738

738739
serverStats := make(map[string]interface{}, len(snapshot.Servers))
@@ -819,17 +820,21 @@ func (s *Server) GetUpstreamStats() map[string]interface{} {
819820
if connecting {
820821
connectingCount++
821822
}
823+
if status.Quarantined {
824+
quarantinedCount++
825+
}
822826
totalTools += status.ToolCount
823827

824828
serverStats[name] = entry
825829
}
826830

827831
return map[string]interface{}{
828-
"connected_servers": connectedCount,
829-
"connecting_servers": connectingCount,
830-
"total_servers": len(snapshot.Servers),
831-
"servers": serverStats,
832-
"total_tools": totalTools,
832+
"connected_servers": connectedCount,
833+
"connecting_servers": connectingCount,
834+
"quarantined_servers": quarantinedCount,
835+
"total_servers": len(snapshot.Servers),
836+
"servers": serverStats,
837+
"total_tools": totalTools,
833838
}
834839
}
835840
}

internal/upstream/manager.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,7 @@ func (m *Manager) GetStats() map[string]interface{} {
15261526
// Now process clients without holding lock to avoid deadlock
15271527
connectedCount := 0
15281528
connectingCount := 0
1529+
quarantinedCount := 0
15291530
serverStatus := make(map[string]interface{})
15301531

15311532
for id, client := range clientsCopy {
@@ -1542,6 +1543,9 @@ func (m *Manager) GetStats() map[string]interface{} {
15421543
name, url, protocol := "", "", ""
15431544
if cfg := client.GetConfig(); cfg != nil {
15441545
name, url, protocol = cfg.Name, cfg.URL, cfg.Protocol
1546+
if cfg.Quarantined {
1547+
quarantinedCount++
1548+
}
15451549
}
15461550

15471551
status := map[string]interface{}{
@@ -1595,11 +1599,12 @@ func (m *Manager) GetStats() map[string]interface{} {
15951599
totalTools := m.GetTotalToolCount()
15961600

15971601
return map[string]interface{}{
1598-
"connected_servers": connectedCount,
1599-
"connecting_servers": connectingCount,
1600-
"total_servers": totalCount,
1601-
"servers": serverStatus,
1602-
"total_tools": totalTools,
1602+
"connected_servers": connectedCount,
1603+
"connecting_servers": connectingCount,
1604+
"quarantined_servers": quarantinedCount,
1605+
"total_servers": totalCount,
1606+
"servers": serverStatus,
1607+
"total_tools": totalTools,
16031608
}
16041609
}
16051610

0 commit comments

Comments
 (0)