From 7a1aadf05e7ddc6ba79acd8c0a9be28a9c3877d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 18:36:59 +0000 Subject: [PATCH 1/6] feat: require valid $schema field in server.json (#805) This change enforces that all server.json files must have a valid $schema URL. Empty or invalid schema URLs are now rejected by both the API validator and the mcp-publisher CLI. Changes: - Add ValidSchemaVersions constant listing all accepted schema versions - Add IsValidSchemaURL() helper function for validation - Update API validator to require exact match of valid schema URLs - Update mcp-publisher to reject empty or invalid $schema fields - Add migration 011 to fix 17 existing entries with empty $schema - Add comprehensive tests for schema validation Fixes #805 --- cmd/publisher/commands/publish.go | 21 ++- .../011_fix_empty_schema_fields.sql | 63 ++++++++ internal/validators/validators.go | 6 +- internal/validators/validators_test.go | 86 ++++++++++- pkg/model/constants.go | 37 +++++ pkg/model/constants_test.go | 142 ++++++++++++++++++ 6 files changed, 345 insertions(+), 10 deletions(-) create mode 100644 internal/database/migrations/011_fix_empty_schema_fields.sql create mode 100644 pkg/model/constants_test.go diff --git a/cmd/publisher/commands/publish.go b/cmd/publisher/commands/publish.go index 6a42c943..66115e0c 100644 --- a/cmd/publisher/commands/publish.go +++ b/cmd/publisher/commands/publish.go @@ -38,15 +38,24 @@ func PublishCommand(args []string) error { return fmt.Errorf("invalid server.json: %w", err) } - // Check for deprecated schema and recommend migration - // Allow empty schema (will use default) but reject old schemas - if serverJSON.Schema != "" && !strings.Contains(serverJSON.Schema, model.CurrentSchemaVersion) { - return fmt.Errorf(`deprecated schema detected: %s. + // Validate $schema field is required and must be a valid schema URL + if serverJSON.Schema == "" { + return fmt.Errorf(`$schema field is required. -Migrate to the current schema format for new servers. +Add the following to your server.json: + "$schema": "%s" + +📖 Schema reference: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, model.CurrentSchemaURL) + } + + if !model.IsValidSchemaURL(serverJSON.Schema) { + return fmt.Errorf(`invalid $schema URL: %s + +Update your server.json to use a valid schema URL, e.g.: + "$schema": "%s" 📋 Migration checklist: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md#migration-checklist-for-publishers -📖 Full changelog with examples: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, serverJSON.Schema) +📖 Full changelog with examples: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, serverJSON.Schema, model.CurrentSchemaURL) } // Load saved token diff --git a/internal/database/migrations/011_fix_empty_schema_fields.sql b/internal/database/migrations/011_fix_empty_schema_fields.sql new file mode 100644 index 00000000..a5d7cec2 --- /dev/null +++ b/internal/database/migrations/011_fix_empty_schema_fields.sql @@ -0,0 +1,63 @@ +-- Migration: Fix empty $schema fields in 17 specific server entries +-- +-- This migration updates exactly 17 server entries that were published with +-- empty $schema fields (""). These entries are explicitly listed below to +-- ensure no other data is affected. +-- +-- Issue: https://github.com/modelcontextprotocol/registry/issues/805 +-- +-- Affected entries (17 total): +-- io.github.OtherVibes/mcp-as-a-judge: 0.3.12, 0.3.13, 0.3.14, 0.3.20 +-- io.github.Skills03/scrimba-teaching: 1.0.1, 1.1.0, 1.2.0 +-- io.github.antuelle78/weather-mcp: 1.0.0 +-- io.github.jkakar/cookwith-mcp: 1.0.0, 1.0.1, 1.0.2 +-- io.github.ruvnet/claude-flow: 2.0.0-alpha.104, 2.0.0-alpha.105 +-- io.github.ruvnet/ruv-swarm: 1.0.18, 1.0.19 +-- io.github.toby/mirror-mcp: 0.0.4 +-- travel.kismet/mcp-server: 0.0.0 + +BEGIN; + +-- Define the exact list of affected entries +-- Using a CTE to make the affected entries explicit and auditable +WITH affected_entries AS ( + SELECT server_name, version FROM (VALUES + -- io.github.OtherVibes/mcp-as-a-judge (4 versions) + ('io.github.OtherVibes/mcp-as-a-judge', '0.3.12'), + ('io.github.OtherVibes/mcp-as-a-judge', '0.3.13'), + ('io.github.OtherVibes/mcp-as-a-judge', '0.3.14'), + ('io.github.OtherVibes/mcp-as-a-judge', '0.3.20'), + -- io.github.Skills03/scrimba-teaching (3 versions) + ('io.github.Skills03/scrimba-teaching', '1.0.1'), + ('io.github.Skills03/scrimba-teaching', '1.1.0'), + ('io.github.Skills03/scrimba-teaching', '1.2.0'), + -- io.github.antuelle78/weather-mcp (1 version) + ('io.github.antuelle78/weather-mcp', '1.0.0'), + -- io.github.jkakar/cookwith-mcp (3 versions) + ('io.github.jkakar/cookwith-mcp', '1.0.0'), + ('io.github.jkakar/cookwith-mcp', '1.0.1'), + ('io.github.jkakar/cookwith-mcp', '1.0.2'), + -- io.github.ruvnet/claude-flow (2 versions) + ('io.github.ruvnet/claude-flow', '2.0.0-alpha.104'), + ('io.github.ruvnet/claude-flow', '2.0.0-alpha.105'), + -- io.github.ruvnet/ruv-swarm (2 versions) + ('io.github.ruvnet/ruv-swarm', '1.0.18'), + ('io.github.ruvnet/ruv-swarm', '1.0.19'), + -- io.github.toby/mirror-mcp (1 version) + ('io.github.toby/mirror-mcp', '0.0.4'), + -- travel.kismet/mcp-server (1 version) + ('travel.kismet/mcp-server', '0.0.0') + ) AS t(server_name, version) +) +UPDATE servers s +SET value = jsonb_set( + s.value, + '{$schema}', + '"https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json"'::jsonb +) +FROM affected_entries ae +WHERE s.server_name = ae.server_name + AND s.version = ae.version + AND (s.value->>'$schema' = '' OR s.value->>'$schema' IS NULL); + +COMMIT; diff --git a/internal/validators/validators.go b/internal/validators/validators.go index 3c9789df..d8621716 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -56,10 +56,10 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON) error { // Note: Schema field is also marked as required in the ServerJSON struct definition // for API-level validation and documentation if serverJSON.Schema == "" { - return fmt.Errorf("$schema field is required") + return fmt.Errorf("$schema field is required. Use: %s", model.CurrentSchemaURL) } - if !strings.Contains(serverJSON.Schema, model.CurrentSchemaVersion) { - return fmt.Errorf("schema version %s is not supported. Please use schema version %s", serverJSON.Schema, model.CurrentSchemaVersion) + if !model.IsValidSchemaURL(serverJSON.Schema) { + return fmt.Errorf("invalid $schema URL: %s. Must be one of the valid schema URLs (e.g., %s)", serverJSON.Schema, model.CurrentSchemaURL) } // Validate server name exists and format diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index e5d39092..46fda92f 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -44,7 +44,91 @@ func TestValidate(t *testing.T) { }, Version: "1.0.0", }, - expectedError: "schema version https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json is not supported", + expectedError: "invalid $schema URL", + }, + { + name: "Schema version accepts 2025-10-11 schema", + serverDetail: apiv0.ServerJSON{ + Schema: "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json", + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "", + }, + { + name: "Schema version accepts 2025-09-29 schema", + serverDetail: apiv0.ServerJSON{ + Schema: "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "", + }, + { + name: "Schema version accepts 2025-09-16 schema", + serverDetail: apiv0.ServerJSON{ + Schema: "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "", + }, + { + name: "Schema version accepts 2025-07-09 schema", + serverDetail: apiv0.ServerJSON{ + Schema: "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "", + }, + { + name: "Schema version rejects random URL", + serverDetail: apiv0.ServerJSON{ + Schema: "https://example.com/my-schema.json", + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "invalid $schema URL", + }, + { + name: "Schema version rejects schema URL with wrong suffix", + serverDetail: apiv0.ServerJSON{ + Schema: "https://static.modelcontextprotocol.io/schemas/2025-10-17/wrong.json", + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "invalid $schema URL", }, { name: "Schema version accepts current schema (2025-10-17)", diff --git a/pkg/model/constants.go b/pkg/model/constants.go index 433319ef..175e9021 100644 --- a/pkg/model/constants.go +++ b/pkg/model/constants.go @@ -39,4 +39,41 @@ const ( CurrentSchemaVersion = "2025-10-17" // CurrentSchemaURL is the full URL to the current schema CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json" + // SchemaURLPrefix is the common prefix for all schema URLs + SchemaURLPrefix = "https://static.modelcontextprotocol.io/schemas/" + // SchemaURLSuffix is the common suffix for all schema URLs + SchemaURLSuffix = "/server.schema.json" ) + +// ValidSchemaVersions is the list of all accepted schema versions. +// This is used by validators to accept any valid schema version (not just the current one). +// Note: We accept all historical schema versions to support existing published servers. +var ValidSchemaVersions = []string{ + "2025-10-17", // Current: Optional version field for MCPB packages + "2025-10-11", // Package format enhancements (version optional for OCI) + "2025-09-29", // Schema simplification (removed status, official metadata) + "2025-09-16", // Breaking: snake_case → camelCase field names + "2025-07-09", // Initial release +} + +// IsValidSchemaURL checks if the given schema URL is a valid MCP server schema URL. +// It must match the pattern: https://static.modelcontextprotocol.io/schemas/{version}/server.schema.json +// where {version} is one of the valid schema versions. +func IsValidSchemaURL(schemaURL string) bool { + for _, version := range ValidSchemaVersions { + expectedURL := SchemaURLPrefix + version + SchemaURLSuffix + if schemaURL == expectedURL { + return true + } + } + return false +} + +// ValidSchemaURLs returns the list of all valid schema URLs. +func ValidSchemaURLs() []string { + urls := make([]string, len(ValidSchemaVersions)) + for i, version := range ValidSchemaVersions { + urls[i] = SchemaURLPrefix + version + SchemaURLSuffix + } + return urls +} diff --git a/pkg/model/constants_test.go b/pkg/model/constants_test.go new file mode 100644 index 00000000..86b8a04e --- /dev/null +++ b/pkg/model/constants_test.go @@ -0,0 +1,142 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsValidSchemaURL(t *testing.T) { + tests := []struct { + name string + url string + expected bool + }{ + // Valid schema URLs + { + name: "current schema version 2025-10-17", + url: "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", + expected: true, + }, + { + name: "schema version 2025-10-11", + url: "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json", + expected: true, + }, + { + name: "schema version 2025-09-29", + url: "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", + expected: true, + }, + { + name: "schema version 2025-09-16", + url: "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", + expected: true, + }, + { + name: "schema version 2025-07-09", + url: "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", + expected: true, + }, + { + name: "CurrentSchemaURL constant", + url: CurrentSchemaURL, + expected: true, + }, + // Invalid schema URLs + { + name: "empty string", + url: "", + expected: false, + }, + { + name: "non-existent version", + url: "https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json", + expected: false, + }, + { + name: "wrong domain", + url: "https://example.com/schemas/2025-10-17/server.schema.json", + expected: false, + }, + { + name: "wrong suffix", + url: "https://static.modelcontextprotocol.io/schemas/2025-10-17/wrong.json", + expected: false, + }, + { + name: "missing version", + url: "https://static.modelcontextprotocol.io/schemas//server.schema.json", + expected: false, + }, + { + name: "random URL", + url: "https://example.com/my-schema.json", + expected: false, + }, + { + name: "partial match - version only", + url: "2025-10-17", + expected: false, + }, + { + name: "http instead of https", + url: "http://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsValidSchemaURL(tt.url) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestValidSchemaURLs(t *testing.T) { + urls := ValidSchemaURLs() + + // Should return the same number of URLs as ValidSchemaVersions + assert.Equal(t, len(ValidSchemaVersions), len(urls)) + + // All returned URLs should be valid + for _, url := range urls { + assert.True(t, IsValidSchemaURL(url), "URL %s should be valid", url) + } + + // CurrentSchemaURL should be in the list + found := false + for _, url := range urls { + if url == CurrentSchemaURL { + found = true + break + } + } + assert.True(t, found, "CurrentSchemaURL should be in ValidSchemaURLs()") +} + +func TestValidSchemaVersions(t *testing.T) { + // Ensure CurrentSchemaVersion is in the list + found := false + for _, version := range ValidSchemaVersions { + if version == CurrentSchemaVersion { + found = true + break + } + } + assert.True(t, found, "CurrentSchemaVersion should be in ValidSchemaVersions") + + // Ensure all versions follow the expected date format + for _, version := range ValidSchemaVersions { + assert.Regexp(t, `^\d{4}-\d{2}-\d{2}$`, version, "Version %s should be in YYYY-MM-DD format", version) + } +} + +func TestSchemaURLConstruction(t *testing.T) { + // Verify that SchemaURLPrefix + version + SchemaURLSuffix = valid URL + for _, version := range ValidSchemaVersions { + expectedURL := SchemaURLPrefix + version + SchemaURLSuffix + assert.True(t, IsValidSchemaURL(expectedURL), "Constructed URL %s should be valid", expectedURL) + } +} From 8b062c22412cc5e619d7336ef4cbad15a6dd92d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 19:12:35 +0000 Subject: [PATCH 2/6] fix: update schema reference URL to docs directory --- cmd/publisher/commands/publish.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/publisher/commands/publish.go b/cmd/publisher/commands/publish.go index 66115e0c..ef7ff73e 100644 --- a/cmd/publisher/commands/publish.go +++ b/cmd/publisher/commands/publish.go @@ -45,7 +45,7 @@ func PublishCommand(args []string) error { Add the following to your server.json: "$schema": "%s" -📖 Schema reference: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, model.CurrentSchemaURL) +📖 Schema reference: https://github.com/modelcontextprotocol/registry/tree/main/docs/reference/server-json`, model.CurrentSchemaURL) } if !model.IsValidSchemaURL(serverJSON.Schema) { @@ -54,8 +54,7 @@ Add the following to your server.json: Update your server.json to use a valid schema URL, e.g.: "$schema": "%s" -📋 Migration checklist: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md#migration-checklist-for-publishers -📖 Full changelog with examples: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, serverJSON.Schema, model.CurrentSchemaURL) +📖 Schema reference: https://github.com/modelcontextprotocol/registry/tree/main/docs/reference/server-json`, serverJSON.Schema, model.CurrentSchemaURL) } // Load saved token From 79b278583fa64ff0ee7e1ec055e0277be31c595f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 19:13:56 +0000 Subject: [PATCH 3/6] chore: simplify ValidSchemaVersions comments --- pkg/model/constants.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/model/constants.go b/pkg/model/constants.go index 175e9021..9260efc5 100644 --- a/pkg/model/constants.go +++ b/pkg/model/constants.go @@ -47,13 +47,13 @@ const ( // ValidSchemaVersions is the list of all accepted schema versions. // This is used by validators to accept any valid schema version (not just the current one). -// Note: We accept all historical schema versions to support existing published servers. +// TODO: Consider only supporting more recent schema versions. var ValidSchemaVersions = []string{ - "2025-10-17", // Current: Optional version field for MCPB packages - "2025-10-11", // Package format enhancements (version optional for OCI) - "2025-09-29", // Schema simplification (removed status, official metadata) - "2025-09-16", // Breaking: snake_case → camelCase field names - "2025-07-09", // Initial release + "2025-10-17", + "2025-10-11", + "2025-09-29", + "2025-09-16", + "2025-07-09", } // IsValidSchemaURL checks if the given schema URL is a valid MCP server schema URL. From db0d6bda736f281ae5ebb7a1e9df85aa4ca7fa02 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 19:17:55 +0000 Subject: [PATCH 4/6] refactor: simplify to only support current schema version --- cmd/publisher/commands/publish.go | 6 +- internal/validators/validators.go | 4 +- internal/validators/validators_test.go | 58 +--------- pkg/model/constants.go | 36 ------- pkg/model/constants_test.go | 142 ------------------------- 5 files changed, 6 insertions(+), 240 deletions(-) delete mode 100644 pkg/model/constants_test.go diff --git a/cmd/publisher/commands/publish.go b/cmd/publisher/commands/publish.go index ef7ff73e..979cb2a1 100644 --- a/cmd/publisher/commands/publish.go +++ b/cmd/publisher/commands/publish.go @@ -38,7 +38,7 @@ func PublishCommand(args []string) error { return fmt.Errorf("invalid server.json: %w", err) } - // Validate $schema field is required and must be a valid schema URL + // Validate $schema field is required and must be the current schema URL if serverJSON.Schema == "" { return fmt.Errorf(`$schema field is required. @@ -48,10 +48,10 @@ Add the following to your server.json: 📖 Schema reference: https://github.com/modelcontextprotocol/registry/tree/main/docs/reference/server-json`, model.CurrentSchemaURL) } - if !model.IsValidSchemaURL(serverJSON.Schema) { + if serverJSON.Schema != model.CurrentSchemaURL { return fmt.Errorf(`invalid $schema URL: %s -Update your server.json to use a valid schema URL, e.g.: +Update your server.json to use: "$schema": "%s" 📖 Schema reference: https://github.com/modelcontextprotocol/registry/tree/main/docs/reference/server-json`, serverJSON.Schema, model.CurrentSchemaURL) diff --git a/internal/validators/validators.go b/internal/validators/validators.go index d8621716..a3ab5119 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -58,8 +58,8 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON) error { if serverJSON.Schema == "" { return fmt.Errorf("$schema field is required. Use: %s", model.CurrentSchemaURL) } - if !model.IsValidSchemaURL(serverJSON.Schema) { - return fmt.Errorf("invalid $schema URL: %s. Must be one of the valid schema URLs (e.g., %s)", serverJSON.Schema, model.CurrentSchemaURL) + if serverJSON.Schema != model.CurrentSchemaURL { + return fmt.Errorf("invalid $schema URL: %s. Must be: %s", serverJSON.Schema, model.CurrentSchemaURL) } // Validate server name exists and format diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index 46fda92f..5fa2c869 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -33,7 +33,7 @@ func TestValidate(t *testing.T) { expectedError: "$schema field is required", }, { - name: "Schema version rejects old schema (2025-01-27)", + name: "Schema version rejects old schema", serverDetail: apiv0.ServerJSON{ Schema: "https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json", Name: "com.example/test-server", @@ -46,62 +46,6 @@ func TestValidate(t *testing.T) { }, expectedError: "invalid $schema URL", }, - { - name: "Schema version accepts 2025-10-11 schema", - serverDetail: apiv0.ServerJSON{ - Schema: "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json", - Name: "com.example/test-server", - Description: "A test server", - Repository: &model.Repository{ - URL: "https://github.com/owner/repo", - Source: "github", - }, - Version: "1.0.0", - }, - expectedError: "", - }, - { - name: "Schema version accepts 2025-09-29 schema", - serverDetail: apiv0.ServerJSON{ - Schema: "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", - Name: "com.example/test-server", - Description: "A test server", - Repository: &model.Repository{ - URL: "https://github.com/owner/repo", - Source: "github", - }, - Version: "1.0.0", - }, - expectedError: "", - }, - { - name: "Schema version accepts 2025-09-16 schema", - serverDetail: apiv0.ServerJSON{ - Schema: "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", - Name: "com.example/test-server", - Description: "A test server", - Repository: &model.Repository{ - URL: "https://github.com/owner/repo", - Source: "github", - }, - Version: "1.0.0", - }, - expectedError: "", - }, - { - name: "Schema version accepts 2025-07-09 schema", - serverDetail: apiv0.ServerJSON{ - Schema: "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", - Name: "com.example/test-server", - Description: "A test server", - Repository: &model.Repository{ - URL: "https://github.com/owner/repo", - Source: "github", - }, - Version: "1.0.0", - }, - expectedError: "", - }, { name: "Schema version rejects random URL", serverDetail: apiv0.ServerJSON{ diff --git a/pkg/model/constants.go b/pkg/model/constants.go index 9260efc5..28e7cb99 100644 --- a/pkg/model/constants.go +++ b/pkg/model/constants.go @@ -39,41 +39,5 @@ const ( CurrentSchemaVersion = "2025-10-17" // CurrentSchemaURL is the full URL to the current schema CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json" - // SchemaURLPrefix is the common prefix for all schema URLs - SchemaURLPrefix = "https://static.modelcontextprotocol.io/schemas/" - // SchemaURLSuffix is the common suffix for all schema URLs - SchemaURLSuffix = "/server.schema.json" ) -// ValidSchemaVersions is the list of all accepted schema versions. -// This is used by validators to accept any valid schema version (not just the current one). -// TODO: Consider only supporting more recent schema versions. -var ValidSchemaVersions = []string{ - "2025-10-17", - "2025-10-11", - "2025-09-29", - "2025-09-16", - "2025-07-09", -} - -// IsValidSchemaURL checks if the given schema URL is a valid MCP server schema URL. -// It must match the pattern: https://static.modelcontextprotocol.io/schemas/{version}/server.schema.json -// where {version} is one of the valid schema versions. -func IsValidSchemaURL(schemaURL string) bool { - for _, version := range ValidSchemaVersions { - expectedURL := SchemaURLPrefix + version + SchemaURLSuffix - if schemaURL == expectedURL { - return true - } - } - return false -} - -// ValidSchemaURLs returns the list of all valid schema URLs. -func ValidSchemaURLs() []string { - urls := make([]string, len(ValidSchemaVersions)) - for i, version := range ValidSchemaVersions { - urls[i] = SchemaURLPrefix + version + SchemaURLSuffix - } - return urls -} diff --git a/pkg/model/constants_test.go b/pkg/model/constants_test.go deleted file mode 100644 index 86b8a04e..00000000 --- a/pkg/model/constants_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package model - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestIsValidSchemaURL(t *testing.T) { - tests := []struct { - name string - url string - expected bool - }{ - // Valid schema URLs - { - name: "current schema version 2025-10-17", - url: "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", - expected: true, - }, - { - name: "schema version 2025-10-11", - url: "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json", - expected: true, - }, - { - name: "schema version 2025-09-29", - url: "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", - expected: true, - }, - { - name: "schema version 2025-09-16", - url: "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", - expected: true, - }, - { - name: "schema version 2025-07-09", - url: "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", - expected: true, - }, - { - name: "CurrentSchemaURL constant", - url: CurrentSchemaURL, - expected: true, - }, - // Invalid schema URLs - { - name: "empty string", - url: "", - expected: false, - }, - { - name: "non-existent version", - url: "https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json", - expected: false, - }, - { - name: "wrong domain", - url: "https://example.com/schemas/2025-10-17/server.schema.json", - expected: false, - }, - { - name: "wrong suffix", - url: "https://static.modelcontextprotocol.io/schemas/2025-10-17/wrong.json", - expected: false, - }, - { - name: "missing version", - url: "https://static.modelcontextprotocol.io/schemas//server.schema.json", - expected: false, - }, - { - name: "random URL", - url: "https://example.com/my-schema.json", - expected: false, - }, - { - name: "partial match - version only", - url: "2025-10-17", - expected: false, - }, - { - name: "http instead of https", - url: "http://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := IsValidSchemaURL(tt.url) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestValidSchemaURLs(t *testing.T) { - urls := ValidSchemaURLs() - - // Should return the same number of URLs as ValidSchemaVersions - assert.Equal(t, len(ValidSchemaVersions), len(urls)) - - // All returned URLs should be valid - for _, url := range urls { - assert.True(t, IsValidSchemaURL(url), "URL %s should be valid", url) - } - - // CurrentSchemaURL should be in the list - found := false - for _, url := range urls { - if url == CurrentSchemaURL { - found = true - break - } - } - assert.True(t, found, "CurrentSchemaURL should be in ValidSchemaURLs()") -} - -func TestValidSchemaVersions(t *testing.T) { - // Ensure CurrentSchemaVersion is in the list - found := false - for _, version := range ValidSchemaVersions { - if version == CurrentSchemaVersion { - found = true - break - } - } - assert.True(t, found, "CurrentSchemaVersion should be in ValidSchemaVersions") - - // Ensure all versions follow the expected date format - for _, version := range ValidSchemaVersions { - assert.Regexp(t, `^\d{4}-\d{2}-\d{2}$`, version, "Version %s should be in YYYY-MM-DD format", version) - } -} - -func TestSchemaURLConstruction(t *testing.T) { - // Verify that SchemaURLPrefix + version + SchemaURLSuffix = valid URL - for _, version := range ValidSchemaVersions { - expectedURL := SchemaURLPrefix + version + SchemaURLSuffix - assert.True(t, IsValidSchemaURL(expectedURL), "Constructed URL %s should be valid", expectedURL) - } -} From 9fba6e731701f2a6c9fa9435588c00aa9d124dd3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 19:21:43 +0000 Subject: [PATCH 5/6] fix: correct formatting in constants.go --- pkg/model/constants.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/model/constants.go b/pkg/model/constants.go index 28e7cb99..433319ef 100644 --- a/pkg/model/constants.go +++ b/pkg/model/constants.go @@ -40,4 +40,3 @@ const ( // CurrentSchemaURL is the full URL to the current schema CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json" ) - From 176292bdced2bba0873f4350169d4415452b6ab1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 19:26:03 +0000 Subject: [PATCH 6/6] refactor: reduce to pure data migration Remove unnecessary code changes since schema validation was already deployed. This PR now only contains the migration to fix historical entries that were published before schema validation was enforced. --- cmd/publisher/commands/publish.go | 22 ++++++------------ internal/validators/validators.go | 6 ++--- internal/validators/validators_test.go | 32 ++------------------------ 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/cmd/publisher/commands/publish.go b/cmd/publisher/commands/publish.go index 979cb2a1..6a42c943 100644 --- a/cmd/publisher/commands/publish.go +++ b/cmd/publisher/commands/publish.go @@ -38,23 +38,15 @@ func PublishCommand(args []string) error { return fmt.Errorf("invalid server.json: %w", err) } - // Validate $schema field is required and must be the current schema URL - if serverJSON.Schema == "" { - return fmt.Errorf(`$schema field is required. + // Check for deprecated schema and recommend migration + // Allow empty schema (will use default) but reject old schemas + if serverJSON.Schema != "" && !strings.Contains(serverJSON.Schema, model.CurrentSchemaVersion) { + return fmt.Errorf(`deprecated schema detected: %s. -Add the following to your server.json: - "$schema": "%s" +Migrate to the current schema format for new servers. -📖 Schema reference: https://github.com/modelcontextprotocol/registry/tree/main/docs/reference/server-json`, model.CurrentSchemaURL) - } - - if serverJSON.Schema != model.CurrentSchemaURL { - return fmt.Errorf(`invalid $schema URL: %s - -Update your server.json to use: - "$schema": "%s" - -📖 Schema reference: https://github.com/modelcontextprotocol/registry/tree/main/docs/reference/server-json`, serverJSON.Schema, model.CurrentSchemaURL) +📋 Migration checklist: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md#migration-checklist-for-publishers +📖 Full changelog with examples: https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/CHANGELOG.md`, serverJSON.Schema) } // Load saved token diff --git a/internal/validators/validators.go b/internal/validators/validators.go index a3ab5119..3c9789df 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -56,10 +56,10 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON) error { // Note: Schema field is also marked as required in the ServerJSON struct definition // for API-level validation and documentation if serverJSON.Schema == "" { - return fmt.Errorf("$schema field is required. Use: %s", model.CurrentSchemaURL) + return fmt.Errorf("$schema field is required") } - if serverJSON.Schema != model.CurrentSchemaURL { - return fmt.Errorf("invalid $schema URL: %s. Must be: %s", serverJSON.Schema, model.CurrentSchemaURL) + if !strings.Contains(serverJSON.Schema, model.CurrentSchemaVersion) { + return fmt.Errorf("schema version %s is not supported. Please use schema version %s", serverJSON.Schema, model.CurrentSchemaVersion) } // Validate server name exists and format diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index 5fa2c869..e5d39092 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -33,7 +33,7 @@ func TestValidate(t *testing.T) { expectedError: "$schema field is required", }, { - name: "Schema version rejects old schema", + name: "Schema version rejects old schema (2025-01-27)", serverDetail: apiv0.ServerJSON{ Schema: "https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json", Name: "com.example/test-server", @@ -44,35 +44,7 @@ func TestValidate(t *testing.T) { }, Version: "1.0.0", }, - expectedError: "invalid $schema URL", - }, - { - name: "Schema version rejects random URL", - serverDetail: apiv0.ServerJSON{ - Schema: "https://example.com/my-schema.json", - Name: "com.example/test-server", - Description: "A test server", - Repository: &model.Repository{ - URL: "https://github.com/owner/repo", - Source: "github", - }, - Version: "1.0.0", - }, - expectedError: "invalid $schema URL", - }, - { - name: "Schema version rejects schema URL with wrong suffix", - serverDetail: apiv0.ServerJSON{ - Schema: "https://static.modelcontextprotocol.io/schemas/2025-10-17/wrong.json", - Name: "com.example/test-server", - Description: "A test server", - Repository: &model.Repository{ - URL: "https://github.com/owner/repo", - Source: "github", - }, - Version: "1.0.0", - }, - expectedError: "invalid $schema URL", + expectedError: "schema version https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json is not supported", }, { name: "Schema version accepts current schema (2025-10-17)",