Wielded Claude Code to do this; across many iterations and have reviewed all the code + largely rewritten the PR commentary here. This PR implements the server ID consistency changes from [#396](https://github.com/modelcontextprotocol/registry/issues/396). Our goal is to: - Rename `id` to `versionId` to make it clear that it is the ID for a specific version, not a server - Introduce `serverId` as a net new concept. This will be 1:1 with `name`. While not strictly necessary (we could just use name as a PK), the convention of having a UUID as an ID on our objects is just a helpful subjective architecture decision. Plus also leaves open the door to reconsidering the "should we allow renames" question. ## Key Changes ### 🗄️ Database Schema Changes - **Renamed**: `id` column → `version_id` (unique identifier for each server version) ### 📋 Metadata Structure Updates - **JSON Schema**: Updated `_meta.io.modelcontextprotocol.registry/official` to include: - `serverId`: Consistent ID across all versions of a server - `versionId`: Unique ID for this specific version (renamed from `id`) ### 🔧 API Endpoint Updates - **New endpoint structure**: Changed from version-ID-based to server-ID-based URLs: - `GET /v0/servers/{server_id}` - Get latest version of server - `GET /v0/servers/{server_id}?version=X.X.X` - Get specific version - `GET /v0/servers/{server_id}/versions` - List all versions of a server - `PUT /v0/servers/{server_id}?version=X.X.X` - Edit specific version - **Consistent URL patterns**: All server endpoints now use `server_id` as the primary identifier ### Database Migration Strategy The migration (`005_add_server_id_rename_version_id.sql`): 1. Adds new `version_id` column 2. Generates deterministic UUIDs for servers with the same name 3. Updates JSON metadata to add `serverId` and rename `id` to `versionId` 4. Copies existing `id` values to `version_id` column 5. Drops old `id` column and makes `version_id` the new primary key ## Testing Besides of course automated tests passing, I: 1. Set up my local db with the pre-005-migration state, imported all the production data 2. Ran the migration 3. Spot checked some diffs of the data that resulted -- ```json // before { "name": "io.github.ruvnet/ruv-swarm", "_meta": { "io.modelcontextprotocol.registry/official": { "id": "0a9cda7f-2b73-4839-844b-6582f335de42", "is_latest": false, "updated_at": "2025-09-10T17:21:34.92137012Z", "published_at": "2025-09-10T17:15:32.69690433Z" } }, "version": "1.0.18", "repository": { "url": "", "source": "" }, "description": "Neural network swarm orchestration with WebAssembly acceleration and MCP integration" } // after { "name": "io.github.ruvnet/ruv-swarm", "_meta": { "io.modelcontextprotocol.registry/official": { "serverId": "e8538952-96af-4a33-9e31-7fbed012521e", "is_latest": false, "versionId": "0a9cda7f-2b73-4839-844b-6582f335de42", "updated_at": "2025-09-10T17:21:34.92137012Z", "published_at": "2025-09-10T17:15:32.69690433Z" } }, "version": "1.0.18", "repository": { "url": "", "source": "" }, "description": "Neural network swarm orchestration with WebAssembly acceleration and MCP integration" } ``` Servers that have multiple versions stayed consistent in their `serverId`: ```json // First version { "name": "app.getdialer/dialer", "_meta": { "io.modelcontextprotocol.registry/official": { "serverId": "80f08bdb-f3e4-4f19-b072-1676ae49b663", "is_latest": false, "versionId": "04f25b17-331d-48d7-b08b-37baece208a7", "updated_at": "2025-09-09T00:16:49.162620072Z", "published_at": "2025-09-08T23:47:09.452137971Z" } }, "status": "active", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", "remotes": [ { "url": "https://getdialer.app/mcp", "type": "streamable-http" }, { "url": "https://getdialer.app/sse", "type": "sse" } ], "version": "1.0.0", "repository": { "url": "", "source": "" }, "description": "An MCP server that provides your you make outbound phone calls using your own phone number" } // Second version { "name": "app.getdialer/dialer", "_meta": { "io.modelcontextprotocol.registry/official": { "serverId": "80f08bdb-f3e4-4f19-b072-1676ae49b663", "is_latest": true, "versionId": "faa26cb9-3aad-415e-aece-f715fff5fe5e", "updated_at": "2025-09-09T00:16:49.107842808Z", "published_at": "2025-09-09T00:16:49.107842808Z" } }, "status": "active", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", "remotes": [ { "url": "https://getdialer.app/mcp", "type": "streamable-http" }, { "url": "https://getdialer.app/sse", "type": "sse" } ], "version": "1.0.1", "repository": { "url": "", "source": "" }, "description": "An MCP server that provides lets you make outbound phone calls using your own phone number" } ``` And I also tried publishing a test server anew; looked good: ```bash ⏺ Bash(curl -s "http://localhost:8080/v0/servers/102dc4e4-6ca8-40e6-aa11-cd45763d3352" | jq '.') ⎿ { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", "name": "io.modelcontextprotocol.anonymous/test-claude-migration-server", "description": "A test server to verify the new migration and API endpoints work correctly", "status": "active", "repository": { "url": "https://github.com/example/test-claude-migration-server", "source": "github", "id": "example/test-claude-migration-server" }, "version": "1.0.0", "_meta": { "io.modelcontextprotocol.registry/official": { "serverId": "102dc4e4-6ca8-40e6-aa11-cd45763d3352", "versionId": "48c9cd55-7bc4-4ac2-896e-59e81eb653e4", "published_at": "2025-09-12T18:33:23.264203-07:00", "updated_at": "2025-09-12T18:33:23.264203-07:00", "is_latest": true } } } ⏺ Bash(curl -s "http://localhost:8080/v0/servers/102dc4e4-6ca8-40e6-aa11-cd45763d3352/versions" | jq '.') ⎿ { "servers": [ { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", "name": "io.modelcontextprotocol.anonymous/test-claude-migration-server", "description": "A test server to verify the new migration and API endpoints work correctly", "status": "active", "repository": { "url": "https://github.com/example/test-claude-migration-server", "source": "github", "id": "example/test-claude-migration-server" }, "version": "1.0.0", "_meta": { "io.modelcontextprotocol.registry/official": { "serverId": "102dc4e4-6ca8-40e6-aa11-cd45763d3352", "versionId": "48c9cd55-7bc4-4ac2-896e-59e81eb653e4", "published_at": "2025-09-12T18:33:23.264203-07:00", "updated_at": "2025-09-12T18:33:23.264203-07:00", "is_latest": true } } } ], "metadata": { "count": 1 } } ``` Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Preeti Dewani <preetidewani1990@gmail.com> Co-authored-by: adam jones <adamj+git@anthropic.com> Co-authored-by: Adam Jones <adamj@anthropic.com>
5 KiB
Adding a new package registry
The MCP Registry project is a metaregistry, meaning that it hosts metadata for MCP servers but does not host the code for the servers directly.
For local MCP servers, the MCP Registry has pointers in the packages node of the server.json schema that refer to packages in supported package managers.
The list of supported package managers for hosting MCP servers is defined by the properties.packages[N].properties.registryType string enum in the server.json schema. For example, this could be "npm" (for npmjs.com packages) or "pypi" (for PyPI packages).
For remote MCP servers, the package registry is not relevant. The MCP client consumes the server via a URL instead of by downloading and running a package. In other words, this document only applies to local MCP servers.
For the sake of illustration, this document will use npm (the Node.js package manager) as an example at each step.
Prerequisites
The package registry must meet the following requirements:
- The package registry supports packaging and executing CLI apps. Local MCP servers use the stdio transport.
- npm CLI tools typically express their CLI commands in the
binproperty of the package.json
- npm CLI tools typically express their CLI commands in the
- The package registry (or associated client tooling) has a widely accepted single-shot CLI command.
- npm's
npxtool executes CLI commands using a documented execution heuristic - For example, the MCP client can map the
server.jsonmetadata to annpxCLI execution, with args and environment variables populated via user input.
- npm's
- The package registry supports anonymous package downloads. This allows the MCP client software to use the metadata found in the MCP registry to discover, download, and execute package-based local MCP servers with minimal user intervention.
npxby default connects to the public npmjs.com registry, allowing simple consumption of public npm packages.
- The package registry should support a validation mechanism to verify ownership of the server name. This prevents misattribution and ensures that only the actual package owner can reference their packages in server registrations. For example:
- npm requires an
mcpNamefield inpackage.jsonthat matches the server name being registered - PyPI requires a
mcp-name:line in the package README/description - Each registry type must implement a validation mechanism accessible via public API
- npm requires an
Steps
These steps may evolve as additional validations or details are discovered and mandated.
- Create a feature request issue on the MCP Registry repository to begin the discussion about adding the package registry.
- Example for NuGet: https://github.com/modelcontextprotocol/registry/issues/126
- Open a PR with the following changes:
- Update the
server.jsonschema- Add your package registry name to the
registryTypeexample array. - Add your package registry base url to the
registryBaseUrlexample array. - Add the single-shot CLI command name to the
runtimeHintexample value array.
- Add your package registry name to the
- Update the
openapi.yaml- Add your package registry name to the
registryTypeenum value array. - Add your package registry base url to the
registryBaseUrlenum value array. - Add the single-shot CLI command name to the
runtimeHintexample value array.
- Add your package registry name to the
- Add a sample, minimal
server.jsonto theserver.jsonexamples. - Implement a registry validator:
- Create a new validator file:
internal/validators/registries/yourregistry.go, following the pattern of existing validators. Examples:- npm: Checks for an
mcpNamefield inpackage.jsonthat matches the server name - PyPI: Searches for
mcp-name: server-nameformat in the package README content - NuGet: Looks for
mcp-name: server-nameformat in the package README file - Docker/OCI: Validates a Docker image label
io.modelcontextprotocol.server.namein the image manifest
- npm: Checks for an
- Add corresponding unit tests:
internal/validators/registries/yourregistry_test.go - Register your validator in
internal/validators/validators.go
- Create a new validator file:
- Update the publishing documentation:
- Add a new publishing guide:
docs/guides/publishing/publish-[yourregistry].md, following the pattern of existing publishing guides (e.g.,publish-npm.md,publish-pypi.md) - Include instructions on how to prepare packages for your registry, including any specific validation requirements
- Update
docs/guides/publishing/README.mdto reference your new publishing guide
- Add a new publishing guide:
- Update the