Make mcpb package version optional and fix schema validation (#686)

<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR:
* Makes mcpb package version property optional
* Bumps the schema and reflects that in the code
* Makes sure the publisher tests are being covered by doing `make check`

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] 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
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] 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
<!-- Add any other context, implementation notes, or design decisions
-->

---------

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
This commit is contained in:
Radoslav Dimitrov 2025-10-17 01:35:29 +03:00 • committed by GitHub
commit 0828bc483c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 131 additions and 81 deletions

View file

@ -32,7 +32,7 @@ test-unit: ## Run unit tests with coverage (requires PostgreSQL)
@echo "Waiting for PostgreSQL to be ready..." @echo "Waiting for PostgreSQL to be ready..."
@sleep 3 @sleep 3
@echo "Running unit tests..." @echo "Running unit tests..."
go test -v -race -coverprofile=coverage.out -covermode=atomic ./internal/... go test -v -race -coverprofile=coverage.out -covermode=atomic ./internal/... ./cmd/...
go tool cover -html=coverage.out -o coverage.html go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report generated: coverage.html" @echo "Coverage report generated: coverage.html"
@echo "Stopping PostgreSQL..." @echo "Stopping PostgreSQL..."

View file

@ -44,8 +44,8 @@ func TestPublishCommand_DeprecatedSchema(t *testing.T) {
errorSubstr: "deprecated schema detected", errorSubstr: "deprecated schema detected",
}, },
{ {
name: "current 2025-09-29 schema should pass validation", name: "current 2025-10-17 schema should pass validation",
schema: "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", schema: "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
expectError: false, expectError: false,
}, },
{ {

View file

@ -1,6 +1,6 @@
[ [
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.domdomegg/airtable-mcp-server", "name": "io.github.domdomegg/airtable-mcp-server",
"description": "Read and write access to Airtable database schemas, tables, and records.", "description": "Read and write access to Airtable database schemas, tables, and records.",
"repository": { "repository": {
@ -62,7 +62,7 @@
] ]
}, },
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.domdomegg/airtable-mcp-server", "name": "io.github.domdomegg/airtable-mcp-server",
"description": "Read and write access to Airtable database schemas, tables, and records.", "description": "Read and write access to Airtable database schemas, tables, and records.",
"repository": { "repository": {
@ -115,7 +115,7 @@
] ]
}, },
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.domdomegg/time-mcp-nuget", "name": "io.github.domdomegg/time-mcp-nuget",
"description": "Get the current UTC time in RFC 3339 format.", "description": "Get the current UTC time in RFC 3339 format.",
"repository": { "repository": {
@ -136,7 +136,7 @@
] ]
}, },
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.domdomegg/time-mcp-pypi", "name": "io.github.domdomegg/time-mcp-pypi",
"description": "Get the current UTC time in RFC 3339 format.", "description": "Get the current UTC time in RFC 3339 format.",
"repository": { "repository": {

View file

@ -67,7 +67,7 @@ You can also add custom metadata to servers using the `_meta` field. For example
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.yourname/weather-server", "name": "io.github.yourname/weather-server",
"description": "MCP server for weather data access", "description": "MCP server for weather data access",
"status": "active", "status": "active",

View file

@ -93,7 +93,7 @@ This creates a `server.json` with auto-detected values. You'll see something lik
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.yourname/weather-data-mcp", "name": "io.github.yourname/weather-data-mcp",
"title": "Weather Data", "title": "Weather Data",
"description": "Access real-time weather data and forecasts", "description": "Access real-time weather data and forecasts",
@ -153,7 +153,7 @@ Add an `mcpName` field to your `package.json`:
### Example server.json ### Example server.json
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.username/slack-integration-mcp", "name": "io.github.username/slack-integration-mcp",
"title": "Slack Integration", "title": "Slack Integration",
"description": "Send messages and manage Slack workspaces", "description": "Send messages and manage Slack workspaces",
@ -192,7 +192,7 @@ Add it to your README.md file (which becomes the package description on PyPI). T
### Example server.json ### Example server.json
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.username/database-query-mcp", "name": "io.github.username/database-query-mcp",
"title": "Database Query", "title": "Database Query",
"description": "Execute SQL queries and manage database connections", "description": "Execute SQL queries and manage database connections",
@ -231,7 +231,7 @@ Add a README file to your NuGet package that includes the server name. This can
### Example server.json ### Example server.json
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.username/azure-devops-mcp", "name": "io.github.username/azure-devops-mcp",
"title": "Azure DevOps", "title": "Azure DevOps",
"description": "Manage Azure DevOps work items and pipelines", "description": "Manage Azure DevOps work items and pipelines",
@ -274,7 +274,7 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
### Example server.json (Docker Hub) ### Example server.json (Docker Hub)
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.username/kubernetes-manager-mcp", "name": "io.github.username/kubernetes-manager-mcp",
"title": "Kubernetes Manager", "title": "Kubernetes Manager",
"description": "Deploy and manage Kubernetes resources", "description": "Deploy and manage Kubernetes resources",
@ -282,9 +282,7 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
"packages": [ "packages": [
{ {
"registryType": "oci", "registryType": "oci",
"registryBaseUrl": "https://docker.io", "identifier": "docker.io/yourusername/kubernetes-manager-mcp:1.0.0",
"identifier": "yourusername/kubernetes-manager-mcp",
"version": "1.0.0",
"transport": { "transport": {
"type": "stdio" "type": "stdio"
} }
@ -296,7 +294,7 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
### Example server.json (GitHub Container Registry) ### Example server.json (GitHub Container Registry)
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.username/git-operations-mcp", "name": "io.github.username/git-operations-mcp",
"title": "Git Operations", "title": "Git Operations",
"description": "Advanced Git repository management and operations", "description": "Advanced Git repository management and operations",
@ -304,9 +302,7 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
"packages": [ "packages": [
{ {
"registryType": "oci", "registryType": "oci",
"registryBaseUrl": "https://ghcr.io", "identifier": "ghcr.io/username/git-operations-mcp:1.0.0",
"identifier": "username/git-operations-mcp",
"version": "1.0.0",
"transport": { "transport": {
"type": "stdio" "type": "stdio"
} }
@ -315,9 +311,9 @@ LABEL io.modelcontextprotocol.server.name="io.github.username/server-name"
} }
``` ```
The identifier is `namespace/repository`, and version is the tag and optionally digest. The identifier format is `registry/namespace/repository:tag` (e.g., `docker.io/user/app:1.0.0` or `ghcr.io/user/app:1.0.0`). The version can also be specified as a digest.
The official MCP registry currently supports Docker Hub (`https://docker.io`) and GitHub Container Registry (`https://ghcr.io`). The official MCP registry currently supports Docker Hub (`docker.io`) and GitHub Container Registry (`ghcr.io`).
</details> </details>
@ -339,7 +335,7 @@ openssl dgst -sha256 server.mcpb
### Example server.json ### Example server.json
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.username/image-processor-mcp", "name": "io.github.username/image-processor-mcp",
"title": "Image Processor", "title": "Image Processor",
"description": "Process and transform images with various filters", "description": "Process and transform images with various filters",
@ -348,7 +344,6 @@ openssl dgst -sha256 server.mcpb
{ {
"registryType": "mcpb", "registryType": "mcpb",
"identifier": "https://github.com/username/image-processor-mcp/releases/download/v1.0.0/image-processor.mcpb", "identifier": "https://github.com/username/image-processor-mcp/releases/download/v1.0.0/image-processor.mcpb",
"version": "1.0.0",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce", "fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": { "transport": {
"type": "stdio" "type": "stdio"
@ -385,7 +380,7 @@ Add the `remotes` field to your `server.json` (can coexist with `packages`):
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "com.yourcompany/acme-analytics", "name": "com.yourcompany/acme-analytics",
"title": "ACME Analytics", "title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform", "description": "Real-time business intelligence and reporting platform",

View file

@ -1,9 +1,9 @@
openapi: 3.1.0 openapi: 3.1.0
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema" jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"
$id: https://modelcontextprotocol.io/schemas/draft/2025-10-11/server-registry-openapi $id: https://modelcontextprotocol.io/schemas/draft/2025-10-17/server-registry-openapi
info: info:
title: MCP Server Registry API title: MCP Server Registry API
version: "2025-10-11" version: "2025-10-17"
summary: API for discovering and accessing MCP server metadata summary: API for discovering and accessing MCP server metadata
description: | description: |
Specification for a theoretical REST API that serves up metadata about MCP servers. Specification for a theoretical REST API that serves up metadata about MCP servers.
@ -616,7 +616,7 @@ components:
type: string type: string
format: uri format: uri
description: JSON Schema URI for this server.json format description: JSON Schema URI for this server.json format
example: "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json" example: "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json"
packages: packages:
type: array type: array
items: items:

View file

@ -2,6 +2,60 @@
Changes to the server.json schema and format. Changes to the server.json schema and format.
## 2025-10-17
### Changed
The `version` field is now **optional** for MCPB packages, providing flexibility for publishers.
**Key Changes:**
- **MCPB packages can now include an optional `version` field** - Previously rejected by validation, MCPB packages can now optionally specify a version field for clarity and metadata purposes.
- **Both formats are valid**:
- MCPB packages **with** version field: Provides explicit version metadata
- MCPB packages **without** version field: Version information is embedded in the download URL (as before)
**Migration:**
Publishers using MCPB packages can optionally add a `version` field to their package configuration. This is particularly useful when:
- The version information is not clearly visible in the download URL
- You want to provide explicit version metadata for tooling and clients
- You need consistent version tracking across different package types
Existing MCPB packages without the version field continue to work without any changes.
**Example - MCPB Package with optional version:**
```json
{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"version": "1.0.0",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
```
**Example - MCPB Package without version (still valid):**
```json
{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
```
### Schema Version
- Schema version: `2025-10-11` → `2025-10-17`
## 2025-10-11 ## 2025-10-11
### Changed ### Changed

View file

@ -22,7 +22,7 @@ The official registry has some more restrictions on top of this. See the [offici
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/brave-search", "name": "io.modelcontextprotocol.anonymous/brave-search",
"description": "MCP server for Brave Search API integration", "description": "MCP server for Brave Search API integration",
"title": "Brave Search", "title": "Brave Search",
@ -69,7 +69,7 @@ For MCP servers located within a subdirectory of a larger repository (monorepo s
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.modelcontextprotocol/everything", "name": "io.modelcontextprotocol/everything",
"description": "MCP server that exercises all the features of the MCP protocol", "description": "MCP server that exercises all the features of the MCP protocol",
"title": "Everything", "title": "Everything",
@ -108,7 +108,7 @@ Suppose your MCP server application requires a `mcp start` CLI arguments to star
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.joelverhagen/knapcode-samplemcpserver", "name": "io.github.joelverhagen/knapcode-samplemcpserver",
"description": "Sample NuGet MCP server for a random number and random weather", "description": "Sample NuGet MCP server for a random number and random weather",
"version": "0.4.0-beta", "version": "0.4.0-beta",
@ -152,7 +152,7 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.modelcontextprotocol/filesystem", "name": "io.github.modelcontextprotocol/filesystem",
"description": "Node.js server implementing Model Context Protocol (MCP) for filesystem operations.", "description": "Node.js server implementing Model Context Protocol (MCP) for filesystem operations.",
"title": "Filesystem", "title": "Filesystem",
@ -252,7 +252,7 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/mcp-fs", "name": "io.modelcontextprotocol.anonymous/mcp-fs",
"description": "Cloud-hosted MCP filesystem server", "description": "Cloud-hosted MCP filesystem server",
"repository": { "repository": {
@ -286,7 +286,7 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.example/weather-mcp", "name": "io.github.example/weather-mcp",
"description": "Python MCP server for weather data access", "description": "Python MCP server for weather data access",
"title": "Weather", "title": "Weather",
@ -342,7 +342,7 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.joelverhagen/knapcode-samplemcpserver", "name": "io.github.joelverhagen/knapcode-samplemcpserver",
"description": "Sample NuGet MCP server for a random number and random weather", "description": "Sample NuGet MCP server for a random number and random weather",
"repository": { "repository": {
@ -391,7 +391,7 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.github.example/database-manager", "name": "io.github.example/database-manager",
"description": "MCP server for database operations with support for multiple database types", "description": "MCP server for database operations with support for multiple database types",
"repository": { "repository": {
@ -500,7 +500,7 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/hybrid-mcp", "name": "io.modelcontextprotocol.anonymous/hybrid-mcp",
"description": "MCP server available as both local package and remote service", "description": "MCP server available as both local package and remote service",
"title": "Hybrid", "title": "Hybrid",
@ -586,7 +586,7 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.modelcontextprotocol/text-editor", "name": "io.modelcontextprotocol/text-editor",
"description": "MCP Bundle server for advanced text editing capabilities", "description": "MCP Bundle server for advanced text editing capabilities",
"title": "Text Editor", "title": "Text Editor",
@ -629,7 +629,7 @@ Some CLI tools bundle an MCP server, without a standalone MCP package or a publi
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.snyk/cli-mcp", "name": "io.snyk/cli-mcp",
"description": "MCP server provided by the Snyk CLI", "description": "MCP server provided by the Snyk CLI",
"title": "Snyk", "title": "Snyk",
@ -664,7 +664,7 @@ For MCP servers that follow a custom installation path or are embedded in applic
```json ```json
{ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/embedded-mcp", "name": "io.modelcontextprotocol.anonymous/embedded-mcp",
"description": "MCP server embedded in a Desktop app", "description": "MCP server embedded in a Desktop app",
"websiteUrl": "https://anonymous.modelcontextprotocol.io/embedded-mcp-guide", "websiteUrl": "https://anonymous.modelcontextprotocol.io/embedded-mcp-guide",

View file

@ -1,6 +1,6 @@
{ {
"$comment": "This file is auto-generated from docs/reference/api/openapi.yaml. Do not edit manually. Run 'make generate-schema' to update.", "$comment": "This file is auto-generated from docs/reference/api/openapi.yaml. Do not edit manually. Run 'make generate-schema' to update.",
"$id": "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json", "$id": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"$ref": "#/definitions/ServerDetail", "$ref": "#/definitions/ServerDetail",
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"definitions": { "definitions": {
@ -373,7 +373,7 @@
"properties": { "properties": {
"$schema": { "$schema": {
"description": "JSON Schema URI for this server.json format", "description": "JSON Schema URI for this server.json format",
"example": "https://static.modelcontextprotocol.io/schemas/2025-10-11/server.schema.json", "example": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"format": "uri", "format": "uri",
"type": "string" "type": "string"
}, },

View file

@ -27,14 +27,13 @@ func ValidateMCPB(ctx context.Context, pkg model.Package, _ string) error {
return ErrMissingIdentifierForMCPB return ErrMissingIdentifierForMCPB
} }
// Validate that old format fields are not present // Validate that registryBaseUrl is not present
// MCPB packages use URL in identifier, version is embedded in the URL // MCPB packages use full download URLs in identifier
if pkg.Version != "" {
return fmt.Errorf("MCPB packages must not have 'version' field - version should be embedded in the download URL")
}
if pkg.RegistryBaseURL != "" { if pkg.RegistryBaseURL != "" {
return fmt.Errorf("MCPB packages must not have 'registryBaseUrl' field - use the full download URL in 'identifier' instead") return fmt.Errorf("MCPB packages must not have 'registryBaseUrl' field - use the full download URL in 'identifier' instead")
} }
// Note: version field is optional for MCPB packages
// It can be included for clarity or omitted if the version is embedded in the download URL
err := validateMCPBUrl(pkg.Identifier) err := validateMCPBUrl(pkg.Identifier)
if err != nil { if err != nil {

View file

@ -120,67 +120,68 @@ func TestValidateMCPB(t *testing.T) {
} }
} }
func TestValidateMCPB_RejectsExtraFields(t *testing.T) { func TestValidateMCPB_OptionalFields(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tests := []struct { tests := []struct {
name string name string
pkg model.Package pkg model.Package
expectError bool
errorMessage string errorMessage string
}{ }{
{ {
name: "MCPB package with version field should be rejected", name: "MCPB package with optional version field should pass",
pkg: model.Package{ pkg: model.Package{
RegistryType: model.RegistryTypeMCPB, RegistryType: model.RegistryTypeMCPB,
Identifier: "https://github.com/example/repo/releases/download/v1.0.0/server.mcpb", Identifier: "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.2/airtable-mcp-server.mcpb",
Version: "1.0.0", Version: "1.7.2",
FileSHA256: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce", FileSHA256: "8220de07a08ebe908f04da139ea03dbfe29758141347e945da60535fb7bcca20",
}, },
errorMessage: "MCPB packages must not have 'version' field", expectError: false,
},
{
name: "MCPB package without version field should pass",
pkg: model.Package{
RegistryType: model.RegistryTypeMCPB,
Identifier: "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.2/airtable-mcp-server.mcpb",
FileSHA256: "8220de07a08ebe908f04da139ea03dbfe29758141347e945da60535fb7bcca20",
},
expectError: false,
}, },
{ {
name: "MCPB package with registryBaseUrl should be rejected", name: "MCPB package with registryBaseUrl should be rejected",
pkg: model.Package{ pkg: model.Package{
RegistryType: model.RegistryTypeMCPB, RegistryType: model.RegistryTypeMCPB,
Identifier: "https://github.com/example/repo/releases/download/v1.0.0/server.mcpb", Identifier: "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.2/airtable-mcp-server.mcpb",
RegistryBaseURL: "https://github.com", RegistryBaseURL: "https://github.com",
FileSHA256: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce", FileSHA256: "8220de07a08ebe908f04da139ea03dbfe29758141347e945da60535fb7bcca20",
}, },
expectError: true,
errorMessage: "MCPB packages must not have 'registryBaseUrl' field", errorMessage: "MCPB packages must not have 'registryBaseUrl' field",
}, },
{ {
name: "MCPB package with both extra fields should fail on version first", name: "MCPB package with both version and registryBaseUrl should fail on registryBaseUrl",
pkg: model.Package{ pkg: model.Package{
RegistryType: model.RegistryTypeMCPB, RegistryType: model.RegistryTypeMCPB,
Identifier: "https://github.com/example/repo/releases/download/v1.0.0/server.mcpb", Identifier: "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.2/airtable-mcp-server.mcpb",
Version: "1.0.0", Version: "1.7.2",
RegistryBaseURL: "https://github.com", RegistryBaseURL: "https://github.com",
FileSHA256: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce", FileSHA256: "8220de07a08ebe908f04da139ea03dbfe29758141347e945da60535fb7bcca20",
}, },
errorMessage: "MCPB packages must not have 'version' field", expectError: true,
}, errorMessage: "MCPB packages must not have 'registryBaseUrl' field",
{
name: "MCPB package with canonical format should pass extra field validation",
pkg: model.Package{
RegistryType: model.RegistryTypeMCPB,
Identifier: "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.2/airtable-mcp-server.mcpb",
FileSHA256: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
},
errorMessage: "", // Should pass extra field check (will succeed as this is a real package)
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := registries.ValidateMCPB(ctx, tt.pkg, "com.example/test") err := registries.ValidateMCPB(ctx, tt.pkg, "io.github.domdomegg/airtable-mcp-server")
if tt.errorMessage != "" { if tt.expectError {
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), tt.errorMessage) assert.Contains(t, err.Error(), tt.errorMessage)
} else if err != nil { } else {
// Should not fail with extra field error (may pass completely for real package) assert.NoError(t, err)
assert.NotContains(t, err.Error(), "must not have 'version'")
assert.NotContains(t, err.Error(), "must not have 'registryBaseUrl'")
} }
}) })
} }

View file

@ -47,7 +47,7 @@ func TestValidate(t *testing.T) {
expectedError: "schema version https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json is not supported", expectedError: "schema version https://static.modelcontextprotocol.io/schemas/2025-01-27/server.schema.json is not supported",
}, },
{ {
name: "Schema version accepts current schema (2025-09-29)", name: "Schema version accepts current schema (2025-10-17)",
serverDetail: apiv0.ServerJSON{ serverDetail: apiv0.ServerJSON{
Schema: model.CurrentSchemaURL, Schema: model.CurrentSchemaURL,
Name: "com.example/test-server", Name: "com.example/test-server",

View file

@ -32,7 +32,7 @@ type ServerMeta struct {
} }
type ServerJSON struct { type ServerJSON struct {
Schema string `json:"$schema" required:"true" minLength:"1" format:"uri" doc:"JSON Schema URI for this server.json format" example:"https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json"` Schema string `json:"$schema" required:"true" minLength:"1" format:"uri" doc:"JSON Schema URI for this server.json format" example:"https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json"`
Name string `json:"name" minLength:"3" maxLength:"200" pattern:"^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$" doc:"Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name." example:"io.github.user/weather"` Name string `json:"name" minLength:"3" maxLength:"200" pattern:"^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$" doc:"Server name in reverse-DNS format. Must contain exactly one forward slash separating namespace from server name." example:"io.github.user/weather"`
Description string `json:"description" minLength:"1" maxLength:"100" doc:"Clear human-readable explanation of server functionality." example:"MCP server providing weather data and forecasts via OpenWeatherMap API"` Description string `json:"description" minLength:"1" maxLength:"100" doc:"Clear human-readable explanation of server functionality." example:"MCP server providing weather data and forecasts via OpenWeatherMap API"`
Title string `json:"title,omitempty" minLength:"1" maxLength:"100" doc:"Optional human-readable title or display name for the MCP server." example:"Weather API"` Title string `json:"title,omitempty" minLength:"1" maxLength:"100" doc:"Optional human-readable title or display name for the MCP server." example:"Weather API"`

View file

@ -38,7 +38,7 @@ const (
// Schema versions // Schema versions
const ( const (
// CurrentSchemaVersion is the current supported schema version date // CurrentSchemaVersion is the current supported schema version date
CurrentSchemaVersion = "2025-09-29" CurrentSchemaVersion = "2025-10-17"
// CurrentSchemaURL is the full URL to the current schema // CurrentSchemaURL is the full URL to the current schema
CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json" CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json"
) )

View file

@ -20,7 +20,7 @@ type Transport struct {
// - PyPI: RegistryType, Identifier (package name), Version, RegistryBaseURL (optional) // - PyPI: RegistryType, Identifier (package name), Version, RegistryBaseURL (optional)
// - NuGet: RegistryType, Identifier (package ID), Version, RegistryBaseURL (optional) // - NuGet: RegistryType, Identifier (package ID), Version, RegistryBaseURL (optional)
// - OCI: RegistryType, Identifier (full image reference like "ghcr.io/owner/repo:tag") // - OCI: RegistryType, Identifier (full image reference like "ghcr.io/owner/repo:tag")
// - MCPB: RegistryType, Identifier (download URL), FileSHA256 (required) // - MCPB: RegistryType, Identifier (download URL), Version (optional), FileSHA256 (required)
type Package struct { type Package struct {
// RegistryType indicates how to download packages (e.g., "npm", "pypi", "oci", "nuget", "mcpb") // RegistryType indicates how to download packages (e.g., "npm", "pypi", "oci", "nuget", "mcpb")
RegistryType string `json:"registryType" minLength:"1" doc:"Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')" example:"npm"` RegistryType string `json:"registryType" minLength:"1" doc:"Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')" example:"npm"`
@ -31,7 +31,7 @@ type Package struct {
// - For OCI: full image reference (e.g., "ghcr.io/owner/repo:v1.0.0") // - For OCI: full image reference (e.g., "ghcr.io/owner/repo:v1.0.0")
// - For MCPB: direct download URL // - For MCPB: direct download URL
Identifier string `json:"identifier" minLength:"1" doc:"Package identifier - either a package name (for registries) or URL (for direct downloads)" example:"@modelcontextprotocol/server-brave-search"` Identifier string `json:"identifier" minLength:"1" doc:"Package identifier - either a package name (for registries) or URL (for direct downloads)" example:"@modelcontextprotocol/server-brave-search"`
// Version is the package version (used by npm, pypi, nuget; not used by oci, mcpb where version is in the identifier) // Version is the package version (required for npm, pypi, nuget; optional for mcpb; not used by oci where version is in the identifier)
Version string `json:"version,omitempty" minLength:"1" doc:"Package version. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*')." example:"1.0.2"` Version string `json:"version,omitempty" minLength:"1" doc:"Package version. Must be a specific version. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', '>=1.2.3', '1.x', '1.*')." example:"1.0.2"`
// FileSHA256 is the SHA-256 hash for integrity verification (required for mcpb, optional for others) // FileSHA256 is the SHA-256 hash for integrity verification (required for mcpb, optional for others)
FileSHA256 string `json:"fileSha256,omitempty" pattern:"^[a-f0-9]{64}$" doc:"SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity." example:"fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"` FileSHA256 string `json:"fileSha256,omitempty" pattern:"^[a-f0-9]{64}$" doc:"SHA-256 hash of the package file for integrity verification. Required for MCPB packages and optional for other package types. Authors are responsible for generating correct SHA-256 hashes when creating server.json. If present, MCP clients must validate the downloaded file matches the hash before running packages to ensure file integrity." example:"fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"`

View file

@ -24,7 +24,8 @@ cleanup() {
go build -o ./bin/publisher ./cmd/publisher go build -o ./bin/publisher ./cmd/publisher
docker build -t registry . # Build the registry image with both tags, forcing a rebuild to ensure latest code
docker build --no-cache -t registry -t modelcontextprotocol/registry:dev .
trap cleanup EXIT trap cleanup EXIT