Branch: 013-structured-server-state | Date: 2025-12-16 | Spec: spec.md
Depends On: #192 (Unified Health Status) - merged to main
Make Health the single source of truth for per-server issues. Extend Health with new actions (set_secret, configure). Refactor Doctor() to aggregate from Health. Update UI to navigate to fix locations instead of showing CLI hints.
Language/Version: Go 1.24.0 Primary Dependencies: mcp-go, zap, chi, Vue 3/TypeScript Testing: go test, ./scripts/test-api-e2e.sh
| Principle | Status | Notes |
|---|---|---|
| I. Performance at Scale | PASS | Health already calculated; Diagnostics aggregates existing data |
| II. Actor-Based Concurrency | PASS | No new goroutines; uses existing state |
| V. Test-Driven Development | PASS | Update existing tests for new actions |
Gate Result: PASS
# Backend (Go)
internal/
├── health/
│ ├── constants.go # Add ActionSetSecret, ActionConfigure
│ └── calculator.go # Add missing secret/OAuth config detection
├── management/
│ └── diagnostics.go # Refactor Doctor() to aggregate from Health
└── upstream/
└── manager.go # Populate MissingSecret, OAuthConfigErr in input
# CLI (Go)
cmd/mcpproxy/
└── upstream_cmd.go # Add set_secret, configure action hints
# Frontend (Vue)
frontend/src/
├── components/
│ └── ServerCard.vue # Add set_secret, configure action handlers
└── views/
└── Dashboard.vue # Remove duplicate banner, add navigation
# Tests
internal/
├── health/calculator_test.go # Add tests for new actions
└── management/diagnostics_test.go # Update for aggregation
-
Add constants to
internal/health/constants.go:ActionSetSecret = "set_secret" ActionConfigure = "configure"
-
Add fields to
HealthCalculatorInput:MissingSecret string // Secret name if unresolved OAuthConfigErr string // OAuth config error message
-
Update
CalculateHealth()priority:- After admin state checks
- Before connection state checks
- Check
MissingSecret→ returnset_secretaction - Check
OAuthConfigErr→ returnconfigureaction
-
Update
internal/upstream/manager.goto populate new input fields
- Remove independent detection logic from
Doctor() - Iterate servers, switch on
Health.Action:case "restart": → diag.UpstreamErrors case "login": → diag.OAuthRequired case "configure": → diag.OAuthIssues case "set_secret": → aggregate by secret name
- Keep system-level checks (Docker status)
- Update tests
-
Add action handlers in
ServerCard.vue:case 'set_secret': router.push('/secrets') case 'configure': router.push(`/servers/${server.name}?tab=config`)
-
Update
Dashboard.vue:- Remove "System Diagnostics" banner (lines 3-33)
- Add action handlers to "Servers Needing Attention" for new actions
-
Update TypeScript types if needed (add new action values)
- Update
cmd/mcpproxy/upstream_cmd.gooutputServers():case "set_secret": actionHint = fmt.Sprintf("Set %s", health.Detail) case "configure": actionHint = "Edit config"
# Unit tests
go test ./internal/health/... -v
go test ./internal/management/... -v
# E2E tests
./scripts/test-api-e2e.sh
# Frontend
cd frontend && npm run build && npm run test