Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
212bccd
feat: Add support for MCP Bundles (MCPB) in registry
joan-anthropic Aug 21, 2025
2ee6e00
feat: add PackageLocation
joan-anthropic Aug 22, 2025
84cdc81
feat: Add file hash generation support to publisher CLI
joan-anthropic Aug 22, 2025
aafe260
feat: refactor package metadata to use PackageLocation structure
joan-anthropic Aug 26, 2025
fb0283e
Merge branch 'main' into joan/dxt-support-server-json
joan-anthropic Aug 26, 2025
cec8cdd
chore: remove raw.githubusercontent.com from MCPB allowed hosts
joan-anthropic Aug 26, 2025
17b3824
fix: resolve compilation errors and test failures after merge
joan-anthropic Aug 26, 2025
8287e88
fix: resolve linting issues in publisher tool
joan-anthropic Aug 26, 2025
ca7fc20
fix: correct JSON syntax errors in server-json examples
joan-anthropic Aug 26, 2025
4ccfc41
refactor: remove hash generation/validation, keep schema field
joan-anthropic Aug 26, 2025
fb39c44
refactor: migrate from registry_name/name to package_type/registry/id…
joan-anthropic Aug 26, 2025
00fd953
fix: restore Unicode escapes for angle brackets in seed.json
joan-anthropic Aug 26, 2025
6b8dee7
refactor: change 'registry' field to 'registry_name' for clarity
joan-anthropic Aug 26, 2025
c7ed33c
Merge branch 'main' into joan/dxt-support-server-json
joan-anthropic Aug 26, 2025
e242a8d
fix: remove unnecessary repository IDs from examples
joan-anthropic Aug 26, 2025
c194b3d
fix: correct indentation in examples.md
joan-anthropic Aug 26, 2025
1aab2b4
fix: correct all indentation and syntax errors in examples.md
joan-anthropic Aug 26, 2025
6a3a4e1
fix: correct all block indentation in examples.md
joan-anthropic Aug 26, 2025
0ec0235
Revert test and auth files to minimal changes
joan-anthropic Aug 26, 2025
ac5fedb
Fix migrate-seed tool to handle schema conversion
joan-anthropic Aug 26, 2025
55f1b48
fix: add missing packages to test server responses in TestReadSeedFil…
joan-anthropic Aug 26, 2025
f91f45d
Merge main into joan/dxt-support-server-json
domdomegg Aug 27, 2025
ed35452
refactor: update package schema to use registry_type and registry_bas…
domdomegg Aug 27, 2025
b6cd4d3
refactor: update registry type to 'mcpb' across documentation and cod…
domdomegg Aug 27, 2025
53fb0e2
refactor: flatten file_hashes map with just sha256 hash
domdomegg Aug 27, 2025
89971fd
Merge pull request #1 from modelcontextprotocol/adamj/registry-field-…
joan-anthropic Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/server-json/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,38 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
}
```

## MCP Bundle (MCPB) Package Example

```json
{
"name": "io.modelcontextprotocol/text-editor",
"description": "MCP Bundle server for advanced text editing capabilities",
"repository": {
"url": "https://github.com/modelcontextprotocol/text-editor-mcpb",
"source": "github",
"id": "mcpb-123ab-cdef4-56789-012ghi-jklmnopqrstu"
},
"version_detail": {
"version": "1.0.2"
},
"packages": [
{
"registry_name": "mcpb",
"name": "https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb",
Copy link
Member

@tadasant tadasant Aug 22, 2025

Choose a reason for hiding this comment

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

This is right with our current schema. But a thought for potentially renaming some details here: name doesn't really make sense for a URL like this, and registry_name doesn't for mcpb either (it's more of a package type).

I think I'd be in favor of something more like this here:

{
      "location": {
        "type": "mcpb",
        "url": "https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb"
      },
      "runtime_hint": ...
      ...
}

And that's it - drop version, because the only reason we have version is to effectively "construct" the url in the registry_name case.

Basically, my proposal is to add a nested PackageLocation object inside here, where we currently just store package_name, name, and version in the top level of the Package object. It would be an "OR" situation where either the original fields (registry_name, name, and version) or the new fields (type, url) are required. mcpb is the only valid entry for type.

Then the original case for other examples, like the one just before this, becomes:

  "packages": [
    {
      "location": {
        "registry_name": "npm",
        "name": "@example/hybrid-mcp-server",
        "version": "1.5.0"
      },
      "runtime_hint": "npx",
      "package_arguments": [
        {
          "type": "named",
          "name": "--mode",
          "description": "Operation mode",
          "default": "local",
          "choices": ["local", "cached", "proxy"]
        }
      ]
    }
  ]

It's a breaking change but I think at this point, pre-release, is a good time to make this rather than shoehorn values into registry_name and name. What do you all think? cc @domdomegg @joan-anthropic @toby

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tadasant thanks for the quick turnaround. Agree that registry_name and name feel a bit overloaded in the current proposal - I like the distinction.

It'd be nice to keep version as a separate field - to better enable future auto-update workflows for DXTs. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Not sure I follow the use case, could you explain auto-update workflows for DXTs? Is there a reason you wouldn't be able to use the https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb value for that (which has the version in it)?

We've had some feedback from @toby that all these version fields throughout the various server.json levels are causing confusion, so my thinking is that this is a good opportunity to potentially eliminate one of them as it's not actually necessary (but maybe I'm missing something).

Copy link
Member

@domdomegg domdomegg Aug 23, 2025

Choose a reason for hiding this comment

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

  • Maybe package_type and package_identifier seems good? From question on the docker runtime type #249 (reply in thread). I think also as a meta thing I'd prefer them to stay in the package object rather than a nested location object, because I think unnecessary nesting is painful for many ecosystems (e.g. parsing nested structs like this in Java creates a lot of boilerplate).

So together I think I might propose something like:

"packages": [
    {
      "package_type": "mcpb",
      "package_identifier": "https://github.com/modelcontextprotocol/cool-mcp/releases/download/v1.2.3/cool-mcp.mcpb",
      // there's something that aesthetically feels a little worse about this not being an object, but I think it will make it easier on consumers. and very unlikely to really need other hash functions unless sha256 is broken
      "package_hash_sha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
    },
    {
      "package_type": "docker",
      // docker implicitly has registry name in package_identifier, I think we should just have package_identifier because of this
      "package_identifier": "ghcr.io/domdomegg/cool-mcp:1.2.3@sha256:4a7f307d128da53e99ea7f45064df440c742aa3f648a09a09b51dfd1cd55378b"
    },
    {
      "package_type": "commonjs",
      // i think for now only allowlist npmjs. but subregistries could use e.g. https://npm.pkg.github.com or https://artifactory.corp.internal/npm
      "registry_name": "https://registry.npmjs.org/",
      "package_identifier": "@domdomegg/cool-mcp@1.2.3",
      "runtime_hint": "npx",
      "package_arguments": [
        {
          "type": "named",
          "name": "--mode",
          "description": "Operation mode",
          "default": "local",
          "choices": ["local", "cached", "proxy"]
        }
      ]
    }
  ]
  • I think I agree with @tadasant that the version is not necessary in the packages array for DXT. There will still be a version at the top level in server.json. For auto-update I think we'd use this top-level version, and maybe is_latest.

"version": "1.0.2",
"file_hashes": {
"sha-256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
}
Copy link
Member

Choose a reason for hiding this comment

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

Should we consider lifting this inside the PackageLocation object I am proposing above, because it is only a concern relevant to non-package-registry entries?

Copy link
Member

@tadasant tadasant Aug 22, 2025

Choose a reason for hiding this comment

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

I guess there is the possibility we add some registry in the future that does not do its own checks. So I'm fine keeping it here as an optional field.

I assume we need to rely on the MCP Registry to generate this file hash at publish-time, right? Or is the idea it will get generated by the CLI tool en route to creating a server.json, and the registry just validates it's correct and not tampered with in the publish flow?

I think we should map out and document this feature in a little more detail, maybe a dedicated doc in docs/ explaining the flows, who's responsible for what. I worry a bit about how file_hashes is something we are expecting in the immutable server.json file, but also need to verify they're actually valid hashes. Seems doable but would like to see a step by step explanation somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not opinionated here and will defer to you all re: what makes sense for the workflow the registry committee would prefer to support; happy add docs to reflect.

Copy link
Member

Choose a reason for hiding this comment

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

@joan-anthropic @domdomegg did we work out who is responsible for what in this file hash verification flow somewhere? Sorry if I missed it, still working through catching up on emails and messages

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tadasant apologies for the delay - I'm OOO through the rest of the week so might be a bit slow to respond.

I ultimately wanted to leave the hash validation workflow as a question for you / @domdomegg / @toby as it'll be supported by the registry committee (iiuc). I'm happy to open a follow-up PR to reflect whatever decision ends up being made, though let me know if you're un-opinionated and I'm happy take a stab at it.

Just so I understand: do we actually need to generate/verify the hash at publish time? My impression was that it was a guard against future tampering with mcpb contents - so could we trust that the publication is correct and have registry clients take responsibility for hash verification before they install the mcpb? Ie, make this an contract between mcpb author <> registry client, and not MCP registry <> registry client?

Copy link
Member

Choose a reason for hiding this comment

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

Adding a ticket to think through this to make sure we tackle before go-live (apologies if I'm still missing context somewhere, haven't given this field much thought yet): #351

}
]
}
```

This example shows an MCPB (MCP Bundle) package that:
- Is hosted on GitHub Releases (an allowlisted provider)
- Includes a SHA-256 hash for integrity verification
- Can be downloaded and executed directly by MCP clients that support MCPB

## Deprecated Server Example

```json
Expand Down
3 changes: 2 additions & 1 deletion docs/server-json/registry-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"npm",
"pypi",
"docker",
"nuget"
"nuget",
"mcpb"
]
},
"runtime_hint": {
Expand Down
10 changes: 10 additions & 0 deletions docs/server-json/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@
"description": "Package version",
"example": "1.0.2"
},
"file_hashes": {
"type": "object",
"description": "Cryptographic hashes of the package file for integrity verification. Keys are hash algorithm names (e.g., 'sha-256'), values are the hash strings.",
"additionalProperties": {
"type": "string"
},
"example": {
"sha-256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
}
},
"runtime_hint": {
"type": "string",
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtime_arguments` are present.",
Expand Down
7 changes: 7 additions & 0 deletions docs/server-registry-api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ components:
type: string
description: Package version
example: "1.0.2"
file_hashes:
type: object
description: Cryptographic hashes of the package file for integrity verification. Keys are hash algorithm names (e.g., 'sha-256'), values are the hash strings.
additionalProperties:
type: string
example:
sha-256: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
runtime_hint:
type: string
description: A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtime_arguments` are present.
Expand Down
15 changes: 8 additions & 7 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,14 @@ type Argument struct {
}

type Package struct {
RegistryName string `json:"registry_name" bson:"registry_name"`
Name string `json:"name" bson:"name"`
Version string `json:"version" bson:"version"`
RunTimeHint string `json:"runtime_hint,omitempty" bson:"runtime_hint,omitempty"`
RuntimeArguments []Argument `json:"runtime_arguments,omitempty" bson:"runtime_arguments,omitempty"`
PackageArguments []Argument `json:"package_arguments,omitempty" bson:"package_arguments,omitempty"`
EnvironmentVariables []KeyValueInput `json:"environment_variables,omitempty" bson:"environment_variables,omitempty"`
RegistryName string `json:"registry_name" bson:"registry_name"`
Name string `json:"name" bson:"name"`
Version string `json:"version" bson:"version"`
FileHashes map[string]string `json:"file_hashes,omitempty" bson:"file_hashes,omitempty"`
RunTimeHint string `json:"runtime_hint,omitempty" bson:"runtime_hint,omitempty"`
RuntimeArguments []Argument `json:"runtime_arguments,omitempty" bson:"runtime_arguments,omitempty"`
PackageArguments []Argument `json:"package_arguments,omitempty" bson:"package_arguments,omitempty"`
EnvironmentVariables []KeyValueInput `json:"environment_variables,omitempty" bson:"environment_variables,omitempty"`
}

// Remote represents a remote connection endpoint
Expand Down
57 changes: 57 additions & 0 deletions internal/service/registry_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package service

import (
"context"
"fmt"
"net/url"
"strings"
"time"

"github.com/modelcontextprotocol/registry/internal/database"
Expand Down Expand Up @@ -84,6 +87,51 @@ func (s *registryServiceImpl) GetByID(id string) (*model.ServerDetail, error) {
return serverDetail, nil
}

// validateMCPBPackage validates MCPB packages to ensure they meet requirements
func validateMCPBPackage(pkg *model.Package) error {
// Validate that the URL is from an allowlisted host
parsedURL, err := url.Parse(pkg.Name)
if err != nil {
return fmt.Errorf("invalid MCPB package URL: %w", err)
}

// Allowlist of trusted hosts for MCPB packages
allowedHosts := []string{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

to verify

Copy link
Member

Choose a reason for hiding this comment

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

lgtm

"github.com",
"www.github.com",
"raw.githubusercontent.com",
Copy link
Member

@domdomegg domdomegg Aug 21, 2025

Choose a reason for hiding this comment

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

possibly this one (raw.githubusercontent.com) is not necessary

"gitlab.com",
"www.gitlab.com",
}

host := strings.ToLower(parsedURL.Host)
isAllowed := false
for _, allowed := range allowedHosts {
if host == allowed {
isAllowed = true
break
}
}

if !isAllowed {
return fmt.Errorf("MCPB packages must be hosted on allowlisted providers (GitHub or GitLab). Host '%s' is not allowed", host)
}

// Validate that file_hashes is provided for MCPB packages
if len(pkg.FileHashes) == 0 {
return fmt.Errorf("MCPB packages must include file_hashes for integrity verification")
}

// Validate that at least SHA-256 is provided
if _, hasSHA256 := pkg.FileHashes["sha-256"]; !hasSHA256 {
if _, hasSHA256Alt := pkg.FileHashes["sha256"]; !hasSHA256Alt {
return fmt.Errorf("MCPB packages must include a SHA-256 hash")
}
}

return nil
}

// Publish adds a new server detail to the registry
func (s *registryServiceImpl) Publish(serverDetail *model.ServerDetail) error {
// Create a timeout context for the database operation
Expand All @@ -94,6 +142,15 @@ func (s *registryServiceImpl) Publish(serverDetail *model.ServerDetail) error {
return database.ErrInvalidInput
}

// Validate MCPB packages
for _, pkg := range serverDetail.Packages {
if strings.ToLower(pkg.RegistryName) == "mcpb" {
if err := validateMCPBPackage(&pkg); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
}
}

err := s.db.Publish(ctx, serverDetail)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion tools/validate-examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const (
// IMPORTANT: Only change this count if you have intentionally added or removed examples
// from the examples.md file. This check prevents accidental formatting changes from
// causing examples to be skipped during validation.
expectedExampleCount = 9
expectedExampleCount = 10
)

func main() {
Expand Down
Loading