feat: implement DNS namespace validation and remote URL uniqueness checks (#306)

## Summary

- Add reverse-DNS namespace format validation for server names
(namespace/name)
- Validate remote URLs match the DNS namespace derived from hostname  
- Prevent duplicate remote URLs across different servers
- Support localhost/development URLs without namespace restrictions
- Add comprehensive test coverage for validation logic
- Update database layer to support remote_url filtering

Fixes #180 by ensuring proper namespace governance and preventing URL
conflicts.

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
adam jones 2025-08-26 23:03:38 +01:00 • committed by GitHub
commit 923b2e07b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 497 additions and 29 deletions

View file

@ -13,7 +13,7 @@ var (
ErrAlreadyExists = errors.New("record already exists")
ErrInvalidInput = errors.New("invalid input")
ErrDatabase = errors.New("database error")
ErrInvalidVersion = errors.New("invalid version: cannot publish older version after newer version")
ErrInvalidVersion = errors.New("invalid version: cannot publish duplicate version")
ErrMaxServersReached = errors.New("maximum number of versions for this server reached (10000): please reach out at https://github.com/modelcontextprotocol/registry to explain your use case")
)

View file

@ -42,7 +42,7 @@ func NewMemoryDB(e map[string]*model.ServerDetail) *MemoryDB {
}
// List retrieves ServerRecord entries with optional filtering and pagination
//nolint:cyclop // Complexity from filtering logic is acceptable for memory implementation
func (db *MemoryDB) List(
ctx context.Context,
filter map[string]any,
@ -89,6 +89,18 @@ func (db *MemoryDB) List(
if string(entry.ServerJSON.Status) != value.(string) {
include = false
}
case "remote_url":
found := false
remoteURL := value.(string)
for _, remote := range entry.ServerJSON.Remotes {
if remote.URL == remoteURL {
found = true
break
}
}
if !found {
include = false
}
}
}

View file

@ -76,6 +76,10 @@ func (db *PostgreSQL) List(
whereClause += fmt.Sprintf(" AND s.status = $%d", argIndex)
args = append(args, v)
argIndex++
case "remote_url":
whereClause += fmt.Sprintf(" AND EXISTS (SELECT 1 FROM jsonb_array_elements(CASE WHEN jsonb_typeof(s.remotes) = 'array' THEN s.remotes ELSE '[]'::jsonb END) AS remote WHERE remote->>'url' = $%d)", argIndex)
args = append(args, v)
argIndex++
}
}