Skip to content

Changes on the server.json format and the API#562

Merged
rdimitrov merged 45 commits intomodelcontextprotocol:mainfrom
rdimitrov:immutable
Sep 29, 2025
Merged

Changes on the server.json format and the API#562
rdimitrov merged 45 commits intomodelcontextprotocol:mainfrom
rdimitrov:immutable

Conversation

@rdimitrov
Copy link
Member

@rdimitrov rdimitrov commented Sep 28, 2025

Motivation and Context

The following PR will address some of the changes we've discussed on Discord along with superseding the PR around ensuring the immutability of server.json (#527). My main reason behind combining these changes is I find it much easier to implement them together instead of separately.

The goal is to ensure better stability of both the format but also the registry API that will get us closer to GA.

The changes that will be introduced are:

  • Ensure the server.json is immutable. The main updates for this are moving status out from server.json and have it under the official registry extensions. Also moving the registry extensions out of server.json and wrap them together with the server as part of the returned response.
  • Streamline the API to use server name and version instead of UUIDs. This can be revisited in the future but let's start with something simple and compliant with the referenced openapi spec.
  • Changes to the integration tests (noticed a few conflicts between the integration setup and running it locally so added extra protections to avoid this)

What's left:

  • Update the Go types
  • Update the database layer (interface + psql implementation + migration file for existing data to the new layout)
  • Update the registry service layer
  • Update the API handlers
  • Update the publisher CLI
  • Reflect these to the reference schemas (server.json + openapi)
  • Update the seed import feature
  • Update the tests and docs
  • Address a few small things (mostly polishing)

How Has This Been Tested?

Breaking Changes

Migration Summary: UUID -> Server Name/Version

API Endpoints (Before/After)

Before (UUID-based):

  • GET /v0/servers/{server_id}
  • PUT /v0/servers/{server_id}
  • GET /v0/servers/{server_id}/versions

After (Name/Version-based):

  • GET /v0/servers/{server_name} # Latest version
  • GET /v0/servers/{server_name}/versions/{version} # Specific version
  • GET /v0/servers/{server_name}/versions # All versions
  • PUT /v0/servers/{server_name}/versions/{version} # Edit specific version

Examples:

  • GET /v0/servers/com.example%2Fmy-server (URL-encoded)

  • PUT /v0/servers/com.example%2Fmy-server/versions/1.2.0?status=deprecated


Server.json Format

Before:

  {
    "name": "com.example/my-server",
    "version": "1.0.0",
    "description": "My MCP server",
    "status": "active",
    "meta": {
      "io.modelcontextprotocol.registry/publisher-provided": {
        "tags": ["database", "postgresql"],
        "category": "data-access",
        "maturity": "beta"
      },
      "io.modelcontextprotocol.registry/official": {
        "publishedAt": "2025-01-01T00:00:00Z",
        "isLatest": true
      }
    }
  }

After (MCP Spec Only):

  {
    "name": "com.example/my-server",
    "version": "1.0.0",
    "description": "My MCP server",
    "repository": {
      "url": "https://github.com/user/repo"
    },
    "packages": [...],
    "_meta": {
      "io.modelcontextprotocol.registry/publisher-provided": {
        "tags": ["database", "postgresql"],
        "category": "data-access",
        "maturity": "beta"
      }
    }
  }

Key Changes:

  • ❌ Removed status and meta.official (now registry-only metadata)

  • ✅ Pure MCP specification format for server.json


API Response Format

Before (Mixed):

  {
    "id": "uuid-123",
    "name": "com.example/my-server",
    "version": "1.0.0",
    "status": "active",
    "_meta": {
      "io.modelcontextprotocol.registry/official": {
        "publishedAt": "2025-01-01T00:00:00Z",
        "isLatest": true
      },
      "io.modelcontextprotocol.registry/publisher-provided": {
        "tags": ["database", "postgresql"],
        "category": "data-access",
        "maturity": "beta"
      }
  }

After (Separated):

  {
    "server": {
      "name": "com.example/my-server",
      "version": "1.0.0",
      "description": "My MCP server",
      "repository": {...},
      "packages": [...],
      "_meta": {
        "io.modelcontextprotocol.registry/publisher-provided": {
          "tags": ["database", "postgresql"],
          "category": "data-access",
          "maturity": "beta"
        }
    },
    "_meta": {
      "io.modelcontextprotocol.registry/official": {
        "status": "active",
        "publishedAt": "2025-01-01T00:00:00Z",
        "updatedAt": "2025-01-01T00:00:00Z",
        "isLatest": true
      }
    }
  }

Key Changes:

  • ✅ Clean separation: server = MCP spec, _meta = registry metadata
  • ✅ No more UUIDs - identify by name + version
  • ✅ Status changes via query parameter: ?status=deprecated

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@rdimitrov rdimitrov marked this pull request as draft September 28, 2025 00:17
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
…et endpoints

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>

// Get server details endpoint
// Get server details endpoint (latest version)
huma.Register(api, huma.Operation{
Copy link
Member

@domdomegg domdomegg Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional) I would be tempted to delete this endpoint completely, and just have people use /v0/servers/{name}/versions/latest

Copy link
Member

@domdomegg domdomegg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am generally happy with this! Only blocking change is the bug with pagination: https://github.com/modelcontextprotocol/registry/pull/562/files#r2388526356

The UUIDs -> name + version string change I think makes things a lot easier, although might want to give @tadasant some time to review (at least just on this point) as I think he was a proponent of the IDs in the past?

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
@tadasant
Copy link
Member

The UUIDs -> name + version string change I think makes things a lot easier, although might want to give @tadasant some time to review (at least just on this point) as I think he was a proponent of the IDs in the past?

I was at first but have since been convinced otherwise: #333 (comment)

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
if _, err := uuid.Parse(cursor); err != nil {
return nil, "", fmt.Errorf("invalid cursor format: %w", err)
// Parse cursor format: "serverName:version"
parts := strings.SplitN(cursor, ":", 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for me to check: is this always safe? i.e. can we have : in server names/versions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we don't allow this on server names and, assuming we follow proper semver, it is not allowed there too, but you have a good point since it was discussed to not enforce semver so potentially this can become an issue. Do you have any suggestions to replace it with another delimiter? Although as long as we allow the version to be everything this wouldn't work either I guess

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I came back to this later, it's not possible to put a : in the server name by the server.json spec so this is fine to use as delimiter 👍

I think in the ideal implementation we:

  • split at :
    • first part is name
    • rest joined with : is version string (and for most cases, this should just be one. but if there's more it's them joined by :

This should handle all cases reliably right now


In the future if we do want to change this, because we're treating it as opaque string we could properly escape it e.g. make this actually a json stringified thing like {"n":"io.github.domdomegg/airtable-mcp-server","v":"1.2.3"} (but don't need to do this rn)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to prioritise a follow up if you think we should get this before the freeze?

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
@domdomegg domdomegg dismissed tadasant’s stale review September 29, 2025 20:25

Comment is addressed

@domdomegg
Copy link
Member

@rdimitrov I am happy to merge, if you are?

@rdimitrov
Copy link
Member Author

@rdimitrov I am happy to merge, if you are?

Yep, let's go 🙏

@rdimitrov rdimitrov merged commit b21c6f5 into modelcontextprotocol:main Sep 29, 2025
9 checks passed
@rdimitrov rdimitrov deleted the immutable branch September 29, 2025 20:37
tadasant added a commit that referenced this pull request Sep 29, 2025
## Summary
- Remove the `status` field from all server.json examples in
documentation
- Remove the deprecated server example that was primarily about
demonstrating the status field
- Update example count validation from 13 to 12

## Context
The `status` field was removed from the server.json schema in commit
b21c6f5 (#562). This PR cleans up the documentation to reflect that
change.

## Why didn't tests catch this?
The schema validation didn't catch the invalid `status` field because
the JSON schema doesn't have `additionalProperties: false` set. This
means the schema allows arbitrary additional properties by default.

## Test plan
- [x] All validation checks pass (`make check`)
- [x] Example count updated to match the new total (12 examples)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
slimslenderslacks pushed a commit to slimslenderslacks/registry that referenced this pull request Dec 18, 2025
slimslenderslacks pushed a commit to slimslenderslacks/registry that referenced this pull request Dec 18, 2025
…ocol#575)

## Summary
- Remove the `status` field from all server.json examples in
documentation
- Remove the deprecated server example that was primarily about
demonstrating the status field
- Update example count validation from 13 to 12

## Context
The `status` field was removed from the server.json schema in commit
b21c6f5 (modelcontextprotocol#562). This PR cleans up the documentation to reflect that
change.

## Why didn't tests catch this?
The schema validation didn't catch the invalid `status` field because
the JSON schema doesn't have `additionalProperties: false` set. This
means the schema allows arbitrary additional properties by default.

## Test plan
- [x] All validation checks pass (`make check`)
- [x] Example count updated to match the new total (12 examples)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants