From 991825c202b8cb31063c532d5842babbfe290672 Mon Sep 17 00:00:00 2001 From: rshoemaker Date: Fri, 3 Apr 2026 15:28:06 -0400 Subject: [PATCH 1/2] feat: add metadata_ttl MCP config parameter Allow users to configure how long the MCP server caches database metadata. The parameter is an optional string (e.g. "5m", "1h") passed through to the MCP container config. When omitted, the MCP server defaults to 5 minutes. --- docs/services/mcp.md | 14 ++++----- .../internal/database/mcp_service_config.go | 8 +++++ .../database/mcp_service_config_test.go | 29 +++++++++++++++++++ .../internal/orchestrator/swarm/mcp_config.go | 6 ++++ .../orchestrator/swarm/mcp_config_test.go | 24 +++++++++++++++ 5 files changed, 74 insertions(+), 7 deletions(-) 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..4c5b6227 100644 --- a/server/internal/orchestrator/swarm/mcp_config_test.go +++ b/server/internal/orchestrator/swarm/mcp_config_test.go @@ -207,6 +207,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 +240,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) + } + + cfg := parseYAML(t, data) + if cfg.Databases[0].MetadataTTL != "" { + t.Errorf("databases[0].metadata_ttl = %q, want empty (omitted)", cfg.Databases[0].MetadataTTL) + } } func TestGenerateMCPConfig_ProviderKeys_Anthropic(t *testing.T) { From f2a24b148527ac4182185f6bb33d5dcf74ae55f6 Mon Sep 17 00:00:00 2001 From: rshoemaker Date: Mon, 6 Apr 2026 13:51:29 -0400 Subject: [PATCH 2/2] fix: coderabbit suggestion --- server/internal/orchestrator/swarm/mcp_config_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/internal/orchestrator/swarm/mcp_config_test.go b/server/internal/orchestrator/swarm/mcp_config_test.go index 4c5b6227..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" @@ -259,9 +260,9 @@ func TestGenerateMCPConfig_MetadataTTL_Omitted(t *testing.T) { t.Fatalf("GenerateMCPConfig() error = %v", err) } - cfg := parseYAML(t, data) - if cfg.Databases[0].MetadataTTL != "" { - t.Errorf("databases[0].metadata_ttl = %q, want empty (omitted)", cfg.Databases[0].MetadataTTL) + // 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") } }