From 65c12812207525477ecaf185b8d889282b23e637 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Fri, 14 Nov 2025 11:06:39 -0500 Subject: [PATCH 1/3] Remove namespace match validation for websiteUrl --- internal/validators/validators.go | 20 -------------------- internal/validators/validators_test.go | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/internal/validators/validators.go b/internal/validators/validators.go index ffdb4a8b..f294fad1 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -113,11 +113,6 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON) error { return err } - // Validate reverse-DNS namespace matching for website URL - if err := validateWebsiteURLNamespaceMatch(*serverJSON); err != nil { - return err - } - return nil } @@ -523,21 +518,6 @@ func validateRemoteNamespaceMatch(serverJSON apiv0.ServerJSON) error { return nil } -// validateWebsiteURLNamespaceMatch validates that website URL matches the reverse-DNS namespace -func validateWebsiteURLNamespaceMatch(serverJSON apiv0.ServerJSON) error { - // Skip validation if website URL is not provided - if serverJSON.WebsiteURL == "" { - return nil - } - - namespace := serverJSON.Name - if err := validateRemoteURLMatchesNamespace(serverJSON.WebsiteURL, namespace); err != nil { - return fmt.Errorf("websiteUrl %s does not match namespace %s: %w", serverJSON.WebsiteURL, namespace, err) - } - - return nil -} - // validateRemoteURLMatchesNamespace checks if a remote URL's hostname matches the publisher domain from the namespace func validateRemoteURLMatchesNamespace(remoteURL, namespace string) error { // Parse the URL to extract the hostname diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index 1b08c9b7..0c279bc3 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -454,6 +454,21 @@ func TestValidate(t *testing.T) { }, expectedError: "websiteUrl must use https scheme: ftp://example.com/docs", }, + { + name: "server with invalid websiteUrl - required HTTPS", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + WebsiteURL: "http://example.com/docs", + }, + expectedError: "websiteUrl must use https scheme: http://example.com/docs", + }, { name: "server with malformed websiteUrl", serverDetail: apiv0.ServerJSON{ @@ -512,7 +527,7 @@ func TestValidate(t *testing.T) { Version: "1.0.0", WebsiteURL: "https://different.com/docs", }, - expectedError: "websiteUrl https://different.com/docs does not match namespace com.example/test-server", + expectedError: "", }, { name: "package with spaces in name", From 90e80fd310124b8341d6f87009dea713a018f1d9 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Fri, 14 Nov 2025 12:34:00 -0500 Subject: [PATCH 2/3] Allow any remote URL as long as it's HTTPS --- internal/importer/importer_test.go | 3 +- internal/validators/utils.go | 4 ++ internal/validators/validators.go | 87 -------------------------- internal/validators/validators_test.go | 15 +++-- 4 files changed, 13 insertions(+), 96 deletions(-) diff --git a/internal/importer/importer_test.go b/internal/importer/importer_test.go index 8337e935..db0cc2a6 100644 --- a/internal/importer/importer_test.go +++ b/internal/importer/importer_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "testing" "github.com/modelcontextprotocol/registry/internal/config" @@ -20,7 +21,7 @@ import ( func TestImportService_LocalFile(t *testing.T) { // Create a temporary seed file - tempFile := "/tmp/test_import_seed.json" + tempFile := filepath.Join(os.TempDir(), "test_import_seed.json") seedData := []*apiv0.ServerJSON{ { Schema: model.CurrentSchemaURL, diff --git a/internal/validators/utils.go b/internal/validators/utils.go index 69f2615a..9a6b4cf5 100644 --- a/internal/validators/utils.go +++ b/internal/validators/utils.go @@ -144,6 +144,10 @@ func IsValidRemoteURL(rawURL string) bool { return false } + if u.Scheme != "https" { + return false + } + return true } diff --git a/internal/validators/validators.go b/internal/validators/validators.go index f294fad1..3c9789df 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -6,7 +6,6 @@ import ( "fmt" "net/url" "regexp" - "slices" "strings" "github.com/modelcontextprotocol/registry/internal/config" @@ -108,11 +107,6 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON) error { } } - // Validate reverse-DNS namespace matching for remote URLs - if err := validateRemoteNamespaceMatch(*serverJSON); err != nil { - return err - } - return nil } @@ -504,84 +498,3 @@ func parseServerName(serverJSON apiv0.ServerJSON) (string, error) { return name, nil } - -// validateRemoteNamespaceMatch validates that remote URLs match the reverse-DNS namespace -func validateRemoteNamespaceMatch(serverJSON apiv0.ServerJSON) error { - namespace := serverJSON.Name - - for _, remote := range serverJSON.Remotes { - if err := validateRemoteURLMatchesNamespace(remote.URL, namespace); err != nil { - return fmt.Errorf("remote URL %s does not match namespace %s: %w", remote.URL, namespace, err) - } - } - - return nil -} - -// validateRemoteURLMatchesNamespace checks if a remote URL's hostname matches the publisher domain from the namespace -func validateRemoteURLMatchesNamespace(remoteURL, namespace string) error { - // Parse the URL to extract the hostname - parsedURL, err := url.Parse(remoteURL) - if err != nil { - return fmt.Errorf("invalid URL format: %w", err) - } - - hostname := parsedURL.Hostname() - if hostname == "" { - return fmt.Errorf("URL must have a valid hostname") - } - - // Skip validation for localhost and local development URLs - if hostname == "localhost" || strings.HasSuffix(hostname, ".localhost") || hostname == "127.0.0.1" { - return nil - } - - // Extract publisher domain from reverse-DNS namespace - publisherDomain := extractPublisherDomainFromNamespace(namespace) - if publisherDomain == "" { - return fmt.Errorf("invalid namespace format: cannot extract domain from %s", namespace) - } - - // Check if the remote URL hostname matches the publisher domain or is a subdomain - if !isValidHostForDomain(hostname, publisherDomain) { - return fmt.Errorf("remote URL host %s does not match publisher domain %s", hostname, publisherDomain) - } - - return nil -} - -// extractPublisherDomainFromNamespace converts reverse-DNS namespace to normal domain format -// e.g., "com.example" -> "example.com" -func extractPublisherDomainFromNamespace(namespace string) string { - // Extract the namespace part before the first slash - namespacePart := namespace - if slashIdx := strings.Index(namespace, "/"); slashIdx != -1 { - namespacePart = namespace[:slashIdx] - } - - // Split into parts and reverse them to get normal domain format - parts := strings.Split(namespacePart, ".") - if len(parts) < 2 { - return "" - } - - // Reverse the parts to convert from reverse-DNS to normal domain - slices.Reverse(parts) - - return strings.Join(parts, ".") -} - -// isValidHostForDomain checks if a hostname is the domain or a subdomain of the publisher domain -func isValidHostForDomain(hostname, publisherDomain string) bool { - // Exact match - if hostname == publisherDomain { - return true - } - - // Subdomain match - hostname should end with "." + publisherDomain - if strings.HasSuffix(hostname, "."+publisherDomain) { - return true - } - - return false -} diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index 0c279bc3..e5d39092 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -812,7 +812,7 @@ func TestValidate_RemoteNamespaceMatch(t *testing.T) { expectError: false, }, { - name: "invalid - wrong domain", + name: "valid - different domain", serverDetail: apiv0.ServerJSON{ Schema: model.CurrentSchemaURL, Name: "com.example/test-server", @@ -823,23 +823,22 @@ func TestValidate_RemoteNamespaceMatch(t *testing.T) { }, }, }, - expectError: true, - errorMsg: "remote URL host google.com does not match publisher domain example.com", + expectError: false, }, { - name: "invalid - different domain entirely", + name: "invalid - not HTTPS", serverDetail: apiv0.ServerJSON{ Schema: model.CurrentSchemaURL, Name: "com.microsoft/server", Remotes: []model.Transport{ { Type: "streamable-http", - URL: "https://api.github.com/endpoint", + URL: "http://api.github.com/endpoint", }, }, }, expectError: true, - errorMsg: "remote URL host api.github.com does not match publisher domain microsoft.com", + errorMsg: "invalid remote URL: http://api.github.com/endpoint", }, { name: "invalid URL format", @@ -895,12 +894,12 @@ func TestValidate_RemoteNamespaceMatch(t *testing.T) { }, { Type: "streamable-http", - URL: "https://google.com/websocket", + URL: "http://example.com/sse", }, }, }, expectError: true, - errorMsg: "remote URL host google.com does not match publisher domain example.com", + errorMsg: "invalid remote URL: http://example.com/sse", }, } From 5c1f63af612017820cdc247ae9fc2671f9b6bfa1 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Fri, 14 Nov 2025 12:37:18 -0500 Subject: [PATCH 3/3] Fix sample --- docs/reference/server-json/generic-server-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/server-json/generic-server-json.md b/docs/reference/server-json/generic-server-json.md index 5e64bbdd..c30120e0 100644 --- a/docs/reference/server-json/generic-server-json.md +++ b/docs/reference/server-json/generic-server-json.md @@ -280,7 +280,7 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp "remotes": [ { "type": "streamable-http", - "url": "http://mcp-fs.anonymous.modelcontextprotocol.io/http" + "url": "https://mcp-fs.anonymous.modelcontextprotocol.io/http" } ], "_meta": {