Introduce serverId, rename id to versionId (#465)

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>
This commit is contained in:
Tadas Antanavicius 2025-09-21 08:00:09 -04:00 • committed by GitHub
commit 56e8b4879b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 5185 additions and 3466 deletions

View file

@ -16,31 +16,111 @@ This is a brief guide for admins and moderators managing content on the registry
./tools/admin/auth.sh
```
## Edit a Server
## Edit a Specific Server Version
Step 1: Download Server
Use this when you need to modify details of a specific version (e.g., fix description, update status, modify packages).
### Step 1: Download Specific Version
```bash
export SERVER_ID="<server-uuid>"
export SERVER_ID="<server-uuid>" # This is the server ID (consistent across versions)
export VERSION="<version-string>" # e.g., "1.0.0" (optional, defaults to latest)
# Get specific version
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}?version=${VERSION}" > server.json
# Or get latest version (omit version parameter)
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}" > server.json
```
Step 2: Open `server.json` and make changes. You cannot change the server name.
### Step 2: Make Changes
Step 3: Push Changes
Open `server.json` and edit the specific version details. You cannot change the server name or version number.
### Step 3: Update Version
```bash
curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}" \
# Update specific version
curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}?version=${VERSION}" \
-H "Authorization: Bearer ${REGISTRY_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"server\": $(cat server.json)}"
-d "$(cat server.json)"
```
## Takedown a Server
## Edit an Entire Server (All Versions)
Use this when you need to apply changes across all versions of a server (e.g., mark entire server as deleted, apply content scrubbing).
### Step 1: List All Versions
```bash
export SERVER_ID="<server-uuid>" # This is the server ID (consistent across versions)
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}/versions" > all_versions.json
```
### Step 2: Extract Version IDs
```bash
# Extract all version IDs from the server
jq -r '.servers[]._meta."io.modelcontextprotocol.registry/official".version_id' all_versions.json > version_ids.txt
```
### Step 3: Apply Changes to All Versions
```bash
# For each version, download, modify, and update
while read VERSION_ID; do
echo "Processing version: $VERSION_ID"
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${VERSION_ID}" > temp_version.json
# Apply your changes here (e.g., set status to "deleted")
jq '.status = "deleted"' temp_version.json > modified_version.json
curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${VERSION_ID}" \
-H "Authorization: Bearer ${REGISTRY_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"server\": $(cat modified_version.json)}"
done < version_ids.txt
# Clean up temporary files
rm temp_version.json modified_version.json version_ids.txt all_versions.json
```
## Quick Operations
### Get Latest Version of a Server
```bash
export SERVER_ID="<server-uuid>"
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}" > latest_version.json
export VERSION_ID=$(jq -r '._meta."io.modelcontextprotocol.registry/official".version_id' latest_version.json)
echo "Latest version ID: $VERSION_ID"
```
### Takedown a Specific Version
```bash
export VERSION_ID="<version-uuid>"
./tools/admin/takedown.sh
```
This soft deletes the server. If you need to delete the content of a server (usually only where legally necessary), use the edit workflow above to scrub it all.
### Takedown an Entire Server (All Versions)
```bash
export SERVER_ID="<server-uuid>"
# Get all version IDs and takedown each one
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${SERVER_ID}/versions" | \
jq -r '.servers[]._meta."io.modelcontextprotocol.registry/official".version_id' | \
while read VERSION_ID; do
echo "Taking down version: $VERSION_ID"
./tools/admin/takedown.sh "$VERSION_ID"
done
```
## Notes
- **Version-specific changes**: Only affect that particular version
- **Server-wide changes**: Must be applied to each version individually
- **Content scrubbing**: Use the version-specific edit workflow to scrub sensitive content
- **Server name**: Cannot be changed in any version (it's the immutable identifier)

View file

@ -9,7 +9,9 @@ Integration patterns and best practices for building applications that consume M
**Authentication**: Not required for read-only access
- **`GET /v0/servers`** - List all servers with pagination
- **`GET /v0/servers/{id}`** - Get server details by UUID
- **`GET /v0/servers/{server_id}`** - Get latest version of server by server ID
- **`GET /v0/servers/{server_id}?version=X.X.X`** - Get specific version of server
- **`GET /v0/servers/{server_id}/versions`** - List all versions of a server
See the [interactive API documentation](https://registry.modelcontextprotocol.io/docs) for complete request/response schemas.
@ -18,7 +20,7 @@ See the [interactive API documentation](https://registry.modelcontextprotocol.io
## Building a subregistry
**Create enhanced registries** - ETL official registry data and add your own metadata like ratings, security scans, or compatibility info.
For now we recommend scraping the `GET /v0/servers` endpoint on some regular basis. In the future we might provide a filter for updated_at ([#291](https://github.com/modelcontextprotocol/registry/issues/291)) to get only recently changed servers.
For now we recommend scraping the `GET /v0/servers` endpoint on some regular basis. In the future we might provide a filter for updatedAt ([#291](https://github.com/modelcontextprotocol/registry/issues/291)) to get only recently changed servers.
Servers are generally immutable, except for the `status` field which can be updated to `deleted` (among other states). For these packages, we recommend you also update the status field to `deleted` or remove the package from your registry quickly. This is because this status generally indicates it has violated our permissive [moderation guidelines](../administration/moderation-guidelines.md), suggesting it is illegal, malware or spam.
@ -30,14 +32,14 @@ You can also add custom metadata to servers using the `_meta` field. For example
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"name": "io.github.yourname/weather-server",
"description": "MCP server for weather data access",
"status": "active",
"version": "1.0.0",
"packages": [
{
"registry_type": "npm",
"registryType": "npm",
"identifier": "weather-mcp-server",
"version": "1.0.0"
}

View file

@ -4,7 +4,7 @@ The MCP Registry project is a **metaregistry**, meaning that it hosts metadata f
For local MCP servers, the MCP Registry has pointers in the `packages` node of the [`server.json`](../../reference/server-json/generic-server-json.md) 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.registry_type` string enum in the [`server.json` schema](../../reference/server-json/server.schema.json). For example, this could be "npm" (for npmjs.com packages) or "pypi" (for PyPI packages).
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](../../reference/server-json/server.schema.json). 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.
@ -34,13 +34,13 @@ These steps may evolve as additional validations or details are discovered and m
- Example for NuGet: https://github.com/modelcontextprotocol/registry/issues/126
1. Open a PR with the following changes:
- Update the [`server.json` schema](../../reference/server-json/server.schema.json)
- Add your package registry name to the `registry_type` example array.
- Add your package registry base url to the `registry_base_url` example array.
- Add the single-shot CLI command name to the `runtime_hint` example value array.
- Add your package registry name to the `registryType` example array.
- Add your package registry base url to the `registryBaseUrl` example array.
- Add the single-shot CLI command name to the `runtimeHint` example value array.
- Update the [`openapi.yaml`](../../reference/api/openapi.yaml)
- Add your package registry name to the `registry_type` enum value array.
- Add your package registry base url to the `registry_base_url` enum value array.
- Add the single-shot CLI command name to the `runtime_hint` example value array.
- Add your package registry name to the `registryType` enum value array.
- Add your package registry base url to the `registryBaseUrl` enum value array.
- Add the single-shot CLI command name to the `runtimeHint` example value array.
- Add a sample, minimal `server.json` to the [`server.json` examples](../../reference/server-json/generic-server-json.md).
- Implement a registry validator:
- Create a new validator file: `internal/validators/registries/yourregistry.go`, following the pattern of existing validators. Examples:

View file

@ -92,13 +92,13 @@ This creates a `server.json` with auto-detected values. You'll see something lik
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"name": "io.github.yourname/your-server",
"description": "A description of your MCP server",
"version": "1.0.0",
"packages": [
{
"registry_type": "npm",
"registryType": "npm",
"identifier": "your-package-name",
"version": "1.0.0"
}
@ -151,7 +151,7 @@ Add an `mcpName` field to your `package.json`:
"name": "io.github.username/server-name",
"packages": [
{
"registry_type": "npm",
"registryType": "npm",
"identifier": "your-npm-package",
"version": "1.0.0"
}
@ -183,7 +183,7 @@ Add it to your README.md file (which becomes the package description on PyPI). T
"name": "io.github.username/server-name",
"packages": [
{
"registry_type": "pypi",
"registryType": "pypi",
"identifier": "your-pypi-package",
"version": "1.0.0"
}
@ -215,7 +215,7 @@ Add a README file to your NuGet package that includes the server name. This can
"name": "io.github.username/server-name",
"packages": [
{
"registry_type": "nuget",
"registryType": "nuget",
"identifier": "Your.NuGet.Package",
"version": "1.0.0"
}
@ -251,8 +251,8 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
"name": "io.github.username/server-name",
"packages": [
{
"registry_type": "oci",
"registry_base_url": "https://docker.io",
"registryType": "oci",
"registryBaseUrl": "https://docker.io",
"identifier": "yourusername/your-mcp-server",
"version": "1.0.0"
}
@ -266,8 +266,8 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
"name": "io.github.username/server-name",
"packages": [
{
"registry_type": "oci",
"registry_base_url": "https://ghcr.io",
"registryType": "oci",
"registryBaseUrl": "https://ghcr.io",
"identifier": "yourusername/your-mcp-server",
"version": "1.0.0"
}
@ -302,9 +302,9 @@ openssl dgst -sha256 server.mcpb
"name": "io.github.username/server-name",
"packages": [
{
"registry_type": "mcpb",
"registryType": "mcpb",
"identifier": "https://github.com/you/your-repo/releases/download/v1.0.0/server.mcpb",
"file_sha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
}
]
}
@ -337,7 +337,7 @@ Add the `remotes` field to your `server.json` (can coexist with `packages`):
```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json",
"name": "com.yourcompany/api-server",
"description": "Cloud-hosted MCP server for API operations",
"version": "2.0.0",
@ -388,8 +388,8 @@ Configure headers that clients should send when connecting:
{
"name": "X-API-Key",
"description": "API key for authentication",
"is_required": true,
"is_secret": true
"isRequired": true,
"isSecret": true
}
]
}