Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d97db44
Add discriminated union to Package schema for canonical references
rdimitrov Oct 8, 2025
90a363c
Make transport required in Package struct to match schema
rdimitrov Oct 8, 2025
e7159e6
Update OCI validator to support canonical image references with diges…
rdimitrov Oct 8, 2025
3923da9
Regenerate server.schema.json with discriminated union for packages
rdimitrov Oct 8, 2025
00554ea
Update OCI validator tests to use canonical references
rdimitrov Oct 8, 2025
8209174
Add implementation summary for canonical package references
rdimitrov Oct 8, 2025
46d10ef
Clarify database data migration requirement
rdimitrov Oct 8, 2025
6534597
Add database migration for canonical package references
rdimitrov Oct 8, 2025
803b35c
Convert seed data to canonical package format
rdimitrov Oct 8, 2025
4636b97
Update implementation summary with completed phases
rdimitrov Oct 8, 2025
252714b
Update publisher CLI to generate canonical OCI references
rdimitrov Oct 8, 2025
89b8bad
Update OCI package examples to use canonical references
rdimitrov Oct 8, 2025
9004288
Mark implementation as complete in summary
rdimitrov Oct 8, 2025
7bf9672
Add validation to reject old OCI package format
rdimitrov Oct 8, 2025
5123d11
Add validation to reject extra fields in MCPB packages
rdimitrov Oct 8, 2025
26e31d9
Add validation to reject fileSha256 in non-MCPB packages
rdimitrov Oct 8, 2025
b6f12cb
Fix linting errors and unit tests
rdimitrov Oct 8, 2025
1c81e60
Update the migration to handle the MCPB changes too
rdimitrov Oct 8, 2025
b6a47f7
Update the migration to remove fileSha256 properties
rdimitrov Oct 8, 2025
7c0bbf2
Streamline schema changes from type per package to flavors
rdimitrov Oct 10, 2025
fe2cdf5
Make schema types looser
rdimitrov Oct 10, 2025
dd2a68a
Revert the openapi schema to main
rdimitrov Oct 10, 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
75 changes: 51 additions & 24 deletions cmd/publisher/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,36 +267,63 @@ func createServerJSON(
packageType, packageIdentifier, packageVersion string,
envVars []model.KeyValueInput,
) apiv0.ServerJSON {
// Determine registry type and base URL
var registryType, registryBaseURL string
// Create package based on type
var pkg model.Package

switch packageType {
case model.RegistryTypeNPM:
registryType = model.RegistryTypeNPM
registryBaseURL = model.RegistryURLNPM
pkg = model.Package{
Copy link
Member

Choose a reason for hiding this comment

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

[can be a follow up PR if you agree] I feel like we should use the "flavors" of Package types internally, and just map each of them to a "list of supported registry types" that can be expanded over time as support is added for more registry types. Instead of bespoke recreating each of the flavors on a per-type basis from Package in this switch statement.

RegistryType: model.RegistryTypeNPM,
Identifier: packageIdentifier,
Version: packageVersion,
EnvironmentVariables: envVars,
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
}
case model.RegistryTypePyPI:
registryType = model.RegistryTypePyPI
registryBaseURL = model.RegistryURLPyPI
pkg = model.Package{
RegistryType: model.RegistryTypePyPI,
Identifier: packageIdentifier,
Version: packageVersion,
EnvironmentVariables: envVars,
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
}
case model.RegistryTypeOCI:
registryType = model.RegistryTypeOCI
registryBaseURL = model.RegistryURLDocker
// OCI packages use canonical references: registry/namespace/image:tag
// Format: docker.io/username/image:version
canonicalRef := fmt.Sprintf("docker.io/%s:%s", packageIdentifier, packageVersion)
pkg = model.Package{
RegistryType: model.RegistryTypeOCI,
Identifier: canonicalRef,
// No Version field for OCI - it's embedded in the canonical reference
EnvironmentVariables: envVars,
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
}
case "url":
registryType = "url"
registryBaseURL = ""
pkg = model.Package{
RegistryType: "url",
Identifier: packageIdentifier,
Version: packageVersion,
EnvironmentVariables: envVars,
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
}
default:
registryType = packageType
registryBaseURL = ""
}

// Create package
pkg := model.Package{
RegistryType: registryType,
RegistryBaseURL: registryBaseURL,
Identifier: packageIdentifier,
Version: packageVersion,
EnvironmentVariables: envVars,
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
pkg = model.Package{
RegistryType: packageType,
Identifier: packageIdentifier,
Version: packageVersion,
EnvironmentVariables: envVars,
Transport: model.Transport{
Type: model.TransportTypeStdio,
},
}
}

// Create server structure
Expand Down
12 changes: 5 additions & 7 deletions data/seed.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
{
"src": "https://airtable.com/images/favicon/favicon-32x32.png",
"mimeType": "image/png",
"sizes": ["32x32"]
"sizes": [
"32x32"
]
}
],
"packages": [
Expand All @@ -35,8 +37,7 @@
},
{
"registryType": "oci",
"identifier": "domdomegg/airtable-mcp-server",
"version": "1.7.2",
"identifier": "docker.io/domdomegg/airtable-mcp-server:1.7.2",
"runtimeHint": "docker",
"transport": {
"type": "stdio"
Expand All @@ -53,7 +54,6 @@
{
"registryType": "mcpb",
"identifier": "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.2/airtable-mcp-server.mcpb",
"version": "1.7.2",
"fileSha256": "8220de07a08ebe908f04da139ea03dbfe29758141347e945da60535fb7bcca20",
"transport": {
"type": "stdio"
Expand Down Expand Up @@ -90,8 +90,7 @@
},
{
"registryType": "oci",
"identifier": "domdomegg/airtable-mcp-server",
"version": "1.7.3",
"identifier": "docker.io/domdomegg/airtable-mcp-server:1.7.3",
"runtimeHint": "docker",
"transport": {
"type": "stdio"
Expand All @@ -108,7 +107,6 @@
{
"registryType": "mcpb",
"identifier": "https://github.com/domdomegg/airtable-mcp-server/releases/download/v1.7.3/airtable-mcp-server.mcpb",
"version": "1.7.3",
"fileSha256": "0f28a9129cfebd262dfb77854c872355d21401bb3e056575b3027081f5d570ca",
"transport": {
"type": "stdio"
Expand Down
167 changes: 130 additions & 37 deletions docs/reference/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ paths:
registryBaseUrl: "https://registry.npmjs.org"
identifier: "@modelcontextprotocol/server-filesystem"
version: "1.0.2"
transport:
type: "stdio"
_meta:
io.modelcontextprotocol.registry/publisher-provided:
tool: "publisher-cli"
Expand All @@ -159,6 +161,8 @@ paths:
registryBaseUrl: "https://registry.npmjs.org"
identifier: "@example/mcp-demo-server"
version: "1.0.0"
transport:
type: "stdio"
_meta:
io.modelcontextprotocol.registry/publisher-provided:
contactEmail: "[email protected]"
Expand Down Expand Up @@ -266,51 +270,28 @@ components:
example: 30

Package:
description: "Package types grouped by structural similarity. The 'registryType' field determines package behavior and which fields are expected. Packages should match at least one of the structural patterns below."
anyOf:
- $ref: '#/components/schemas/RegistryPackage'
- $ref: '#/components/schemas/ImagePackage'
- $ref: '#/components/schemas/BinaryPackage'

PackageBase:
type: object
description: "Common fields shared across all package types"
required:
- registryType
- identifier
- version
- transport
properties:
registryType:
type: string
description: Registry type indicating how to download packages (e.g., 'npm', 'pypi', 'oci', 'nuget', 'mcpb')
examples:
- "npm"
- "pypi"
- "oci"
- "nuget"
- "mcpb"
registryBaseUrl:
type: string
format: uri
description: Base URL of the package registry
description: "Package type discriminator. Common values include npm, pypi, nuget (registry-based), oci (container images), and mcpb (direct binaries)."
examples:
- "https://registry.npmjs.org"
- "https://pypi.org"
- "https://docker.io"
- "https://api.nuget.org"
- "https://github.com"
- "https://gitlab.com"
identifier:
type: string
description: Package identifier - either a package name (for registries) or URL (for direct downloads)
examples:
- "@modelcontextprotocol/server-brave-search"
- "https://github.com/example/releases/download/v1.0.0/package.mcpb"
version:
type: string
description: "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"
minLength: 1
not:
const: "latest"
fileSha256:
type: string
description: "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"
pattern: "^[a-f0-9]{64}$"
- npm
- pypi
- nuget
- oci
- mcpb
runtimeHint:
type: string
description: A hint to help clients determine the appropriate runtime for the package. This field should be provided when `runtimeArguments` are present.
Expand All @@ -337,6 +318,118 @@ components:
items:
$ref: '#/components/schemas/KeyValueInput'

RegistryPackage:
Copy link
Member

Choose a reason for hiding this comment

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

RegistryPackage as a name feels a little overloaded. Maybe LanguagePackage?

description: |
Registry-based packages (npm, pypi, nuget, and others).
These packages use a registry to store and distribute packages, identified by package name + version.
The registryBaseUrl can be customized or defaults to the standard registry for each type:
- npm: https://registry.npmjs.org
- pypi: https://pypi.org
- nuget: https://api.nuget.org/v3/index.json
allOf:
- $ref: '#/components/schemas/PackageBase'
- type: object
required:
- identifier
- version
properties:
registryType:
type: string
description: "Type of package registry"
examples:
- npm
- pypi
- nuget
identifier:
type: string
description: |
Package name or identifier in the registry.
- npm: Package name with or without @scope (e.g., "@modelcontextprotocol/server-filesystem", "express")
- pypi: Package name (e.g., "mcp-server-time", "flask")
- nuget: Package ID (e.g., "ModelContextProtocol.Server", "Newtonsoft.Json")
examples:
- "@modelcontextprotocol/server-filesystem"
- "mcp-server-time"
- "ModelContextProtocol.Server"
minLength: 1
version:
type: string
description: "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.*', 'latest')."
example: "1.0.2"
minLength: 1
not:
const: "latest"
registryBaseUrl:
type: string
format: uri
description: |
Registry base URL. Optional, with type-specific defaults:
- npm: https://registry.npmjs.org
- pypi: https://pypi.org
- nuget: https://api.nuget.org/v3/index.json
examples:
- "https://registry.npmjs.org"
- "https://pypi.org"
- "https://api.nuget.org/v3/index.json"

ImagePackage:
description: |
OCI container image packages.
Uses a single canonical reference that includes registry, namespace, image, and tag/digest.
Format: [registry/]namespace/image[:tag][@digest]
Registry defaults to docker.io if not specified.
Examples: 'ghcr.io/owner/repo:v1.0.0', 'docker.io/library/postgres:16', 'owner/repo:latest'
allOf:
- $ref: '#/components/schemas/PackageBase'
- type: object
required:
- identifier
properties:
registryType:
type: string
const: oci
identifier:
type: string
description: |
Full OCI image reference including registry, namespace, image, and tag/digest.
Registry defaults to docker.io if not specified. Must include at least a tag or digest.
examples:
- "ghcr.io/modelcontextprotocol/server-example:v1.0.0"
- "docker.io/library/postgres:16"
- "owner/repo:latest"
- "ghcr.io/owner/repo@sha256:abc123..."
- "owner/repo:v1.0.0@sha256:abc123..."
minLength: 1

BinaryPackage:
description: |
Direct binary download packages (MCPB format).
Uses a direct HTTPS download URL with SHA-256 hash for integrity verification.
Currently supports GitHub and GitLab release assets.
allOf:
- $ref: '#/components/schemas/PackageBase'
- type: object
required:
- identifier
- fileSha256
properties:
registryType:
type: string
const: mcpb
Copy link
Member

Choose a reason for hiding this comment

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

Can we drop this const in favor of an example?

Copy link
Member

Choose a reason for hiding this comment

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

Same for the other consts on the other Package types

identifier:
type: string
format: uri
description: "Direct HTTPS download URL to the MCPB binary. Must be from GitHub or GitLab release assets."
examples:
- "https://github.com/owner/repo/releases/download/v1.0.0/server-macos-arm64.mcpb"
- "https://gitlab.com/owner/repo/-/releases/v1.0.0/downloads/server.mcpb"
minLength: 1
fileSha256:
type: string
description: "SHA-256 hash of the file for integrity verification. Required for MCPB packages. MCP clients must validate the downloaded file matches this hash before execution."
example: "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce"
pattern: "^[a-f0-9]{64}$"

Input:
type: object
properties:
Expand Down
10 changes: 2 additions & 8 deletions docs/reference/server-json/generic-server-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,7 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp
},
{
"registryType": "oci",
"registryBaseUrl": "https://docker.io",
"identifier": "mcp/filesystem",
"version": "1.0.2",
"identifier": "docker.io/mcp/filesystem:1.0.2",
"transport": {
"type": "stdio"
},
Expand Down Expand Up @@ -405,9 +403,7 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
"packages": [
{
"registryType": "oci",
"registryBaseUrl": "https://docker.io",
"identifier": "example/database-manager-mcp",
"version": "3.1.0",
"identifier": "docker.io/example/database-manager-mcp:3.1.0",
"transport": {
"type": "stdio"
},
Expand Down Expand Up @@ -602,9 +598,7 @@ The `dnx` tool ships with the .NET 10 SDK, starting with Preview 6.
"packages": [
{
"registryType": "mcpb",
"registryBaseUrl": "https://github.com",
"identifier": "https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb",
"version": "1.0.2",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
Expand Down
Loading
Loading