|
| 1 | +# Package Canonical Reference Format |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document describes the canonical reference format for each package type in the MCP registry. The goal is to align with industry standards while using a single `Package` struct that can represent all types. |
| 6 | + |
| 7 | +## Design Principles |
| 8 | + |
| 9 | +1. **Industry alignment**: Follow each ecosystem's standard conventions |
| 10 | +2. **Single struct**: Use one `Package` type with a `type` discriminator field |
| 11 | +3. **Validation-based**: Rely on schema validation + Go validation rather than compile-time type safety |
| 12 | +4. **Database-friendly**: Design for future migration from JSONB to individual columns |
| 13 | + |
| 14 | +## Package Types and Their Canonical Formats |
| 15 | + |
| 16 | +### NPM Packages (`type: "npm"`) |
| 17 | + |
| 18 | +**Industry Standard**: Separate name + version |
| 19 | +- CLI: `npm install @scope/[email protected]` |
| 20 | +- package.json: `"@scope/package": "1.0.0"` |
| 21 | + |
| 22 | +**Our Format**: |
| 23 | +```json |
| 24 | +{ |
| 25 | + "type": "npm", |
| 26 | + "identifier": "@modelcontextprotocol/server-filesystem", |
| 27 | + "version": "1.0.2", |
| 28 | + "registryBaseUrl": "https://registry.npmjs.org", |
| 29 | + "transport": { "type": "stdio" } |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +**Fields**: |
| 34 | +- `identifier`: NPM package name (with or without @scope) |
| 35 | +- `version`: Specific version (no ranges) |
| 36 | +- `registryBaseUrl`: Optional, defaults to https://registry.npmjs.org |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +### PyPI Packages (`type: "pypi"`) |
| 41 | + |
| 42 | +**Industry Standard**: Combined name==version |
| 43 | +- CLI: `pip install package==1.0.0` |
| 44 | +- requirements.txt: `package==1.0.0` |
| 45 | + |
| 46 | +**Our Format**: |
| 47 | +```json |
| 48 | +{ |
| 49 | + "type": "pypi", |
| 50 | + "identifier": "mcp-server-time", |
| 51 | + "version": "1.0.2", |
| 52 | + "registryBaseUrl": "https://pypi.org", |
| 53 | + "transport": { "type": "stdio" } |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +**Fields**: |
| 58 | +- `identifier`: PyPI package name |
| 59 | +- `version`: Specific version |
| 60 | +- `registryBaseUrl`: Optional, defaults to https://pypi.org |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +### NuGet Packages (`type: "nuget"`) |
| 65 | + |
| 66 | +**Industry Standard**: Separate ID + version |
| 67 | +- CLI: `dotnet add package PackageId --version 1.0.0` |
| 68 | +- .csproj: `<PackageReference Include="PackageId" Version="1.0.0" />` |
| 69 | + |
| 70 | +**Our Format**: |
| 71 | +```json |
| 72 | +{ |
| 73 | + "type": "nuget", |
| 74 | + "identifier": "ModelContextProtocol.Server", |
| 75 | + "version": "1.0.2", |
| 76 | + "registryBaseUrl": "https://api.nuget.org/v3/index.json", |
| 77 | + "transport": { "type": "stdio" } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +**Fields**: |
| 82 | +- `identifier`: NuGet package ID |
| 83 | +- `version`: Specific version |
| 84 | +- `registryBaseUrl`: Optional, defaults to https://api.nuget.org/v3/index.json |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +### OCI Container Images (`type: "oci"`) |
| 89 | + |
| 90 | +**Industry Standard**: Single canonical reference |
| 91 | +- CLI: `docker pull ghcr.io/owner/repo:v1.0.0` |
| 92 | +- Also supports: `ghcr.io/owner/repo@sha256:abc...` |
| 93 | +- Format: `[registry/]namespace/image[:tag][@digest]` |
| 94 | + |
| 95 | +**Our Format**: |
| 96 | +```json |
| 97 | +{ |
| 98 | + "type": "oci", |
| 99 | + "identifier": "ghcr.io/modelcontextprotocol/server-example:v1.0.0", |
| 100 | + "transport": { "type": "stdio" } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +**Fields**: |
| 105 | +- `identifier`: Full OCI image reference including registry, namespace, image, and tag/digest |
| 106 | +- `version`: NOT USED (version info is in the identifier tag/digest) |
| 107 | +- `registryBaseUrl`: NOT USED (registry is part of the identifier) |
| 108 | + |
| 109 | +**Valid identifier formats**: |
| 110 | +- `ghcr.io/owner/repo:v1.0.0` (registry + tag) |
| 111 | +- `ghcr.io/owner/repo@sha256:abc...` (registry + digest) |
| 112 | +- `ghcr.io/owner/repo:v1.0.0@sha256:abc...` (registry + tag + digest) |
| 113 | +- `owner/repo:latest` (defaults to docker.io registry) |
| 114 | +- `library/postgres:16` (defaults to docker.io registry, official image) |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +### MCPB Binary Packages (`type: "mcpb"`) |
| 119 | + |
| 120 | +**Industry Standard**: Direct download URL |
| 121 | +- Just a URL to the binary file |
| 122 | + |
| 123 | +**Our Format**: |
| 124 | +```json |
| 125 | +{ |
| 126 | + "type": "mcpb", |
| 127 | + "identifier": "https://github.com/owner/repo/releases/download/v1.0.0/server-macos-arm64.mcpb", |
| 128 | + "fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce", |
| 129 | + "transport": { "type": "stdio" } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +**Fields**: |
| 134 | +- `identifier`: Full HTTPS URL to the binary file (must be GitHub or GitLab release) |
| 135 | +- `version`: NOT USED (version info is in the URL) |
| 136 | +- `registryBaseUrl`: NOT USED (inferred from URL hostname) |
| 137 | +- `fileSha256`: REQUIRED for integrity verification |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## Single Package Struct |
| 142 | + |
| 143 | +```go |
| 144 | +type Package struct { |
| 145 | + // Discriminator |
| 146 | + Type string `json:"type"` // "npm", "pypi", "nuget", "oci", "mcpb" |
| 147 | + |
| 148 | + // Universal identifier (used by all types) |
| 149 | + Identifier string `json:"identifier"` |
| 150 | + |
| 151 | + // Optional version (used by npm, pypi, nuget; NOT used by oci, mcpb) |
| 152 | + Version string `json:"version,omitempty"` |
| 153 | + |
| 154 | + // Optional registry URL (used by npm, pypi, nuget; NOT used by oci, mcpb) |
| 155 | + RegistryBaseURL string `json:"registryBaseUrl,omitempty"` |
| 156 | + |
| 157 | + // Optional file hash (REQUIRED for mcpb, optional for others) |
| 158 | + FileSHA256 string `json:"fileSha256,omitempty"` |
| 159 | + |
| 160 | + // Common fields (all types) |
| 161 | + Transport Transport `json:"transport"` |
| 162 | + RuntimeHint string `json:"runtimeHint,omitempty"` |
| 163 | + RuntimeArguments []Argument `json:"runtimeArguments,omitempty"` |
| 164 | + PackageArguments []Argument `json:"packageArguments,omitempty"` |
| 165 | + EnvironmentVariables []KeyValueInput `json:"environmentVariables,omitempty"` |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +## Validation Rules |
| 170 | + |
| 171 | +### Type-specific field requirements: |
| 172 | + |
| 173 | +**NPM, PyPI, NuGet**: |
| 174 | +- REQUIRED: `type`, `identifier`, `version`, `transport` |
| 175 | +- OPTIONAL: `registryBaseUrl` (has defaults) |
| 176 | + |
| 177 | +**OCI**: |
| 178 | +- REQUIRED: `type`, `identifier`, `transport` |
| 179 | +- UNUSED: `version`, `registryBaseUrl` (must be empty or omitted) |
| 180 | + |
| 181 | +**MCPB**: |
| 182 | +- REQUIRED: `type`, `identifier`, `fileSha256`, `transport` |
| 183 | +- UNUSED: `version`, `registryBaseUrl` (must be empty or omitted, though registryBaseUrl can be inferred) |
| 184 | + |
| 185 | +## Migration Path |
| 186 | + |
| 187 | +1. **Phase 1**: Update OpenAPI schema with discriminated union using `oneOf` |
| 188 | +2. **Phase 2**: Keep existing `Package` struct, ensure it matches schema |
| 189 | +3. **Phase 3**: Update validators to handle canonical references |
| 190 | +4. **Phase 4**: Create database migration to transform existing JSONB data |
| 191 | +5. **Phase 5**: Update API layer to use new format |
| 192 | +6. **Phase 6**: Update publisher CLI to generate new format |
| 193 | +7. **Phase 7**: Update seed data and tests |
| 194 | +8. **Phase 8**: Update documentation |
| 195 | + |
| 196 | +## Examples |
| 197 | + |
| 198 | +See the OpenAPI schema examples for complete working examples of each package type. |
0 commit comments