Skip to content

Commit 30aa373

Browse files
rdimitrovclaude
andcommitted
feat: Add discriminated union to Package schema for canonical references
- Updated OpenAPI schema with oneOf discriminated union pattern - Created separate schemas for NPM, PyPI, NuGet, OCI, and MCPB packages - Made field requirements type-specific (e.g., OCI doesn't require version) - Preserved all existing field names (registryType, identifier, etc.) - Added transport field requirement to PackageBase - Updated examples to include required transport field - Created PACKAGE_CANONICAL_REFS.md design document This enables: - OCI packages to use single canonical image references (ghcr.io/owner/repo:tag) - MCPB packages to use direct download URLs - NPM/PyPI/NuGet to continue using separate identifier + version - Clear validation rules per package type 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 568748e commit 30aa373

File tree

2 files changed

+348
-38
lines changed

2 files changed

+348
-38
lines changed

PACKAGE_CANONICAL_REFS.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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

Comments
 (0)