Skip to content

feat: namespace parsing validation #218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 41 commits into
base: remote-verification
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
72a1967
feat: implement 128-bit cryptographically secure token generation for…
aphansal123 Jul 29, 2025
ffbed48
Merge branch 'main' into feature/domain-verification-token-generation
aphansal123 Jul 29, 2025
f375640
Fix package references and eliminate duplicate string literals in tok…
aphansal123 Jul 29, 2025
f7a85fa
Update internal/verification/README.md
aphansal123 Jul 29, 2025
36579a1
Update internal/verification/token_test.go
aphansal123 Jul 29, 2025
382925e
Capitalize MaxDNSRecordLength variable name for consistency
aphansal123 Jul 29, 2025
a9afe32
Add comprehensive DNS TXT record RFC compliance validation tests
aphansal123 Jul 29, 2025
bc4d621
Fix gci linter error by removing trailing whitespace
aphansal123 Jul 29, 2025
60ac929
Implement DNS TXT record verification for domain ownership validation
aphansal123 Jul 29, 2025
1202ae0
Implement DNS record verification for domain ownership validation
aphansal123 Jul 29, 2025
a19b6b5
Fix golangci-lint issues: improve error handling, formatting, and tes…
aphansal123 Jul 30, 2025
d8dba84
Update server-name-verification.md
aphansal123 Jul 30, 2025
2e4dc96
fix: prevent timer leaks in DNS verification retry logic
aphansal123 Jul 30, 2025
2be9c9c
fix: correct spelling from 'cancelled' to 'canceled' in comment
aphansal123 Jul 30, 2025
bf1c847
Update internal/verification/dns.go
aphansal123 Jul 30, 2025
b36f6af
fix: correct retry attempt counter in DNS verification logging
aphansal123 Jul 30, 2025
21ef581
fix: use mock DNS resolver in tests instead of real DNS queries
aphansal123 Jul 30, 2025
1d0316d
fix: replace real DNS queries with mock resolver in timeout test
aphansal123 Jul 30, 2025
f2e1fde
fix: convert DNS tests to mocks and optimize error handling for perfo…
aphansal123 Jul 30, 2025
80b36bf
User time.NewTimer and explicitly stop
aphansal123 Jul 30, 2025
147fc46
Add maximum iteration limit to prevent infinite loops
aphansal123 Jul 30, 2025
36262ff
fix: correct syntax error in DNS mock delay simulation
aphansal123 Jul 30, 2025
0ebca38
test: improve DNS record format test and remove unused import
aphansal123 Jul 30, 2025
efc077a
Remove ValidateTokenFormat and TokenInfo per PR feedback
aphansal123 Jul 30, 2025
ea7857d
Merge branch 'feature/domain-verification-token-generation' into feat…
aphansal123 Jul 30, 2025
d378b27
Update internal/verification/README.md
aphansal123 Jul 31, 2025
adacb2c
make DNS record prefix configurable in DNSVerification
aphansal123 Jul 31, 2025
3322f15
Merge branch 'feature/domain-verification-token-generation' into feat…
aphansal123 Jul 31, 2025
3b1c54a
Merge remote-tracking branch 'origin/remote-verification' into featur…
aphansal123 Jul 31, 2025
455ec12
docs: fix README syntax errors and add RecordPrefix documentation
aphansal123 Jul 31, 2025
7343dfb
Implement domain namespace parsing and validation for server registra…
aphansal123 Aug 1, 2025
ec1c6d2
Merge branch 'main' into feature/namespace-parsing-validation
aphansal123 Aug 1, 2025
758f0f4
Fix golangci-lint issues: improve error wrapping and code formatting
aphansal123 Aug 1, 2025
80b74a6
Add nolint comments for testpackage warnings to resolve all golangci-…
aphansal123 Aug 1, 2025
dd203a1
Fix spelling issue in readme doc
aphansal123 Aug 1, 2025
c506ed0
Fix linter issues with jsonschema imports
aphansal123 Aug 3, 2025
d675d28
Merge branch 'remote-verification' into feature/namespace-parsing-val…
aphansal123 Aug 3, 2025
3e7ae7e
Merge branch 'remote-verification' into feature/namespace-parsing-val…
aphansal123 Aug 5, 2025
87978d6
Merge branch 'remote-verification' into feature/namespace-parsing-val…
aphansal123 Aug 5, 2025
8502e4e
Merge branch 'remote-verification' into feature/namespace-parsing-val…
aphansal123 Aug 8, 2025
a96a31a
fix: update comment to reflect pseudo-domain io.github format
claude[bot] Aug 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions internal/api/handlers/v0/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/modelcontextprotocol/registry/internal/auth"
"github.com/modelcontextprotocol/registry/internal/database"
"github.com/modelcontextprotocol/registry/internal/model"
"github.com/modelcontextprotocol/registry/internal/namespace"
"github.com/modelcontextprotocol/registry/internal/service"
"golang.org/x/net/html"
)
Expand Down Expand Up @@ -54,6 +55,17 @@ func PublishHandler(registry service.RegistryService, authService auth.Service)
return
}

// Validate namespace format if it follows domain-scoped convention
if err := namespace.ValidateNamespace(serverDetail.Name); err != nil {
// If the namespace doesn't follow domain-scoped format, check if it's a legacy format
if !errors.Is(err, namespace.ErrInvalidNamespace) {
http.Error(w, "Invalid namespace: "+err.Error(), http.StatusBadRequest)
return
}
// For pseudo-domain io.github format/borrowed domain, we'll allow them to pass through
// This provides support for the borrowed io.github domain while encouraging new domain-scoped formats
}

// Version is required
if serverDetail.VersionDetail.Version == "" {
http.Error(w, "Version is required", http.StatusBadRequest)
Expand All @@ -73,15 +85,31 @@ func PublishHandler(registry service.RegistryService, authService auth.Service)
token = authHeader[7:]
}

// Determine authentication method based on server name prefix
// Determine authentication method based on server name format
var authMethod model.AuthMethod
switch {
case strings.HasPrefix(serverDetail.Name, "io.github"):
authMethod = model.AuthMethodGitHub
// Additional cases can be added here for other prefixes
default:
// Keep the default auth method as AuthMethodNone
authMethod = model.AuthMethodNone

// Check if the namespace is domain-scoped and extract domain for auth
if parsed, err := namespace.ParseNamespace(serverDetail.Name); err == nil {
// For domain-scoped namespaces, determine auth method based on domain
switch parsed.Domain {
case "github.com":
authMethod = model.AuthMethodGitHub
// Additional domain-specific auth methods can be added here
default:
// For other domains, require GitHub auth for now
// NOTE: Domain verification system needs to be implemented
authMethod = model.AuthMethodGitHub
}
} else {
// Legacy namespace format - use existing logic
switch {
case strings.HasPrefix(serverDetail.Name, "io.github"):
authMethod = model.AuthMethodGitHub
// Additional cases can be added here for other prefixes
default:
// Keep the default auth method as AuthMethodNone
authMethod = model.AuthMethodNone
}
}

serverName := html.EscapeString(serverDetail.Name)
Expand Down
126 changes: 126 additions & 0 deletions internal/api/handlers/v0/publish_namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//nolint:testpackage // Internal package testing allows access to private functions
package v0

import (
"testing"

"github.com/modelcontextprotocol/registry/internal/namespace"
)

// TestNamespaceValidationIntegration tests that the namespace validation
// functionality is working correctly for various namespace formats
func TestNamespaceValidationIntegration(t *testing.T) {
tests := []struct {
name string
serverName string
shouldPass bool
description string
}{
{
name: "valid domain-scoped namespace",
serverName: "com.github/my-server",
shouldPass: true,
description: "Standard domain-scoped namespace should be valid",
},
{
name: "valid subdomain namespace",
serverName: "com.github.api/tool",
shouldPass: true,
description: "Subdomain-scoped namespace should be valid",
},
{
name: "valid apache commons namespace",
serverName: "org.apache.commons/utility",
shouldPass: true,
description: "Apache commons style namespace should be valid",
},
{
name: "valid kubernetes namespace",
serverName: "io.kubernetes/plugin",
shouldPass: true,
description: "Kubernetes.io style namespace should be valid",
},
{
name: "reserved namespace - localhost",
serverName: "com.localhost/server",
shouldPass: false,
description: "Reserved localhost namespace should be rejected",
},
{
name: "reserved namespace - example",
serverName: "com.example/server",
shouldPass: false,
description: "Reserved example namespace should be rejected",
},
{
name: "legacy format - io.github prefix",
serverName: "io.github.username/my-server",
shouldPass: true, // This is valid reverse domain notation
description: "Legacy io.github format should be valid as reverse domain notation",
},
{
name: "invalid format - forward domain notation",
serverName: "github.com/my-server",
shouldPass: false,
description: "Forward domain notation should be rejected",
},
{
name: "invalid format - simple name",
serverName: "my-server",
shouldPass: false,
description: "Simple server name should not match domain-scoped pattern",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := namespace.ValidateNamespace(tt.serverName)

if tt.shouldPass {
if err != nil {
t.Errorf("Expected namespace '%s' to be valid, but got error: %v", tt.serverName, err)
}
} else {
if err == nil {
t.Errorf("Expected namespace '%s' to be invalid, but validation passed", tt.serverName)
}
}
})
}
}

// TestDomainExtractionIntegration tests the domain extraction functionality
func TestDomainExtractionIntegration(t *testing.T) {
// Test valid extractions
validTests := []struct {
namespace string
domain string
}{
{"com.github/my-server", "github.com"},
{"com.github.api/tool", "api.github.com"},
{"org.apache.commons/utility", "commons.apache.org"},
{"io.kubernetes/plugin", "kubernetes.io"},
}

for _, test := range validTests {
t.Run("extract_"+test.domain, func(t *testing.T) {
domain, err := namespace.ParseDomainFromNamespace(test.namespace)
if err != nil {
t.Errorf("Unexpected error: %v", err)
} else if domain != test.domain {
t.Errorf("Expected '%s', got '%s'", test.domain, domain)
}
})
}

// Test invalid extractions
invalidTests := []string{"invalid-format", "github.com/server", "simple"}
for _, test := range invalidTests {
t.Run("invalid_"+test, func(t *testing.T) {
_, err := namespace.ParseDomainFromNamespace(test)
if err == nil {
t.Errorf("Expected error for '%s'", test)
}
})
}
}
Loading