Skip to content

Commit 212bccd

Browse files
feat: Add support for MCP Bundles (MCPB) in registry
- Add MCPB as a new registry_name option - Add file_hashes field to Package model for integrity verification - Implement URL validation restricting MCPB packages to GitHub/GitLab hosts - Require SHA-256 hash for all MCPB packages - Add example MCPB package in documentation - Update schemas and OpenAPI specification Implements Option 1 from issue #260 - MCPB as an additional package type pointing to hosted .mcpb files with cryptographic verification.
1 parent fcb104f commit 212bccd

File tree

7 files changed

+117
-9
lines changed

7 files changed

+117
-9
lines changed

docs/server-json/examples.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,38 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
389389
}
390390
```
391391

392+
## MCP Bundle (MCPB) Package Example
393+
394+
```json
395+
{
396+
"name": "io.modelcontextprotocol/text-editor",
397+
"description": "MCP Bundle server for advanced text editing capabilities",
398+
"repository": {
399+
"url": "https://github.com/modelcontextprotocol/text-editor-mcpb",
400+
"source": "github",
401+
"id": "mcpb-123ab-cdef4-56789-012ghi-jklmnopqrstu"
402+
},
403+
"version_detail": {
404+
"version": "1.0.2"
405+
},
406+
"packages": [
407+
{
408+
"registry_name": "mcpb",
409+
"name": "https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb",
410+
"version": "1.0.2",
411+
"file_hashes": {
412+
"sha-256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
413+
}
414+
}
415+
]
416+
}
417+
```
418+
419+
This example shows an MCPB (MCP Bundle) package that:
420+
- Is hosted on GitHub Releases (an allowlisted provider)
421+
- Includes a SHA-256 hash for integrity verification
422+
- Can be downloaded and executed directly by MCP clients that support MCPB
423+
392424
## Deprecated Server Example
393425

394426
```json

docs/server-json/registry-schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"npm",
2323
"pypi",
2424
"docker",
25-
"nuget"
25+
"nuget",
26+
"mcpb"
2627
]
2728
},
2829
"runtime_hint": {

docs/server-json/schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@
9797
"description": "Package version",
9898
"example": "1.0.2"
9999
},
100+
"file_hashes": {
101+
"type": "object",
102+
"description": "Cryptographic hashes of the package file for integrity verification. Keys are hash algorithm names (e.g., 'sha-256'), values are the hash strings.",
103+
"additionalProperties": {
104+
"type": "string"
105+
},
106+
"example": {
107+
"sha-256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
108+
}
109+
},
100110
"runtime_hint": {
101111
"type": "string",
102112
"description": "A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtime_arguments` are present.",

docs/server-registry-api/openapi.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ components:
185185
type: string
186186
description: Package version
187187
example: "1.0.2"
188+
file_hashes:
189+
type: object
190+
description: Cryptographic hashes of the package file for integrity verification. Keys are hash algorithm names (e.g., 'sha-256'), values are the hash strings.
191+
additionalProperties:
192+
type: string
193+
example:
194+
sha-256: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
188195
runtime_hint:
189196
type: string
190197
description: A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtime_arguments` are present.

internal/model/model.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,14 @@ type Argument struct {
8787
}
8888

8989
type Package struct {
90-
RegistryName string `json:"registry_name" bson:"registry_name"`
91-
Name string `json:"name" bson:"name"`
92-
Version string `json:"version" bson:"version"`
93-
RunTimeHint string `json:"runtime_hint,omitempty" bson:"runtime_hint,omitempty"`
94-
RuntimeArguments []Argument `json:"runtime_arguments,omitempty" bson:"runtime_arguments,omitempty"`
95-
PackageArguments []Argument `json:"package_arguments,omitempty" bson:"package_arguments,omitempty"`
96-
EnvironmentVariables []KeyValueInput `json:"environment_variables,omitempty" bson:"environment_variables,omitempty"`
90+
RegistryName string `json:"registry_name" bson:"registry_name"`
91+
Name string `json:"name" bson:"name"`
92+
Version string `json:"version" bson:"version"`
93+
FileHashes map[string]string `json:"file_hashes,omitempty" bson:"file_hashes,omitempty"`
94+
RunTimeHint string `json:"runtime_hint,omitempty" bson:"runtime_hint,omitempty"`
95+
RuntimeArguments []Argument `json:"runtime_arguments,omitempty" bson:"runtime_arguments,omitempty"`
96+
PackageArguments []Argument `json:"package_arguments,omitempty" bson:"package_arguments,omitempty"`
97+
EnvironmentVariables []KeyValueInput `json:"environment_variables,omitempty" bson:"environment_variables,omitempty"`
9798
}
9899

99100
// Remote represents a remote connection endpoint

internal/service/registry_service.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package service
22

33
import (
44
"context"
5+
"fmt"
6+
"net/url"
7+
"strings"
58
"time"
69

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

90+
// validateMCPBPackage validates MCPB packages to ensure they meet requirements
91+
func validateMCPBPackage(pkg *model.Package) error {
92+
// Validate that the URL is from an allowlisted host
93+
parsedURL, err := url.Parse(pkg.Name)
94+
if err != nil {
95+
return fmt.Errorf("invalid MCPB package URL: %w", err)
96+
}
97+
98+
// Allowlist of trusted hosts for MCPB packages
99+
allowedHosts := []string{
100+
"github.com",
101+
"www.github.com",
102+
"raw.githubusercontent.com",
103+
"gitlab.com",
104+
"www.gitlab.com",
105+
}
106+
107+
host := strings.ToLower(parsedURL.Host)
108+
isAllowed := false
109+
for _, allowed := range allowedHosts {
110+
if host == allowed {
111+
isAllowed = true
112+
break
113+
}
114+
}
115+
116+
if !isAllowed {
117+
return fmt.Errorf("MCPB packages must be hosted on allowlisted providers (GitHub or GitLab). Host '%s' is not allowed", host)
118+
}
119+
120+
// Validate that file_hashes is provided for MCPB packages
121+
if len(pkg.FileHashes) == 0 {
122+
return fmt.Errorf("MCPB packages must include file_hashes for integrity verification")
123+
}
124+
125+
// Validate that at least SHA-256 is provided
126+
if _, hasSHA256 := pkg.FileHashes["sha-256"]; !hasSHA256 {
127+
if _, hasSHA256Alt := pkg.FileHashes["sha256"]; !hasSHA256Alt {
128+
return fmt.Errorf("MCPB packages must include a SHA-256 hash")
129+
}
130+
}
131+
132+
return nil
133+
}
134+
87135
// Publish adds a new server detail to the registry
88136
func (s *registryServiceImpl) Publish(serverDetail *model.ServerDetail) error {
89137
// Create a timeout context for the database operation
@@ -94,6 +142,15 @@ func (s *registryServiceImpl) Publish(serverDetail *model.ServerDetail) error {
94142
return database.ErrInvalidInput
95143
}
96144

145+
// Validate MCPB packages
146+
for _, pkg := range serverDetail.Packages {
147+
if strings.ToLower(pkg.RegistryName) == "mcpb" {
148+
if err := validateMCPBPackage(&pkg); err != nil {
149+
return fmt.Errorf("validation failed: %w", err)
150+
}
151+
}
152+
}
153+
97154
err := s.db.Publish(ctx, serverDetail)
98155
if err != nil {
99156
return err

tools/validate-examples/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const (
2222
// IMPORTANT: Only change this count if you have intentionally added or removed examples
2323
// from the examples.md file. This check prevents accidental formatting changes from
2424
// causing examples to be skipped during validation.
25-
expectedExampleCount = 9
25+
expectedExampleCount = 10
2626
)
2727

2828
func main() {

0 commit comments

Comments
 (0)