Skip to content

Commit 4ac6528

Browse files
committed
Fix _meta field format for MCP spec compliance
Updates the _meta field structure to comply with MCP specification: - Changes from "io.modelcontextprotocol.registry" to "io.modelcontextprotocol.registry/official" - Changes from "publisher" to "io.modelcontextprotocol.registry/publisher-provided" The new format uses proper prefix/name structure as required by the MCP spec for vendor extensions, resolving naming collision with planned top-level publisher field. Includes database migration to update existing indexes for new field paths. Fixes #356 🏠 Remote-Dev: homespace
1 parent 2e7108d commit 4ac6528

File tree

12 files changed

+455
-444
lines changed

12 files changed

+455
-444
lines changed

data/seed.json

Lines changed: 396 additions & 396 deletions
Large diffs are not rendered by default.

internal/api/handlers/v0/edit_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ func TestEditServerEndpoint(t *testing.T) {
4242
assert.NoError(t, err)
4343
assert.NotNil(t, published)
4444
assert.NotNil(t, published.Meta)
45-
assert.NotNil(t, published.Meta.IOModelContextProtocolRegistry)
45+
assert.NotNil(t, published.Meta.Official)
4646

47-
testServerID := published.Meta.IOModelContextProtocolRegistry.ID
47+
testServerID := published.Meta.Official.ID
4848

4949
// Publish a second server for permission testing
5050
otherServer := apiv0.ServerJSON{
@@ -64,9 +64,9 @@ func TestEditServerEndpoint(t *testing.T) {
6464
assert.NoError(t, err)
6565
assert.NotNil(t, otherPublished)
6666
assert.NotNil(t, otherPublished.Meta)
67-
assert.NotNil(t, otherPublished.Meta.IOModelContextProtocolRegistry)
67+
assert.NotNil(t, otherPublished.Meta.Official)
6868

69-
otherServerID := otherPublished.Meta.IOModelContextProtocolRegistry.ID
69+
otherServerID := otherPublished.Meta.Official.ID
7070

7171
// Publish a deleted server for undelete testing
7272
deletedServer := apiv0.ServerJSON{
@@ -86,9 +86,9 @@ func TestEditServerEndpoint(t *testing.T) {
8686
assert.NoError(t, err)
8787
assert.NotNil(t, deletedPublished)
8888
assert.NotNil(t, deletedPublished.Meta)
89-
assert.NotNil(t, deletedPublished.Meta.IOModelContextProtocolRegistry)
89+
assert.NotNil(t, deletedPublished.Meta.Official)
9090

91-
deletedServerID := deletedPublished.Meta.IOModelContextProtocolRegistry.ID
91+
deletedServerID := deletedPublished.Meta.Official.ID
9292

9393
testCases := []struct {
9494
name string

internal/api/handlers/v0/servers_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ func TestServersListEndpoint(t *testing.T) {
175175
assert.NotEmpty(t, server.Name)
176176
assert.NotEmpty(t, server.Description)
177177
assert.NotNil(t, server.Meta)
178-
assert.NotNil(t, server.Meta.IOModelContextProtocolRegistry)
179-
assert.NotEmpty(t, server.Meta.IOModelContextProtocolRegistry.ID)
178+
assert.NotNil(t, server.Meta.Official)
179+
assert.NotEmpty(t, server.Meta.Official.ID)
180180
}
181181
}
182182

@@ -223,7 +223,7 @@ func TestServersDetailEndpoint(t *testing.T) {
223223
}{
224224
{
225225
name: "successful get server detail",
226-
serverID: testServer.Meta.IOModelContextProtocolRegistry.ID,
226+
serverID: testServer.Meta.Official.ID,
227227
expectedStatus: http.StatusOK,
228228
},
229229
{
@@ -305,7 +305,7 @@ func TestServersEndpointsIntegration(t *testing.T) {
305305
assert.NoError(t, err)
306306
assert.NotNil(t, published)
307307

308-
serverID := published.Meta.IOModelContextProtocolRegistry.ID
308+
serverID := published.Meta.Official.ID
309309
servers := []apiv0.ServerJSON{*published}
310310
serverDetail := published
311311

internal/api/handlers/v0/telemetry_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestPrometheusHandler(t *testing.T) {
5353
mux.Handle("/metrics", metrics.PrometheusHandler())
5454

5555
// Create request
56-
url := "/v0/servers/" + server.Meta.IOModelContextProtocolRegistry.ID
56+
url := "/v0/servers/" + server.Meta.Official.ID
5757
req := httptest.NewRequest(http.MethodGet, url, nil)
5858
w := httptest.NewRecorder()
5959

internal/database/memory.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ func (db *MemoryDB) CreateServer(ctx context.Context, server *apiv0.ServerJSON)
103103
}
104104

105105
// Get the ID from the registry metadata
106-
if server.Meta == nil || server.Meta.IOModelContextProtocolRegistry == nil {
106+
if server.Meta == nil || server.Meta.Official == nil {
107107
return nil, fmt.Errorf("server must have registry metadata with ID")
108108
}
109109

110-
id := server.Meta.IOModelContextProtocolRegistry.ID
110+
id := server.Meta.Official.ID
111111

112112
db.mu.Lock()
113113
defer db.mu.Unlock()
@@ -193,8 +193,8 @@ func (db *MemoryDB) matchesFilter(entry *apiv0.ServerJSON, filter *ServerFilter)
193193

194194
// getRegistryID safely extracts the registry ID from an entry
195195
func (db *MemoryDB) getRegistryID(entry *apiv0.ServerJSON) string {
196-
if entry.Meta != nil && entry.Meta.IOModelContextProtocolRegistry != nil {
197-
return entry.Meta.IOModelContextProtocolRegistry.ID
196+
if entry.Meta != nil && entry.Meta.Official != nil {
197+
return entry.Meta.Official.ID
198198
}
199199
return ""
200200
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Update database indexes to use new MCP spec compliant _meta field paths
2+
-- Data has been updated manually, only need to update indexes
3+
4+
-- Drop old indexes that reference the old paths
5+
DROP INDEX IF EXISTS idx_servers_name_latest;
6+
DROP INDEX IF EXISTS idx_servers_updated_at;
7+
8+
-- Create new indexes with updated paths
9+
CREATE INDEX idx_servers_name_latest ON servers ((value->>'name'), (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'is_latest'))
10+
WHERE (value->'_meta'->'io.modelcontextprotocol.registry/official'->>'is_latest')::boolean = true;
11+
CREATE INDEX idx_servers_updated_at ON servers ((value->'_meta'->'io.modelcontextprotocol.registry/official'->>'updated_at'));

internal/database/postgres.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ func (db *PostgreSQL) List(
131131
nextCursor := ""
132132
if len(results) > 0 && len(results) >= limit {
133133
lastResult := results[len(results)-1]
134-
if lastResult.Meta != nil && lastResult.Meta.IOModelContextProtocolRegistry != nil {
135-
nextCursor = lastResult.Meta.IOModelContextProtocolRegistry.ID
134+
if lastResult.Meta != nil && lastResult.Meta.Official != nil {
135+
nextCursor = lastResult.Meta.Official.ID
136136
}
137137
}
138138

@@ -177,11 +177,11 @@ func (db *PostgreSQL) CreateServer(ctx context.Context, server *apiv0.ServerJSON
177177
}
178178

179179
// Get the ID from the registry metadata
180-
if server.Meta == nil || server.Meta.IOModelContextProtocolRegistry == nil {
180+
if server.Meta == nil || server.Meta.Official == nil {
181181
return nil, fmt.Errorf("server must have registry metadata with ID")
182182
}
183183

184-
id := server.Meta.IOModelContextProtocolRegistry.ID
184+
id := server.Meta.Official.ID
185185

186186
// Marshal the complete server to JSONB
187187
valueJSON, err := json.Marshal(server)

internal/importer/importer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestImportService_LocalFile(t *testing.T) {
3333
Version: "1.0.0",
3434
},
3535
Meta: &apiv0.ServerMeta{
36-
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
36+
Official: &apiv0.RegistryExtensions{
3737
ID: "test-id-1",
3838
PublishedAt: time.Now(),
3939
UpdatedAt: time.Now(),
@@ -79,7 +79,7 @@ func TestImportService_HTTPFile(t *testing.T) {
7979
Version: "2.0.0",
8080
},
8181
Meta: &apiv0.ServerMeta{
82-
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
82+
Official: &apiv0.RegistryExtensions{
8383
ID: "test-id-2",
8484
PublishedAt: time.Now(),
8585
UpdatedAt: time.Now(),
@@ -124,7 +124,7 @@ func TestImportService_RegistryAPI(t *testing.T) {
124124
Version: "1.0.0",
125125
},
126126
Meta: &apiv0.ServerMeta{
127-
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
127+
Official: &apiv0.RegistryExtensions{
128128
ID: "api-test-id-1",
129129
PublishedAt: time.Now(),
130130
UpdatedAt: time.Now(),
@@ -144,7 +144,7 @@ func TestImportService_RegistryAPI(t *testing.T) {
144144
Version: "2.0.0",
145145
},
146146
Meta: &apiv0.ServerMeta{
147-
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
147+
Official: &apiv0.RegistryExtensions{
148148
ID: "api-test-id-2",
149149
PublishedAt: time.Now(),
150150
UpdatedAt: time.Now(),

internal/service/registry_service.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ func (s *registryServiceImpl) Publish(req apiv0.ServerJSON) (*apiv0.ServerJSON,
113113
isNewLatest := true
114114
if existingLatest != nil {
115115
var existingPublishedAt time.Time
116-
if existingLatest.Meta != nil && existingLatest.Meta.IOModelContextProtocolRegistry != nil {
117-
existingPublishedAt = existingLatest.Meta.IOModelContextProtocolRegistry.PublishedAt
116+
if existingLatest.Meta != nil && existingLatest.Meta.Official != nil {
117+
existingPublishedAt = existingLatest.Meta.Official.PublishedAt
118118
}
119119
isNewLatest = CompareVersions(
120120
serverJSON.VersionDetail.Version,
@@ -133,7 +133,7 @@ func (s *registryServiceImpl) Publish(req apiv0.ServerJSON) (*apiv0.ServerJSON,
133133
}
134134

135135
// Set registry metadata
136-
server.Meta.IOModelContextProtocolRegistry = &apiv0.RegistryExtensions{
136+
server.Meta.Official = &apiv0.RegistryExtensions{
137137
ID: uuid.New().String(),
138138
PublishedAt: publishTime,
139139
UpdatedAt: publishTime,
@@ -150,13 +150,13 @@ func (s *registryServiceImpl) Publish(req apiv0.ServerJSON) (*apiv0.ServerJSON,
150150
// Mark previous latest as no longer latest
151151
if isNewLatest && existingLatest != nil {
152152
var existingLatestID string
153-
if existingLatest.Meta != nil && existingLatest.Meta.IOModelContextProtocolRegistry != nil {
154-
existingLatestID = existingLatest.Meta.IOModelContextProtocolRegistry.ID
153+
if existingLatest.Meta != nil && existingLatest.Meta.Official != nil {
154+
existingLatestID = existingLatest.Meta.Official.ID
155155
}
156156
if existingLatestID != "" {
157157
// Update the existing server to set is_latest = false
158-
existingLatest.Meta.IOModelContextProtocolRegistry.IsLatest = false
159-
existingLatest.Meta.IOModelContextProtocolRegistry.UpdatedAt = time.Now()
158+
existingLatest.Meta.Official.IsLatest = false
159+
existingLatest.Meta.Official.UpdatedAt = time.Now()
160160
if _, err := s.db.UpdateServer(ctx, existingLatestID, existingLatest); err != nil {
161161
return nil, err
162162
}
@@ -193,8 +193,8 @@ func (s *registryServiceImpl) validateNoDuplicateRemoteURLs(ctx context.Context,
193193
// getCurrentLatestVersion finds the current latest version from existing server versions
194194
func (s *registryServiceImpl) getCurrentLatestVersion(existingServerVersions []*apiv0.ServerJSON) *apiv0.ServerJSON {
195195
for _, server := range existingServerVersions {
196-
if server.Meta != nil && server.Meta.IOModelContextProtocolRegistry != nil &&
197-
server.Meta.IOModelContextProtocolRegistry.IsLatest {
196+
if server.Meta != nil && server.Meta.Official != nil &&
197+
server.Meta.Official.IsLatest {
198198
return server
199199
}
200200
}

internal/validators/validators.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,21 +251,21 @@ func ValidatePublishRequest(req apiv0.ServerJSON, cfg *config.Config) error {
251251
func validatePublisherExtensions(req apiv0.ServerJSON) error {
252252
const maxExtensionSize = 4 * 1024 // 4KB limit
253253

254-
// Check size limit for _meta.publisher extension
255-
if req.Meta != nil && req.Meta.Publisher != nil {
256-
extensionsJSON, err := json.Marshal(req.Meta.Publisher)
254+
// Check size limit for _meta publisher-provided extension
255+
if req.Meta != nil && req.Meta.PublisherProvided != nil {
256+
extensionsJSON, err := json.Marshal(req.Meta.PublisherProvided)
257257
if err != nil {
258-
return fmt.Errorf("failed to marshal _meta.publisher extension: %w", err)
258+
return fmt.Errorf("failed to marshal _meta.io.modelcontextprotocol.registry/publisher-provided extension: %w", err)
259259
}
260260
if len(extensionsJSON) > maxExtensionSize {
261-
return fmt.Errorf("_meta.publisher extension exceeds 4KB limit (%d bytes)", len(extensionsJSON))
261+
return fmt.Errorf("_meta.io.modelcontextprotocol.registry/publisher-provided extension exceeds 4KB limit (%d bytes)", len(extensionsJSON))
262262
}
263263
}
264264

265265
if req.Meta != nil {
266-
// Validate that only "publisher" is allowed in _meta during publish (no registry metadata should be present)
267-
if req.Meta.IOModelContextProtocolRegistry != nil {
268-
return fmt.Errorf("registry metadata '_meta.io.modelcontextprotocol.registry' is not allowed during publish")
266+
// Validate that only publisher-provided data is allowed in _meta during publish (no official registry metadata should be present)
267+
if req.Meta.Official != nil {
268+
return fmt.Errorf("official registry metadata '_meta.io.modelcontextprotocol.registry/official' is not allowed during publish")
269269
}
270270
}
271271

0 commit comments

Comments
 (0)