From 13dcd0ab958b4617c0180d5194131fdae7aaaa63 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sat, 28 Feb 2026 18:21:47 +0200 Subject: [PATCH 1/2] Deprecate dead config params and wire activity retention - Wire activity retention config (activity_retention_days, activity_max_records, activity_cleanup_interval_min) to runtime by calling SetRetentionConfig() - Add deprecation detection for top_k, enable_tray, and features config fields - Surface deprecated config warnings in `mcpproxy doctor` output - Log deprecation warnings at startup - Mark TopK, EnableTray, Features as deprecated in config struct - Remove dead fields from hot-reload change detection - Remove dead params from docs and OpenAPI schema Co-Authored-By: Claude Opus 4.6 --- cmd/mcpproxy/doctor_cmd.go | 31 +++++++ docs/configuration.md | 10 +-- docs/configuration/config-file.md | 2 - docs/features/search-discovery.md | 6 +- docs/repository-detection.md | 1 - docs/setup.md | 3 - internal/config/config.go | 31 ++----- internal/config/config_test.go | 24 +---- internal/config/deprecated.go | 54 +++++++++++ internal/config/deprecated_test.go | 90 +++++++++++++++++++ internal/config/validation_test.go | 39 -------- internal/contracts/types.go | 24 +++-- internal/management/diagnostics.go | 13 +++ internal/management/diagnostics_test.go | 18 ++-- internal/management/service.go | 3 + internal/management/service_test.go | 62 ++++++------- internal/runtime/apply_config_restart_test.go | 14 +-- internal/runtime/config_hotreload.go | 8 -- internal/runtime/config_hotreload_test.go | 43 ++------- internal/runtime/runtime.go | 21 +++++ internal/server/mcp_test.go | 2 - .../server/quarantine_config_apply_test.go | 3 - internal/server/server.go | 9 +- oas/swagger.yaml | 8 -- 24 files changed, 297 insertions(+), 222 deletions(-) create mode 100644 internal/config/deprecated.go create mode 100644 internal/config/deprecated_test.go diff --git a/cmd/mcpproxy/doctor_cmd.go b/cmd/mcpproxy/doctor_cmd.go index 4fc28ef5..8e85cd4e 100644 --- a/cmd/mcpproxy/doctor_cmd.go +++ b/cmd/mcpproxy/doctor_cmd.go @@ -160,6 +160,9 @@ func outputDiagnostics(diag map[string]interface{}, info map[string]interface{}) fmt.Println("✅ All systems operational! No issues detected.") fmt.Println() + // Show deprecated config warnings even when no issues + displayDeprecatedConfigs(diag) + // Display security features status even when no issues fmt.Println("🔒 Security Features") fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") @@ -317,6 +320,9 @@ func outputDiagnostics(diag map[string]interface{}, info map[string]interface{}) fmt.Println() } + // Deprecated Configuration warnings + displayDeprecatedConfigs(diag) + fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") fmt.Println() fmt.Println("For more details, run: mcpproxy doctor --output=json") @@ -381,6 +387,31 @@ func sortArrayByServerName(arr []interface{}) { }) } +// displayDeprecatedConfigs shows deprecated configuration warnings in the doctor output. +func displayDeprecatedConfigs(diag map[string]interface{}) { + deprecatedConfigs := getArrayField(diag, "deprecated_configs") + if len(deprecatedConfigs) == 0 { + return + } + + fmt.Println("⚠️ Deprecated Configuration") + fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + for _, item := range deprecatedConfigs { + if cfgMap, ok := item.(map[string]interface{}); ok { + field := getStringField(cfgMap, "field") + message := getStringField(cfgMap, "message") + replacement := getStringField(cfgMap, "replacement") + + fmt.Printf("\n • %s\n", field) + fmt.Printf(" %s\n", message) + if replacement != "" { + fmt.Printf(" Suggestion: %s\n", replacement) + } + } + } + fmt.Println() +} + // displaySecurityFeaturesStatus shows the status of security features in the doctor output. func displaySecurityFeaturesStatus() { // Load config to check security feature settings diff --git a/docs/configuration.md b/docs/configuration.md index a9ff8f59..8c779766 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -70,7 +70,6 @@ MCPProxy looks for configuration in these locations (in order): ```json { - "enable_tray": true, "enable_socket": true, "tray_endpoint": "" } @@ -78,7 +77,6 @@ MCPProxy looks for configuration in these locations (in order): | Field | Type | Default | Description | |-------|------|---------|-------------| -| `enable_tray` | boolean | `true` | Enable system tray application | | `enable_socket` | boolean | `true` | Enable Unix socket (macOS/Linux) or named pipe (Windows) for secure local IPC between tray and core | | `tray_endpoint` | string | `""` | Override socket/pipe path (advanced, usually not needed) | @@ -86,7 +84,6 @@ MCPProxy looks for configuration in these locations (in order): ```json { - "top_k": 5, "tools_limit": 15, "tool_response_limit": 20000, "call_tool_timeout": "2m" @@ -95,7 +92,6 @@ MCPProxy looks for configuration in these locations (in order): | Field | Type | Default | Description | |-------|------|---------|-------------| -| `top_k` | integer | `5` | Number of top search results to return (1-100) | | `tools_limit` | integer | `15` | Maximum number of tools to return per request (1-1000) | | `tool_response_limit` | integer | `20000` | Maximum characters in tool responses (0 = unlimited) | | `call_tool_timeout` | string | `"2m"` | Timeout for tool calls (e.g., `"30s"`, `"2m"`, `"5m"`). **Note**: When using agents like Codex or Claude as MCP servers, you may need to increase this timeout significantly, even up to 10 minutes (`"10m"`), as these agents may require longer processing times for complex operations | @@ -667,7 +663,6 @@ See [Code Execution Documentation](code_execution/overview.md) for complete deta "enable_caching": true, "enable_async_storage": true, "enable_web_ui": true, - "enable_tray": true, "enable_debug_logging": false, "enable_contract_tests": false } @@ -728,10 +723,8 @@ Here's a complete configuration example with all major sections: { "listen": "127.0.0.1:8080", "data_dir": "~/.mcpproxy", - "enable_tray": true, "enable_socket": true, "api_key": "", - "top_k": 5, "tools_limit": 15, "tool_response_limit": 20000, "call_tool_timeout": "2m", @@ -832,7 +825,7 @@ Many configuration options can be overridden via environment variables: | `HEADLESS` | - | Run in headless mode | **Prefix rules:** -- General settings also accept the `MCPP_` prefix (hyphens become underscores), e.g., `MCPP_TOP_K`, `MCPP_TOOLS_LIMIT`, `MCPP_ENABLE_PROMPTS`. +- General settings also accept the `MCPP_` prefix (hyphens become underscores), e.g., `MCPP_TOOLS_LIMIT`, `MCPP_ENABLE_PROMPTS`. - TLS/listen/data have additional convenience overrides with the `MCPPROXY_` prefix as listed above. **Priority:** Environment variables > Config file > Defaults @@ -844,7 +837,6 @@ Many configuration options can be overridden via environment variables: MCPProxy validates configuration on startup. Common validation errors: - **Invalid listen address**: Must be `host:port` or `:port` format -- **Invalid top_k**: Must be between 1 and 100 - **Invalid tools_limit**: Must be between 1 and 1000 - **Missing server name**: Each server must have a unique name - **Invalid protocol**: Must be `stdio`, `http`, `sse`, `streamable-http`, or `auto` diff --git a/docs/configuration/config-file.md b/docs/configuration/config-file.md index dd91e1c0..f047614a 100644 --- a/docs/configuration/config-file.md +++ b/docs/configuration/config-file.md @@ -27,7 +27,6 @@ MCPProxy uses a JSON configuration file located at `~/.mcpproxy/mcp_config.json` "data_dir": "~/.mcpproxy", "api_key": "your-secret-api-key", "enable_socket": true, - "top_k": 5, "tools_limit": 15, "tool_response_limit": 20000, "enable_code_execution": false, @@ -62,7 +61,6 @@ MCPProxy uses a JSON configuration file located at `~/.mcpproxy/mcp_config.json` | Option | Type | Default | Description | |--------|------|---------|-------------| -| `top_k` | integer | `5` | Number of top results for tool search | | `tools_limit` | integer | `15` | Maximum tools to return in a single request | | `tool_response_limit` | integer | `20000` | Maximum characters in tool response | diff --git a/docs/features/search-discovery.md b/docs/features/search-discovery.md index 23bbb156..f57dba71 100644 --- a/docs/features/search-discovery.md +++ b/docs/features/search-discovery.md @@ -86,14 +86,12 @@ curl -H "X-API-Key: your-key" \ ```json { - "top_k": 5, "tools_limit": 15 } ``` | Option | Type | Default | Description | |--------|------|---------|-------------| -| `top_k` | integer | `5` | Default number of results | | `tools_limit` | integer | `15` | Maximum results per query | ### Index Location @@ -225,8 +223,8 @@ mcpproxy serve AI clients typically use MCPProxy's search in two ways: 1. **Direct Tool Call**: AI calls `retrieve_tools` to find relevant tools -2. **Automatic Discovery**: MCPProxy returns top-K tools matching the task context +2. **Automatic Discovery**: MCPProxy returns matching tools based on the task context -The `top_k` setting controls how many tools are suggested to the AI, balancing between: +The `tools_limit` setting controls how many tools are suggested to the AI, balancing between: - More tools = better coverage but higher token usage - Fewer tools = faster responses but may miss relevant tools diff --git a/docs/repository-detection.md b/docs/repository-detection.md index 9e8fbb55..7ee3aab9 100644 --- a/docs/repository-detection.md +++ b/docs/repository-detection.md @@ -71,7 +71,6 @@ Repository detection is enabled by default in new configurations: { "listen": ":8080", "data_dir": "", - "enable_tray": true, "debug_search": false, "check_server_repo": true, "mcpServers": [], diff --git a/docs/setup.md b/docs/setup.md index ab399892..b9f78e5e 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -92,10 +92,8 @@ MCPProxy looks for configuration in these locations (in order): { "listen": ":8080", "data_dir": "~/.mcpproxy", - "enable_tray": true, // Search & tool limits - "top_k": 5, "tools_limit": 15, "tool_response_limit": 20000, @@ -748,7 +746,6 @@ tail -f ~/Library/Logs/mcpproxy/main.log | grep -E "(github-server|oauth|error)" ```json { - "top_k": 10, // More search results "tools_limit": 25, // More tools per request "tool_response_limit": 50000 // Larger response limit } diff --git a/internal/config/config.go b/internal/config/config.go index 579a8bf5..ac9111fb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -51,10 +51,12 @@ type Config struct { TrayEndpoint string `json:"tray_endpoint,omitempty" mapstructure:"tray-endpoint"` // Tray endpoint override (unix:// or npipe://) EnableSocket bool `json:"enable_socket" mapstructure:"enable-socket"` // Enable Unix socket/named pipe for local IPC (default: true) DataDir string `json:"data_dir" mapstructure:"data-dir"` - EnableTray bool `json:"enable_tray" mapstructure:"tray"` + // Deprecated: EnableTray is unused and has no runtime effect. Kept for backward compatibility. + EnableTray bool `json:"enable_tray,omitempty" mapstructure:"tray"` DebugSearch bool `json:"debug_search" mapstructure:"debug-search"` Servers []*ServerConfig `json:"mcpServers" mapstructure:"servers"` - TopK int `json:"top_k" mapstructure:"top-k"` + // Deprecated: TopK is superseded by ToolsLimit and has no runtime effect. Kept for backward compatibility. + TopK int `json:"top_k,omitempty" mapstructure:"top-k"` ToolsLimit int `json:"tools_limit" mapstructure:"tools-limit"` ToolResponseLimit int `json:"tool_response_limit" mapstructure:"tool-response-limit"` CallToolTimeout Duration `json:"call_tool_timeout" mapstructure:"call-tool-timeout" swaggertype:"string"` @@ -90,7 +92,7 @@ type Config struct { // Registries configuration for MCP server discovery Registries []RegistryEntry `json:"registries,omitempty" mapstructure:"registries"` - // Feature flags for modular functionality + // Deprecated: Features flags are unused and have no runtime effect. Kept for backward compatibility. Features *FeatureFlags `json:"features,omitempty" mapstructure:"features"` // TLS configuration @@ -573,10 +575,8 @@ func DefaultConfig() *Config { Listen: defaultPort, EnableSocket: true, // Enable Unix socket/named pipe by default for local IPC DataDir: "", // Will be set to ~/.mcpproxy by loader - EnableTray: true, DebugSearch: false, Servers: []*ServerConfig{}, - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 20000, // Default 20000 characters CallToolTimeout: Duration(2 * time.Minute), // Default 2 minutes for tool calls @@ -783,14 +783,6 @@ func (c *Config) ValidateDetailed() []ValidationError { } } - // Validate TopK range - if c.TopK < 1 || c.TopK > 100 { - errors = append(errors, ValidationError{ - Field: "top_k", - Message: "must be between 1 and 100", - }) - } - // Validate ToolsLimit range if c.ToolsLimit < 1 || c.ToolsLimit > 1000 { errors = append(errors, ValidationError{ @@ -951,9 +943,6 @@ func (c *Config) Validate() error { if c.Listen == "" { c.Listen = defaultPort } - if c.TopK <= 0 { - c.TopK = 5 - } if c.ToolsLimit <= 0 { c.ToolsLimit = 15 } @@ -999,16 +988,6 @@ func (c *Config) Validate() error { c.DockerIsolation = DefaultDockerIsolationConfig() } - // Ensure Features config is not nil and validate dependencies - if c.Features == nil { - flags := DefaultFeatureFlags() - c.Features = &flags - } else { - if err := c.Features.ValidateFeatureFlags(); err != nil { - return fmt.Errorf("feature flag validation failed: %w", err) - } - } - // Ensure TLS config is not nil if c.TLS == nil { c.TLS = &TLSConfig{ diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 56d1afac..c814f9fb 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -17,9 +17,7 @@ func TestDefaultConfig(t *testing.T) { // Test default values assert.Equal(t, "127.0.0.1:8080", config.Listen) assert.Equal(t, "", config.DataDir) - assert.True(t, config.EnableTray) assert.False(t, config.DebugSearch) - assert.Equal(t, 5, config.TopK) assert.Equal(t, 15, config.ToolsLimit) assert.Equal(t, 20000, config.ToolResponseLimit) @@ -49,19 +47,6 @@ func TestConfigValidation(t *testing.T) { }, expected: &Config{ Listen: "127.0.0.1:8080", - TopK: 5, - ToolsLimit: 15, - ToolResponseLimit: 0, - }, - }, - { - name: "zero TopK defaults to 5", - config: &Config{ - TopK: 0, - }, - expected: &Config{ - Listen: "127.0.0.1:8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 0, }, @@ -73,7 +58,6 @@ func TestConfigValidation(t *testing.T) { }, expected: &Config{ Listen: "127.0.0.1:8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 0, }, @@ -85,7 +69,6 @@ func TestConfigValidation(t *testing.T) { }, expected: &Config{ Listen: "127.0.0.1:8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 0, }, @@ -97,7 +80,6 @@ func TestConfigValidation(t *testing.T) { err := tt.config.Validate() require.NoError(t, err) assert.Equal(t, tt.expected.Listen, tt.config.Listen) - assert.Equal(t, tt.expected.TopK, tt.config.TopK) assert.Equal(t, tt.expected.ToolsLimit, tt.config.ToolsLimit) assert.Equal(t, tt.expected.ToolResponseLimit, tt.config.ToolResponseLimit) }) @@ -436,8 +418,8 @@ func TestSaveAndLoadConfig(t *testing.T) { t.Errorf("Expected Listen %s, got %s", cfg.Listen, loaded.Listen) } - if loaded.TopK != cfg.TopK { - t.Errorf("Expected TopK %d, got %d", cfg.TopK, loaded.TopK) + if loaded.ToolsLimit != cfg.ToolsLimit { + t.Errorf("Expected ToolsLimit %d, got %d", cfg.ToolsLimit, loaded.ToolsLimit) } } @@ -491,8 +473,6 @@ func TestLoadEmptyConfigFile(t *testing.T) { // Verify the config still has default values assert.Equal(t, "127.0.0.1:8080", cfg.Listen, "Default listen address should be preserved") - assert.True(t, cfg.EnableTray, "Default EnableTray should be preserved") - assert.Equal(t, 5, cfg.TopK, "Default TopK should be preserved") }) } } diff --git a/internal/config/deprecated.go b/internal/config/deprecated.go new file mode 100644 index 00000000..72b9c191 --- /dev/null +++ b/internal/config/deprecated.go @@ -0,0 +1,54 @@ +package config + +import ( + "encoding/json" + "os" +) + +// DeprecatedField describes a deprecated configuration field. +type DeprecatedField struct { + JSONKey string `json:"json_key"` + Message string `json:"message"` + Replacement string `json:"replacement,omitempty"` +} + +// deprecatedFields is the list of known deprecated config keys. +var deprecatedFields = []DeprecatedField{ + { + JSONKey: "top_k", + Message: "top_k is deprecated and has no effect", + Replacement: "Use tools_limit instead", + }, + { + JSONKey: "enable_tray", + Message: "enable_tray is deprecated and has no effect", + Replacement: "Remove from config (tray is managed by the tray application)", + }, + { + JSONKey: "features", + Message: "features is deprecated and has no effect", + Replacement: "Remove from config (all feature flags are unused)", + }, +} + +// CheckDeprecatedFields reads the raw JSON config file and returns which +// deprecated keys are present. It does not validate or parse the full config. +func CheckDeprecatedFields(configPath string) []DeprecatedField { + data, err := os.ReadFile(configPath) + if err != nil { + return nil + } + + var raw map[string]json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return nil + } + + var found []DeprecatedField + for _, df := range deprecatedFields { + if _, exists := raw[df.JSONKey]; exists { + found = append(found, df) + } + } + return found +} diff --git a/internal/config/deprecated_test.go b/internal/config/deprecated_test.go new file mode 100644 index 00000000..5de18bce --- /dev/null +++ b/internal/config/deprecated_test.go @@ -0,0 +1,90 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckDeprecatedFields(t *testing.T) { + tests := []struct { + name string + configJSON string + expectedKeys []string + }{ + { + name: "no deprecated fields", + configJSON: `{"listen": ":8080", "tools_limit": 15}`, + expectedKeys: nil, + }, + { + name: "top_k present", + configJSON: `{"listen": ":8080", "top_k": 5}`, + expectedKeys: []string{"top_k"}, + }, + { + name: "enable_tray present", + configJSON: `{"listen": ":8080", "enable_tray": true}`, + expectedKeys: []string{"enable_tray"}, + }, + { + name: "features present", + configJSON: `{"listen": ":8080", "features": {}}`, + expectedKeys: []string{"features"}, + }, + { + name: "all deprecated fields present", + configJSON: `{"listen": ":8080", "top_k": 5, "enable_tray": true, "features": {"enable_runtime": true}}`, + expectedKeys: []string{"top_k", "enable_tray", "features"}, + }, + { + name: "activity fields are NOT deprecated", + configJSON: `{"listen": ":8080", "activity_retention_days": 90, "activity_max_records": 100000}`, + expectedKeys: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Write temp config file + dir := t.TempDir() + cfgPath := filepath.Join(dir, "mcp_config.json") + err := os.WriteFile(cfgPath, []byte(tt.configJSON), 0644) + require.NoError(t, err) + + found := CheckDeprecatedFields(cfgPath) + + if tt.expectedKeys == nil { + assert.Empty(t, found) + return + } + + assert.Len(t, found, len(tt.expectedKeys)) + foundKeys := make([]string, len(found)) + for i, f := range found { + foundKeys[i] = f.JSONKey + } + for _, key := range tt.expectedKeys { + assert.Contains(t, foundKeys, key) + } + }) + } +} + +func TestCheckDeprecatedFields_MissingFile(t *testing.T) { + found := CheckDeprecatedFields("/nonexistent/config.json") + assert.Nil(t, found) +} + +func TestCheckDeprecatedFields_InvalidJSON(t *testing.T) { + dir := t.TempDir() + cfgPath := filepath.Join(dir, "bad.json") + err := os.WriteFile(cfgPath, []byte("not json"), 0644) + require.NoError(t, err) + + found := CheckDeprecatedFields(cfgPath) + assert.Nil(t, found) +} diff --git a/internal/config/validation_test.go b/internal/config/validation_test.go index 6efb4e41..b3893abc 100644 --- a/internal/config/validation_test.go +++ b/internal/config/validation_test.go @@ -18,7 +18,6 @@ func TestValidateDetailed(t *testing.T) { name: "valid config", config: &Config{ Listen: "127.0.0.1:8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), // 1 minute @@ -27,35 +26,10 @@ func TestValidateDetailed(t *testing.T) { expectedErrors: 0, errorFields: []string{}, }, - { - name: "invalid listen address", - config: &Config{ - Listen: "", // Will fail validation (empty not valid unless it's truly empty) - TopK: 0, // Will fail validation - ToolsLimit: 15, - ToolResponseLimit: 1000, - CallToolTimeout: Duration(60000000000), // Add valid timeout - }, - expectedErrors: 1, // Only top_k error (empty listen is actually not validated as error) - errorFields: []string{"top_k"}, - }, - { - name: "TopK out of range", - config: &Config{ - Listen: ":8080", - TopK: 101, // Too high - ToolsLimit: 15, - ToolResponseLimit: 1000, - CallToolTimeout: Duration(60000000000), // Add valid timeout - }, - expectedErrors: 1, - errorFields: []string{"top_k"}, - }, { name: "ToolsLimit out of range", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 0, // Too low ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), // Add valid timeout @@ -67,7 +41,6 @@ func TestValidateDetailed(t *testing.T) { name: "negative ToolResponseLimit", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: -100, // Negative CallToolTimeout: Duration(60000000000), // Add valid timeout @@ -79,7 +52,6 @@ func TestValidateDetailed(t *testing.T) { name: "invalid timeout", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(0), // Zero @@ -91,7 +63,6 @@ func TestValidateDetailed(t *testing.T) { name: "server missing name", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -110,7 +81,6 @@ func TestValidateDetailed(t *testing.T) { name: "duplicate server names", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -134,7 +104,6 @@ func TestValidateDetailed(t *testing.T) { name: "invalid protocol", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -153,7 +122,6 @@ func TestValidateDetailed(t *testing.T) { name: "stdio server missing command", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -172,7 +140,6 @@ func TestValidateDetailed(t *testing.T) { name: "http server missing url", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -191,7 +158,6 @@ func TestValidateDetailed(t *testing.T) { name: "invalid log level", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -206,7 +172,6 @@ func TestValidateDetailed(t *testing.T) { name: "oauth with empty client_id (DCR mode)", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -229,7 +194,6 @@ func TestValidateDetailed(t *testing.T) { name: "oauth with only scopes and pkce", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -252,7 +216,6 @@ func TestValidateDetailed(t *testing.T) { name: "oauth with client_id provided", config: &Config{ Listen: ":8080", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: Duration(60000000000), @@ -329,7 +292,6 @@ func TestValidateWithDefaults(t *testing.T) { // Test that Validate applies defaults before validation cfg := &Config{ Listen: "", // Should default to 127.0.0.1:8080 - TopK: 0, // Should default to 5 ToolsLimit: 0, // Should default to 15 ToolResponseLimit: -1, // Should default to 0 CallToolTimeout: 0, // Should default to 2 minutes @@ -340,7 +302,6 @@ func TestValidateWithDefaults(t *testing.T) { require.NoError(t, err, "Validation should succeed after applying defaults") assert.Equal(t, "127.0.0.1:8080", cfg.Listen) - assert.Equal(t, 5, cfg.TopK) assert.Equal(t, 15, cfg.ToolsLimit) assert.Equal(t, 0, cfg.ToolResponseLimit) assert.Greater(t, cfg.CallToolTimeout.Duration().Seconds(), 0.0) diff --git a/internal/contracts/types.go b/internal/contracts/types.go index 85c7766f..de0a1126 100644 --- a/internal/contracts/types.go +++ b/internal/contracts/types.go @@ -304,14 +304,22 @@ type DiagnosticsResponse struct { // Diagnostics represents aggregated health information from all MCPProxy components. // This is the new unified diagnostics format for the management service. type Diagnostics struct { - TotalIssues int `json:"total_issues"` - UpstreamErrors []UpstreamError `json:"upstream_errors"` - OAuthRequired []OAuthRequirement `json:"oauth_required"` - OAuthIssues []OAuthIssue `json:"oauth_issues"` // OAuth parameter mismatches - MissingSecrets []MissingSecretInfo `json:"missing_secrets"` // Renamed to avoid conflict - RuntimeWarnings []string `json:"runtime_warnings"` - DockerStatus *DockerStatus `json:"docker_status,omitempty"` - Timestamp time.Time `json:"timestamp"` + TotalIssues int `json:"total_issues"` + UpstreamErrors []UpstreamError `json:"upstream_errors"` + OAuthRequired []OAuthRequirement `json:"oauth_required"` + OAuthIssues []OAuthIssue `json:"oauth_issues"` // OAuth parameter mismatches + MissingSecrets []MissingSecretInfo `json:"missing_secrets"` // Renamed to avoid conflict + RuntimeWarnings []string `json:"runtime_warnings"` + DeprecatedConfigs []DeprecatedConfigWarning `json:"deprecated_configs,omitempty"` // Deprecated config fields found + DockerStatus *DockerStatus `json:"docker_status,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +// DeprecatedConfigWarning represents a deprecated configuration field found in the config file. +type DeprecatedConfigWarning struct { + Field string `json:"field"` + Message string `json:"message"` + Replacement string `json:"replacement,omitempty"` } // UpstreamError represents a connection or runtime error from an upstream MCP server. diff --git a/internal/management/diagnostics.go b/internal/management/diagnostics.go index 4b7a279a..9e2980ca 100644 --- a/internal/management/diagnostics.go +++ b/internal/management/diagnostics.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/smart-mcp-proxy/mcpproxy-go/internal/config" "github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts" "github.com/smart-mcp-proxy/mcpproxy-go/internal/health" ) @@ -136,6 +137,18 @@ func (s *service) Doctor(ctx context.Context) (*contracts.Diagnostics, error) { }) } + // Check for deprecated configuration fields + if s.configPath != "" { + deprecated := config.CheckDeprecatedFields(s.configPath) + for _, df := range deprecated { + diag.DeprecatedConfigs = append(diag.DeprecatedConfigs, contracts.DeprecatedConfigWarning{ + Field: df.JSONKey, + Message: df.Message, + Replacement: df.Replacement, + }) + } + } + // Check Docker status if isolation is enabled if s.config.DockerIsolation != nil && s.config.DockerIsolation.Enabled { diag.DockerStatus = s.checkDockerDaemon() diff --git a/internal/management/diagnostics_test.go b/internal/management/diagnostics_test.go index b291aac3..aed8b09d 100644 --- a/internal/management/diagnostics_test.go +++ b/internal/management/diagnostics_test.go @@ -32,7 +32,7 @@ func TestDoctor(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -59,7 +59,7 @@ func TestDoctor(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -75,7 +75,7 @@ func TestDoctor(t *testing.T) { runtime := newMockRuntime() runtime.getAllError = fmt.Errorf("runtime failure") - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) assert.Error(t, err) @@ -111,7 +111,7 @@ func TestDoctorOAuthDetection(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -144,7 +144,7 @@ func TestDoctorOAuthDetection(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -180,7 +180,7 @@ func TestDoctorMissingSecrets(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -211,7 +211,7 @@ func TestDoctorMissingSecrets(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -234,7 +234,7 @@ func TestDoctorDockerStatus(t *testing.T) { runtime := newMockRuntime() runtime.servers = []map[string]interface{}{} - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) @@ -249,7 +249,7 @@ func TestDoctorDockerStatus(t *testing.T) { runtime := newMockRuntime() runtime.servers = []map[string]interface{}{} - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) diag, err := svc.Doctor(context.Background()) require.NoError(t, err) diff --git a/internal/management/service.go b/internal/management/service.go index c3fad237..93d26c29 100644 --- a/internal/management/service.go +++ b/internal/management/service.go @@ -144,6 +144,7 @@ type RuntimeOperations interface { type service struct { runtime RuntimeOperations config *config.Config + configPath string eventEmitter EventEmitter secretResolver *secret.Resolver logger *zap.SugaredLogger @@ -154,6 +155,7 @@ type service struct { func NewService( runtime RuntimeOperations, cfg *config.Config, + configPath string, eventEmitter EventEmitter, secretResolver *secret.Resolver, logger *zap.SugaredLogger, @@ -161,6 +163,7 @@ func NewService( return &service{ runtime: runtime, config: cfg, + configPath: configPath, eventEmitter: eventEmitter, secretResolver: secretResolver, logger: logger, diff --git a/internal/management/service_test.go b/internal/management/service_test.go index d49ca257..81b64591 100644 --- a/internal/management/service_test.go +++ b/internal/management/service_test.go @@ -75,7 +75,7 @@ func TestCheckWriteGates(t *testing.T) { ReadOnlyMode: tt.readOnlyMode, } - svc := NewService(nil, cfg, &mockEventEmitter{}, nil, logger).(*service) + svc := NewService(nil, cfg, "", &mockEventEmitter{}, nil, logger).(*service) err := svc.checkWriteGates() if tt.expectError { @@ -113,7 +113,7 @@ func TestListServers(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) servers, stats, err := svc.ListServers(context.Background()) require.NoError(t, err) @@ -153,7 +153,7 @@ func TestListServers(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) servers, stats, err := svc.ListServers(context.Background()) require.NoError(t, err) @@ -182,7 +182,7 @@ func TestListServers(t *testing.T) { runtime := newMockRuntime() runtime.getAllError = fmt.Errorf("runtime error") - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) servers, stats, err := svc.ListServers(context.Background()) assert.Error(t, err) @@ -210,7 +210,7 @@ func TestListServers(t *testing.T) { }, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) servers, stats, err := svc.ListServers(context.Background()) require.NoError(t, err) @@ -238,7 +238,7 @@ func TestEnableServer(t *testing.T) { t.Run("blocks when disable_management is true", func(t *testing.T) { cfg := &config.Config{DisableManagement: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) err := svc.EnableServer(context.Background(), "test-server", true) @@ -250,7 +250,7 @@ func TestEnableServer(t *testing.T) { t.Run("blocks when read_only_mode is true", func(t *testing.T) { cfg := &config.Config{ReadOnlyMode: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) err := svc.EnableServer(context.Background(), "test-server", true) @@ -263,7 +263,7 @@ func TestEnableServer(t *testing.T) { cfg := &config.Config{} emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) err := svc.EnableServer(context.Background(), "test-server", true) @@ -281,7 +281,7 @@ func TestRestartServer(t *testing.T) { t.Run("blocks when disable_management is true", func(t *testing.T) { cfg := &config.Config{DisableManagement: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) err := svc.RestartServer(context.Background(), "test-server") @@ -293,7 +293,7 @@ func TestRestartServer(t *testing.T) { t.Run("blocks when read_only_mode is true", func(t *testing.T) { cfg := &config.Config{ReadOnlyMode: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) err := svc.RestartServer(context.Background(), "test-server") @@ -306,7 +306,7 @@ func TestRestartServer(t *testing.T) { cfg := &config.Config{} emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) err := svc.RestartServer(context.Background(), "test-server") @@ -452,7 +452,7 @@ func TestRestartAll(t *testing.T) { t.Run("blocks when disable_management is true", func(t *testing.T) { cfg := &config.Config{DisableManagement: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) result, err := svc.RestartAll(context.Background()) @@ -465,7 +465,7 @@ func TestRestartAll(t *testing.T) { t.Run("blocks when read_only_mode is true", func(t *testing.T) { cfg := &config.Config{ReadOnlyMode: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) result, err := svc.RestartAll(context.Background()) @@ -484,7 +484,7 @@ func TestRestartAll(t *testing.T) { {"name": "server2"}, {"name": "server3"}, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.RestartAll(context.Background()) @@ -512,7 +512,7 @@ func TestRestartAll(t *testing.T) { // Configure mock to fail on server2 runtime.restartError = fmt.Errorf("restart failed") runtime.failOnServer = "server2" - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.RestartAll(context.Background()) @@ -530,7 +530,7 @@ func TestRestartAll(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() runtime.servers = []map[string]interface{}{} - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.RestartAll(context.Background()) @@ -550,7 +550,7 @@ func TestEnableAll(t *testing.T) { t.Run("blocks when disable_management is true", func(t *testing.T) { cfg := &config.Config{DisableManagement: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) result, err := svc.EnableAll(context.Background()) @@ -563,7 +563,7 @@ func TestEnableAll(t *testing.T) { t.Run("blocks when read_only_mode is true", func(t *testing.T) { cfg := &config.Config{ReadOnlyMode: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) result, err := svc.EnableAll(context.Background()) @@ -582,7 +582,7 @@ func TestEnableAll(t *testing.T) { {"name": "server2"}, {"name": "server3"}, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.EnableAll(context.Background()) @@ -610,7 +610,7 @@ func TestEnableAll(t *testing.T) { } runtime.enableError = fmt.Errorf("enable failed") runtime.failOnServer = "server2" - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.EnableAll(context.Background()) @@ -631,7 +631,7 @@ func TestDisableAll(t *testing.T) { t.Run("blocks when disable_management is true", func(t *testing.T) { cfg := &config.Config{DisableManagement: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) result, err := svc.DisableAll(context.Background()) @@ -644,7 +644,7 @@ func TestDisableAll(t *testing.T) { t.Run("blocks when read_only_mode is true", func(t *testing.T) { cfg := &config.Config{ReadOnlyMode: true} emitter := &mockEventEmitter{} - svc := NewService(nil, cfg, emitter, nil, logger) + svc := NewService(nil, cfg, "", emitter, nil, logger) result, err := svc.DisableAll(context.Background()) @@ -663,7 +663,7 @@ func TestDisableAll(t *testing.T) { {"name": "server2"}, {"name": "server3"}, } - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.DisableAll(context.Background()) @@ -691,7 +691,7 @@ func TestDisableAll(t *testing.T) { } runtime.enableError = fmt.Errorf("disable failed") runtime.failOnServer = "server3" - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) result, err := svc.DisableAll(context.Background()) @@ -712,7 +712,7 @@ func TestGetServerTools_ValidServer(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) tools, err := svc.GetServerTools(context.Background(), "test-server") require.NoError(t, err) @@ -728,7 +728,7 @@ func TestGetServerTools_EmptyServerName(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) tools, err := svc.GetServerTools(context.Background(), "") require.Error(t, err) @@ -744,7 +744,7 @@ func TestGetServerTools_NonexistentServer(t *testing.T) { runtime := newMockRuntime() runtime.failOnServer = "nonexistent" - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) tools, err := svc.GetServerTools(context.Background(), "nonexistent") require.Error(t, err) @@ -759,7 +759,7 @@ func TestTriggerOAuthLogin_ValidServer(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) err := svc.TriggerOAuthLogin(context.Background(), "test-server") require.NoError(t, err) @@ -772,7 +772,7 @@ func TestTriggerOAuthLogin_DisableManagement(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) err := svc.TriggerOAuthLogin(context.Background(), "test-server") require.Error(t, err) @@ -787,7 +787,7 @@ func TestTriggerOAuthLogin_ReadOnly(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) err := svc.TriggerOAuthLogin(context.Background(), "test-server") require.Error(t, err) @@ -802,7 +802,7 @@ func TestTriggerOAuthLogin_EmptyServerName(t *testing.T) { emitter := &mockEventEmitter{} runtime := newMockRuntime() - svc := NewService(runtime, cfg, emitter, nil, logger) + svc := NewService(runtime, cfg, "", emitter, nil, logger) err := svc.TriggerOAuthLogin(context.Background(), "") require.Error(t, err) diff --git a/internal/runtime/apply_config_restart_test.go b/internal/runtime/apply_config_restart_test.go index 6e10dffa..5bef971b 100644 --- a/internal/runtime/apply_config_restart_test.go +++ b/internal/runtime/apply_config_restart_test.go @@ -69,7 +69,7 @@ func TestApplyConfig_HotReloadableChange(t *testing.T) { initialCfg := config.DefaultConfig() initialCfg.Listen = "127.0.0.1:8080" initialCfg.DataDir = tmpDir - initialCfg.TopK = 5 + initialCfg.ToolsLimit = 15 // Save initial config err := config.SaveConfig(initialCfg, cfgPath) @@ -82,26 +82,26 @@ func TestApplyConfig_HotReloadableChange(t *testing.T) { _ = rt.Close() }() - // Create new config with different TopK (hot-reloadable) + // Create new config with different ToolsLimit (hot-reloadable) newCfg := config.DefaultConfig() newCfg.Listen = "127.0.0.1:8080" // Same listen address newCfg.DataDir = tmpDir - newCfg.TopK = 10 // Changed TopK + newCfg.ToolsLimit = 20 // Changed ToolsLimit // Apply the new config result, err := rt.ApplyConfig(newCfg, cfgPath) require.NoError(t, err) require.NotNil(t, result) - // Verify that restart is NOT required (TopK is hot-reloadable) - assert.False(t, result.RequiresRestart, "TopK change should not require restart") - assert.True(t, result.AppliedImmediately, "TopK change should be applied immediately") + // Verify that restart is NOT required (ToolsLimit is hot-reloadable) + assert.False(t, result.RequiresRestart, "ToolsLimit change should not require restart") + assert.True(t, result.AppliedImmediately, "ToolsLimit change should be applied immediately") // Verify config was saved to disk savedCfg, err := config.LoadFromFile(cfgPath) require.NoError(t, err) - assert.Equal(t, 10, savedCfg.TopK, "Config file should be updated with new TopK value") + assert.Equal(t, 20, savedCfg.ToolsLimit, "Config file should be updated with new ToolsLimit value") } // TestApplyConfig_SaveFailure tests handling of save errors diff --git a/internal/runtime/config_hotreload.go b/internal/runtime/config_hotreload.go index 1432fc05..3c454036 100644 --- a/internal/runtime/config_hotreload.go +++ b/internal/runtime/config_hotreload.go @@ -89,9 +89,6 @@ func DetectConfigChanges(oldCfg, newCfg *config.Config) *ConfigApplyResult { } // Tool limits (can be hot-reloaded) - if oldCfg.TopK != newCfg.TopK { - result.ChangedFields = append(result.ChangedFields, "top_k") - } if oldCfg.ToolsLimit != newCfg.ToolsLimit { result.ChangedFields = append(result.ChangedFields, "tools_limit") } @@ -112,11 +109,6 @@ func DetectConfigChanges(oldCfg, newCfg *config.Config) *ConfigApplyResult { result.ChangedFields = append(result.ChangedFields, "docker_isolation") } - // Feature flags (can be hot-reloaded) - if !reflect.DeepEqual(oldCfg.Features, newCfg.Features) { - result.ChangedFields = append(result.ChangedFields, "features") - } - // Registries (can be hot-reloaded) if !reflect.DeepEqual(oldCfg.Registries, newCfg.Registries) { result.ChangedFields = append(result.ChangedFields, "registries") diff --git a/internal/runtime/config_hotreload_test.go b/internal/runtime/config_hotreload_test.go index c194efbe..dacf6597 100644 --- a/internal/runtime/config_hotreload_test.go +++ b/internal/runtime/config_hotreload_test.go @@ -14,7 +14,6 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/test/data", APIKey: "test-key", - TopK: 5, ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), @@ -50,8 +49,7 @@ func TestDetectConfigChanges(t *testing.T) { Listen: ":9090", // Changed DataDir: "/test/data", APIKey: "test-key", - TopK: 5, - ToolsLimit: 15, + ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), Servers: []*config.ServerConfig{}, @@ -69,8 +67,7 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/different/data", // Changed APIKey: "test-key", - TopK: 5, - ToolsLimit: 15, + ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), Servers: []*config.ServerConfig{}, @@ -88,8 +85,7 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/test/data", APIKey: "new-key", // Changed - TopK: 5, - ToolsLimit: 15, + ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), Servers: []*config.ServerConfig{}, @@ -107,8 +103,7 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/test/data", APIKey: "test-key", - TopK: 5, - ToolsLimit: 15, + ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), Servers: []*config.ServerConfig{}, @@ -122,27 +117,6 @@ func TestDetectConfigChanges(t *testing.T) { expectRestartReason: "TLS configuration changed", expectChangedFields: []string{"tls"}, }, - { - name: "hot-reloadable: TopK changed", - oldConfig: baseConfig, - newConfig: &config.Config{ - Listen: "127.0.0.1:8080", - DataDir: "/test/data", - APIKey: "test-key", - TopK: 10, // Changed - ToolsLimit: 15, - ToolResponseLimit: 1000, - CallToolTimeout: config.Duration(60 * time.Second), - Servers: []*config.ServerConfig{}, - TLS: &config.TLSConfig{ - Enabled: false, - }, - }, - expectSuccess: true, - expectAppliedNow: true, - expectRequiresRestart: false, - expectChangedFields: []string{"top_k"}, - }, { name: "hot-reloadable: ToolsLimit changed", oldConfig: baseConfig, @@ -150,8 +124,7 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/test/data", APIKey: "test-key", - TopK: 5, - ToolsLimit: 20, // Changed + ToolsLimit: 20, // Changed ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), Servers: []*config.ServerConfig{}, @@ -171,8 +144,7 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/test/data", APIKey: "test-key", - TopK: 5, - ToolsLimit: 15, + ToolsLimit: 15, ToolResponseLimit: 1000, CallToolTimeout: config.Duration(60 * time.Second), Servers: []*config.ServerConfig{ // Changed @@ -199,7 +171,6 @@ func TestDetectConfigChanges(t *testing.T) { Listen: "127.0.0.1:8080", DataDir: "/test/data", APIKey: "test-key", - TopK: 10, // Changed ToolsLimit: 20, // Changed ToolResponseLimit: 2000, // Changed CallToolTimeout: config.Duration(120 * time.Second), // Changed @@ -211,7 +182,7 @@ func TestDetectConfigChanges(t *testing.T) { expectSuccess: true, expectAppliedNow: true, expectRequiresRestart: false, - expectChangedFields: []string{"top_k", "tools_limit", "tool_response_limit", "call_tool_timeout"}, + expectChangedFields: []string{"tools_limit", "tool_response_limit", "call_tool_timeout"}, }, } diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 66c00deb..46e8661b 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -175,6 +175,27 @@ func New(cfg *config.Config, cfgPath string, logger *zap.Logger) (*Runtime, erro zap.Bool("scan_responses", cfg.SensitiveDataDetection.ScanResponses)) } + // Wire activity retention config from config file + if cfg.ActivityRetentionDays > 0 || cfg.ActivityMaxRecords > 0 || cfg.ActivityCleanupIntervalMin > 0 { + maxAge := time.Duration(cfg.ActivityRetentionDays) * 24 * time.Hour + checkInterval := time.Duration(cfg.ActivityCleanupIntervalMin) * time.Minute + activityService.SetRetentionConfig(maxAge, cfg.ActivityMaxRecords, checkInterval) + logger.Info("Activity retention config applied", + zap.Int("retention_days", cfg.ActivityRetentionDays), + zap.Int("max_records", cfg.ActivityMaxRecords), + zap.Int("cleanup_interval_min", cfg.ActivityCleanupIntervalMin)) + } + + // Log warnings for deprecated config fields + if deprecated := config.CheckDeprecatedFields(cfgPath); len(deprecated) > 0 { + for _, df := range deprecated { + logger.Warn("Deprecated config field", + zap.String("field", df.JSONKey), + zap.String("message", df.Message), + zap.String("replacement", df.Replacement)) + } + } + rt := &Runtime{ cfg: cfg, cfgPath: cfgPath, diff --git a/internal/server/mcp_test.go b/internal/server/mcp_test.go index c65e0816..735e0192 100644 --- a/internal/server/mcp_test.go +++ b/internal/server/mcp_test.go @@ -637,9 +637,7 @@ func TestDefaultConfigSettings(t *testing.T) { // Test default values assert.Equal(t, "127.0.0.1:8080", config.Listen) assert.Equal(t, "", config.DataDir) - assert.True(t, config.EnableTray) assert.False(t, config.DebugSearch) - assert.Equal(t, 5, config.TopK) assert.Equal(t, 15, config.ToolsLimit) assert.Equal(t, 20000, config.ToolResponseLimit) diff --git a/internal/server/quarantine_config_apply_test.go b/internal/server/quarantine_config_apply_test.go index b4605507..c4befecb 100644 --- a/internal/server/quarantine_config_apply_test.go +++ b/internal/server/quarantine_config_apply_test.go @@ -56,9 +56,6 @@ func TestE2E_QuarantineConfigApply(t *testing.T) { require.NoError(t, err) // Set default values if not present (config from API may be incomplete) - if currentConfig.TopK == 0 { - currentConfig.TopK = 5 - } if currentConfig.ToolsLimit == 0 { currentConfig.ToolsLimit = 15 } diff --git a/internal/server/server.go b/internal/server/server.go index ca809303..0297db16 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -101,10 +101,11 @@ func NewServerWithConfigPath(cfg *config.Config, configPath string, logger *zap. // Initialize management service and set it on runtime secretResolver := secret.NewResolver() mgmtService := management.NewService( - rt, // RuntimeOperations - cfg, // Config - rt, // EventEmitter - secretResolver, // SecretResolver + rt, // RuntimeOperations + cfg, // Config + rt.ConfigPath(), // Config file path for deprecation checks + rt, // EventEmitter + secretResolver, // SecretResolver logger.Sugar(), ) rt.SetManagementService(mgmtService) diff --git a/oas/swagger.yaml b/oas/swagger.yaml index 9bc6aea4..295fd556 100644 --- a/oas/swagger.yaml +++ b/oas/swagger.yaml @@ -54,12 +54,8 @@ components: enable_socket: description: 'Enable Unix socket/named pipe for local IPC (default: true)' type: boolean - enable_tray: - type: boolean environment: $ref: '#/components/schemas/secureenv.EnvConfig' - features: - $ref: '#/components/schemas/config.FeatureFlags' intent_declaration: $ref: '#/components/schemas/config.IntentDeclarationConfig' listen: @@ -92,8 +88,6 @@ components: type: integer tools_limit: type: integer - top_k: - type: integer tray_endpoint: description: Tray endpoint override (unix:// or npipe://) type: string @@ -224,8 +218,6 @@ components: type: boolean enable_tracing: type: boolean - enable_tray: - type: boolean enable_web_ui: description: UI features type: boolean From 7006432ecad1187db7e842022f63e3dced7180d4 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sat, 28 Feb 2026 18:25:11 +0200 Subject: [PATCH 2/2] Regenerate OpenAPI artifacts after adding DeprecatedConfigWarning Co-Authored-By: Claude Opus 4.6 --- oas/docs.go | 2 +- oas/swagger.yaml | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/oas/docs.go b/oas/docs.go index dfaec298..cbf3c877 100644 --- a/oas/docs.go +++ b/oas/docs.go @@ -6,7 +6,7 @@ import "github.com/swaggo/swag/v2" const docTemplate = `{ "schemes": {{ marshal .Schemes }}, - "components": {"schemas":{"config.Config":{"properties":{"activity_cleanup_interval_min":{"description":"Background cleanup interval in minutes (default: 60)","type":"integer"},"activity_max_records":{"description":"Max records before pruning (default: 100000)","type":"integer"},"activity_max_response_size":{"description":"Response truncation limit in bytes (default: 65536)","type":"integer"},"activity_retention_days":{"description":"Activity logging settings (RFC-003)","type":"integer"},"allow_server_add":{"type":"boolean"},"allow_server_remove":{"type":"boolean"},"api_key":{"description":"Security settings","type":"string"},"call_tool_timeout":{"type":"string"},"check_server_repo":{"description":"Repository detection settings","type":"boolean"},"code_execution_max_tool_calls":{"description":"Max tool calls per execution (0 = unlimited, default: 0)","type":"integer"},"code_execution_pool_size":{"description":"JavaScript runtime pool size (default: 10)","type":"integer"},"code_execution_timeout_ms":{"description":"Timeout in milliseconds (default: 120000, max: 600000)","type":"integer"},"data_dir":{"type":"string"},"debug_search":{"type":"boolean"},"disable_management":{"type":"boolean"},"docker_isolation":{"$ref":"#/components/schemas/config.DockerIsolationConfig"},"docker_recovery":{"$ref":"#/components/schemas/config.DockerRecoveryConfig"},"enable_code_execution":{"description":"Code execution settings","type":"boolean"},"enable_prompts":{"description":"Prompts settings","type":"boolean"},"enable_socket":{"description":"Enable Unix socket/named pipe for local IPC (default: true)","type":"boolean"},"enable_tray":{"type":"boolean"},"environment":{"$ref":"#/components/schemas/secureenv.EnvConfig"},"features":{"$ref":"#/components/schemas/config.FeatureFlags"},"intent_declaration":{"$ref":"#/components/schemas/config.IntentDeclarationConfig"},"listen":{"type":"string"},"logging":{"$ref":"#/components/schemas/config.LogConfig"},"mcpServers":{"items":{"$ref":"#/components/schemas/config.ServerConfig"},"type":"array","uniqueItems":false},"oauth_expiry_warning_hours":{"description":"Health status settings","type":"number"},"read_only_mode":{"type":"boolean"},"registries":{"description":"Registries configuration for MCP server discovery","items":{"$ref":"#/components/schemas/config.RegistryEntry"},"type":"array","uniqueItems":false},"sensitive_data_detection":{"$ref":"#/components/schemas/config.SensitiveDataDetectionConfig"},"tls":{"$ref":"#/components/schemas/config.TLSConfig"},"tokenizer":{"$ref":"#/components/schemas/config.TokenizerConfig"},"tool_response_limit":{"type":"integer"},"tools_limit":{"type":"integer"},"top_k":{"type":"integer"},"tray_endpoint":{"description":"Tray endpoint override (unix:// or npipe://)","type":"string"}},"type":"object"},"config.CustomPattern":{"properties":{"category":{"description":"Category (defaults to \"custom\")","type":"string"},"keywords":{"description":"Keywords to match (mutually exclusive with Regex)","items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"description":"Unique identifier for this pattern","type":"string"},"regex":{"description":"Regex pattern (mutually exclusive with Keywords)","type":"string"},"severity":{"description":"Risk level: critical, high, medium, low","type":"string"}},"type":"object"},"config.DockerIsolationConfig":{"description":"Docker isolation settings","properties":{"cpu_limit":{"description":"CPU limit for containers","type":"string"},"default_images":{"additionalProperties":{"type":"string"},"description":"Map of runtime type to Docker image","type":"object"},"enabled":{"description":"Global enable/disable for Docker isolation","type":"boolean"},"extra_args":{"description":"Additional docker run arguments","items":{"type":"string"},"type":"array","uniqueItems":false},"log_driver":{"description":"Docker log driver (default: json-file)","type":"string"},"log_max_files":{"description":"Maximum number of log files (default: 3)","type":"string"},"log_max_size":{"description":"Maximum size of log files (default: 100m)","type":"string"},"memory_limit":{"description":"Memory limit for containers","type":"string"},"network_mode":{"description":"Docker network mode (default: bridge)","type":"string"},"registry":{"description":"Custom registry (defaults to docker.io)","type":"string"},"timeout":{"description":"Container startup timeout","type":"string"}},"type":"object"},"config.DockerRecoveryConfig":{"description":"Docker recovery settings","properties":{"enabled":{"description":"Enable Docker recovery monitoring (default: true)","type":"boolean"},"max_retries":{"description":"Maximum retry attempts (0 = unlimited)","type":"integer"},"notify_on_failure":{"description":"Show notification on recovery failure (default: true)","type":"boolean"},"notify_on_retry":{"description":"Show notification on each retry (default: false)","type":"boolean"},"notify_on_start":{"description":"Show notification when recovery starts (default: true)","type":"boolean"},"notify_on_success":{"description":"Show notification on successful recovery (default: true)","type":"boolean"},"persistent_state":{"description":"Save recovery state across restarts (default: true)","type":"boolean"}},"type":"object"},"config.FeatureFlags":{"description":"Feature flags for modular functionality","properties":{"enable_async_storage":{"type":"boolean"},"enable_caching":{"type":"boolean"},"enable_contract_tests":{"type":"boolean"},"enable_debug_logging":{"description":"Development features","type":"boolean"},"enable_docker_isolation":{"type":"boolean"},"enable_event_bus":{"type":"boolean"},"enable_health_checks":{"type":"boolean"},"enable_metrics":{"type":"boolean"},"enable_oauth":{"description":"Security features","type":"boolean"},"enable_observability":{"description":"Observability features","type":"boolean"},"enable_quarantine":{"type":"boolean"},"enable_runtime":{"description":"Runtime features","type":"boolean"},"enable_search":{"description":"Storage features","type":"boolean"},"enable_sse":{"type":"boolean"},"enable_tracing":{"type":"boolean"},"enable_tray":{"type":"boolean"},"enable_web_ui":{"description":"UI features","type":"boolean"}},"type":"object"},"config.IntentDeclarationConfig":{"description":"Intent declaration settings (Spec 018)","properties":{"strict_server_validation":{"description":"StrictServerValidation controls whether server annotation mismatches\ncause rejection (true) or just warnings (false).\nDefault: true (reject mismatches)","type":"boolean"}},"type":"object"},"config.IsolationConfig":{"description":"Per-server isolation settings","properties":{"enabled":{"description":"Enable Docker isolation for this server (nil = inherit global)","type":"boolean"},"extra_args":{"description":"Additional docker run arguments for this server","items":{"type":"string"},"type":"array","uniqueItems":false},"image":{"description":"Custom Docker image (overrides default)","type":"string"},"log_driver":{"description":"Docker log driver override for this server","type":"string"},"log_max_files":{"description":"Maximum number of log files override","type":"string"},"log_max_size":{"description":"Maximum size of log files override","type":"string"},"network_mode":{"description":"Custom network mode for this server","type":"string"},"working_dir":{"description":"Custom working directory in container","type":"string"}},"type":"object"},"config.LogConfig":{"description":"Logging configuration","properties":{"compress":{"type":"boolean"},"enable_console":{"type":"boolean"},"enable_file":{"type":"boolean"},"filename":{"type":"string"},"json_format":{"type":"boolean"},"level":{"type":"string"},"log_dir":{"description":"Custom log directory","type":"string"},"max_age":{"description":"days","type":"integer"},"max_backups":{"description":"number of backup files","type":"integer"},"max_size":{"description":"MB","type":"integer"}},"type":"object"},"config.OAuthConfig":{"description":"OAuth configuration (keep even when empty to signal OAuth requirement)","properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"description":"Additional OAuth parameters (e.g., RFC 8707 resource)","type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_uri":{"type":"string"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.RegistryEntry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"config.SensitiveDataDetectionConfig":{"description":"Sensitive data detection settings (Spec 026)","properties":{"categories":{"additionalProperties":{"type":"boolean"},"description":"Enable/disable specific detection categories","type":"object"},"custom_patterns":{"description":"User-defined detection patterns","items":{"$ref":"#/components/schemas/config.CustomPattern"},"type":"array","uniqueItems":false},"enabled":{"description":"Enable sensitive data detection (default: true)","type":"boolean"},"entropy_threshold":{"description":"Shannon entropy threshold for high-entropy detection (default: 4.5)","type":"number"},"max_payload_size_kb":{"description":"Max size to scan before truncating (default: 1024)","type":"integer"},"scan_requests":{"description":"Scan tool call arguments (default: true)","type":"boolean"},"scan_responses":{"description":"Scan tool responses (default: true)","type":"boolean"},"sensitive_keywords":{"description":"Keywords to flag","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.ServerConfig":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"description":"For HTTP servers","type":"object"},"isolation":{"$ref":"#/components/schemas/config.IsolationConfig"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/config.OAuthConfig"},"protocol":{"description":"stdio, http, sse, streamable-http, auto","type":"string"},"quarantined":{"description":"Security quarantine status","type":"boolean"},"updated":{"type":"string"},"url":{"type":"string"},"working_dir":{"description":"Working directory for stdio servers","type":"string"}},"type":"object"},"config.TLSConfig":{"description":"TLS configuration","properties":{"certs_dir":{"description":"Directory for certificates","type":"string"},"enabled":{"description":"Enable HTTPS","type":"boolean"},"hsts":{"description":"Enable HTTP Strict Transport Security","type":"boolean"},"require_client_cert":{"description":"Enable mTLS","type":"boolean"}},"type":"object"},"config.TokenizerConfig":{"description":"Tokenizer configuration for token counting","properties":{"default_model":{"description":"Default model for tokenization (e.g., \"gpt-4\")","type":"string"},"enabled":{"description":"Enable token counting","type":"boolean"},"encoding":{"description":"Default encoding (e.g., \"cl100k_base\")","type":"string"}},"type":"object"},"configimport.FailedServer":{"properties":{"details":{"type":"string"},"error":{"type":"string"},"name":{"type":"string"}},"type":"object"},"configimport.ImportSummary":{"properties":{"failed":{"type":"integer"},"imported":{"type":"integer"},"skipped":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"configimport.SkippedServer":{"properties":{"name":{"type":"string"},"reason":{"description":"\"already_exists\", \"filtered_out\", \"invalid_name\"","type":"string"}},"type":"object"},"contracts.APIResponse":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ActivityDetailResponse":{"properties":{"activity":{"$ref":"#/components/schemas/contracts.ActivityRecord"}},"type":"object"},"contracts.ActivityListResponse":{"properties":{"activities":{"items":{"$ref":"#/components/schemas/contracts.ActivityRecord"},"type":"array","uniqueItems":false},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"contracts.ActivityRecord":{"properties":{"arguments":{"description":"Tool call arguments","type":"object"},"detection_types":{"description":"List of detection types found","items":{"type":"string"},"type":"array","uniqueItems":false},"duration_ms":{"description":"Execution duration in milliseconds","type":"integer"},"error_message":{"description":"Error details if status is \"error\"","type":"string"},"has_sensitive_data":{"description":"Sensitive data detection fields (Spec 026)","type":"boolean"},"id":{"description":"Unique identifier (ULID format)","type":"string"},"max_severity":{"description":"Highest severity level detected (critical, high, medium, low)","type":"string"},"metadata":{"description":"Additional context-specific data","type":"object"},"request_id":{"description":"HTTP request ID for correlation","type":"string"},"response":{"description":"Tool response (potentially truncated)","type":"string"},"response_truncated":{"description":"True if response was truncated","type":"boolean"},"server_name":{"description":"Name of upstream MCP server","type":"string"},"session_id":{"description":"MCP session ID for correlation","type":"string"},"source":{"$ref":"#/components/schemas/contracts.ActivitySource"},"status":{"description":"Result status: \"success\", \"error\", \"blocked\"","type":"string"},"timestamp":{"description":"When activity occurred","type":"string"},"tool_name":{"description":"Name of tool called","type":"string"},"type":{"$ref":"#/components/schemas/contracts.ActivityType"}},"type":"object"},"contracts.ActivitySource":{"description":"How activity was triggered: \"mcp\", \"cli\", \"api\"","type":"string","x-enum-varnames":["ActivitySourceMCP","ActivitySourceCLI","ActivitySourceAPI"]},"contracts.ActivitySummaryResponse":{"properties":{"blocked_count":{"description":"Count of blocked activities","type":"integer"},"end_time":{"description":"End of the period (RFC3339)","type":"string"},"error_count":{"description":"Count of error activities","type":"integer"},"period":{"description":"Time period (1h, 24h, 7d, 30d)","type":"string"},"start_time":{"description":"Start of the period (RFC3339)","type":"string"},"success_count":{"description":"Count of successful activities","type":"integer"},"top_servers":{"description":"Top servers by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopServer"},"type":"array","uniqueItems":false},"top_tools":{"description":"Top tools by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopTool"},"type":"array","uniqueItems":false},"total_count":{"description":"Total activity count","type":"integer"}},"type":"object"},"contracts.ActivityTopServer":{"properties":{"count":{"description":"Activity count","type":"integer"},"name":{"description":"Server name","type":"string"}},"type":"object"},"contracts.ActivityTopTool":{"properties":{"count":{"description":"Activity count","type":"integer"},"server":{"description":"Server name","type":"string"},"tool":{"description":"Tool name","type":"string"}},"type":"object"},"contracts.ActivityType":{"description":"Type of activity","type":"string","x-enum-varnames":["ActivityTypeToolCall","ActivityTypePolicyDecision","ActivityTypeQuarantineChange","ActivityTypeServerChange"]},"contracts.ConfigApplyResult":{"properties":{"applied_immediately":{"type":"boolean"},"changed_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"requires_restart":{"type":"boolean"},"restart_reason":{"type":"string"},"success":{"type":"boolean"},"validation_errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DCRStatus":{"properties":{"attempted":{"type":"boolean"},"error":{"type":"string"},"status_code":{"type":"integer"},"success":{"type":"boolean"}},"type":"object"},"contracts.Diagnostics":{"properties":{"docker_status":{"$ref":"#/components/schemas/contracts.DockerStatus"},"missing_secrets":{"description":"Renamed to avoid conflict","items":{"$ref":"#/components/schemas/contracts.MissingSecretInfo"},"type":"array","uniqueItems":false},"oauth_issues":{"description":"OAuth parameter mismatches","items":{"$ref":"#/components/schemas/contracts.OAuthIssue"},"type":"array","uniqueItems":false},"oauth_required":{"items":{"$ref":"#/components/schemas/contracts.OAuthRequirement"},"type":"array","uniqueItems":false},"runtime_warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false},"timestamp":{"type":"string"},"total_issues":{"type":"integer"},"upstream_errors":{"items":{"$ref":"#/components/schemas/contracts.UpstreamError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DockerStatus":{"properties":{"available":{"type":"boolean"},"error":{"type":"string"},"version":{"type":"string"}},"type":"object"},"contracts.ErrorResponse":{"properties":{"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.GetConfigResponse":{"properties":{"config":{"description":"The configuration object","type":"object"},"config_path":{"description":"Path to config file","type":"string"}},"type":"object"},"contracts.GetRegistriesResponse":{"properties":{"registries":{"items":{"$ref":"#/components/schemas/contracts.Registry"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerLogsResponse":{"properties":{"count":{"type":"integer"},"logs":{"items":{"$ref":"#/components/schemas/contracts.LogEntry"},"type":"array","uniqueItems":false},"server_name":{"type":"string"}},"type":"object"},"contracts.GetServerToolCallsResponse":{"properties":{"server_name":{"type":"string"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerToolsResponse":{"properties":{"count":{"type":"integer"},"server_name":{"type":"string"},"tools":{"items":{"$ref":"#/components/schemas/contracts.Tool"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.GetServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/contracts.Server"},"type":"array","uniqueItems":false},"stats":{"$ref":"#/components/schemas/contracts.ServerStats"}},"type":"object"},"contracts.GetSessionDetailResponse":{"properties":{"session":{"$ref":"#/components/schemas/contracts.MCPSession"}},"type":"object"},"contracts.GetSessionsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"sessions":{"items":{"$ref":"#/components/schemas/contracts.MCPSession"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetToolCallDetailResponse":{"properties":{"tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"}},"type":"object"},"contracts.GetToolCallsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.HealthStatus":{"description":"Unified health status calculated by the backend","properties":{"action":{"description":"Action is the suggested fix action: \"login\", \"restart\", \"enable\", \"approve\", \"view_logs\", \"set_secret\", \"configure\", or \"\" (none)","type":"string"},"admin_state":{"description":"AdminState indicates the admin state: \"enabled\", \"disabled\", or \"quarantined\"","type":"string"},"detail":{"description":"Detail is an optional longer explanation of the status","type":"string"},"level":{"description":"Level indicates the health level: \"healthy\", \"degraded\", or \"unhealthy\"","type":"string"},"summary":{"description":"Summary is a human-readable status message (e.g., \"Connected (5 tools)\")","type":"string"}},"type":"object"},"contracts.InfoEndpoints":{"description":"Available API endpoints","properties":{"http":{"description":"HTTP endpoint address (e.g., \"127.0.0.1:8080\")","type":"string"},"socket":{"description":"Unix socket path (empty if disabled)","type":"string"}},"type":"object"},"contracts.InfoResponse":{"properties":{"endpoints":{"$ref":"#/components/schemas/contracts.InfoEndpoints"},"listen_addr":{"description":"Listen address (e.g., \"127.0.0.1:8080\")","type":"string"},"update":{"$ref":"#/components/schemas/contracts.UpdateInfo"},"version":{"description":"Current MCPProxy version","type":"string"},"web_ui_url":{"description":"URL to access the web control panel","type":"string"}},"type":"object"},"contracts.IsolationConfig":{"properties":{"cpu_limit":{"type":"string"},"enabled":{"type":"boolean"},"image":{"type":"string"},"memory_limit":{"type":"string"},"timeout":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"contracts.LogEntry":{"properties":{"fields":{"type":"object"},"level":{"type":"string"},"message":{"type":"string"},"server":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.MCPSession":{"properties":{"client_name":{"type":"string"},"client_version":{"type":"string"},"end_time":{"type":"string"},"experimental":{"items":{"type":"string"},"type":"array","uniqueItems":false},"has_roots":{"description":"MCP Client Capabilities","type":"boolean"},"has_sampling":{"type":"boolean"},"id":{"type":"string"},"last_activity":{"type":"string"},"start_time":{"type":"string"},"status":{"type":"string"},"tool_call_count":{"type":"integer"},"total_tokens":{"type":"integer"}},"type":"object"},"contracts.MetadataStatus":{"properties":{"authorization_servers":{"items":{"type":"string"},"type":"array","uniqueItems":false},"error":{"type":"string"},"found":{"type":"boolean"},"url_checked":{"type":"string"}},"type":"object"},"contracts.MissingSecretInfo":{"properties":{"secret_name":{"type":"string"},"used_by":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.NPMPackageInfo":{"properties":{"exists":{"type":"boolean"},"install_cmd":{"type":"string"}},"type":"object"},"contracts.OAuthConfig":{"properties":{"auth_url":{"type":"string"},"client_id":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_port":{"type":"integer"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false},"token_expires_at":{"description":"When the OAuth token expires","type":"string"},"token_url":{"type":"string"},"token_valid":{"description":"Whether token is currently valid","type":"boolean"}},"type":"object"},"contracts.OAuthErrorDetails":{"description":"Structured discovery/failure details","properties":{"authorization_server_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"dcr_status":{"$ref":"#/components/schemas/contracts.DCRStatus"},"protected_resource_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"server_url":{"type":"string"}},"type":"object"},"contracts.OAuthFlowError":{"properties":{"correlation_id":{"description":"Flow tracking ID for log correlation","type":"string"},"debug_hint":{"description":"CLI command for log lookup","type":"string"},"details":{"$ref":"#/components/schemas/contracts.OAuthErrorDetails"},"error_code":{"description":"Machine-readable error code (e.g., OAUTH_NO_METADATA)","type":"string"},"error_type":{"description":"Category of OAuth runtime failure","type":"string"},"message":{"description":"Human-readable error description","type":"string"},"request_id":{"description":"HTTP request ID (from PR #237)","type":"string"},"server_name":{"description":"Server that failed OAuth","type":"string"},"success":{"description":"Always false","type":"boolean"},"suggestion":{"description":"Actionable remediation hint","type":"string"}},"type":"object"},"contracts.OAuthIssue":{"properties":{"documentation_url":{"type":"string"},"error":{"type":"string"},"issue":{"type":"string"},"missing_params":{"items":{"type":"string"},"type":"array","uniqueItems":false},"resolution":{"type":"string"},"server_name":{"type":"string"}},"type":"object"},"contracts.OAuthRequirement":{"properties":{"expires_at":{"type":"string"},"message":{"type":"string"},"server_name":{"type":"string"},"state":{"type":"string"}},"type":"object"},"contracts.OAuthStartResponse":{"properties":{"auth_url":{"description":"Authorization URL (always included for manual use)","type":"string"},"browser_error":{"description":"Error message if browser launch failed","type":"string"},"browser_opened":{"description":"Whether browser launch succeeded","type":"boolean"},"correlation_id":{"description":"UUID for tracking this flow","type":"string"},"message":{"description":"Human-readable status message","type":"string"},"server_name":{"description":"Name of the server being authenticated","type":"string"},"success":{"description":"Always true for successful start","type":"boolean"}},"type":"object"},"contracts.Registry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"contracts.ReplayToolCallRequest":{"properties":{"arguments":{"description":"Modified arguments for replay","type":"object"}},"type":"object"},"contracts.ReplayToolCallResponse":{"properties":{"error":{"description":"Error if replay failed","type":"string"},"new_call_id":{"description":"ID of the newly created call","type":"string"},"new_tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"replayed_from":{"description":"Original call ID","type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.RepositoryInfo":{"description":"Detected package info","properties":{"npm":{"$ref":"#/components/schemas/contracts.NPMPackageInfo"}},"type":"object"},"contracts.RepositoryServer":{"properties":{"connect_url":{"description":"Alternative connection URL","type":"string"},"created_at":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"install_cmd":{"description":"Installation command","type":"string"},"name":{"type":"string"},"registry":{"description":"Which registry this came from","type":"string"},"repository_info":{"$ref":"#/components/schemas/contracts.RepositoryInfo"},"source_code_url":{"description":"Source repository URL","type":"string"},"updated_at":{"type":"string"},"url":{"description":"MCP endpoint for remote servers only","type":"string"}},"type":"object"},"contracts.SearchRegistryServersResponse":{"properties":{"query":{"type":"string"},"registry_id":{"type":"string"},"servers":{"items":{"$ref":"#/components/schemas/contracts.RepositoryServer"},"type":"array","uniqueItems":false},"tag":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.SearchResult":{"properties":{"matches":{"type":"integer"},"score":{"type":"number"},"snippet":{"type":"string"},"tool":{"$ref":"#/components/schemas/contracts.Tool"}},"type":"object"},"contracts.SearchToolsResponse":{"properties":{"query":{"type":"string"},"results":{"items":{"$ref":"#/components/schemas/contracts.SearchResult"},"type":"array","uniqueItems":false},"took":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.Server":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"authenticated":{"description":"OAuth authentication status","type":"boolean"},"command":{"type":"string"},"connected":{"type":"boolean"},"connected_at":{"type":"string"},"connecting":{"type":"boolean"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"health":{"$ref":"#/components/schemas/contracts.HealthStatus"},"id":{"type":"string"},"isolation":{"$ref":"#/components/schemas/contracts.IsolationConfig"},"last_error":{"type":"string"},"last_reconnect_at":{"type":"string"},"last_retry_time":{"type":"string"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/contracts.OAuthConfig"},"oauth_status":{"description":"OAuth status: \"authenticated\", \"expired\", \"error\", \"none\"","type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"reconnect_count":{"type":"integer"},"retry_count":{"type":"integer"},"should_retry":{"type":"boolean"},"status":{"type":"string"},"token_expires_at":{"description":"When the OAuth token expires (ISO 8601)","type":"string"},"tool_count":{"type":"integer"},"tool_list_token_size":{"description":"Token size for this server's tools","type":"integer"},"updated":{"type":"string"},"url":{"type":"string"},"user_logged_out":{"description":"True if user explicitly logged out (prevents auto-reconnection)","type":"boolean"},"working_dir":{"type":"string"}},"type":"object"},"contracts.ServerActionResponse":{"properties":{"action":{"type":"string"},"async":{"type":"boolean"},"server":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ServerStats":{"properties":{"connected_servers":{"type":"integer"},"docker_containers":{"type":"integer"},"quarantined_servers":{"type":"integer"},"token_metrics":{"$ref":"#/components/schemas/contracts.ServerTokenMetrics"},"total_servers":{"type":"integer"},"total_tools":{"type":"integer"}},"type":"object"},"contracts.ServerTokenMetrics":{"properties":{"average_query_result_size":{"description":"Typical retrieve_tools output (tokens)","type":"integer"},"per_server_tool_list_sizes":{"additionalProperties":{"type":"integer"},"description":"Token size per server","type":"object"},"saved_tokens":{"description":"Difference","type":"integer"},"saved_tokens_percentage":{"description":"Percentage saved","type":"number"},"total_server_tool_list_size":{"description":"All upstream tools combined (tokens)","type":"integer"}},"type":"object"},"contracts.SuccessResponse":{"properties":{"data":{"type":"object"},"success":{"type":"boolean"}},"type":"object"},"contracts.TokenMetrics":{"description":"Token usage metrics (nil for older records)","properties":{"encoding":{"description":"Encoding used (e.g., cl100k_base)","type":"string"},"estimated_cost":{"description":"Optional cost estimate","type":"number"},"input_tokens":{"description":"Tokens in the request","type":"integer"},"model":{"description":"Model used for tokenization","type":"string"},"output_tokens":{"description":"Tokens in the response","type":"integer"},"total_tokens":{"description":"Total tokens (input + output)","type":"integer"},"truncated_tokens":{"description":"Tokens removed by truncation","type":"integer"},"was_truncated":{"description":"Whether response was truncated","type":"boolean"}},"type":"object"},"contracts.Tool":{"properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"description":{"type":"string"},"last_used":{"type":"string"},"name":{"type":"string"},"schema":{"type":"object"},"server_name":{"type":"string"},"usage":{"type":"integer"}},"type":"object"},"contracts.ToolAnnotation":{"description":"Tool behavior hints snapshot","properties":{"destructiveHint":{"type":"boolean"},"idempotentHint":{"type":"boolean"},"openWorldHint":{"type":"boolean"},"readOnlyHint":{"type":"boolean"},"title":{"type":"string"}},"type":"object"},"contracts.ToolCallRecord":{"description":"The new tool call record","properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"arguments":{"description":"Tool arguments","type":"object"},"config_path":{"description":"Active config file path","type":"string"},"duration":{"description":"Duration in nanoseconds","type":"integer"},"error":{"description":"Error message (failure only)","type":"string"},"execution_type":{"description":"\"direct\" or \"code_execution\"","type":"string"},"id":{"description":"Unique identifier","type":"string"},"mcp_client_name":{"description":"MCP client name from InitializeRequest","type":"string"},"mcp_client_version":{"description":"MCP client version","type":"string"},"mcp_session_id":{"description":"MCP session identifier","type":"string"},"metrics":{"$ref":"#/components/schemas/contracts.TokenMetrics"},"parent_call_id":{"description":"Links nested calls to parent code_execution","type":"string"},"request_id":{"description":"Request correlation ID","type":"string"},"response":{"description":"Tool response (success only)","type":"object"},"server_id":{"description":"Server identity hash","type":"string"},"server_name":{"description":"Human-readable server name","type":"string"},"timestamp":{"description":"When the call was made","type":"string"},"tool_name":{"description":"Tool name (without server prefix)","type":"string"}},"type":"object"},"contracts.UpdateInfo":{"description":"Update information (if available)","properties":{"available":{"description":"Whether an update is available","type":"boolean"},"check_error":{"description":"Error message if update check failed","type":"string"},"checked_at":{"description":"When the update check was performed","type":"string"},"is_prerelease":{"description":"Whether the latest version is a prerelease","type":"boolean"},"latest_version":{"description":"Latest version available (e.g., \"v1.2.3\")","type":"string"},"release_url":{"description":"URL to the release page","type":"string"}},"type":"object"},"contracts.UpstreamError":{"properties":{"error_message":{"type":"string"},"server_name":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.ValidateConfigResponse":{"properties":{"errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false},"valid":{"type":"boolean"}},"type":"object"},"contracts.ValidationError":{"properties":{"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"data":{"properties":{"data":{"$ref":"#/components/schemas/contracts.InfoResponse"}},"type":"object"},"httpapi.AddServerRequest":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"url":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"httpapi.CanonicalConfigPath":{"properties":{"description":{"description":"Brief description","type":"string"},"exists":{"description":"Whether the file exists","type":"boolean"},"format":{"description":"Format identifier (e.g., \"claude_desktop\")","type":"string"},"name":{"description":"Display name (e.g., \"Claude Desktop\")","type":"string"},"os":{"description":"Operating system (darwin, windows, linux)","type":"string"},"path":{"description":"Full path to the config file","type":"string"}},"type":"object"},"httpapi.CanonicalConfigPathsResponse":{"properties":{"os":{"description":"Current operating system","type":"string"},"paths":{"description":"List of canonical config paths","items":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPath"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportFromPathRequest":{"properties":{"format":{"description":"Optional format hint","type":"string"},"path":{"description":"File path to import from","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportRequest":{"properties":{"content":{"description":"Raw JSON or TOML content","type":"string"},"format":{"description":"Optional format hint","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportResponse":{"properties":{"failed":{"items":{"$ref":"#/components/schemas/configimport.FailedServer"},"type":"array","uniqueItems":false},"format":{"type":"string"},"format_name":{"type":"string"},"imported":{"items":{"$ref":"#/components/schemas/httpapi.ImportedServerResponse"},"type":"array","uniqueItems":false},"skipped":{"items":{"$ref":"#/components/schemas/configimport.SkippedServer"},"type":"array","uniqueItems":false},"summary":{"$ref":"#/components/schemas/configimport.ImportSummary"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportedServerResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"fields_skipped":{"items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"type":"string"},"original_name":{"type":"string"},"protocol":{"type":"string"},"source_format":{"type":"string"},"url":{"type":"string"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"management.BulkOperationResult":{"properties":{"errors":{"additionalProperties":{"type":"string"},"description":"Map of server name to error message","type":"object"},"failed":{"description":"Number of failed operations","type":"integer"},"successful":{"description":"Number of successful operations","type":"integer"},"total":{"description":"Total servers processed","type":"integer"}},"type":"object"},"observability.HealthResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"observability.HealthStatus":{"properties":{"error":{"type":"string"},"latency":{"type":"string"},"name":{"type":"string"},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"}},"type":"object"},"observability.ReadinessResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"ready\" or \"not_ready\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"secureenv.EnvConfig":{"description":"Environment configuration for secure variable filtering","properties":{"allowed_system_vars":{"items":{"type":"string"},"type":"array","uniqueItems":false},"custom_vars":{"additionalProperties":{"type":"string"},"type":"object"},"enhance_path":{"description":"Enable PATH enhancement for Launchd scenarios","type":"boolean"},"inherit_system_safe":{"type":"boolean"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"description":"API key authentication via query parameter. Use ?apikey=your-key","in":"query","name":"apikey","type":"apiKey"}}}, + "components": {"schemas":{"config.Config":{"properties":{"activity_cleanup_interval_min":{"description":"Background cleanup interval in minutes (default: 60)","type":"integer"},"activity_max_records":{"description":"Max records before pruning (default: 100000)","type":"integer"},"activity_max_response_size":{"description":"Response truncation limit in bytes (default: 65536)","type":"integer"},"activity_retention_days":{"description":"Activity logging settings (RFC-003)","type":"integer"},"allow_server_add":{"type":"boolean"},"allow_server_remove":{"type":"boolean"},"api_key":{"description":"Security settings","type":"string"},"call_tool_timeout":{"type":"string"},"check_server_repo":{"description":"Repository detection settings","type":"boolean"},"code_execution_max_tool_calls":{"description":"Max tool calls per execution (0 = unlimited, default: 0)","type":"integer"},"code_execution_pool_size":{"description":"JavaScript runtime pool size (default: 10)","type":"integer"},"code_execution_timeout_ms":{"description":"Timeout in milliseconds (default: 120000, max: 600000)","type":"integer"},"data_dir":{"type":"string"},"debug_search":{"type":"boolean"},"disable_management":{"type":"boolean"},"docker_isolation":{"$ref":"#/components/schemas/config.DockerIsolationConfig"},"docker_recovery":{"$ref":"#/components/schemas/config.DockerRecoveryConfig"},"enable_code_execution":{"description":"Code execution settings","type":"boolean"},"enable_prompts":{"description":"Prompts settings","type":"boolean"},"enable_socket":{"description":"Enable Unix socket/named pipe for local IPC (default: true)","type":"boolean"},"enable_tray":{"description":"Deprecated: EnableTray is unused and has no runtime effect. Kept for backward compatibility.","type":"boolean"},"environment":{"$ref":"#/components/schemas/secureenv.EnvConfig"},"features":{"$ref":"#/components/schemas/config.FeatureFlags"},"intent_declaration":{"$ref":"#/components/schemas/config.IntentDeclarationConfig"},"listen":{"type":"string"},"logging":{"$ref":"#/components/schemas/config.LogConfig"},"mcpServers":{"items":{"$ref":"#/components/schemas/config.ServerConfig"},"type":"array","uniqueItems":false},"oauth_expiry_warning_hours":{"description":"Health status settings","type":"number"},"read_only_mode":{"type":"boolean"},"registries":{"description":"Registries configuration for MCP server discovery","items":{"$ref":"#/components/schemas/config.RegistryEntry"},"type":"array","uniqueItems":false},"sensitive_data_detection":{"$ref":"#/components/schemas/config.SensitiveDataDetectionConfig"},"tls":{"$ref":"#/components/schemas/config.TLSConfig"},"tokenizer":{"$ref":"#/components/schemas/config.TokenizerConfig"},"tool_response_limit":{"type":"integer"},"tools_limit":{"type":"integer"},"top_k":{"description":"Deprecated: TopK is superseded by ToolsLimit and has no runtime effect. Kept for backward compatibility.","type":"integer"},"tray_endpoint":{"description":"Tray endpoint override (unix:// or npipe://)","type":"string"}},"type":"object"},"config.CustomPattern":{"properties":{"category":{"description":"Category (defaults to \"custom\")","type":"string"},"keywords":{"description":"Keywords to match (mutually exclusive with Regex)","items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"description":"Unique identifier for this pattern","type":"string"},"regex":{"description":"Regex pattern (mutually exclusive with Keywords)","type":"string"},"severity":{"description":"Risk level: critical, high, medium, low","type":"string"}},"type":"object"},"config.DockerIsolationConfig":{"description":"Docker isolation settings","properties":{"cpu_limit":{"description":"CPU limit for containers","type":"string"},"default_images":{"additionalProperties":{"type":"string"},"description":"Map of runtime type to Docker image","type":"object"},"enabled":{"description":"Global enable/disable for Docker isolation","type":"boolean"},"extra_args":{"description":"Additional docker run arguments","items":{"type":"string"},"type":"array","uniqueItems":false},"log_driver":{"description":"Docker log driver (default: json-file)","type":"string"},"log_max_files":{"description":"Maximum number of log files (default: 3)","type":"string"},"log_max_size":{"description":"Maximum size of log files (default: 100m)","type":"string"},"memory_limit":{"description":"Memory limit for containers","type":"string"},"network_mode":{"description":"Docker network mode (default: bridge)","type":"string"},"registry":{"description":"Custom registry (defaults to docker.io)","type":"string"},"timeout":{"description":"Container startup timeout","type":"string"}},"type":"object"},"config.DockerRecoveryConfig":{"description":"Docker recovery settings","properties":{"enabled":{"description":"Enable Docker recovery monitoring (default: true)","type":"boolean"},"max_retries":{"description":"Maximum retry attempts (0 = unlimited)","type":"integer"},"notify_on_failure":{"description":"Show notification on recovery failure (default: true)","type":"boolean"},"notify_on_retry":{"description":"Show notification on each retry (default: false)","type":"boolean"},"notify_on_start":{"description":"Show notification when recovery starts (default: true)","type":"boolean"},"notify_on_success":{"description":"Show notification on successful recovery (default: true)","type":"boolean"},"persistent_state":{"description":"Save recovery state across restarts (default: true)","type":"boolean"}},"type":"object"},"config.FeatureFlags":{"description":"Deprecated: Features flags are unused and have no runtime effect. Kept for backward compatibility.","properties":{"enable_async_storage":{"type":"boolean"},"enable_caching":{"type":"boolean"},"enable_contract_tests":{"type":"boolean"},"enable_debug_logging":{"description":"Development features","type":"boolean"},"enable_docker_isolation":{"type":"boolean"},"enable_event_bus":{"type":"boolean"},"enable_health_checks":{"type":"boolean"},"enable_metrics":{"type":"boolean"},"enable_oauth":{"description":"Security features","type":"boolean"},"enable_observability":{"description":"Observability features","type":"boolean"},"enable_quarantine":{"type":"boolean"},"enable_runtime":{"description":"Runtime features","type":"boolean"},"enable_search":{"description":"Storage features","type":"boolean"},"enable_sse":{"type":"boolean"},"enable_tracing":{"type":"boolean"},"enable_tray":{"type":"boolean"},"enable_web_ui":{"description":"UI features","type":"boolean"}},"type":"object"},"config.IntentDeclarationConfig":{"description":"Intent declaration settings (Spec 018)","properties":{"strict_server_validation":{"description":"StrictServerValidation controls whether server annotation mismatches\ncause rejection (true) or just warnings (false).\nDefault: true (reject mismatches)","type":"boolean"}},"type":"object"},"config.IsolationConfig":{"description":"Per-server isolation settings","properties":{"enabled":{"description":"Enable Docker isolation for this server (nil = inherit global)","type":"boolean"},"extra_args":{"description":"Additional docker run arguments for this server","items":{"type":"string"},"type":"array","uniqueItems":false},"image":{"description":"Custom Docker image (overrides default)","type":"string"},"log_driver":{"description":"Docker log driver override for this server","type":"string"},"log_max_files":{"description":"Maximum number of log files override","type":"string"},"log_max_size":{"description":"Maximum size of log files override","type":"string"},"network_mode":{"description":"Custom network mode for this server","type":"string"},"working_dir":{"description":"Custom working directory in container","type":"string"}},"type":"object"},"config.LogConfig":{"description":"Logging configuration","properties":{"compress":{"type":"boolean"},"enable_console":{"type":"boolean"},"enable_file":{"type":"boolean"},"filename":{"type":"string"},"json_format":{"type":"boolean"},"level":{"type":"string"},"log_dir":{"description":"Custom log directory","type":"string"},"max_age":{"description":"days","type":"integer"},"max_backups":{"description":"number of backup files","type":"integer"},"max_size":{"description":"MB","type":"integer"}},"type":"object"},"config.OAuthConfig":{"description":"OAuth configuration (keep even when empty to signal OAuth requirement)","properties":{"client_id":{"type":"string"},"client_secret":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"description":"Additional OAuth parameters (e.g., RFC 8707 resource)","type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_uri":{"type":"string"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.RegistryEntry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"config.SensitiveDataDetectionConfig":{"description":"Sensitive data detection settings (Spec 026)","properties":{"categories":{"additionalProperties":{"type":"boolean"},"description":"Enable/disable specific detection categories","type":"object"},"custom_patterns":{"description":"User-defined detection patterns","items":{"$ref":"#/components/schemas/config.CustomPattern"},"type":"array","uniqueItems":false},"enabled":{"description":"Enable sensitive data detection (default: true)","type":"boolean"},"entropy_threshold":{"description":"Shannon entropy threshold for high-entropy detection (default: 4.5)","type":"number"},"max_payload_size_kb":{"description":"Max size to scan before truncating (default: 1024)","type":"integer"},"scan_requests":{"description":"Scan tool call arguments (default: true)","type":"boolean"},"scan_responses":{"description":"Scan tool responses (default: true)","type":"boolean"},"sensitive_keywords":{"description":"Keywords to flag","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"config.ServerConfig":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"description":"For HTTP servers","type":"object"},"isolation":{"$ref":"#/components/schemas/config.IsolationConfig"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/config.OAuthConfig"},"protocol":{"description":"stdio, http, sse, streamable-http, auto","type":"string"},"quarantined":{"description":"Security quarantine status","type":"boolean"},"updated":{"type":"string"},"url":{"type":"string"},"working_dir":{"description":"Working directory for stdio servers","type":"string"}},"type":"object"},"config.TLSConfig":{"description":"TLS configuration","properties":{"certs_dir":{"description":"Directory for certificates","type":"string"},"enabled":{"description":"Enable HTTPS","type":"boolean"},"hsts":{"description":"Enable HTTP Strict Transport Security","type":"boolean"},"require_client_cert":{"description":"Enable mTLS","type":"boolean"}},"type":"object"},"config.TokenizerConfig":{"description":"Tokenizer configuration for token counting","properties":{"default_model":{"description":"Default model for tokenization (e.g., \"gpt-4\")","type":"string"},"enabled":{"description":"Enable token counting","type":"boolean"},"encoding":{"description":"Default encoding (e.g., \"cl100k_base\")","type":"string"}},"type":"object"},"configimport.FailedServer":{"properties":{"details":{"type":"string"},"error":{"type":"string"},"name":{"type":"string"}},"type":"object"},"configimport.ImportSummary":{"properties":{"failed":{"type":"integer"},"imported":{"type":"integer"},"skipped":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"configimport.SkippedServer":{"properties":{"name":{"type":"string"},"reason":{"description":"\"already_exists\", \"filtered_out\", \"invalid_name\"","type":"string"}},"type":"object"},"contracts.APIResponse":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ActivityDetailResponse":{"properties":{"activity":{"$ref":"#/components/schemas/contracts.ActivityRecord"}},"type":"object"},"contracts.ActivityListResponse":{"properties":{"activities":{"items":{"$ref":"#/components/schemas/contracts.ActivityRecord"},"type":"array","uniqueItems":false},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"type":"object"},"contracts.ActivityRecord":{"properties":{"arguments":{"description":"Tool call arguments","type":"object"},"detection_types":{"description":"List of detection types found","items":{"type":"string"},"type":"array","uniqueItems":false},"duration_ms":{"description":"Execution duration in milliseconds","type":"integer"},"error_message":{"description":"Error details if status is \"error\"","type":"string"},"has_sensitive_data":{"description":"Sensitive data detection fields (Spec 026)","type":"boolean"},"id":{"description":"Unique identifier (ULID format)","type":"string"},"max_severity":{"description":"Highest severity level detected (critical, high, medium, low)","type":"string"},"metadata":{"description":"Additional context-specific data","type":"object"},"request_id":{"description":"HTTP request ID for correlation","type":"string"},"response":{"description":"Tool response (potentially truncated)","type":"string"},"response_truncated":{"description":"True if response was truncated","type":"boolean"},"server_name":{"description":"Name of upstream MCP server","type":"string"},"session_id":{"description":"MCP session ID for correlation","type":"string"},"source":{"$ref":"#/components/schemas/contracts.ActivitySource"},"status":{"description":"Result status: \"success\", \"error\", \"blocked\"","type":"string"},"timestamp":{"description":"When activity occurred","type":"string"},"tool_name":{"description":"Name of tool called","type":"string"},"type":{"$ref":"#/components/schemas/contracts.ActivityType"}},"type":"object"},"contracts.ActivitySource":{"description":"How activity was triggered: \"mcp\", \"cli\", \"api\"","type":"string","x-enum-varnames":["ActivitySourceMCP","ActivitySourceCLI","ActivitySourceAPI"]},"contracts.ActivitySummaryResponse":{"properties":{"blocked_count":{"description":"Count of blocked activities","type":"integer"},"end_time":{"description":"End of the period (RFC3339)","type":"string"},"error_count":{"description":"Count of error activities","type":"integer"},"period":{"description":"Time period (1h, 24h, 7d, 30d)","type":"string"},"start_time":{"description":"Start of the period (RFC3339)","type":"string"},"success_count":{"description":"Count of successful activities","type":"integer"},"top_servers":{"description":"Top servers by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopServer"},"type":"array","uniqueItems":false},"top_tools":{"description":"Top tools by activity count","items":{"$ref":"#/components/schemas/contracts.ActivityTopTool"},"type":"array","uniqueItems":false},"total_count":{"description":"Total activity count","type":"integer"}},"type":"object"},"contracts.ActivityTopServer":{"properties":{"count":{"description":"Activity count","type":"integer"},"name":{"description":"Server name","type":"string"}},"type":"object"},"contracts.ActivityTopTool":{"properties":{"count":{"description":"Activity count","type":"integer"},"server":{"description":"Server name","type":"string"},"tool":{"description":"Tool name","type":"string"}},"type":"object"},"contracts.ActivityType":{"description":"Type of activity","type":"string","x-enum-varnames":["ActivityTypeToolCall","ActivityTypePolicyDecision","ActivityTypeQuarantineChange","ActivityTypeServerChange"]},"contracts.ConfigApplyResult":{"properties":{"applied_immediately":{"type":"boolean"},"changed_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"requires_restart":{"type":"boolean"},"restart_reason":{"type":"string"},"success":{"type":"boolean"},"validation_errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DCRStatus":{"properties":{"attempted":{"type":"boolean"},"error":{"type":"string"},"status_code":{"type":"integer"},"success":{"type":"boolean"}},"type":"object"},"contracts.DeprecatedConfigWarning":{"properties":{"field":{"type":"string"},"message":{"type":"string"},"replacement":{"type":"string"}},"type":"object"},"contracts.Diagnostics":{"properties":{"deprecated_configs":{"description":"Deprecated config fields found","items":{"$ref":"#/components/schemas/contracts.DeprecatedConfigWarning"},"type":"array","uniqueItems":false},"docker_status":{"$ref":"#/components/schemas/contracts.DockerStatus"},"missing_secrets":{"description":"Renamed to avoid conflict","items":{"$ref":"#/components/schemas/contracts.MissingSecretInfo"},"type":"array","uniqueItems":false},"oauth_issues":{"description":"OAuth parameter mismatches","items":{"$ref":"#/components/schemas/contracts.OAuthIssue"},"type":"array","uniqueItems":false},"oauth_required":{"items":{"$ref":"#/components/schemas/contracts.OAuthRequirement"},"type":"array","uniqueItems":false},"runtime_warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false},"timestamp":{"type":"string"},"total_issues":{"type":"integer"},"upstream_errors":{"items":{"$ref":"#/components/schemas/contracts.UpstreamError"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.DockerStatus":{"properties":{"available":{"type":"boolean"},"error":{"type":"string"},"version":{"type":"string"}},"type":"object"},"contracts.ErrorResponse":{"properties":{"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.GetConfigResponse":{"properties":{"config":{"description":"The configuration object","type":"object"},"config_path":{"description":"Path to config file","type":"string"}},"type":"object"},"contracts.GetRegistriesResponse":{"properties":{"registries":{"items":{"$ref":"#/components/schemas/contracts.Registry"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerLogsResponse":{"properties":{"count":{"type":"integer"},"logs":{"items":{"$ref":"#/components/schemas/contracts.LogEntry"},"type":"array","uniqueItems":false},"server_name":{"type":"string"}},"type":"object"},"contracts.GetServerToolCallsResponse":{"properties":{"server_name":{"type":"string"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetServerToolsResponse":{"properties":{"count":{"type":"integer"},"server_name":{"type":"string"},"tools":{"items":{"$ref":"#/components/schemas/contracts.Tool"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.GetServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/contracts.Server"},"type":"array","uniqueItems":false},"stats":{"$ref":"#/components/schemas/contracts.ServerStats"}},"type":"object"},"contracts.GetSessionDetailResponse":{"properties":{"session":{"$ref":"#/components/schemas/contracts.MCPSession"}},"type":"object"},"contracts.GetSessionsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"sessions":{"items":{"$ref":"#/components/schemas/contracts.MCPSession"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.GetToolCallDetailResponse":{"properties":{"tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"}},"type":"object"},"contracts.GetToolCallsResponse":{"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"tool_calls":{"items":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"contracts.HealthStatus":{"description":"Unified health status calculated by the backend","properties":{"action":{"description":"Action is the suggested fix action: \"login\", \"restart\", \"enable\", \"approve\", \"view_logs\", \"set_secret\", \"configure\", or \"\" (none)","type":"string"},"admin_state":{"description":"AdminState indicates the admin state: \"enabled\", \"disabled\", or \"quarantined\"","type":"string"},"detail":{"description":"Detail is an optional longer explanation of the status","type":"string"},"level":{"description":"Level indicates the health level: \"healthy\", \"degraded\", or \"unhealthy\"","type":"string"},"summary":{"description":"Summary is a human-readable status message (e.g., \"Connected (5 tools)\")","type":"string"}},"type":"object"},"contracts.InfoEndpoints":{"description":"Available API endpoints","properties":{"http":{"description":"HTTP endpoint address (e.g., \"127.0.0.1:8080\")","type":"string"},"socket":{"description":"Unix socket path (empty if disabled)","type":"string"}},"type":"object"},"contracts.InfoResponse":{"properties":{"endpoints":{"$ref":"#/components/schemas/contracts.InfoEndpoints"},"listen_addr":{"description":"Listen address (e.g., \"127.0.0.1:8080\")","type":"string"},"update":{"$ref":"#/components/schemas/contracts.UpdateInfo"},"version":{"description":"Current MCPProxy version","type":"string"},"web_ui_url":{"description":"URL to access the web control panel","type":"string"}},"type":"object"},"contracts.IsolationConfig":{"properties":{"cpu_limit":{"type":"string"},"enabled":{"type":"boolean"},"image":{"type":"string"},"memory_limit":{"type":"string"},"timeout":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"contracts.LogEntry":{"properties":{"fields":{"type":"object"},"level":{"type":"string"},"message":{"type":"string"},"server":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.MCPSession":{"properties":{"client_name":{"type":"string"},"client_version":{"type":"string"},"end_time":{"type":"string"},"experimental":{"items":{"type":"string"},"type":"array","uniqueItems":false},"has_roots":{"description":"MCP Client Capabilities","type":"boolean"},"has_sampling":{"type":"boolean"},"id":{"type":"string"},"last_activity":{"type":"string"},"start_time":{"type":"string"},"status":{"type":"string"},"tool_call_count":{"type":"integer"},"total_tokens":{"type":"integer"}},"type":"object"},"contracts.MetadataStatus":{"properties":{"authorization_servers":{"items":{"type":"string"},"type":"array","uniqueItems":false},"error":{"type":"string"},"found":{"type":"boolean"},"url_checked":{"type":"string"}},"type":"object"},"contracts.MissingSecretInfo":{"properties":{"secret_name":{"type":"string"},"used_by":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"contracts.NPMPackageInfo":{"properties":{"exists":{"type":"boolean"},"install_cmd":{"type":"string"}},"type":"object"},"contracts.OAuthConfig":{"properties":{"auth_url":{"type":"string"},"client_id":{"type":"string"},"extra_params":{"additionalProperties":{"type":"string"},"type":"object"},"pkce_enabled":{"type":"boolean"},"redirect_port":{"type":"integer"},"scopes":{"items":{"type":"string"},"type":"array","uniqueItems":false},"token_expires_at":{"description":"When the OAuth token expires","type":"string"},"token_url":{"type":"string"},"token_valid":{"description":"Whether token is currently valid","type":"boolean"}},"type":"object"},"contracts.OAuthErrorDetails":{"description":"Structured discovery/failure details","properties":{"authorization_server_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"dcr_status":{"$ref":"#/components/schemas/contracts.DCRStatus"},"protected_resource_metadata":{"$ref":"#/components/schemas/contracts.MetadataStatus"},"server_url":{"type":"string"}},"type":"object"},"contracts.OAuthFlowError":{"properties":{"correlation_id":{"description":"Flow tracking ID for log correlation","type":"string"},"debug_hint":{"description":"CLI command for log lookup","type":"string"},"details":{"$ref":"#/components/schemas/contracts.OAuthErrorDetails"},"error_code":{"description":"Machine-readable error code (e.g., OAUTH_NO_METADATA)","type":"string"},"error_type":{"description":"Category of OAuth runtime failure","type":"string"},"message":{"description":"Human-readable error description","type":"string"},"request_id":{"description":"HTTP request ID (from PR #237)","type":"string"},"server_name":{"description":"Server that failed OAuth","type":"string"},"success":{"description":"Always false","type":"boolean"},"suggestion":{"description":"Actionable remediation hint","type":"string"}},"type":"object"},"contracts.OAuthIssue":{"properties":{"documentation_url":{"type":"string"},"error":{"type":"string"},"issue":{"type":"string"},"missing_params":{"items":{"type":"string"},"type":"array","uniqueItems":false},"resolution":{"type":"string"},"server_name":{"type":"string"}},"type":"object"},"contracts.OAuthRequirement":{"properties":{"expires_at":{"type":"string"},"message":{"type":"string"},"server_name":{"type":"string"},"state":{"type":"string"}},"type":"object"},"contracts.OAuthStartResponse":{"properties":{"auth_url":{"description":"Authorization URL (always included for manual use)","type":"string"},"browser_error":{"description":"Error message if browser launch failed","type":"string"},"browser_opened":{"description":"Whether browser launch succeeded","type":"boolean"},"correlation_id":{"description":"UUID for tracking this flow","type":"string"},"message":{"description":"Human-readable status message","type":"string"},"server_name":{"description":"Name of the server being authenticated","type":"string"},"success":{"description":"Always true for successful start","type":"boolean"}},"type":"object"},"contracts.Registry":{"properties":{"count":{"description":"number or string","type":"string"},"description":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"protocol":{"type":"string"},"servers_url":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"url":{"type":"string"}},"type":"object"},"contracts.ReplayToolCallRequest":{"properties":{"arguments":{"description":"Modified arguments for replay","type":"object"}},"type":"object"},"contracts.ReplayToolCallResponse":{"properties":{"error":{"description":"Error if replay failed","type":"string"},"new_call_id":{"description":"ID of the newly created call","type":"string"},"new_tool_call":{"$ref":"#/components/schemas/contracts.ToolCallRecord"},"replayed_from":{"description":"Original call ID","type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.RepositoryInfo":{"description":"Detected package info","properties":{"npm":{"$ref":"#/components/schemas/contracts.NPMPackageInfo"}},"type":"object"},"contracts.RepositoryServer":{"properties":{"connect_url":{"description":"Alternative connection URL","type":"string"},"created_at":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"install_cmd":{"description":"Installation command","type":"string"},"name":{"type":"string"},"registry":{"description":"Which registry this came from","type":"string"},"repository_info":{"$ref":"#/components/schemas/contracts.RepositoryInfo"},"source_code_url":{"description":"Source repository URL","type":"string"},"updated_at":{"type":"string"},"url":{"description":"MCP endpoint for remote servers only","type":"string"}},"type":"object"},"contracts.SearchRegistryServersResponse":{"properties":{"query":{"type":"string"},"registry_id":{"type":"string"},"servers":{"items":{"$ref":"#/components/schemas/contracts.RepositoryServer"},"type":"array","uniqueItems":false},"tag":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.SearchResult":{"properties":{"matches":{"type":"integer"},"score":{"type":"number"},"snippet":{"type":"string"},"tool":{"$ref":"#/components/schemas/contracts.Tool"}},"type":"object"},"contracts.SearchToolsResponse":{"properties":{"query":{"type":"string"},"results":{"items":{"$ref":"#/components/schemas/contracts.SearchResult"},"type":"array","uniqueItems":false},"took":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"contracts.Server":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"authenticated":{"description":"OAuth authentication status","type":"boolean"},"command":{"type":"string"},"connected":{"type":"boolean"},"connected_at":{"type":"string"},"connecting":{"type":"boolean"},"created":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"health":{"$ref":"#/components/schemas/contracts.HealthStatus"},"id":{"type":"string"},"isolation":{"$ref":"#/components/schemas/contracts.IsolationConfig"},"last_error":{"type":"string"},"last_reconnect_at":{"type":"string"},"last_retry_time":{"type":"string"},"name":{"type":"string"},"oauth":{"$ref":"#/components/schemas/contracts.OAuthConfig"},"oauth_status":{"description":"OAuth status: \"authenticated\", \"expired\", \"error\", \"none\"","type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"reconnect_count":{"type":"integer"},"retry_count":{"type":"integer"},"should_retry":{"type":"boolean"},"status":{"type":"string"},"token_expires_at":{"description":"When the OAuth token expires (ISO 8601)","type":"string"},"tool_count":{"type":"integer"},"tool_list_token_size":{"description":"Token size for this server's tools","type":"integer"},"updated":{"type":"string"},"url":{"type":"string"},"user_logged_out":{"description":"True if user explicitly logged out (prevents auto-reconnection)","type":"boolean"},"working_dir":{"type":"string"}},"type":"object"},"contracts.ServerActionResponse":{"properties":{"action":{"type":"string"},"async":{"type":"boolean"},"server":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"contracts.ServerStats":{"properties":{"connected_servers":{"type":"integer"},"docker_containers":{"type":"integer"},"quarantined_servers":{"type":"integer"},"token_metrics":{"$ref":"#/components/schemas/contracts.ServerTokenMetrics"},"total_servers":{"type":"integer"},"total_tools":{"type":"integer"}},"type":"object"},"contracts.ServerTokenMetrics":{"properties":{"average_query_result_size":{"description":"Typical retrieve_tools output (tokens)","type":"integer"},"per_server_tool_list_sizes":{"additionalProperties":{"type":"integer"},"description":"Token size per server","type":"object"},"saved_tokens":{"description":"Difference","type":"integer"},"saved_tokens_percentage":{"description":"Percentage saved","type":"number"},"total_server_tool_list_size":{"description":"All upstream tools combined (tokens)","type":"integer"}},"type":"object"},"contracts.SuccessResponse":{"properties":{"data":{"type":"object"},"success":{"type":"boolean"}},"type":"object"},"contracts.TokenMetrics":{"description":"Token usage metrics (nil for older records)","properties":{"encoding":{"description":"Encoding used (e.g., cl100k_base)","type":"string"},"estimated_cost":{"description":"Optional cost estimate","type":"number"},"input_tokens":{"description":"Tokens in the request","type":"integer"},"model":{"description":"Model used for tokenization","type":"string"},"output_tokens":{"description":"Tokens in the response","type":"integer"},"total_tokens":{"description":"Total tokens (input + output)","type":"integer"},"truncated_tokens":{"description":"Tokens removed by truncation","type":"integer"},"was_truncated":{"description":"Whether response was truncated","type":"boolean"}},"type":"object"},"contracts.Tool":{"properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"description":{"type":"string"},"last_used":{"type":"string"},"name":{"type":"string"},"schema":{"type":"object"},"server_name":{"type":"string"},"usage":{"type":"integer"}},"type":"object"},"contracts.ToolAnnotation":{"description":"Tool behavior hints snapshot","properties":{"destructiveHint":{"type":"boolean"},"idempotentHint":{"type":"boolean"},"openWorldHint":{"type":"boolean"},"readOnlyHint":{"type":"boolean"},"title":{"type":"string"}},"type":"object"},"contracts.ToolCallRecord":{"description":"The new tool call record","properties":{"annotations":{"$ref":"#/components/schemas/contracts.ToolAnnotation"},"arguments":{"description":"Tool arguments","type":"object"},"config_path":{"description":"Active config file path","type":"string"},"duration":{"description":"Duration in nanoseconds","type":"integer"},"error":{"description":"Error message (failure only)","type":"string"},"execution_type":{"description":"\"direct\" or \"code_execution\"","type":"string"},"id":{"description":"Unique identifier","type":"string"},"mcp_client_name":{"description":"MCP client name from InitializeRequest","type":"string"},"mcp_client_version":{"description":"MCP client version","type":"string"},"mcp_session_id":{"description":"MCP session identifier","type":"string"},"metrics":{"$ref":"#/components/schemas/contracts.TokenMetrics"},"parent_call_id":{"description":"Links nested calls to parent code_execution","type":"string"},"request_id":{"description":"Request correlation ID","type":"string"},"response":{"description":"Tool response (success only)","type":"object"},"server_id":{"description":"Server identity hash","type":"string"},"server_name":{"description":"Human-readable server name","type":"string"},"timestamp":{"description":"When the call was made","type":"string"},"tool_name":{"description":"Tool name (without server prefix)","type":"string"}},"type":"object"},"contracts.UpdateInfo":{"description":"Update information (if available)","properties":{"available":{"description":"Whether an update is available","type":"boolean"},"check_error":{"description":"Error message if update check failed","type":"string"},"checked_at":{"description":"When the update check was performed","type":"string"},"is_prerelease":{"description":"Whether the latest version is a prerelease","type":"boolean"},"latest_version":{"description":"Latest version available (e.g., \"v1.2.3\")","type":"string"},"release_url":{"description":"URL to the release page","type":"string"}},"type":"object"},"contracts.UpstreamError":{"properties":{"error_message":{"type":"string"},"server_name":{"type":"string"},"timestamp":{"type":"string"}},"type":"object"},"contracts.ValidateConfigResponse":{"properties":{"errors":{"items":{"$ref":"#/components/schemas/contracts.ValidationError"},"type":"array","uniqueItems":false},"valid":{"type":"boolean"}},"type":"object"},"contracts.ValidationError":{"properties":{"field":{"type":"string"},"message":{"type":"string"}},"type":"object"},"data":{"properties":{"data":{"$ref":"#/components/schemas/contracts.InfoResponse"}},"type":"object"},"httpapi.AddServerRequest":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"enabled":{"type":"boolean"},"env":{"additionalProperties":{"type":"string"},"type":"object"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"protocol":{"type":"string"},"quarantined":{"type":"boolean"},"url":{"type":"string"},"working_dir":{"type":"string"}},"type":"object"},"httpapi.CanonicalConfigPath":{"properties":{"description":{"description":"Brief description","type":"string"},"exists":{"description":"Whether the file exists","type":"boolean"},"format":{"description":"Format identifier (e.g., \"claude_desktop\")","type":"string"},"name":{"description":"Display name (e.g., \"Claude Desktop\")","type":"string"},"os":{"description":"Operating system (darwin, windows, linux)","type":"string"},"path":{"description":"Full path to the config file","type":"string"}},"type":"object"},"httpapi.CanonicalConfigPathsResponse":{"properties":{"os":{"description":"Current operating system","type":"string"},"paths":{"description":"List of canonical config paths","items":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPath"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportFromPathRequest":{"properties":{"format":{"description":"Optional format hint","type":"string"},"path":{"description":"File path to import from","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportRequest":{"properties":{"content":{"description":"Raw JSON or TOML content","type":"string"},"format":{"description":"Optional format hint","type":"string"},"server_names":{"description":"Optional: import only these servers","items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportResponse":{"properties":{"failed":{"items":{"$ref":"#/components/schemas/configimport.FailedServer"},"type":"array","uniqueItems":false},"format":{"type":"string"},"format_name":{"type":"string"},"imported":{"items":{"$ref":"#/components/schemas/httpapi.ImportedServerResponse"},"type":"array","uniqueItems":false},"skipped":{"items":{"$ref":"#/components/schemas/configimport.SkippedServer"},"type":"array","uniqueItems":false},"summary":{"$ref":"#/components/schemas/configimport.ImportSummary"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"httpapi.ImportedServerResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"command":{"type":"string"},"fields_skipped":{"items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"type":"string"},"original_name":{"type":"string"},"protocol":{"type":"string"},"source_format":{"type":"string"},"url":{"type":"string"},"warnings":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"management.BulkOperationResult":{"properties":{"errors":{"additionalProperties":{"type":"string"},"description":"Map of server name to error message","type":"object"},"failed":{"description":"Number of failed operations","type":"integer"},"successful":{"description":"Number of successful operations","type":"integer"},"total":{"description":"Total servers processed","type":"integer"}},"type":"object"},"observability.HealthResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"observability.HealthStatus":{"properties":{"error":{"type":"string"},"latency":{"type":"string"},"name":{"type":"string"},"status":{"description":"\"healthy\" or \"unhealthy\"","type":"string"}},"type":"object"},"observability.ReadinessResponse":{"properties":{"components":{"items":{"$ref":"#/components/schemas/observability.HealthStatus"},"type":"array","uniqueItems":false},"status":{"description":"\"ready\" or \"not_ready\"","type":"string"},"timestamp":{"type":"string"}},"type":"object"},"secureenv.EnvConfig":{"description":"Environment configuration for secure variable filtering","properties":{"allowed_system_vars":{"items":{"type":"string"},"type":"array","uniqueItems":false},"custom_vars":{"additionalProperties":{"type":"string"},"type":"object"},"enhance_path":{"description":"Enable PATH enhancement for Launchd scenarios","type":"boolean"},"inherit_system_safe":{"type":"boolean"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"description":"API key authentication via query parameter. Use ?apikey=your-key","in":"query","name":"apikey","type":"apiKey"}}}, "info": {"contact":{"name":"MCPProxy Support","url":"https://github.com/smart-mcp-proxy/mcpproxy-go"},"description":"{{escape .Description}}","license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"},"title":"{{.Title}}","version":"{{.Version}}"}, "externalDocs": {"description":"","url":""}, "paths": {"/api/v1/activity":{"get":{"description":"Returns paginated list of activity records with optional filtering","parameters":[{"description":"Filter by activity type(s), comma-separated for multiple (Spec 024)","in":"query","name":"type","schema":{"enum":["tool_call","policy_decision","quarantine_change","server_change","system_start","system_stop","internal_tool_call","config_change"],"type":"string"}},{"description":"Filter by server name","in":"query","name":"server","schema":{"type":"string"}},{"description":"Filter by tool name","in":"query","name":"tool","schema":{"type":"string"}},{"description":"Filter by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}},{"description":"Filter by status","in":"query","name":"status","schema":{"enum":["success","error","blocked"],"type":"string"}},{"description":"Filter by intent operation type (Spec 018)","in":"query","name":"intent_type","schema":{"enum":["read","write","destructive"],"type":"string"}},{"description":"Filter by HTTP request ID for log correlation (Spec 021)","in":"query","name":"request_id","schema":{"type":"string"}},{"description":"Include successful call_tool_* internal tool calls (default: false, excluded to avoid duplicates)","in":"query","name":"include_call_tool","schema":{"type":"boolean"}},{"description":"Filter by sensitive data detection (true=has detections, false=no detections)","in":"query","name":"sensitive_data","schema":{"type":"boolean"}},{"description":"Filter by specific detection type (e.g., 'aws_access_key', 'credit_card')","in":"query","name":"detection_type","schema":{"type":"string"}},{"description":"Filter by severity level","in":"query","name":"severity","schema":{"enum":["critical","high","medium","low"],"type":"string"}},{"description":"Filter activities after this time (RFC3339)","in":"query","name":"start_time","schema":{"type":"string"}},{"description":"Filter activities before this time (RFC3339)","in":"query","name":"end_time","schema":{"type":"string"}},{"description":"Maximum records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Pagination offset (default 0)","in":"query","name":"offset","schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"List activity records","tags":["Activity"]}},"/api/v1/activity/export":{"get":{"description":"Exports activity records in JSON Lines or CSV format for compliance","parameters":[{"description":"Export format: json (default) or csv","in":"query","name":"format","schema":{"type":"string"}},{"description":"Filter by activity type","in":"query","name":"type","schema":{"type":"string"}},{"description":"Filter by server name","in":"query","name":"server","schema":{"type":"string"}},{"description":"Filter by tool name","in":"query","name":"tool","schema":{"type":"string"}},{"description":"Filter by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}},{"description":"Filter by status","in":"query","name":"status","schema":{"type":"string"}},{"description":"Filter activities after this time (RFC3339)","in":"query","name":"start_time","schema":{"type":"string"}},{"description":"Filter activities before this time (RFC3339)","in":"query","name":"end_time","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-ndjson":{"schema":{"type":"string"}},"text/csv":{"schema":{"type":"string"}}},"description":"Streamed activity records"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Export activity records","tags":["Activity"]}},"/api/v1/activity/summary":{"get":{"description":"Returns aggregated activity statistics for a time period","parameters":[{"description":"Time period: 1h, 24h (default), 7d, 30d","in":"query","name":"period","schema":{"type":"string"}},{"description":"Group by: server, tool (optional)","in":"query","name":"group_by","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Get activity summary statistics","tags":["Activity"]}},"/api/v1/activity/{id}":{"get":{"description":"Returns full details for a single activity record","parameters":[{"description":"Activity record ID (ULID)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.APIResponse"}}},"description":"Internal Server Error"}},"security":[{"ApiKeyHeader":[]},{"ApiKeyQuery":[]}],"summary":"Get activity record details","tags":["Activity"]}},"/api/v1/config":{"get":{"description":"Retrieves the current MCPProxy configuration including all server definitions, global settings, and runtime parameters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetConfigResponse"}}},"description":"Configuration retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get configuration"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get current configuration","tags":["config"]}},"/api/v1/config/apply":{"post":{"description":"Applies a new MCPProxy configuration. Validates and persists the configuration to disk. Some changes apply immediately, while others may require a restart. Returns detailed information about applied changes and restart requirements.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/config.Config"}}},"description":"Configuration to apply","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ConfigApplyResult"}}},"description":"Configuration applied successfully with change details"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to apply configuration"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Apply configuration","tags":["config"]}},"/api/v1/config/validate":{"post":{"description":"Validates a provided MCPProxy configuration without applying it. Checks for syntax errors, invalid server definitions, conflicting settings, and other configuration issues.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/config.Config"}}},"description":"Configuration to validate","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ValidateConfigResponse"}}},"description":"Configuration validation result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Validation failed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Validate configuration","tags":["config"]}},"/api/v1/diagnostics":{"get":{"description":"Get comprehensive health diagnostics including upstream errors, OAuth requirements, missing secrets, and Docker status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.Diagnostics"}}},"description":"Health diagnostics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get health diagnostics","tags":["diagnostics"]}},"/api/v1/docker/status":{"get":{"description":"Retrieve current Docker availability and recovery status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Docker status information"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get Docker status","tags":["docker"]}},"/api/v1/doctor":{"get":{"description":"Get comprehensive health diagnostics including upstream errors, OAuth requirements, missing secrets, and Docker status","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.Diagnostics"}}},"description":"Health diagnostics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get health diagnostics","tags":["diagnostics"]}},"/api/v1/index/search":{"get":{"description":"Search across all upstream MCP server tools using BM25 keyword search","parameters":[{"description":"Search query","in":"query","name":"q","required":true,"schema":{"type":"string"}},{"description":"Maximum number of results","in":"query","name":"limit","schema":{"default":10,"maximum":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SearchToolsResponse"}}},"description":"Search results"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing query parameter)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Search for tools","tags":["tools"]}},"/api/v1/info":{"get":{"description":"Get essential server metadata including version, web UI URL, endpoint addresses, and update availability\nThis endpoint is designed for tray-core communication and version checking\nUse refresh=true query parameter to force an immediate update check against GitHub","parameters":[{"description":"Force immediate update check against GitHub","in":"query","name":"refresh","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/data"}],"properties":{"data":{"type":"object"},"error":{"type":"string"},"request_id":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"Server information with optional update info"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server information","tags":["status"]}},"/api/v1/registries":{"get":{"description":"Retrieves list of all MCP server registries that can be browsed for discovering and installing new upstream servers. Includes registry metadata, server counts, and API endpoints.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetRegistriesResponse"}}},"description":"Registries retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to list registries"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"List available MCP server registries","tags":["registries"]}},"/api/v1/registries/{id}/servers":{"get":{"description":"Searches for MCP servers within a specific registry by keyword or tag. Returns server metadata including installation commands, source code URLs, and npm package information for easy discovery and installation.","parameters":[{"description":"Registry ID","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Search query keyword","in":"query","name":"q","schema":{"type":"string"}},{"description":"Filter by tag","in":"query","name":"tag","schema":{"type":"string"}},{"description":"Maximum number of results (default 10)","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SearchRegistryServersResponse"}}},"description":"Servers retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Registry ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to search servers"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Search MCP servers in a registry","tags":["registries"]}},"/api/v1/secrets":{"post":{"description":"Stores a secret value in the operating system's secure keyring. The secret can then be referenced in configuration using ${keyring:secret-name} syntax. Automatically notifies runtime to restart affected servers.","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"Secret stored successfully with reference syntax"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Invalid JSON payload, missing name/value, or unsupported type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Secret resolver not available or failed to store secret"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Store a secret in OS keyring","tags":["secrets"]}},"/api/v1/secrets/{name}":{"delete":{"description":"Deletes a secret from the operating system's secure keyring. Automatically notifies runtime to restart affected servers. Only keyring type is supported for security.","parameters":[{"description":"Name of the secret to delete","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Secret type (only 'keyring' supported, defaults to 'keyring')","in":"query","name":"type","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{},"type":"object"}}},"description":"Secret deleted successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Missing secret name or unsupported type"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Secret resolver not available or failed to delete secret"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Delete a secret from OS keyring","tags":["secrets"]}},"/api/v1/servers":{"get":{"description":"Get a list of all configured upstream MCP servers with their connection status and statistics","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServersResponse"}}},"description":"Server list with statistics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"List all upstream MCP servers","tags":["servers"]},"post":{"description":"Add a new MCP upstream server to the configuration. New servers are quarantined by default for security.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.AddServerRequest"}}},"description":"Server configuration","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server added successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid configuration"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Conflict - server with this name already exists"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Add a new upstream server","tags":["servers"]}},"/api/v1/servers/disable_all":{"post":{"description":"Disable all configured upstream MCP servers with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk disable results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Disable all servers","tags":["servers"]}},"/api/v1/servers/enable_all":{"post":{"description":"Enable all configured upstream MCP servers with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk enable results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Enable all servers","tags":["servers"]}},"/api/v1/servers/import":{"post":{"description":"Import MCP server configurations from a Claude Desktop, Claude Code, Cursor IDE, Codex CLI, or Gemini CLI configuration file","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}},{"description":"Force format (claude-desktop, claude-code, cursor, codex, gemini)","in":"query","name":"format","schema":{"type":"string"}},{"description":"Comma-separated list of server names to import","in":"query","name":"server_names","schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"file"}}},"description":"Configuration file to import","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid file or format"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from uploaded configuration file","tags":["servers"]}},"/api/v1/servers/import/json":{"post":{"description":"Import MCP server configurations from raw JSON or TOML content (useful for pasting configurations)","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportRequest"}}},"description":"Import request with content","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid content or format"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from JSON/TOML content","tags":["servers"]}},"/api/v1/servers/import/path":{"post":{"description":"Import MCP server configurations by reading a file from the server's filesystem","parameters":[{"description":"If true, return preview without importing","in":"query","name":"preview","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportFromPathRequest"}}},"description":"Import request with file path","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.ImportResponse"}}},"description":"Import result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request - invalid path or format"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"File not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Import servers from a file path","tags":["servers"]}},"/api/v1/servers/import/paths":{"get":{"description":"Returns well-known configuration file paths for supported formats with existence check","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/httpapi.CanonicalConfigPathsResponse"}}},"description":"Canonical config paths"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get canonical config file paths","tags":["servers"]}},"/api/v1/servers/reconnect":{"post":{"description":"Force reconnection to all upstream MCP servers","parameters":[{"description":"Reason for reconnection","in":"query","name":"reason","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"All servers reconnected successfully"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Reconnect all servers","tags":["servers"]}},"/api/v1/servers/restart_all":{"post":{"description":"Restart all configured upstream MCP servers sequentially with partial failure handling","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/management.BulkOperationResult"}}},"description":"Bulk restart results with success/failure counts"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Restart all servers","tags":["servers"]}},"/api/v1/servers/{id}":{"delete":{"description":"Remove an MCP upstream server from the configuration. This stops the server if running and removes it from config.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server removed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Remove an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/disable":{"post":{"description":"Disable a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server disabled successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Disable an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/discover-tools":{"post":{"description":"Manually trigger tool discovery and indexing for a specific upstream MCP server. This forces an immediate refresh of the server's tool cache.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Tool discovery triggered successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to discover tools"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Discover tools for a specific server","tags":["servers"]}},"/api/v1/servers/{id}/enable":{"post":{"description":"Enable a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server enabled successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Enable an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/login":{"post":{"description":"Initiate OAuth authentication flow for a specific upstream MCP server. Returns structured OAuth start response with correlation ID for tracking.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.OAuthStartResponse"}}},"description":"OAuth login initiated successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.OAuthFlowError"}}},"description":"OAuth error (client_id required, DCR failed, etc.)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Trigger OAuth login for server","tags":["servers"]}},"/api/v1/servers/{id}/logout":{"post":{"description":"Clear OAuth authentication token and disconnect a specific upstream MCP server. The server will need to re-authenticate before tools can be used again.","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"OAuth logout completed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Forbidden (management disabled or read-only mode)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Clear OAuth token and disconnect server","tags":["servers"]}},"/api/v1/servers/{id}/logs":{"get":{"description":"Retrieve log entries for a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Number of log lines to retrieve","in":"query","name":"tail","schema":{"default":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerLogsResponse"}}},"description":"Server logs retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server logs","tags":["servers"]}},"/api/v1/servers/{id}/quarantine":{"post":{"description":"Place a specific upstream MCP server in quarantine to prevent tool execution","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server quarantined successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Quarantine a server","tags":["servers"]}},"/api/v1/servers/{id}/restart":{"post":{"description":"Restart the connection to a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server restarted successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Restart an upstream server","tags":["servers"]}},"/api/v1/servers/{id}/tool-calls":{"get":{"description":"Retrieves tool call history filtered by upstream server ID. Returns recent tool executions for the specified server including timestamps, arguments, results, and errors. Useful for server-specific debugging and monitoring.","parameters":[{"description":"Upstream server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Maximum number of records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerToolCallsResponse"}}},"description":"Server tool calls retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get server tool calls"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call history for specific server","tags":["tool-calls"]}},"/api/v1/servers/{id}/tools":{"get":{"description":"Retrieve all available tools for a specific upstream MCP server","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetServerToolsResponse"}}},"description":"Server tools retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tools for a server","tags":["servers"]}},"/api/v1/servers/{id}/unquarantine":{"post":{"description":"Remove a specific upstream MCP server from quarantine to allow tool execution","parameters":[{"description":"Server ID or name","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ServerActionResponse"}}},"description":"Server unquarantined successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (missing server ID)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Server not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Unquarantine a server","tags":["servers"]}},"/api/v1/sessions":{"get":{"description":"Retrieves paginated list of active and recent MCP client sessions. Each session represents a connection from an MCP client to MCPProxy, tracking initialization time, tool calls, and connection status.","parameters":[{"description":"Maximum number of sessions to return (1-100, default 10)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Number of sessions to skip for pagination (default 0)","in":"query","name":"offset","schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetSessionsResponse"}}},"description":"Sessions retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get sessions"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get active MCP sessions","tags":["sessions"]}},"/api/v1/sessions/{id}":{"get":{"description":"Retrieves detailed information about a specific MCP client session including initialization parameters, connection status, tool call count, and activity timestamps.","parameters":[{"description":"Session ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetSessionDetailResponse"}}},"description":"Session details retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Session ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Session not found"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get MCP session details by ID","tags":["sessions"]}},"/api/v1/stats/tokens":{"get":{"description":"Retrieve token savings statistics across all servers and sessions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Token statistics"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get token savings statistics","tags":["stats"]}},"/api/v1/status":{"get":{"description":"Get comprehensive server status including running state, listen address, upstream statistics, and timestamp","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Server status information"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get server status","tags":["status"]}},"/api/v1/tool-calls":{"get":{"description":"Retrieves paginated tool call history across all upstream servers or filtered by session ID. Includes execution timestamps, arguments, results, and error information for debugging and auditing.","parameters":[{"description":"Maximum number of records to return (1-100, default 50)","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Number of records to skip for pagination (default 0)","in":"query","name":"offset","schema":{"type":"integer"}},{"description":"Filter tool calls by MCP session ID","in":"query","name":"session_id","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetToolCallsResponse"}}},"description":"Tool calls retrieved successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to get tool calls"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call history","tags":["tool-calls"]}},"/api/v1/tool-calls/{id}":{"get":{"description":"Retrieves detailed information about a specific tool call execution including full request arguments, response data, execution time, and any errors encountered.","parameters":[{"description":"Tool call ID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.GetToolCallDetailResponse"}}},"description":"Tool call details retrieved successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call ID required"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call not found"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Get tool call details by ID","tags":["tool-calls"]}},"/api/v1/tool-calls/{id}/replay":{"post":{"description":"Re-executes a previous tool call with optional modified arguments. Useful for debugging and testing tool behavior with different inputs. Creates a new tool call record linked to the original.","parameters":[{"description":"Original tool call ID to replay","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ReplayToolCallRequest"}}},"description":"Optional modified arguments for replay"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ReplayToolCallResponse"}}},"description":"Tool call replayed successfully"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Tool call ID required or invalid JSON payload"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Unauthorized - missing or invalid API key"},"405":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Method not allowed"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Failed to replay tool call"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Replay a tool call","tags":["tool-calls"]}},"/api/v1/tools/call":{"post":{"description":"Execute a tool on an upstream MCP server (wrapper around MCP tool calls)","requestBody":{"content":{"application/json":{"schema":{"properties":{"arguments":{"type":"object"},"tool_name":{"type":"string"}},"type":"object"}}},"description":"Tool call request with tool name and arguments","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.SuccessResponse"}}},"description":"Tool call result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Bad request (invalid payload or missing tool name)"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/contracts.ErrorResponse"}}},"description":"Internal server error or tool execution failure"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyQuery":[]}],"summary":"Call a tool","tags":["tools"]}},"/healthz":{"get":{"description":"Get comprehensive health status including all component health (Kubernetes-compatible liveness probe)","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.HealthResponse"}}},"description":"Service is healthy"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.HealthResponse"}}},"description":"Service is unhealthy"}},"summary":"Get health status","tags":["health"]}},"/readyz":{"get":{"description":"Get readiness status including all component readiness checks (Kubernetes-compatible readiness probe)","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.ReadinessResponse"}}},"description":"Service is ready"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/observability.ReadinessResponse"}}},"description":"Service is not ready"}},"summary":"Get readiness status","tags":["health"]}}}, diff --git a/oas/swagger.yaml b/oas/swagger.yaml index 295fd556..39eabd09 100644 --- a/oas/swagger.yaml +++ b/oas/swagger.yaml @@ -54,8 +54,14 @@ components: enable_socket: description: 'Enable Unix socket/named pipe for local IPC (default: true)' type: boolean + enable_tray: + description: 'Deprecated: EnableTray is unused and has no runtime effect. + Kept for backward compatibility.' + type: boolean environment: $ref: '#/components/schemas/secureenv.EnvConfig' + features: + $ref: '#/components/schemas/config.FeatureFlags' intent_declaration: $ref: '#/components/schemas/config.IntentDeclarationConfig' listen: @@ -88,6 +94,10 @@ components: type: integer tools_limit: type: integer + top_k: + description: 'Deprecated: TopK is superseded by ToolsLimit and has no runtime + effect. Kept for backward compatibility.' + type: integer tray_endpoint: description: Tray endpoint override (unix:// or npipe://) type: string @@ -181,7 +191,8 @@ components: type: boolean type: object config.FeatureFlags: - description: Feature flags for modular functionality + description: 'Deprecated: Features flags are unused and have no runtime effect. + Kept for backward compatibility.' properties: enable_async_storage: type: boolean @@ -218,6 +229,8 @@ components: type: boolean enable_tracing: type: boolean + enable_tray: + type: boolean enable_web_ui: description: UI features type: boolean @@ -666,8 +679,23 @@ components: success: type: boolean type: object + contracts.DeprecatedConfigWarning: + properties: + field: + type: string + message: + type: string + replacement: + type: string + type: object contracts.Diagnostics: properties: + deprecated_configs: + description: Deprecated config fields found + items: + $ref: '#/components/schemas/contracts.DeprecatedConfigWarning' + type: array + uniqueItems: false docker_status: $ref: '#/components/schemas/contracts.DockerStatus' missing_secrets: