Skip to content

Commit 371e04d

Browse files
chambridgeclaude
andcommitted
fix(catalog): replace phantom yamlMCPArtifact struct with generated openapi.MCPArtifact
The custom yamlMCPArtifact struct had fields (Name, Type) not present in the OpenAPI spec and was missing timestamp fields (createTimeSinceEpoch, lastUpdateTimeSinceEpoch) that are defined in the spec. This caused silent data loss of artifact timestamps through the entire MCP server YAML loader pipeline. Replacing it with the generated apimodels.MCPArtifact type ensures struct-to-JSON fidelity and prevents field drift. Adds a round-trip test verifying timestamps survive ToMCPServerProviderRecord() serialization. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Chris Hambridge <chambrid@redhat.com>
1 parent e770f92 commit 371e04d

File tree

2 files changed

+51
-24
lines changed

2 files changed

+51
-24
lines changed

catalog/internal/catalog/mcpcatalog/providers.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ type yamlMCPServer struct {
118118
PublishedDate *string `yaml:"publishedDate,omitempty"`
119119
Transports []string `yaml:"transports,omitempty"`
120120
Tools []*yamlMCPTool `yaml:"tools,omitempty"`
121-
Artifacts []*yamlMCPArtifact `yaml:"artifacts,omitempty"`
121+
Artifacts []apimodels.MCPArtifact `yaml:"artifacts,omitempty"`
122122
DeploymentMode *string `yaml:"deploymentMode,omitempty"`
123123
Endpoints *yamlMCPEndpoints `yaml:"endpoints,omitempty"`
124124
RuntimeMetadata *apimodels.MCPRuntimeMetadata `yaml:"runtimeMetadata,omitempty"`
@@ -138,13 +138,6 @@ type yamlMCPTool struct {
138138
Parameters []yamlMCPParameter `yaml:"parameters,omitempty"`
139139
}
140140

141-
// yamlMCPArtifact represents an MCP artifact (e.g., container image)
142-
type yamlMCPArtifact struct {
143-
Name string `yaml:"name"`
144-
URI string `yaml:"uri"`
145-
Type string `yaml:"type"`
146-
}
147-
148141
// yamlMCPEndpoints represents MCP server endpoints
149142
type yamlMCPEndpoints struct {
150143
HTTP *string `yaml:"http,omitempty" json:"http,omitempty"`
@@ -360,7 +353,7 @@ func (ys *yamlMCPServer) ToMCPServerProviderRecord() MCPServerProviderRecord {
360353
// Validate and convert artifacts to JSON
361354
if len(ys.Artifacts) > 0 {
362355
for i, artifact := range ys.Artifacts {
363-
if err := basecatalog.ValidateArtifactURI(artifact.URI); err != nil {
356+
if err := basecatalog.ValidateArtifactURI(artifact.Uri); err != nil {
364357
return MCPServerProviderRecord{Error: fmt.Errorf("server %q artifact %d: %w", ys.Name, i, err)}
365358
}
366359
}

catalog/internal/catalog/mcpcatalog/providers_test.go

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -438,35 +438,35 @@ func TestYamlMCPArtifactURIValidation(t *testing.T) {
438438
name: "artifact with valid oci URI passes",
439439
server: &yamlMCPServer{
440440
Name: "server-oci-artifact",
441-
Artifacts: []*yamlMCPArtifact{
442-
{Name: "container", URI: "oci://registry.example.com/image:v1", Type: "container"},
441+
Artifacts: []apimodels.MCPArtifact{
442+
{Uri: "oci://registry.example.com/image:v1"},
443443
},
444444
},
445445
},
446446
{
447447
name: "artifact with valid https URI passes",
448448
server: &yamlMCPServer{
449449
Name: "server-https-artifact",
450-
Artifacts: []*yamlMCPArtifact{
451-
{Name: "model", URI: "https://example.com/model.bin", Type: "model"},
450+
Artifacts: []apimodels.MCPArtifact{
451+
{Uri: "https://example.com/model.bin"},
452452
},
453453
},
454454
},
455455
{
456456
name: "artifact with valid s3 URI passes",
457457
server: &yamlMCPServer{
458458
Name: "server-s3-artifact",
459-
Artifacts: []*yamlMCPArtifact{
460-
{Name: "weights", URI: "s3://bucket/path/to/weights", Type: "weights"},
459+
Artifacts: []apimodels.MCPArtifact{
460+
{Uri: "s3://bucket/path/to/weights"},
461461
},
462462
},
463463
},
464464
{
465465
name: "artifact with invalid URI (no scheme) is rejected",
466466
server: &yamlMCPServer{
467467
Name: "server-invalid-uri",
468-
Artifacts: []*yamlMCPArtifact{
469-
{Name: "bad", URI: "not-a-valid-uri", Type: "model"},
468+
Artifacts: []apimodels.MCPArtifact{
469+
{Uri: "not-a-valid-uri"},
470470
},
471471
},
472472
wantErr: true,
@@ -476,8 +476,8 @@ func TestYamlMCPArtifactURIValidation(t *testing.T) {
476476
name: "artifact with unsupported scheme is rejected",
477477
server: &yamlMCPServer{
478478
Name: "server-ftp-artifact",
479-
Artifacts: []*yamlMCPArtifact{
480-
{Name: "bad", URI: "ftp://example.com/model.bin", Type: "model"},
479+
Artifacts: []apimodels.MCPArtifact{
480+
{Uri: "ftp://example.com/model.bin"},
481481
},
482482
},
483483
wantErr: true,
@@ -487,8 +487,8 @@ func TestYamlMCPArtifactURIValidation(t *testing.T) {
487487
name: "artifact with empty URI is rejected",
488488
server: &yamlMCPServer{
489489
Name: "server-empty-uri",
490-
Artifacts: []*yamlMCPArtifact{
491-
{Name: "empty", URI: "", Type: "model"},
490+
Artifacts: []apimodels.MCPArtifact{
491+
{Uri: ""},
492492
},
493493
},
494494
wantErr: true,
@@ -498,9 +498,9 @@ func TestYamlMCPArtifactURIValidation(t *testing.T) {
498498
name: "multiple artifacts with one invalid URI is rejected",
499499
server: &yamlMCPServer{
500500
Name: "server-mixed-artifacts",
501-
Artifacts: []*yamlMCPArtifact{
502-
{Name: "good", URI: "s3://bucket/valid", Type: "model"},
503-
{Name: "bad", URI: "invalid-uri", Type: "model"},
501+
Artifacts: []apimodels.MCPArtifact{
502+
{Uri: "s3://bucket/valid"},
503+
{Uri: "invalid-uri"},
504504
},
505505
},
506506
wantErr: true,
@@ -530,6 +530,40 @@ func TestYamlMCPArtifactURIValidation(t *testing.T) {
530530
}
531531
}
532532

533+
func TestYamlMCPArtifactTimestampRoundTrip(t *testing.T) {
534+
createTime := "1753292581000"
535+
updateTime := "1774534350000"
536+
server := &yamlMCPServer{
537+
Name: "server-with-timestamps",
538+
Artifacts: []apimodels.MCPArtifact{
539+
{Uri: "oci://registry.example.com/image:v1", CreateTimeSinceEpoch: &createTime, LastUpdateTimeSinceEpoch: &updateTime},
540+
},
541+
}
542+
543+
record := server.ToMCPServerProviderRecord()
544+
require.Nil(t, record.Error)
545+
require.NotNil(t, record.Server.Properties)
546+
547+
var artifactsJSON string
548+
for _, prop := range *record.Server.Properties {
549+
if prop.Name == "artifacts" && prop.StringValue != nil {
550+
artifactsJSON = *prop.StringValue
551+
}
552+
}
553+
require.NotEmpty(t, artifactsJSON, "artifacts property should be set")
554+
555+
var artifacts []apimodels.MCPArtifact
556+
err := json.Unmarshal([]byte(artifactsJSON), &artifacts)
557+
require.NoError(t, err)
558+
require.Len(t, artifacts, 1)
559+
560+
assert.Equal(t, "oci://registry.example.com/image:v1", artifacts[0].Uri)
561+
require.NotNil(t, artifacts[0].CreateTimeSinceEpoch, "createTimeSinceEpoch should survive round-trip")
562+
assert.Equal(t, "1753292581000", *artifacts[0].CreateTimeSinceEpoch)
563+
require.NotNil(t, artifacts[0].LastUpdateTimeSinceEpoch, "lastUpdateTimeSinceEpoch should survive round-trip")
564+
assert.Equal(t, "1774534350000", *artifacts[0].LastUpdateTimeSinceEpoch)
565+
}
566+
533567
func TestYamlMCPServerLicenseTransformation(t *testing.T) {
534568
tests := []struct {
535569
name string

0 commit comments

Comments
 (0)