Skip to content

Commit 03aeacf

Browse files
JAORMXclaude
andcommitted
Add RegistryServerName to RunConfig for registry entry tracking
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 3c5da31 commit 03aeacf

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

0 commit comments

Comments
 (0)