registry/docs/explanations/versioning.md
Tadas Antanavicius 56e8b4879b
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>
2025-09-21 15:00:09 +03:00

6.5 KiB

Server Versioning Guide

This document describes the versioning approach for MCP servers published to the registry.

Overview

The MCP Registry supports flexible versioning while encouraging semantic versioning best practices. The registry attempts to parse versions as semantic versions for ordering and comparison, but falls back gracefully for non-semantic versions.

Version Requirements

  1. Version String: version MUST be a string up to 255 characters
  2. Uniqueness: Each version for a given server name must be unique
  3. Immutability: Once published, version metadata cannot be changed

Best Practices

1. Use Semantic Versioning

Server authors SHOULD use semantic versions following the MAJOR.MINOR.PATCH format:

{
  "version": "1.2.3"
}

2. Align with Package Versions

Server authors SHOULD use versions aligned with their underlying packages to reduce confusion:

{
  "version": "1.2.3",
  "packages": [{
    "registry_name": "npm",
    "name": "@myorg/my-server",
    "version": "1.2.3"
  }]
}

3. Multiple Registry Versions

If server authors expect to have multiple registry versions for the same package version, they SHOULD follow the semantic version spec using the prerelease label:

{
  "version": "1.2.3-1",
  "packages": [{
    "registry_name": "npm", 
    "name": "@myorg/my-server",
    "version": "1.2.3"
  }]
}

Note: According to semantic versioning, 1.2.3-1 is considered lower than 1.2.3, so if you expect to need a 1.2.3-1, you should publish that before 1.2.3.

Version Ordering and "Latest" Determination

For Semantic Versions

The registry attempts to parse versions as semantic versions. If successful, it uses semantic version comparison rules to determine:

  • Version ordering in lists
  • Which version is marked as isLatest

For Non-Semantic Versions

If version parsing as semantic version fails:

  • The registry will always mark the version as latest (overriding any previous version)
  • Clients should fall back to using publish timestamp for ordering

Important Note: This behavior means that for servers with mixed semantic and non-semantic versions, the isLatest flag may not align with the total ordering. A non-semantic version published after semantic versions will be marked as latest, even if semantic versions are considered "higher" in the ordering.

Implementation Details

Registry Behavior

  1. Validation: Versions are validated for uniqueness within a server name
  2. Parsing: The registry attempts to parse each version as semantic version
  3. Comparison: Uses semantic version rules when possible, falls back to timestamp
  4. Latest Flag: The isLatest field is set based on the comparison results

Client Recommendations

Registry clients SHOULD:

  1. Attempt to interpret versions as semantic versions when possible
  2. Use the following ordering rules:
    • If one version is marked as isLatest: it is later
    • If both versions are valid semver: use semver comparison
    • If neither are valid semver: use publish timestamp
    • If one is semver and one is not: semver version is considered higher

Examples

Valid Semantic Versions

"1.0.0"          // Basic semantic version
"2.1.3-alpha"    // Prerelease version  
"1.0.0-beta.1"   // Prerelease with numeric suffix
"3.0.0-rc.2"     // Release candidate

Non-Semantic Versions (Allowed)

"v1.0"           // Version with prefix
"2021.03.15"     // Date-based versioning

Not Allowed: Version Ranges

The registry requires specific versions for both the top-level version and any packages[].version. Version ranges or wildcard versions are rejected during publish, including but not limited to:

"^1.2.3"
"~1.2.3"
">=1.2.3"
"<=1.2.3"
">1.2.3"
"<1.2.3"
"1.x"
"1.2.*"
"1 - 2"
"1.2 || 1.3"

Alignment Examples

{
  "version": "1.2.3-1",
  "packages": [{
    "registry_name": "npm",
    "name": "@myorg/k8s-server", 
    "version": "1.2.3"
  }]
}

Migration Path

Existing servers with non-semantic versions will continue to work without changes. However, to benefit from proper version ordering, server maintainers are encouraged to:

  1. Adopt semantic versioning for new releases
  2. Consider the ordering implications when transitioning from non-semantic to semantic versions
  3. Use prerelease labels for registry-specific versioning needs

Common Scenarios

Standard Releases

Package and server versions aligned:

{
  "version": "1.2.3",
  "packages": [
    {
      "registryType": "npm",
      "identifier": "@myorg/server",
      "version": "1.2.3"
    }
  ]
}

Registry-Only Updates

Server metadata changes without package updates:

{
  "version": "1.2.3-1",
  "packages": [
    {
      "registryType": "npm",
      "identifier": "@myorg/server", 
      "version": "1.2.3"
    }
  ]
}

Note: 1.2.3-1 is considered lower than 1.2.3 in semver ordering. Publish prerelease versions first.

Remote Server Versioning

Remote servers without package dependencies:

{
  "version": "2.1.0",
  "remotes": [
    {
      "transport_type": "sse",
      "url": "https://api.myservice.com/mcp/v2.1"
    }
  ]
}

Version strategy options:

  • API versioning: Match your service API version
  • Semantic versioning: Standard semver for feature changes
  • Date-based: 2024.03.15 for regular releases

Multi-Package Versioning

Different package versions:

{
  "version": "1.3.0",
  "packages": [
    {
      "registryType": "npm",
      "identifier": "@myorg/server",
      "version": "1.3.0"
    },
    {
      "registryType": "oci",
      "identifier": "myorg/server",
      "version": "1.2.5"
    }
  ]
}

Use server version to indicate the overall release.

Decision Guide

Which version strategy should I use?

Do you have underlying packages?
├─ Yes: Align with package version
│  ├─ Same package version → Use package version
│  └─ Different package versions → Use highest + indicate relationship
└─ No (remote server): Choose versioning scheme
   ├─ API versioning → Match service API version
   ├─ Feature-based → Use semantic versioning
   └─ Regular releases → Consider date-based versioning

Future Considerations

This versioning approach is designed to be compatible with potential future changes to the MCP specification's Implementation.version field. Any SHOULD requirements introduced here may be proposed as updates to the specification through the SEP (Specification Enhancement Proposal) process.