Skip to content

Commit 284e4ab

Browse files
feat(oci): add support for Quay.io registry
- Add `RegistryURLQuay` in `pkg/model/constants.go`. - Allow `quay.io` in OCI validator allowlist. - Adjust oci tests so Quay is allowed (real image + pattern case). - Document Quay in requirements, package-types, and generic-server-json. Only the quay.io hostname is allowlisted; validation remains public-image only. Signed-off-by: Mikel Olasagasti Uranga <mikel@olasagasti.info>
1 parent 07fa85f commit 284e4ab

7 files changed

Lines changed: 47 additions & 13 deletions

File tree

docs/modelcontextprotocol-io/package-types.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ For Docker/OCI images, the MCP Registry currently supports:
131131

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

159-
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.
160+
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.
160161

161162
### Ownership Verification
162163

docs/reference/server-json/generic-server-json.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,26 @@ This will essentially instruct the MCP client to execute `dnx Knapcode.SampleMcp
264264
}
265265
```
266266

267+
The same `registryType` / `identifier` pattern works for other supported OCI hosts. For example, an image on Quay.io:
268+
269+
```json
270+
{
271+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
272+
"name": "io.github.example/quay-sample-mcp",
273+
"description": "Example MCP server distributed as an OCI image on Quay.io",
274+
"version": "1.0.0",
275+
"packages": [
276+
{
277+
"registryType": "oci",
278+
"identifier": "quay.io/myorg/my-mcp-server:1.0.0",
279+
"transport": {
280+
"type": "stdio"
281+
}
282+
}
283+
]
284+
}
285+
```
286+
267287
### Remote Server Example
268288

269289
```json

docs/reference/server-json/official-registry-requirements.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Only trusted public registries are supported. Private registries and alternative
3636
- **Docker/OCI**:
3737
- Docker Hub (`docker.io`)
3838
- GitHub Container Registry (`ghcr.io`)
39+
- Quay.io (`quay.io`)
3940
- Google Artifact Registry (`*.pkg.dev`)
4041
- Azure Container Registry (`*.azurecr.io`)
4142
- Microsoft Container Registry (`mcr.microsoft.com`)

internal/validators/registries/oci.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ var allowedOCIRegistries = map[string]bool{
3333
"index.docker.io": true, // Docker Hub index
3434
// GitHub Container Registry
3535
"ghcr.io": true,
36+
// Red Hat Quay
37+
"quay.io": true,
3638
// Microsoft Container Registry
3739
"mcr.microsoft.com": true,
3840
// Google Artifact Registry (*.pkg.dev pattern handled in isAllowedRegistry)
@@ -49,6 +51,7 @@ var allowedOCIRegistries = map[string]bool{
4951
// Supported registries:
5052
// - Docker Hub (docker.io)
5153
// - GitHub Container Registry (ghcr.io)
54+
// - Quay.io (quay.io)
5255
// - Google Artifact Registry (*.pkg.dev)
5356
// - Microsoft Container Registry (mcr.microsoft.com)
5457
func ValidateOCI(ctx context.Context, pkg model.Package, serverName string) error {

internal/validators/registries/oci_test.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ func TestValidateOCI_RegistryAllowlist(t *testing.T) {
6666
errorMsg: "missing required annotation",
6767
mustNotContainMsg: "unsupported OCI registry",
6868
},
69+
{
70+
name: "Quay.io should be allowed",
71+
identifier: "quay.io/prometheus/node-exporter:v1.7.0",
72+
expectError: true,
73+
errorMsg: "missing required annotation",
74+
mustNotContainMsg: "unsupported OCI registry",
75+
},
6976
// Removed ACR test with non-existent host - ACR support is tested elsewhere
7077

7178
// Disallowed registries
@@ -75,12 +82,6 @@ func TestValidateOCI_RegistryAllowlist(t *testing.T) {
7582
expectError: true,
7683
errorMsg: "unsupported OCI registry",
7784
},
78-
{
79-
name: "Quay.io should be rejected",
80-
identifier: "quay.io/test/image:latest",
81-
expectError: true,
82-
errorMsg: "unsupported OCI registry",
83-
},
8485
{
8586
name: "ECR Public should be rejected",
8687
identifier: "public.ecr.aws/test/image:latest",
@@ -134,8 +135,9 @@ func TestValidateOCI_RegistryAllowlist(t *testing.T) {
134135
}
135136

136137
func TestValidateOCI_RegistryPatterns(t *testing.T) {
137-
// This test verifies registry pattern matching (wildcards like *.azurecr.io and *.pkg.dev)
138-
// without relying on external images that may not exist
138+
// Verifies allowlist behavior: wildcard hosts (*.azurecr.io, *.pkg.dev) and fixed hosts
139+
// like quay.io. shouldFail=false means the error must not be "unsupported OCI registry"
140+
// (validation may still error later, for example missing annotation or image not found).
139141
tests := []struct {
140142
name string
141143
identifier string
@@ -156,6 +158,12 @@ func TestValidateOCI_RegistryPatterns(t *testing.T) {
156158
identifier: "us-west1-docker.pkg.dev/project/repo/image:tag",
157159
shouldFail: false,
158160
},
161+
{
162+
name: "Quay.io host should be allowed",
163+
identifier: "quay.io/nonexistent/mcp-registry-fake-repo:v1",
164+
// Past allowlist; fake repo typically yields "does not exist", not unsupported registry
165+
shouldFail: false,
166+
},
159167
{
160168
name: "GCR should be rejected at registry check",
161169
identifier: "gcr.io/project/image:latest",

pkg/model/constants.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ const (
1111

1212
// Registry Base URLs - supported package registry base URLs
1313
const (
14-
RegistryURLNPM = "https://registry.npmjs.org"
15-
RegistryURLPyPI = "https://pypi.org"
16-
RegistryURLNuGet = "https://api.nuget.org/v3/index.json"
1714
RegistryURLGitHub = "https://github.com"
1815
RegistryURLGitLab = "https://gitlab.com"
16+
RegistryURLNPM = "https://registry.npmjs.org"
17+
RegistryURLNuGet = "https://api.nuget.org/v3/index.json"
18+
RegistryURLPyPI = "https://pypi.org"
19+
RegistryURLQuay = "https://quay.io"
1920
)
2021

2122
// Transport Types - supported remote transport protocols

tools/validate-examples/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func main() {
3333

3434
func runValidation() error {
3535
// Define what we validate and how
36-
expectedServerJSONCount := 15
36+
expectedServerJSONCount := 16
3737
targets := []validationTarget{
3838
{
3939
path: filepath.Join("docs", "reference", "server-json", "generic-server-json.md"),

0 commit comments

Comments
 (0)