Fix _meta field format for MCP spec compliance (#367)

## Summary

Updates the `_meta` field structure to be compliant with the MCP
specification by using proper `prefix/name` format for vendor
extensions.

### Changes

- **Registry metadata**: `io.modelcontextprotocol.registry` →
`io.modelcontextprotocol.registry/official`
- **Publisher metadata**: `publisher` →
`io.modelcontextprotocol.registry/publisher-provided`
- **Database migration**: Updates indexes to use new field paths
- **Updated all code**: Service layer, validators, tests, and seed data

### Format Before
```json
{
  "_meta": {
    "publisher": { "license": "MIT" },
    "io.modelcontextprotocol.registry": { "id": "...", "is_latest": true }
  }
}
```

### Format After
```json
{
  "_meta": {
    "io.modelcontextprotocol.registry/publisher-provided": { "license": "MIT" },
    "io.modelcontextprotocol.registry/official": { "id": "...", "is_latest": true }
  }
}
```

This resolves the naming collision with the planned top-level
`publisher` field and ensures compliance with MCP specification
requirements.

Fixes #356
This commit is contained in:
adam jones 2025-09-08 11:40:04 -07:00 • committed by GitHub
commit ef4607720d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 490 additions and 466 deletions

View file

@ -103,11 +103,11 @@ func (db *MemoryDB) CreateServer(ctx context.Context, server *apiv0.ServerJSON)
}
// Get the ID from the registry metadata
if server.Meta == nil || server.Meta.IOModelContextProtocolRegistry == nil {
if server.Meta == nil || server.Meta.Official == nil {
return nil, fmt.Errorf("server must have registry metadata with ID")
}
id := server.Meta.IOModelContextProtocolRegistry.ID
id := server.Meta.Official.ID
db.mu.Lock()
defer db.mu.Unlock()
@ -193,8 +193,8 @@ func (db *MemoryDB) matchesFilter(entry *apiv0.ServerJSON, filter *ServerFilter)
// getRegistryID safely extracts the registry ID from an entry
func (db *MemoryDB) getRegistryID(entry *apiv0.ServerJSON) string {
if entry.Meta != nil && entry.Meta.IOModelContextProtocolRegistry != nil {
return entry.Meta.IOModelContextProtocolRegistry.ID
if entry.Meta != nil && entry.Meta.Official != nil {
return entry.Meta.Official.ID
}
return ""
}

View file

@ -0,0 +1,11 @@
-- Update database indexes to use new MCP spec compliant _meta field paths
-- Data has been updated manually, only need to update indexes
-- Drop old indexes that reference the old paths
DROP INDEX IF EXISTS idx_servers_name_latest;
DROP INDEX IF EXISTS idx_servers_updated_at;
-- Create new indexes with updated paths
CREATE INDEX idx_servers_name_latest ON servers ((value->>'name'), (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'is_latest'))
WHERE (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'is_latest')::boolean = true;
CREATE INDEX idx_servers_updated_at ON servers ((value->'_meta'->'io.modelcontextprotocol.registry/official'->>'updated_at'));

View file

@ -78,7 +78,7 @@ func (db *PostgreSQL) List(
if _, err := uuid.Parse(cursor); err != nil {
return nil, "", fmt.Errorf("invalid cursor format: %w", err)
}
whereConditions = append(whereConditions, fmt.Sprintf("(value->'_meta'->'io.modelcontextprotocol.registry'->>'id') > $%d", argIndex))
whereConditions = append(whereConditions, fmt.Sprintf("(value->'_meta'->'io.modelcontextprotocol.registry/official'->>'id') > $%d", argIndex))
args = append(args, cursor)
argIndex++
}
@ -94,7 +94,7 @@ func (db *PostgreSQL) List(
SELECT value
FROM servers
%s
ORDER BY (value->'_meta'->'io.modelcontextprotocol.registry'->>'id')
ORDER BY (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'id')
LIMIT $%d
`, whereClause, argIndex)
args = append(args, limit)
@ -131,8 +131,8 @@ func (db *PostgreSQL) List(
nextCursor := ""
if len(results) > 0 && len(results) >= limit {
lastResult := results[len(results)-1]
if lastResult.Meta != nil && lastResult.Meta.IOModelContextProtocolRegistry != nil {
nextCursor = lastResult.Meta.IOModelContextProtocolRegistry.ID
if lastResult.Meta != nil && lastResult.Meta.Official != nil {
nextCursor = lastResult.Meta.Official.ID
}
}
@ -147,7 +147,7 @@ func (db *PostgreSQL) GetByID(ctx context.Context, id string) (*apiv0.ServerJSON
query := `
SELECT value
FROM servers
WHERE (value->'_meta'->'io.modelcontextprotocol.registry'->>'id') = $1
WHERE (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'id') = $1
`
var valueJSON []byte
@ -177,11 +177,11 @@ func (db *PostgreSQL) CreateServer(ctx context.Context, server *apiv0.ServerJSON
}
// Get the ID from the registry metadata
if server.Meta == nil || server.Meta.IOModelContextProtocolRegistry == nil {
if server.Meta == nil || server.Meta.Official == nil {
return nil, fmt.Errorf("server must have registry metadata with ID")
}
id := server.Meta.IOModelContextProtocolRegistry.ID
id := server.Meta.Official.ID
// Marshal the complete server to JSONB
valueJSON, err := json.Marshal(server)
@ -219,7 +219,7 @@ func (db *PostgreSQL) UpdateServer(ctx context.Context, id string, server *apiv0
query := `
UPDATE servers
SET value = $1
WHERE (value->'_meta'->'io.modelcontextprotocol.registry'->>'id') = $2
WHERE (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'id') = $2
`
result, err := db.conn.Exec(ctx, query, valueJSON, id)