API Endpoints

Complete REST API reference for MCP Registry with request/response examples and error codes.

All API endpoints are prefixed with /v1/ (except the health check). Request and response bodies use JSON. Authentication is provided via the Authorization header.

Base URL

http://localhost:8080

In production, use HTTPS with your actual hostname.

Authentication

The registry supports three authentication methods:

MethodHeader FormatNotes
Bearer JWTAuthorization: Bearer <jwt>Primary method for both OSS and Enterprise modes
API TokenAuthorization: Token <id>:<secret>Long-lived tokens for CI/CD
Basic AuthAuthorization: Basic <base64>OSS mode only, when enable_basic is true

Error Format

All errors return a JSON object:

{
  "error": {
    "code": "not_found",
    "message": "Package acme/unknown not found",
    "details": {}
  }
}

Common Error Codes

HTTP StatusCodeDescription
400bad_requestInvalid request format or missing fields
401unauthorizedMissing or invalid authentication
403forbiddenAuthenticated but lacking required scope or resource
404not_foundResource does not exist
409conflictResource already exists (e.g., duplicate version)
501not_implementedFeature not available (e.g., login in enterprise mode)

System

Health Check

Check server health status. No authentication required.

GET /healthz

Response 200 OK:

{
  "status": "ok"
}

Authentication

Login (OSS Mode Only)

Obtain a JWT using username and password. Only available when auth.mode is oss.

POST /v1/auth/login

Request:

{
  "username": "admin",
  "password": "secret"
}

Response 200 OK:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900
}

Example:

curl -X POST http://localhost:8080/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"secret"}'

Errors:

StatusWhen
401Invalid credentials
501Enterprise mode (login not available)

Catalog

List Packages

List all packages visible to the caller.

GET /v1/catalog
GET /v1/catalog?org={org}

Required scope: mcp:catalog:read

Parameters:

NameLocationRequiredDescription
orgQueryNoFilter by organization

Response 200 OK:

{
  "packages": [
    {
      "id": "acme/weather-service",
      "org_id": "acme",
      "name": "weather-service",
      "visibility": "public",
      "description": "Weather data service",
      "tags": ["weather", "api"]
    }
  ]
}

Example:

# List all packages
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/v1/catalog

# Filter by organization
curl -H "Authorization: Bearer $TOKEN" "http://localhost:8080/v1/catalog?org=acme"

Notes:

  • If public.read_catalog is true, unauthenticated requests return only public packages
  • Authenticated requests return all packages the token has access to

Packages

Get Package Metadata

Get metadata for a specific package.

GET /v1/org/{org}/mcps/{name}

Required scope: mcp:catalog:read

Path parameters:

NameDescription
orgOrganization slug
namePackage name

Response 200 OK:

{
  "id": "acme/weather-service",
  "org_id": "acme",
  "name": "weather-service",
  "visibility": "public",
  "description": "Weather data service",
  "tags": ["weather", "api"],
  "default_policy_ref": null
}

Example:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/v1/org/acme/mcps/weather-service

Errors:

StatusWhen
404Package does not exist

Versions

List Versions

List all versions of a package.

GET /v1/org/{org}/mcps/{name}/versions

Required scope: mcp:catalog:read

Response 200 OK:

{
  "versions": [
    {
      "version": "1.0.0",
      "status": "published",
      "created_at": "2024-01-15T10:30:00Z",
      "git_sha": "abc123def456"
    },
    {
      "version": "1.1.0",
      "status": "published",
      "created_at": "2024-02-01T14:00:00Z",
      "git_sha": "789xyz012abc"
    }
  ]
}

Example:

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/v1/org/acme/mcps/weather-service/versions

Resolve Version

Map a reference to a specific version with artifact URLs.

GET /v1/org/{org}/mcps/{name}/resolve?ref={ref}

Required scope: mcp:resolve for published versions; mcp:resolve:prepublish for draft/ingested.

Parameters:

NameLocationRequiredDescription
refQueryYesVersion reference: semver (1.0.0), git SHA, manifest digest, or bundle digest

Response 200 OK:

{
  "package": "acme/weather-service",
  "ref": "1.0.0",
  "resolved": {
    "version": "1.0.0",
    "status": "published",
    "git_sha": "abc123def456",
    "repo_url": "https://github.com/acme/weather-service",
    "manifest": {
      "digest": "sha256:abc123...",
      "url": "/v1/org/acme/artifacts/sha256:abc123.../manifest"
    },
    "bundle": {
      "digest": "sha256:def456...",
      "url": "/v1/org/acme/artifacts/sha256:def456.../bundle",
      "size_bytes": 1048576
    },
    "evidence": [
      {
        "kind": "sbom",
        "digest": "sha256:789xyz...",
        "url": "/v1/org/acme/artifacts/sha256:789xyz.../evidence/sbom"
      }
    ]
  }
}

Example:

# Resolve by version
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8080/v1/org/acme/mcps/weather-service/resolve?ref=1.0.0"

# Resolve by git SHA
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8080/v1/org/acme/mcps/weather-service/resolve?ref=abc123def456"

Errors:

StatusWhen
404No version matches the ref

Publish Version

Publish a new version of a package.

POST /v1/org/{org}/mcps/{name}/publish

Required scope: mcp:publish

Request:

{
  "version": "1.0.0",
  "bundle_digest": "sha256:def456...",
  "bundle_size_bytes": 1048576,
  "manifest_json": {
    "schema_version": 1,
    "package": {
      "id": "acme/weather-service",
      "version": "1.0.0"
    },
    "runtime": {
      "type": "node",
      "version": ">=18.0.0"
    },
    "entrypoint": {
      "command": ["node", "dist/index.js"]
    }
  },
  "git_sha": "abc123def456",
  "repo_url": "https://github.com/acme/weather-service",
  "repo_visibility": "public",
  "repo_provider": "github",
  "repo_ref": "v1.0.0",
  "repo_commit": "abc123def456",
  "certification_level": 0,
  "evidence_digests": []
}

Request fields:

FieldTypeRequiredDescription
versionstringYesSemantic version string
bundle_digeststringYesSHA-256 digest of the bundle
bundle_size_bytesintegerYesSize of the bundle in bytes
manifest_jsonobjectYesInline manifest JSON
git_shastringYesSource commit hash
repo_urlstringYesSource repository URL
repo_visibilitystringYespublic or private
repo_providerstringYesgithub, gitlab, bitbucket
repo_refstringYesGit ref (tag or branch)
repo_commitstringYesFull commit hash
certification_levelintegerNoCertification level (0-3)
evidence_digests[]stringNoEvidence artifact digests

Response 200 OK:

{
  "version": "1.0.0",
  "status": "ingested",
  "bundle_upload": {
    "url": "https://s3.amazonaws.com/bucket/key?X-Amz-Signature=...",
    "method": "PUT",
    "headers": {
      "Content-Type": "application/octet-stream"
    },
    "expires_in": 300
  }
}

The bundle_upload field contains a presigned URL for uploading the bundle. If presigned URLs are disabled, this field is null and you should upload via the proxy endpoint.

Example:

curl -X POST http://localhost:8080/v1/org/acme/mcps/weather-service/publish \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "1.0.0",
    "bundle_digest": "sha256:def456...",
    "bundle_size_bytes": 1048576,
    "manifest_json": {"schema_version":1, "package":{"id":"acme/weather-service","version":"1.0.0"}, "runtime":{"type":"node","version":">=18.0.0"}, "entrypoint":{"command":["node","dist/index.js"]}},
    "git_sha": "abc123",
    "repo_url": "https://github.com/acme/weather-service",
    "repo_visibility": "public",
    "repo_provider": "github",
    "repo_ref": "v1.0.0",
    "repo_commit": "abc123"
  }'

Errors:

StatusWhen
400Manifest validation failed or package ID mismatch
409Version already exists

Update Version Status

Change the lifecycle status of a version.

POST /v1/org/{org}/mcps/{name}/versions/{version}/status

Required scope: mcp:publish

Path parameters:

NameDescription
orgOrganization slug
namePackage name
versionSemantic version string

Request:

{
  "status": "published"
}

Valid status values: published, revoked

Response 200 OK:

{
  "version": "1.0.0",
  "status": "published"
}

Example:

curl -X POST \
  http://localhost:8080/v1/org/acme/mcps/weather-service/versions/1.0.0/status \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "published"}'

Artifacts

Download Artifact

Download a manifest, bundle, or evidence artifact.

GET /v1/org/{org}/artifacts/{digest}/manifest
GET /v1/org/{org}/artifacts/{digest}/bundle
GET /v1/org/{org}/artifacts/{digest}/evidence/{kind}

Required scope: artifact:download for manifest and bundle; evidence:read for evidence.

Path parameters:

NameDescription
orgOrganization slug
digestSHA-256 digest (e.g., sha256:abc123...)
kindEvidence type for evidence endpoints (e.g., sbom, attestation)

Response:

  • 302 Found with Location header pointing to presigned URL (S3 storage)
  • 200 OK with artifact bytes in body (filesystem storage)

Examples:

# Download manifest (follow redirects with -L)
curl -H "Authorization: Bearer $TOKEN" -L \
  http://localhost:8080/v1/org/acme/artifacts/sha256:abc123.../manifest \
  -o manifest.json

# Download bundle
curl -H "Authorization: Bearer $TOKEN" -L \
  http://localhost:8080/v1/org/acme/artifacts/sha256:def456.../bundle \
  -o bundle.tar.gz

# Download evidence
curl -H "Authorization: Bearer $TOKEN" -L \
  http://localhost:8080/v1/org/acme/artifacts/sha256:789xyz.../evidence/sbom \
  -o sbom.json

Upload Artifact

Upload a manifest, bundle, or evidence artifact through the registry proxy.

PUT /v1/org/{org}/artifacts/{digest}/manifest
PUT /v1/org/{org}/artifacts/{digest}/bundle
PUT /v1/org/{org}/artifacts/{digest}/evidence/{kind}

Required scope: mcp:publish

Request: Raw artifact bytes in the body.

Headers:

HeaderValue
Content-Typeapplication/json for manifests and evidence; application/octet-stream for bundles

Response 200 OK on success.

Examples:

# Upload manifest
curl -X PUT http://localhost:8080/v1/org/acme/artifacts/sha256:abc123.../manifest \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @manifest.json

# Upload bundle
curl -X PUT http://localhost:8080/v1/org/acme/artifacts/sha256:def456.../bundle \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @bundle.tar.gz

# Upload evidence
curl -X PUT http://localhost:8080/v1/org/acme/artifacts/sha256:789xyz.../evidence/sbom \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @sbom.json

Request Presigned Upload URL

Request a time-limited presigned URL for direct upload to S3. Only available when storage.type is s3 and presign.enabled is true.

POST /v1/org/{org}/artifacts/{digest}/presign?kind={kind}

Required scope: mcp:publish

Parameters:

NameLocationRequiredDescription
kindQueryYesArtifact kind: manifest, bundle, or evidence

Response 200 OK:

{
  "url": "https://s3.amazonaws.com/bucket/key?X-Amz-Signature=...",
  "method": "PUT",
  "headers": {
    "Content-Type": "application/octet-stream"
  },
  "expires_in": 300
}

Example:

curl -X POST \
  "http://localhost:8080/v1/org/acme/artifacts/sha256:def456.../presign?kind=bundle" \
  -H "Authorization: Bearer $TOKEN"

API Tokens

Create Token

Create a new API token for CI/CD or service account use.

POST /v1/tokens

Required scope: token:create

Request:

{
  "description": "GitHub Actions - weather-service",
  "scopes": ["mcp:publish", "mcp:resolve"],
  "resources": ["org/acme/mcp/weather-service"],
  "expires_in": 2592000
}
FieldTypeRequiredDescription
descriptionstringYesHuman-readable description
scopes[]stringYesPermission scopes to grant
resources[]stringYesResource patterns accessible
expires_inintegerNoExpiration in seconds (default: 30 days / 2592000)

Response 201 Created:

{
  "token_id": "mcp_abc123def456",
  "secret": "sk_fedcba654321...",
  "expires_at": "2024-03-15T10:30:00Z"
}

The secret is only returned once at creation time. Store it securely.

Example:

curl -X POST http://localhost:8080/v1/tokens \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "GitHub Actions",
    "scopes": ["mcp:publish"],
    "resources": ["org/acme/mcp/weather-service"]
  }'

List Tokens

List all API tokens for the current user.

GET /v1/tokens

Required scope: token:list

Response 200 OK:

{
  "tokens": [
    {
      "token_id": "mcp_abc123def456",
      "description": "GitHub Actions",
      "scopes": ["mcp:publish"],
      "resources": ["org/acme/mcp/weather-service"],
      "expires_at": "2024-03-15T10:30:00Z"
    }
  ]
}

Example:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/v1/tokens

Delete Token

Revoke and delete an API token.

DELETE /v1/tokens/{token_id}

Required scope: token:delete

Response 204 No Content on success.

Example:

curl -X DELETE http://localhost:8080/v1/tokens/mcp_abc123def456 \
  -H "Authorization: Bearer $TOKEN"

Organization Members

List Members

List all members of an organization.

GET /v1/org/{org}/members

Authentication: Required

Response 200 OK:

{
  "members": [
    {
      "username": "alice",
      "org_id": "acme",
      "role": "admin"
    },
    {
      "username": "bob",
      "org_id": "acme",
      "role": "member"
    }
  ]
}

Example:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/v1/org/acme/members

Add Member

Add a user to an organization.

POST /v1/org/{org}/members

Authentication: Required (admin role)

Request:

{
  "username": "bob",
  "role": "member"
}
FieldTypeRequiredDescription
usernamestringYesUsername to add
rolestringYesRole: admin or member

Response 201 Created on success.

Example:

curl -X POST http://localhost:8080/v1/org/acme/members \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"username":"bob","role":"member"}'

Remove Member

Remove a user from an organization.

DELETE /v1/org/{org}/members/{username}

Authentication: Required (admin role)

Response 204 No Content on success.

Example:

curl -X DELETE http://localhost:8080/v1/org/acme/members/bob \
  -H "Authorization: Bearer $TOKEN"

Complete Workflow Examples

Resolve and Download

# 1. Login (OSS mode)
TOKEN=$(curl -s -X POST http://localhost:8080/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"secret"}' | jq -r .access_token)

# 2. Resolve package
RESOLVED=$(curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8080/v1/org/acme/mcps/weather-service/resolve?ref=1.0.0")

# 3. Extract URLs
MANIFEST_URL=$(echo "$RESOLVED" | jq -r '.resolved.manifest.url')
BUNDLE_URL=$(echo "$RESOLVED" | jq -r '.resolved.bundle.url')

# 4. Download artifacts
curl -H "Authorization: Bearer $TOKEN" -L \
  "http://localhost:8080$MANIFEST_URL" -o manifest.json
curl -H "Authorization: Bearer $TOKEN" -L \
  "http://localhost:8080$BUNDLE_URL" -o bundle.tar.gz

Publish from CI/CD Pipeline

# 1. Compute digests
BUNDLE_DIGEST="sha256:$(sha256sum bundle.tar.gz | awk '{print $1}')"
BUNDLE_SIZE=$(stat -c%s bundle.tar.gz)

# 2. Initiate publish
RESPONSE=$(curl -s -X POST \
  http://localhost:8080/v1/org/acme/mcps/weather-service/publish \
  -H "Authorization: Token $TOKEN_ID:$TOKEN_SECRET" \
  -H "Content-Type: application/json" \
  -d "{
    \"version\": \"1.0.0\",
    \"bundle_digest\": \"$BUNDLE_DIGEST\",
    \"bundle_size_bytes\": $BUNDLE_SIZE,
    \"manifest_json\": $(cat manifest.json),
    \"git_sha\": \"$GITHUB_SHA\",
    \"repo_url\": \"https://github.com/$GITHUB_REPOSITORY\",
    \"repo_visibility\": \"public\",
    \"repo_provider\": \"github\",
    \"repo_ref\": \"$GITHUB_REF\",
    \"repo_commit\": \"$GITHUB_SHA\"
  }")

# 3. Upload bundle to presigned URL
UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.bundle_upload.url')
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @bundle.tar.gz

# 4. Mark as published
curl -X POST \
  http://localhost:8080/v1/org/acme/mcps/weather-service/versions/1.0.0/status \
  -H "Authorization: Token $TOKEN_ID:$TOKEN_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"status": "published"}'

Rate Limits and Timeouts

  • The registry does not implement rate limiting. Use a reverse proxy (nginx, Caddy, cloud load balancer) for rate limiting in production.
  • All requests have a 30-second timeout. For large artifact uploads, use presigned URLs for direct S3 access to avoid this limit.