diff --git a/docs/services/mcp.md b/docs/services/mcp.md index 6cbc783a..107f9e1b 100644 --- a/docs/services/mcp.md +++ b/docs/services/mcp.md @@ -94,15 +94,15 @@ the LLM tuning fields: | `llm_temperature` | number | `0.0`–`2.0` | Controls randomness in LLM responses. Lower values produce more deterministic output. | | `llm_max_tokens` | integer | Positive integer | Maximum number of tokens in the LLM response. | -### Connection Pool +### Connection Pool and Caching -The connection pool fields control how many database connections the -MCP server maintains. The following table describes the connection pool -configuration fields: +The following fields control the database connection pool and metadata +caching behavior: -| Field | Type | Description | -|--------------------|---------|-------------| -| `pool_max_conns` | integer | Maximum number of database connections the service maintains in its pool. Must be a positive integer. | +| Field | Type | Default | Description | +|--------------------|---------|---------|-------------| +| `pool_max_conns` | integer | `4` | Maximum number of database connections the service maintains in its pool. Must be a positive integer. | +| `metadata_ttl` | string | `"5m"` | How long the MCP server caches database metadata (e.g., `"5m"`, `"1h"`). Shorter values surface schema changes faster; longer values reduce load on the database. | ## Bootstrapping diff --git a/server/internal/database/mcp_service_config.go b/server/internal/database/mcp_service_config.go index 7dae332c..a8a9693c 100644 --- a/server/internal/database/mcp_service_config.go +++ b/server/internal/database/mcp_service_config.go @@ -44,6 +44,9 @@ type MCPServiceConfig struct { // Optional - connection pool (overridable defaults) PoolMaxConns *int `json:"pool_max_conns,omitempty"` + // Optional - metadata cache TTL (e.g. "5m", "1h"); passed to MCP server + MetadataTTL *string `json:"metadata_ttl,omitempty"` + // Optional - tool toggles (all enabled by default) DisableQueryDatabase *bool `json:"disable_query_database,omitempty"` DisableGetSchemaInfo *bool `json:"disable_get_schema_info,omitempty"` @@ -71,6 +74,7 @@ var mcpKnownKeys = map[string]bool{ "llm_temperature": true, "llm_max_tokens": true, "pool_max_conns": true, + "metadata_ttl": true, "disable_query_database": true, "disable_get_schema_info": true, "disable_similarity_search": true, @@ -217,6 +221,9 @@ func ParseMCPServiceConfig(config map[string]any, isUpdate bool) (*MCPServiceCon poolMaxConns, pmcErrs := optionalInt(config, "pool_max_conns") errs = append(errs, pmcErrs...) + metadataTTL, mttlErrs := optionalString(config, "metadata_ttl") + errs = append(errs, mttlErrs...) + // Tool toggles disableQueryDB, dqErrs := optionalBool(config, "disable_query_database") errs = append(errs, dqErrs...) @@ -289,6 +296,7 @@ func ParseMCPServiceConfig(config map[string]any, isUpdate bool) (*MCPServiceCon LLMTemperature: llmTemperature, LLMMaxTokens: llmMaxTokens, PoolMaxConns: poolMaxConns, + MetadataTTL: metadataTTL, DisableQueryDatabase: disableQueryDB, DisableGetSchemaInfo: disableGetSchema, DisableSimilaritySearch: disableSimilarity, diff --git a/server/internal/database/mcp_service_config_test.go b/server/internal/database/mcp_service_config_test.go index 65ac2800..e2fec733 100644 --- a/server/internal/database/mcp_service_config_test.go +++ b/server/internal/database/mcp_service_config_test.go @@ -122,6 +122,7 @@ func TestParseMCPServiceConfig(t *testing.T) { "llm_temperature": float64(0.7), "llm_max_tokens": float64(2048), "pool_max_conns": float64(10), + "metadata_ttl": "10m", "disable_query_database": true, "disable_get_schema_info": false, "disable_similarity_search": true, @@ -154,6 +155,8 @@ func TestParseMCPServiceConfig(t *testing.T) { assert.Equal(t, 2048, *cfg.LLMMaxTokens) require.NotNil(t, cfg.PoolMaxConns) assert.Equal(t, 10, *cfg.PoolMaxConns) + require.NotNil(t, cfg.MetadataTTL) + assert.Equal(t, "10m", *cfg.MetadataTTL) require.NotNil(t, cfg.DisableQueryDatabase) assert.True(t, *cfg.DisableQueryDatabase) require.NotNil(t, cfg.DisableGetSchemaInfo) @@ -483,6 +486,32 @@ func TestParseMCPServiceConfig(t *testing.T) { }) }) + t.Run("metadata_ttl", func(t *testing.T) { + t.Run("valid metadata_ttl", func(t *testing.T) { + config := anthropicBase() + config["metadata_ttl"] = "10m" + cfg, errs := database.ParseMCPServiceConfig(config, false) + require.Empty(t, errs) + require.NotNil(t, cfg.MetadataTTL) + assert.Equal(t, "10m", *cfg.MetadataTTL) + }) + + t.Run("metadata_ttl omitted", func(t *testing.T) { + config := anthropicBase() + cfg, errs := database.ParseMCPServiceConfig(config, false) + require.Empty(t, errs) + assert.Nil(t, cfg.MetadataTTL) + }) + + t.Run("metadata_ttl wrong type", func(t *testing.T) { + config := anthropicBase() + config["metadata_ttl"] = 300 + _, errs := database.ParseMCPServiceConfig(config, false) + require.NotEmpty(t, errs) + assert.Contains(t, joinedErr(errs).Error(), "metadata_ttl must be a string") + }) + }) + t.Run("embedding config", func(t *testing.T) { t.Run("valid voyage embedding config", func(t *testing.T) { config := anthropicBase() diff --git a/server/internal/orchestrator/swarm/mcp_config.go b/server/internal/orchestrator/swarm/mcp_config.go index 487f0d37..58b2cf48 100644 --- a/server/internal/orchestrator/swarm/mcp_config.go +++ b/server/internal/orchestrator/swarm/mcp_config.go @@ -43,6 +43,7 @@ type mcpDatabaseConfig struct { Password string `yaml:"password"` SSLMode string `yaml:"sslmode"` AllowWrites bool `yaml:"allow_writes"` + MetadataTTL string `yaml:"metadata_ttl,omitempty"` Pool mcpPoolConfig `yaml:"pool"` } @@ -109,6 +110,10 @@ func GenerateMCPConfig(params *MCPConfigParams) ([]byte, error) { if cfg.AllowWrites != nil { allowWrites = *cfg.AllowWrites } + var metadataTTL string + if cfg.MetadataTTL != nil { + metadataTTL = *cfg.MetadataTTL + } // Build LLM config (only when llm_enabled is true) var llm *mcpLLMConfig @@ -222,6 +227,7 @@ func GenerateMCPConfig(params *MCPConfigParams) ([]byte, error) { Password: params.Password, SSLMode: "prefer", AllowWrites: allowWrites, + MetadataTTL: metadataTTL, Pool: mcpPoolConfig{ MaxConns: poolMaxConns, }, diff --git a/server/internal/orchestrator/swarm/mcp_config_test.go b/server/internal/orchestrator/swarm/mcp_config_test.go index 3400fe21..8ef2b087 100644 --- a/server/internal/orchestrator/swarm/mcp_config_test.go +++ b/server/internal/orchestrator/swarm/mcp_config_test.go @@ -2,6 +2,7 @@ package swarm import ( "fmt" + "strings" "testing" "github.com/goccy/go-yaml" @@ -207,6 +208,7 @@ func TestGenerateMCPConfig_CustomValues(t *testing.T) { LLMMaxTokens: &maxTok, PoolMaxConns: &poolMax, AllowWrites: &allowW, + MetadataTTL: strPtr("10m"), }, DatabaseName: "mydb", DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, @@ -239,6 +241,29 @@ func TestGenerateMCPConfig_CustomValues(t *testing.T) { if !cfg.Databases[0].AllowWrites { t.Error("databases[0].allow_writes should be true") } + if cfg.Databases[0].MetadataTTL != "10m" { + t.Errorf("databases[0].metadata_ttl = %q, want %q", cfg.Databases[0].MetadataTTL, "10m") + } +} + +func TestGenerateMCPConfig_MetadataTTL_Omitted(t *testing.T) { + params := &MCPConfigParams{ + Config: &database.MCPServiceConfig{}, + DatabaseName: "mydb", + DatabaseHosts: []database.ServiceHostEntry{{Host: "db-host", Port: 5432}}, + Username: "appuser", + Password: "secret", + } + + data, err := GenerateMCPConfig(params) + if err != nil { + t.Fatalf("GenerateMCPConfig() error = %v", err) + } + + // Check raw YAML to verify omitempty actually omits the key. + if strings.Contains(string(data), "metadata_ttl:") { + t.Error("raw YAML should not contain metadata_ttl key when unset") + } } func TestGenerateMCPConfig_ProviderKeys_Anthropic(t *testing.T) {