Skip to content

fix(api): normalize server $schema to current version on read - #1515

Open
ousamabenyounes wants to merge 1 commit into
modelcontextprotocol:mainfrom
ousamabenyounes:fix/issue-783
Open

fix(api): normalize server $schema to current version on read#1515
ousamabenyounes wants to merge 1 commit into
modelcontextprotocol:mainfrom
ousamabenyounes:fix/issue-783

Conversation

@ousamabenyounes

Copy link
Copy Markdown

Summary

Registry entries published under an older server.json schema are served on the read API with their publish-time $schema value, even though the payload itself is re-serialized into the current schema shape. Strict clients (e.g. VS Code's chat.mcp.gallery.serviceUrl) fetch the advertised schema and fail to validate the response, so a non-trivial share of the catalog is unusable to them. A consumer syncing the full /v0/servers feed measured ~7% of a fresh 100-entry sample advertising a pre-current $schema (four distinct versions served simultaneously).

Fixes #783

Root cause

Server records are stored as JSONB of the apiv0.ServerJSON struct and, on read, are json.Unmarshaled back into that current struct shape (internal/database/postgres.go). Every field is therefore normalized to the current format on read — except the $schema string, which is carried verbatim from publish time. The read path never rewrites it, so old entries keep advertising a schema URL that no longer matches what is actually served.

New publishes are already forced to the current schema (publish-time validation rejects non-current schemas), so this only affects entries published while an older schema was current.

Fix

Normalize $schema to model.CurrentSchemaURL on every service-layer read path — ListServers, GetServerByName, GetServerByNameAndVersion, GetAllVersionsByServerName (internal/service/registry_service.go). All client-facing reads funnel through these methods.

Why this is safe: the served payload is already the current struct shape, and the schema changelog (docs/reference/server-json/CHANGELOG.md) shows every revision since has been additive or relaxing — no field was ever made newly required. So an entry published under an older schema still conforms to the current one; only its advertised $schema string was stale. The write path is untouched (edit/status read-then-write flows re-read through the un-normalized DB layer before persisting, so nothing new is written).

Test verification (RED → GREEN)

Added TestReadNormalizesLegacySchemaVersion, which seeds a server with a legacy $schema (2025-09-16) directly through the database layer (bypassing publish-time validation) and asserts every read path returns model.CurrentSchemaURL.

RED — new test against the unmodified base (fix reverted, test present):

--- FAIL: TestReadNormalizesLegacySchemaVersion (0.51s)
    --- FAIL: TestReadNormalizesLegacySchemaVersion/GetServerByName (0.00s)
        Error: Not equal:
          expected: ".../schemas/2025-12-11/server.schema.json"
          actual  : ".../schemas/2025-09-16/server.schema.json"
    --- FAIL: TestReadNormalizesLegacySchemaVersion/GetServerByNameAndVersion (0.00s)
    --- FAIL: TestReadNormalizesLegacySchemaVersion/GetAllVersionsByServerName (0.00s)
    --- FAIL: TestReadNormalizesLegacySchemaVersion/ListServers (0.00s)
FAIL	github.com/modelcontextprotocol/registry/internal/service

GREEN — same test with the fix applied:

--- PASS: TestReadNormalizesLegacySchemaVersion (0.12s)
    --- PASS: TestReadNormalizesLegacySchemaVersion/GetServerByName (0.00s)
    --- PASS: TestReadNormalizesLegacySchemaVersion/GetServerByNameAndVersion (0.00s)
    --- PASS: TestReadNormalizesLegacySchemaVersion/GetAllVersionsByServerName (0.00s)
    --- PASS: TestReadNormalizesLegacySchemaVersion/ListServers (0.00s)
ok  	github.com/modelcontextprotocol/registry/internal/service

Full local run of golangci-lint, go vet, go build, schema-sync check, and the race-enabled unit suite (./internal/... ./cmd/...) passes with no regressions.

Registry records are stored as JSONB of the current apiv0.ServerJSON struct and
re-serialized into that shape on every read, but the $schema string is carried
verbatim from publish time. Entries published under an older schema therefore
advertise a stale $schema even though their payload conforms to the current
schema (all schema revisions since have been additive/relaxing — no field made
newly required). Strict clients such as VSCode fetch the advertised schema and
fail validation, so ~7% of live entries break consumers.

Normalize $schema to model.CurrentSchemaURL across all read paths (ListServers,
GetServerByName, GetServerByNameAndVersion, GetAllVersionsByServerName) so served
entries advertise the schema they actually conform to.

Fixes modelcontextprotocol#783
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong server schema breaks clients

1 participant