<!-- Provide a brief summary of your changes --> ## Motivation and Context Microsoft uses `mcr.microsoft.com` to host our public containers. These are syndicated to Docker Hub but image references use `mcr.microsoft.com` which is not currently supported by the Official MCP Registry. See https://github.com/modelcontextprotocol/registry/issues/753. Microsoft teams want to use MCR-based images for Microsoft MCP servers. I believe we should be adding ACR support as well, so that our customers using Azure Container Registry can use the MCP Registry as well. In short, MCR is used for Microsoft managed containers. ACR is a solution for Azure customers to host their own containers. ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> Unit tests. Manual test: local push of a server.json with `mcr.microsoft.com/azure-sdk/azure-mcp:2.0.0-beta.1` and `azurearcjumpstart.azurecr.io/hello-arc:latest` in it. The error is now about missing annotation not unsupported OCI registry. ## Breaking Changes <!-- Will users need to update their code or configurations? --> No. ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions -->
244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
package registries_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/modelcontextprotocol/registry/internal/validators/registries"
|
|
"github.com/modelcontextprotocol/registry/pkg/model"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestValidateOCI_RegistryAllowlist(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
identifier string
|
|
expectError bool
|
|
errorMsg string
|
|
}{
|
|
// Allowed registries - use real public images that exist
|
|
// These should fail with "missing required annotation" (no MCP label)
|
|
// NOT with "unsupported registry", "does not exist", or "is private" errors
|
|
{
|
|
name: "Docker Hub should be allowed",
|
|
identifier: "docker.io/library/alpine:latest",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
{
|
|
name: "Docker Hub without explicit registry should default and be allowed",
|
|
identifier: "library/hello-world:latest",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
{
|
|
name: "GHCR should be allowed",
|
|
identifier: "ghcr.io/containerbase/base:latest",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
{
|
|
name: "Artifact Registry regional should be allowed",
|
|
identifier: "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
{
|
|
name: "Artifact Registry multi-region should be allowed",
|
|
identifier: "us-docker.pkg.dev/berglas/berglas/berglas:latest",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
{
|
|
name: "MCR should be allowed",
|
|
identifier: "mcr.microsoft.com/dotnet/aspire-dashboard:9.5.0",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
{
|
|
name: "ACR should be allowed",
|
|
identifier: "azurearcjumpstart.azurecr.io/hello-arc:latest",
|
|
expectError: true,
|
|
errorMsg: "missing required annotation",
|
|
},
|
|
|
|
// Disallowed registries
|
|
{
|
|
name: "GCR should be rejected",
|
|
identifier: "gcr.io/test/image:latest",
|
|
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",
|
|
expectError: true,
|
|
errorMsg: "unsupported OCI registry",
|
|
},
|
|
{
|
|
name: "GitLab registry should be rejected",
|
|
identifier: "registry.gitlab.com/test/image:latest",
|
|
expectError: true,
|
|
errorMsg: "unsupported OCI registry",
|
|
},
|
|
{
|
|
name: "Custom registry should be rejected",
|
|
identifier: "custom-registry.com/test/image:latest",
|
|
expectError: true,
|
|
errorMsg: "unsupported OCI registry",
|
|
},
|
|
{
|
|
name: "Harbor registry should be rejected",
|
|
identifier: "harbor.example.com/test/image:latest",
|
|
expectError: true,
|
|
errorMsg: "unsupported OCI registry",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pkg := model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: tt.identifier,
|
|
}
|
|
|
|
err := registries.ValidateOCI(ctx, pkg, "com.example/test")
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err)
|
|
// Should contain the specific error message
|
|
assert.Contains(t, err.Error(), tt.errorMsg)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateOCI_RejectsOldFormat(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
pkg model.Package
|
|
errorMessage string
|
|
}{
|
|
{
|
|
name: "OCI package with registryBaseUrl should be rejected",
|
|
pkg: model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
RegistryBaseURL: "https://docker.io",
|
|
Identifier: "docker.io/test/image:latest",
|
|
},
|
|
errorMessage: "OCI packages must not have 'registryBaseUrl' field",
|
|
},
|
|
{
|
|
name: "OCI package with version field should be rejected",
|
|
pkg: model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: "docker.io/test/image:latest",
|
|
Version: "1.0.0",
|
|
},
|
|
errorMessage: "OCI packages must not have 'version' field",
|
|
},
|
|
{
|
|
name: "OCI package with fileSha256 field should be rejected",
|
|
pkg: model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: "docker.io/test/image:latest",
|
|
FileSHA256: "abcd1234",
|
|
},
|
|
errorMessage: "OCI packages must not have 'fileSha256' field",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := registries.ValidateOCI(ctx, tt.pkg, "com.example/test")
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.errorMessage)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateOCI_InvalidReferences(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
identifier string
|
|
}{
|
|
{
|
|
name: "invalid characters in reference",
|
|
identifier: "docker.io/test/image:INVALID SPACE",
|
|
},
|
|
{
|
|
name: "malformed reference",
|
|
identifier: "not-a-valid-reference::::",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pkg := model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: tt.identifier,
|
|
}
|
|
|
|
err := registries.ValidateOCI(ctx, pkg, "com.example/test")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid OCI reference")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateOCI_EmptyIdentifier(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
pkg := model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: "",
|
|
}
|
|
|
|
err := registries.ValidateOCI(ctx, pkg, "com.example/test")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "package identifier is required")
|
|
}
|
|
|
|
func TestValidateOCI_SuccessfulValidation(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Test with a real MCP server image that has the correct label
|
|
pkg := model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: "ghcr.io/github/github-mcp-server:latest",
|
|
}
|
|
|
|
err := registries.ValidateOCI(ctx, pkg, "io.github.github/github-mcp-server")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestValidateOCI_LabelMismatch(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Test with a real MCP server image but wrong expected server name
|
|
// This should fail because the label doesn't match
|
|
pkg := model.Package{
|
|
RegistryType: model.RegistryTypeOCI,
|
|
Identifier: "ghcr.io/github/github-mcp-server:latest",
|
|
}
|
|
|
|
err := registries.ValidateOCI(ctx, pkg, "io.github.github/github-mcp-server-mismatch")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "ownership validation failed")
|
|
assert.Contains(t, err.Error(), "Expected annotation")
|
|
}
|