Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion docs/modelcontextprotocol-io/package-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ For Docker/OCI images, the MCP Registry currently supports:

- Docker Hub (`docker.io`)
- GitHub Container Registry (`ghcr.io`)
- Quay.io (`quay.io`)
- Google Artifact Registry (any `*.pkg.dev` domain)
- Azure Container Registry (`*.azurecr.io`)
- Microsoft Container Registry (`mcr.microsoft.com`)
Expand All @@ -156,7 +157,7 @@ Docker/OCI images use `"registryType": "oci"` in `server.json`. For example:
}
```

The format of `identifier` is `registry/namespace/repository:tag`. For example, `docker.io/user/app:1.0.0` or `ghcr.io/user/app:1.0.0`. The tag can also be specified as a digest.
The format of `identifier` is `registry/namespace/repository:tag`. For example, `docker.io/user/app:1.0.0`, `ghcr.io/user/app:1.0.0`, or `quay.io/myorg/my-mcp-server:1.0.0`. The tag can also be specified as a digest.

### Ownership Verification

Expand Down
20 changes: 20 additions & 0 deletions docs/reference/server-json/generic-server-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,26 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp
}
```

The same `registryType` / `identifier` pattern works for other supported OCI hosts. For example, an image on Quay.io:

```json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.example/quay-sample-mcp",
"description": "Example MCP server distributed as an OCI image on Quay.io",
"version": "1.0.0",
"packages": [
{
"registryType": "oci",
"identifier": "quay.io/myorg/my-mcp-server:1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```

### Remote Server Example

```json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Only trusted public registries are supported. Private registries and alternative
- **Docker/OCI**:
- Docker Hub (`docker.io`)
- GitHub Container Registry (`ghcr.io`)
- Quay.io (`quay.io`)
- Google Artifact Registry (`*.pkg.dev`)
- Azure Container Registry (`*.azurecr.io`)
- Microsoft Container Registry (`mcr.microsoft.com`)
Expand Down
3 changes: 3 additions & 0 deletions internal/validators/registries/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ var allowedOCIRegistries = map[string]bool{
"index.docker.io": true, // Docker Hub index
// GitHub Container Registry
"ghcr.io": true,
// Red Hat Quay
"quay.io": true,
// Microsoft Container Registry
"mcr.microsoft.com": true,
// Google Artifact Registry (*.pkg.dev pattern handled in isAllowedRegistry)
Expand All @@ -49,6 +51,7 @@ var allowedOCIRegistries = map[string]bool{
// Supported registries:
// - Docker Hub (docker.io)
// - GitHub Container Registry (ghcr.io)
// - Quay.io (quay.io)
// - Google Artifact Registry (*.pkg.dev)
// - Microsoft Container Registry (mcr.microsoft.com)
func ValidateOCI(ctx context.Context, pkg model.Package, serverName string) error {
Expand Down
24 changes: 16 additions & 8 deletions internal/validators/registries/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ func TestValidateOCI_RegistryAllowlist(t *testing.T) {
errorMsg: "missing required annotation",
mustNotContainMsg: "unsupported OCI registry",
},
{
name: "Quay.io should be allowed",
identifier: "quay.io/prometheus/node-exporter:v1.7.0",
expectError: true,
errorMsg: "missing required annotation",
mustNotContainMsg: "unsupported OCI registry",
},
// Removed ACR test with non-existent host - ACR support is tested elsewhere

// Disallowed registries
Expand All @@ -75,12 +82,6 @@ func TestValidateOCI_RegistryAllowlist(t *testing.T) {
expectError: true,
errorMsg: "unsupported OCI registry",
},
{
name: "Quay.io should be rejected",
identifier: "quay.io/test/image:latest",
expectError: true,
errorMsg: "unsupported OCI registry",
},
{
name: "ECR Public should be rejected",
identifier: "public.ecr.aws/test/image:latest",
Expand Down Expand Up @@ -134,8 +135,9 @@ func TestValidateOCI_RegistryAllowlist(t *testing.T) {
}

func TestValidateOCI_RegistryPatterns(t *testing.T) {
// This test verifies registry pattern matching (wildcards like *.azurecr.io and *.pkg.dev)
// without relying on external images that may not exist
// Verifies allowlist behavior: wildcard hosts (*.azurecr.io, *.pkg.dev) and fixed hosts
// like quay.io. shouldFail=false means the error must not be "unsupported OCI registry"
// (validation may still error later, for example missing annotation or image not found).
tests := []struct {
name string
identifier string
Expand All @@ -156,6 +158,12 @@ func TestValidateOCI_RegistryPatterns(t *testing.T) {
identifier: "us-west1-docker.pkg.dev/project/repo/image:tag",
shouldFail: false,
},
{
name: "Quay.io host should be allowed",
identifier: "quay.io/nonexistent/mcp-registry-fake-repo:v1",
// Past allowlist; fake repo typically yields "does not exist", not unsupported registry
shouldFail: false,
},
{
name: "GCR should be rejected at registry check",
identifier: "gcr.io/project/image:latest",
Expand Down
7 changes: 4 additions & 3 deletions pkg/model/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ const (

// Registry Base URLs - supported package registry base URLs
const (
RegistryURLNPM = "https://registry.npmjs.org"
RegistryURLPyPI = "https://pypi.org"
RegistryURLNuGet = "https://api.nuget.org/v3/index.json"
RegistryURLGitHub = "https://github.com"
RegistryURLGitLab = "https://gitlab.com"
RegistryURLNPM = "https://registry.npmjs.org"
RegistryURLNuGet = "https://api.nuget.org/v3/index.json"
RegistryURLPyPI = "https://pypi.org"
RegistryURLQuay = "https://quay.io"
)

// Transport Types - supported remote transport protocols
Expand Down
2 changes: 1 addition & 1 deletion tools/validate-examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func main() {

func runValidation() error {
// Define what we validate and how
expectedServerJSONCount := 15
expectedServerJSONCount := 16
targets := []validationTarget{
{
path: filepath.Join("docs", "reference", "server-json", "generic-server-json.md"),
Expand Down
Loading