diff --git a/README.md b/README.md index 880cd41..4a5eeff 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,13 @@ or called. Without overrides, all discovered tools are available. Set the server `disableTools: true` to expose no tools, regardless of overrides. Prompts and resources are unaffected. +Each server accepts an optional `discoveryRevision` string (default empty, not +interpolated). Set it to a fresh revision to trigger rediscovery when the updated +configuration is next used. It changes the complete configuration fingerprint, +so all components and feature lists are rediscovered and subsequent stateful +calls use new downstream runtimes. Reusing an earlier value can reuse its cached +catalog. The value is not sent to downstream servers. + ## Running Build and run the HTTP frontend: diff --git a/catalog/registry_test.go b/catalog/registry_test.go index 5c7aebb..d116220 100644 --- a/catalog/registry_test.go +++ b/catalog/registry_test.go @@ -52,6 +52,28 @@ func TestFingerprintIsStableCompleteAndSecretSafe(t *testing.T) { } } +func TestRegistryDiscoveryRevisionTriggersRediscovery(t *testing.T) { + discoverer := &countingDiscoverer{started: make(chan struct{}), release: make(chan struct{})} + close(discoverer.release) + registry := catalog.NewRegistry(discoverer) + defer registry.Close() + for i, field := range []string{"", " discoveryRevision: revision-1\n"} { + cfg, err := config.Load([]byte("servers:\n - name: fixture\n url: https://example.invalid\n"+field), config.LoadOptions{}) + if err != nil { + t.Fatal(err) + } + for range 2 { + _, _, err := registry.Get(t.Context(), cfg) + if err != nil { + t.Fatal(err) + } + } + if got := discoverer.Count(); got != i+1 { + t.Fatalf("discoveries = %d, want %d", got, i+1) + } + } +} + func TestRegistryDeduplicatesConcurrentCompilation(t *testing.T) { discoverer := &countingDiscoverer{started: make(chan struct{}), release: make(chan struct{})} registry := catalog.NewRegistry(discoverer) diff --git a/config/config.go b/config/config.go index b0a3bf8..6a0ef93 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,8 @@ type Config struct { // Server describes one component MCP server. type Server struct { + // DiscoveryRevision participates in the complete configuration fingerprint only. + DiscoveryRevision string Name string Prefix string URL string @@ -81,6 +83,7 @@ type configDTO struct { } type serverDTO struct { + DiscoveryRevision string `yaml:"discoveryRevision"` Name string `yaml:"name"` Prefix string `yaml:"prefix"` URL string `yaml:"url"` diff --git a/config/load.go b/config/load.go index eca8d25..aad8678 100644 --- a/config/load.go +++ b/config/load.go @@ -103,6 +103,7 @@ func (d configDTO) runtime(lookup func(string) (string, bool)) (*Config, error) func (d serverDTO) runtime(path string, lookup func(string) (string, bool)) (Server, error) { server := Server{ + DiscoveryRevision: d.DiscoveryRevision, Name: strings.TrimSpace(d.Name), Prefix: d.Prefix, Headers: make(map[string]string, len(d.Headers)),