Skip to content

Commit 9797bae

Browse files
JAORMXclaude
andauthored
Add RegistryServerName to RunConfig for registry entry tracking (#4696)
Running workloads need to be mapped back to their registry entry by name so that downstream consumers (e.g. the registry export endpoint) can look up server metadata. Add a RegistryServerName field to RunConfig following the same pattern as RegistryAPIURL and RegistryURL: builder option, resolver helper, wiring through all three call sites, and regenerated OpenAPI docs. Part of stacklok/toolhive-rfcs#68 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d308ba0 commit 9797bae

File tree

10 files changed

+127
-6
lines changed

10 files changed

+127
-6
lines changed

cmd/thv/app/run_flags.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,15 @@ func BuildRunnerConfig(
347347
return nil, fmt.Errorf("failed to parse environment variables: %w", err)
348348
}
349349

350-
// Resolve registry source URLs when the server was discovered via registry lookup.
350+
// Resolve registry source URLs and server name when the server was discovered via registry lookup.
351351
regAPIURL, regURL := runner.ResolveRegistrySourceURLs(serverMetadata, appConfig)
352+
regServerName := runner.ResolveRegistryServerName(serverMetadata)
352353

353354
// Build the runner config
354355
return buildRunnerConfig(ctx, runFlags, cmdArgs, debugMode, validatedHost, rt, imageURL, serverMetadata,
355-
envVars, envVarValidator, oidcConfig, telemetryConfig, runner.WithRegistrySourceURLs(regAPIURL, regURL))
356+
envVars, envVarValidator, oidcConfig, telemetryConfig,
357+
runner.WithRegistrySourceURLs(regAPIURL, regURL),
358+
runner.WithRegistryServerName(regServerName))
356359
}
357360

358361
// setupOIDCConfiguration sets up OIDC configuration and validates URLs

docs/server/docs.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/api/v1/workload_service.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,9 @@ func (s *WorkloadService) BuildFullRunConfig(
252252
}
253253
}
254254

255-
// Resolve registry source URLs when the server was discovered via registry lookup.
255+
// Resolve registry source URLs and server name when the server was discovered via registry lookup.
256256
regAPIURL, regURL := runner.ResolveRegistrySourceURLs(serverMetadata, s.appConfig)
257+
regServerName := runner.ResolveRegistryServerName(serverMetadata)
257258

258259
options := []runner.RunConfigBuilderOption{
259260
runner.WithRuntime(s.containerRuntime),
@@ -283,6 +284,7 @@ func (s *WorkloadService) BuildFullRunConfig(
283284
runner.WithToolsOverride(toolsOverride),
284285
runner.WithTelemetryConfigFromFlags("", false, false, false, "", 0.0, nil, false, nil, false),
285286
runner.WithRegistrySourceURLs(regAPIURL, regURL),
287+
runner.WithRegistryServerName(regServerName),
286288
}
287289

288290
// Add header forward configuration if specified

pkg/mcp/server/handler_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ func TestBuildServerConfig(t *testing.T) {
311311
expectError: false, // Actually succeeds and tests the type assertion line
312312
},
313313
{
314-
name: "registry source URLs are recorded on config",
314+
name: "registry source URLs and server name are recorded on config",
315315
imageURL: "test/image:latest",
316316
imageMetadata: &regtypes.ImageMetadata{
317317
BaseServerMetadata: regtypes.BaseServerMetadata{
@@ -321,12 +321,14 @@ func TestBuildServerConfig(t *testing.T) {
321321
},
322322
extraOpts: []runner.RunConfigBuilderOption{
323323
runner.WithRegistrySourceURLs("https://api.example.com", "https://registry.example.com"),
324+
runner.WithRegistryServerName("fetch"),
324325
},
325326
expectError: false,
326327
checkConfig: func(t *testing.T, rc *runner.RunConfig) {
327328
t.Helper()
328329
assert.Equal(t, "https://api.example.com", rc.RegistryAPIURL)
329330
assert.Equal(t, "https://registry.example.com", rc.RegistryURL)
331+
assert.Equal(t, "fetch", rc.RegistryServerName)
330332
},
331333
},
332334
}

pkg/mcp/server/run_server.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ func (h *Handler) RunServer(ctx context.Context, request mcp.CallToolRequest) (*
5050
return mcp.NewToolResultError(fmt.Sprintf("Failed to get MCP server: %v", err)), nil
5151
}
5252

53-
// Resolve registry source URLs when the server was discovered via registry lookup.
53+
// Resolve registry source URLs and server name when the server was discovered via registry lookup.
5454
regAPIURL, regURL := runner.ResolveRegistrySourceURLs(serverMetadata, h.configProvider.GetConfig())
55+
regServerName := runner.ResolveRegistryServerName(serverMetadata)
5556

5657
// Build run configuration.
5758
// Use type assertion with nil check to guard against typed nil pointers.
@@ -61,7 +62,8 @@ func (h *Handler) RunServer(ctx context.Context, request mcp.CallToolRequest) (*
6162
}
6263

6364
runConfig, err := buildServerConfig(ctx, args, imageURL, imageMetadata,
64-
runner.WithRegistrySourceURLs(regAPIURL, regURL))
65+
runner.WithRegistrySourceURLs(regAPIURL, regURL),
66+
runner.WithRegistryServerName(regServerName))
6567
if err != nil {
6668
return mcp.NewToolResultError(fmt.Sprintf("Failed to build run configuration: %v", err)), nil
6769
}

pkg/runner/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ type RunConfig struct {
6363
// Empty when the server was not discovered via registry lookup.
6464
RegistryURL string `json:"registry_url,omitempty" yaml:"registry_url,omitempty"`
6565

66+
// RegistryServerName is the registry entry name used to look up this server's metadata.
67+
// Empty when the server was not discovered via registry lookup.
68+
RegistryServerName string `json:"registry_server_name,omitempty" yaml:"registry_server_name,omitempty"`
69+
6670
// RemoteAuthConfig contains OAuth configuration for remote MCP servers
6771
RemoteAuthConfig *remote.Config `json:"remote_auth_config,omitempty" yaml:"remote_auth_config,omitempty"`
6872

pkg/runner/config_builder.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ func ResolveRegistrySourceURLs(serverMetadata regtypes.ServerMetadata, appConfig
123123
return appConfig.RegistryApiUrl, appConfig.RegistryUrl
124124
}
125125

126+
// WithRegistryServerName records the registry entry name used to look up this server's metadata.
127+
func WithRegistryServerName(name string) RunConfigBuilderOption {
128+
return func(b *runConfigBuilder) error {
129+
b.config.RegistryServerName = name
130+
return nil
131+
}
132+
}
133+
134+
// ResolveRegistryServerName returns the registry entry name from server metadata
135+
// when the server was discovered via registry lookup (non-nil metadata).
136+
// Returns empty string when metadata is nil (direct image reference or protocol scheme).
137+
func ResolveRegistryServerName(serverMetadata regtypes.ServerMetadata) string {
138+
if serverMetadata == nil {
139+
return ""
140+
}
141+
return serverMetadata.GetName()
142+
}
143+
126144
// WithRegistryProxyPort sets the proxy port from registry metadata.
127145
// This is used as a fallback when the CLI --proxy-port flag is not set.
128146
func WithRegistryProxyPort(port int) RunConfigBuilderOption {

pkg/runner/config_builder_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,3 +1418,80 @@ func TestResolveRegistrySourceURLs(t *testing.T) {
14181418
})
14191419
}
14201420
}
1421+
1422+
func TestWithRegistryServerName(t *testing.T) {
1423+
t.Parallel()
1424+
1425+
tests := []struct {
1426+
name string
1427+
input string
1428+
expected string
1429+
}{
1430+
{
1431+
name: "name set",
1432+
input: "my-server",
1433+
expected: "my-server",
1434+
},
1435+
{
1436+
name: "empty name",
1437+
input: "",
1438+
expected: "",
1439+
},
1440+
}
1441+
1442+
for _, tt := range tests {
1443+
t.Run(tt.name, func(t *testing.T) {
1444+
t.Parallel()
1445+
1446+
builder := &runConfigBuilder{config: NewRunConfig()}
1447+
opt := WithRegistryServerName(tt.input)
1448+
err := opt(builder)
1449+
1450+
require.NoError(t, err)
1451+
assert.Equal(t, tt.expected, builder.config.RegistryServerName)
1452+
})
1453+
}
1454+
}
1455+
1456+
func TestResolveRegistryServerName(t *testing.T) {
1457+
t.Parallel()
1458+
1459+
tests := []struct {
1460+
name string
1461+
serverMetadata regtypes.ServerMetadata
1462+
expected string
1463+
}{
1464+
{
1465+
name: "nil metadata returns empty string",
1466+
serverMetadata: nil,
1467+
expected: "",
1468+
},
1469+
{
1470+
name: "metadata with name set",
1471+
serverMetadata: &regtypes.ImageMetadata{
1472+
BaseServerMetadata: regtypes.BaseServerMetadata{
1473+
Name: "fetch",
1474+
},
1475+
},
1476+
expected: "fetch",
1477+
},
1478+
{
1479+
name: "metadata with empty name",
1480+
serverMetadata: &regtypes.ImageMetadata{
1481+
BaseServerMetadata: regtypes.BaseServerMetadata{
1482+
Name: "",
1483+
},
1484+
},
1485+
expected: "",
1486+
},
1487+
}
1488+
1489+
for _, tt := range tests {
1490+
t.Run(tt.name, func(t *testing.T) {
1491+
t.Parallel()
1492+
1493+
result := ResolveRegistryServerName(tt.serverMetadata)
1494+
assert.Equal(t, tt.expected, result)
1495+
})
1496+
}
1497+
}

0 commit comments

Comments
 (0)