registry

GitHub

A community driven registry service for Model Context Protocol (MCP) servers.

6,788 stars Go #mcp#mcp-servers
RAW Doc

Administration/Admin Operations

Admin Operations

This is a brief guide for admins and moderators managing content on the registry. All actions should be taken in line with the moderation policy.

Prerequisites

- Admin account with @modelcontextprotocol.io email
- If you are a maintainer and would like an account, ask in the Discord
- gcloud CLI installed and configured
- curl and jq installed
- kubectl installed with gke-gcloud-auth-plugin (for database access)

Authentication

bash

Run this, then run the export command it outputs


./tools/admin/auth.sh

Edit a Specific Server Version

Use this when you need to modify details of a specific version (e.g., fix description, update status, modify packages).

Step 1: Download Specific Version

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export VERSION="<version-string>" # e.g., "1.0.0" (optional, defaults to latest)

URL encode the server name (replace / with %2F)


ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

Get specific version


curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" > server.json

Or get the latest version (use the special version "latest")


curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" > server.json

Step 2: Make Changes

Open server.json and edit the specific version details. You cannot change the server name or version number.

Step 3: Update Version

bash

Update specific version (requires the full server.json body)


curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" \
-H "Authorization: Bearer ${REGISTRY_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(cat server.json)"

To change only the status of a version, use the status endpoint instead — it does not require
the full server configuration:

bash
curl -X PATCH "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}/status" \
-H "Authorization: Bearer ${REGISTRY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"status": "deprecated", "statusMessage": "Superseded by v2"}'

Edit an Entire Server (All Versions)

Status Changes Across All Versions

A status change applies to every version of a server in a single request. The response reports
how many versions were updated in updatedCount.

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

curl -X PATCH "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/status" \
-H "Authorization: Bearer ${REGISTRY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"status": "deleted", "statusMessage": "Removed per moderation policy"}'

Content Changes Across All Versions

Content edits (e.g. scrubbing sensitive text from descriptions) have no bulk endpoint and must be
applied per version using the edit endpoint, which takes the full server configuration.

#### Step 1: List All Versions

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions" > all_versions.json

#### Step 2: Extract Versions

bash

Extract all versions from the server


jq -r '.servers[].server.version' all_versions.json > versions.txt

#### Step 3: Apply Changes to Each Version

bash
while read VERSION; do
echo "Processing version: $VERSION"

# Download the version, edit it, then send the full body back
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" > version.json

# Apply your changes to version.json here, then:
curl -X PUT "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/${VERSION}" \
-H "Authorization: Bearer ${REGISTRY_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(cat version.json)"

done < versions.txt

Clean up temporary files


rm -f versions.txt all_versions.json version.json

Quick Operations

Get Latest Version of a Server

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" > latest_version.json
export VERSION=$(jq -r '.server.version' latest_version.json)
echo "Latest version: $VERSION"

Takedown a Specific Version

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export VERSION="<version-string>" # e.g., "1.0.0"
export REGISTRY_TOKEN="<your-token>"

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" VERSION="$VERSION" ./tools/admin/takedown.sh

Takedown All Versions of a Server

ALL_VERSIONS=true marks every version as deleted in a single request. The script requires either
VERSION or ALL_VERSIONS to be set explicitly, so a forgotten VERSION cannot take down a whole
server by accident.

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export REGISTRY_TOKEN="<your-token>"

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" ALL_VERSIONS=true ./tools/admin/takedown.sh

Takedown the Latest Version Only

bash
export SERVER_NAME="<server-name>"    # e.g., "com.example/my-server"
export REGISTRY_TOKEN="<your-token>"
ENCODED_SERVER_NAME=$(echo "$SERVER_NAME" | sed 's|/|%2F|g')

Resolve the latest version, then take down that specific version


VERSION=$(curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" | jq -r '.server.version')

REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" VERSION="$VERSION" ./tools/admin/takedown.sh

Record a Reason with a Takedown

bash
REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" ALL_VERSIONS=true \
STATUS_MESSAGE="Removed per moderation policy" ./tools/admin/takedown.sh

Connecting to the Production Database

For debugging or data analysis, you can connect directly to the production PostgreSQL database. Use caution and prefer read-only access.

Prerequisites

Install the GKE auth plugin if you haven't already:

bash
gcloud components install gke-gcloud-auth-plugin

Connect

bash

Get cluster credentials


gcloud container clusters get-credentials mcp-registry-prod --zone us-central1-b --project mcp-registry-prod

Get the database password


kubectl get secret registry-pg-app -o jsonpath='{.data.password}' | base64 -d

Port-forward and connect (enter the password from above)


kubectl port-forward svc/registry-pg-rw 15432:5432 &
sleep 2
psql -h localhost -p 15432 -U app -d app

Read-Only Access

To prevent accidental writes, set your session to read-only after connecting:

sql
SET default_transaction_read_only = on;

Any write attempts will fail with an error until you disconnect.

Notes

- Version-specific changes: Only affect that particular version
- Server-wide status changes: PATCH /v0/servers/{serverName}/status updates every version in one request
- Server-wide content changes: Have no bulk endpoint and must be applied to each version individually
- Status vs. edit: Use PATCH .../status to change status alone; the PUT edit endpoint requires the full server configuration
- Content scrubbing: Use the version-specific edit workflow to scrub sensitive content
- Server name: Cannot be changed in any version (it's the immutable identifier)

---

Administration/Maintainer Onboarding

Registry Maintainer Onboarding

This guide covers onboarding new maintainers to the MCP Registry project.

Checklist

When onboarding a new maintainer, complete the following steps:

1. Access Repository (GitHub, Google Workspace)

- [ ] Open a PR on the modelcontextprotocol/access repository

2. MAINTAINERS.md

- [ ] Add them to the MAINTAINERS.md file in modelcontextprotocol/modelcontextprotocol

3. README.md

- [ ] Add them to the "Registry Working Group" section in README.md

4. Discord

- [ ] Invite them to the MCP Discord server
- [ ] Ask a Community Moderator or Core Maintainer to add them to the appropriate roles

---

Contributing/Add Package Registry

Adding a new package registry

The MCP Registry project is a metaregistry, meaning that it hosts metadata for MCP servers but does not host the code for the servers directly.

For local MCP servers, the MCP Registry has pointers in the packages node of the server.json schema that refer to packages in supported package managers.

The list of supported package managers for hosting MCP servers is defined by the properties.packages[N].properties.registryType string enum in the server.json schema. For example, this could be "npm" (for npmjs.com packages) or "pypi" (for PyPI packages).

For remote MCP servers, the package registry is not relevant. The MCP client consumes the server via a URL instead of by downloading and running a package. In other words, this document only applies to local MCP servers.

For the sake of illustration, this document will use npm (the Node.js package manager) as an example at each step.

Prerequisites

The package registry must meet the following requirements:

1. The package registry supports packaging and executing CLI apps. Local MCP servers use the stdio transport.
- npm CLI tools typically express their CLI commands in the bin property of the package.json
1. The package registry (or associated client tooling) has a widely accepted single-shot CLI command.
- npm's npx tool executes CLI commands using a documented execution heuristic
- For example, the MCP client can map the server.json metadata to an npx CLI execution, with args and environment variables populated via user input.
1. The package registry supports anonymous package downloads. This allows the MCP client software to use the metadata found in the MCP registry to discover, download, and execute package-based local MCP servers with minimal user intervention.
- npx by default connects to the public npmjs.com registry, allowing simple consumption of public npm packages.
1. The package registry should support a validation mechanism to verify ownership of the server name. This prevents misattribution and ensures that only the actual package owner can reference their packages in server registrations. For example:
- npm requires an mcpName field in package.json that matches the server name being registered
- PyPI requires a mcp-name: line in the package README/description
- Each registry type must implement a validation mechanism accessible via public API

Steps

These steps may evolve as additional validations or details are discovered and mandated.

1. Create a feature request issue on the MCP Registry repository to begin the discussion about adding the package registry.
- Example for NuGet: https://github.com/modelcontextprotocol/registry/issues/126
1. Open a PR with the following changes:
- Update the server.json schema
- Add your package registry name to the registryType example array.
- Add your package registry base url to the registryBaseUrl example array.
- Add the single-shot CLI command name to the runtimeHint example value array.
- Update the openapi.yaml
- Add your package registry name to the registryType enum value array.
- Add your package registry base url to the registryBaseUrl enum value array.
- Add the single-shot CLI command name to the runtimeHint example value array.
- Add a sample, minimal server.json to the server.json examples.
- Implement a registry validator:
- Create a new validator file: internal/validators/registries/yourregistry.go, following the pattern of existing validators. Examples:
- npm: Checks for an mcpName field in package.json that matches the server name
- PyPI: Searches for mcp-name: server-name format in the package README content
- NuGet: Looks for mcp-name: server-name format in the package README file
- Docker/OCI: Validates a Docker image label io.modelcontextprotocol.server.name in the image manifest
- Add corresponding unit tests: internal/validators/registries/yourregistry_test.go
- Register your validator in internal/validators/validators.go
- Update the publishing documentation:
- Add a section for your registry to docs/modelcontextprotocol-io/package-types.mdx, following the pattern of the existing registries (npm, PyPI, NuGet, ...)
- Include instructions on how to prepare packages for your registry, and an ### Ownership Verification subsection describing the validation your registry requires

---

Contributing/Releasing

Release Guide

Creating a Release

1. Go to GitHub: Navigate to https://github.com/modelcontextprotocol/registry/releases
2. Click "Draft a new release"
3. Choose a tag: Click "Choose a tag" and type a new semantic version that follows the last one available (e.g., v1.0.0)
4. Generate notes: Click "Generate release notes" to auto-populate the name and description
5. Publish: Click "Publish release"

The release workflow will automatically:
- Build binaries for 6 platforms (Linux, macOS, Windows × amd64, arm64)
- Create and push Docker images with :latest and :X.Y.Z tags (note: no 'v' prefix)
- Attach all artifacts to the GitHub release
- Generate checksums and signatures

After Release

- Docker images will be available at:
- ghcr.io/modelcontextprotocol/registry:latest - Latest stable release
- ghcr.io/modelcontextprotocol/registry:X.Y.Z - Specific release version (note: no 'v' prefix)
- Binaries can be downloaded from the GitHub release page

Deploying to Production

Releases do not automatically deploy to production. To deploy a release:

1. Update mcp-registry:imageTag in deploy/Pulumi.gcpProd.yaml to the desired version (e.g., 1.2.3 - note: no 'v' prefix)
2. Commit and push the change to the main branch (either through a PR or by pushing directly to main)
3. The deploy-production.yml workflow will automatically trigger and deploy the specified version

See the deployment documentation for more details.

Staging

Staging auto-deploys from main via deploy-staging.yml. It always runs the latest main branch code.

Rollback

To rollback production, update deploy/Pulumi.gcpProd.yaml to the previous version and push.

Note: Rollbacks may not work as expected if the release included database migrations, since migrations are not automatically reversed.

Docker Image Tags

The registry publishes different Docker image tags for different use cases:

- :latest - Latest stable release (updated only on releases)
- :X.Y.Z - Specific release versions (e.g., :1.0.0 - note: no 'v' prefix)
- :main - Rolling tag updated on every push to main branch (continuous deployment)
- :main-YYYYMMDD-sha - Specific development builds from main branch

Note: Git release tags include the 'v' prefix (e.g., v1.0.0), but Docker image tags follow the standard Docker convention and do not include the 'v' prefix (e.g., 1.0.0).

Versioning

We use semantic versioning (SemVer):
- v1.0.0 - Major release with breaking changes
- v1.1.0 - Minor release with new features
- v1.0.1 - Patch release with bug fixes

---

Design/Design Principles

MCP Registry Design Principles

These are the core constraints that guide the design of the MCP Registry. They are not exhaustive, but they are the most important principles that we will use to evaluate design decisions.

1. Single Source of Truth

The registry serves as the authoritative metadata repository for publicly-available MCP servers, both locally-run and remote, open source and closed source. Server creators publish once, and all consumers (MCP clients, aggregators, etc.) reference the same canonical data.

2. Minimal Operational Burden

- Design for low maintenance and operational overhead
- Delegate complexity to existing services where possible (GitHub for auth, npm/PyPI/NuGet for packages)
- Avoid features that require constant human intervention or moderation
- Build for reasonable downtime tolerance (24h acceptable) by having consumers cache data for their end-users

3. Vendor Neutrality

- No preferential treatment for specific servers or organizations
- No built-in ranking, curation, or quality judgments
- Let consumers (MCP clients, aggregators) make their own curation decisions

4. Meets Industry Security Standards

- Leverage existing package registries (npm, PyPI, NuGet, Docker Hub, etc.) for source code distribution, obviating the need to reinvent source code security
- Use mechanisms like DNS verification, OAuth to provide base layer of authentication and trust
- Implement rate limiting, field validation, and blacklisting to prevent abuse

5. Reusable, Extensible Shapes; Not Infrastructure

- API shapes (OpenAPI, server.json) designed for reuse
- Enable private/internal registries using same formats
- Don't mandate infrastructure reuse - focus on interface compatibility

6. Progressive Enhancement

- Start with MVP that provides immediate value
- Build foundation that supports future features
- Don't over-engineer for hypothetical needs
- Each milestone should be independently valuable

---

Design/Ecosystem Vision

Ecosystem Vision

How the MCP Registry fits into the broader ecosystem and our vision for the future.

The Registry Ecosystem

The MCP registry provides MCP clients with a list of MCP servers, like an app store for MCP servers. (In the future it might do more, like also hosting a list of clients).

There are two parts to the registry project:

1. 🟦 The MCP registry spec: An API specification that allows anyone to implement a registry.
2. 🟥 The Official MCP registry: A hosted registry following the MCP registry spec at registry.modelcontextprotocol.io. This serves as the authoritative repository for publicly-available MCP servers. Server creators publish once, and all consumers (MCP clients, aggregators, marketplaces) reference the same canonical data. This is owned by the MCP open-source community, backed by major trusted contributors to the MCP ecosystem such as Anthropic, GitHub, PulseMCP and Microsoft.

The registry is built around the server.json format - a standardized way to describe MCP servers that works across discovery, initialization, and packaging scenarios.

In time, we expect the ecosystem to look a bit like this:

Note that MCP registries are _metaregistries_. They host metadata about packages, but not the package code or binaries. Instead, they reference other package registries (like NPM, PyPi or Docker) for this.

Additionally, we expect clients pull from _subregistries_. These subregistries add value to the registry ecosystem by providing curation, or extending it with additional metadata. The Official MCP registry expects a lot of API requests from ETL jobs from these subregistries.

Registry vs Package Registry

Key distinction: MCP Registries are metaregistries.

- Package registries (npm, PyPI, Docker Hub) host actual code/binaries
- The MCP Registry hosts metadata pointing to those packages

text
MCP Registry: "weather-server v1.2.0 is at npm:weather-mcp"
NPM Registry: [actual weather-mcp package code]

Official vs Community Registries

Official MCP Registry (registry.modelcontextprotocol.io):
- Canonical source for publicly-available servers
- Community-owned, backed by trusted contributors
- Focuses on discoverability and basic metadata

Subregistries (Smithery, PulseMCP, etc.):
- Add value through curation, ratings, enhanced metadata
- ETL from official registry + additional annotations
- Serve specific communities or use cases

How Servers Are Represented

Each server entry contains:
- Identity: Unique name (io.github.user/server-name)
- Packages: Where to download it (npm, pypi, docker, etc.)
- Runtime: How to execute it (args, env vars)
- Metadata: Description, capabilities, version

This is stored in a standardized server.json format that works across discovery, installation, and execution.

---

Design/Proposed Enhanced Validation

Enhanced Server Validation Design

NOTE: This document describes a proposed direction for improving validation of server.json data in the Official Registry. This work is in progress (including open PRs and discussions) in a collaborative process and may change significantly or be abandoned.

Overview

This document outlines the design for implementing comprehensive server validation in the MCP Registry, due to the following concerns:

- Currently, the MCP Registry project publishes a server.json schema but does not validate servers against it, allowing non-compliant servers to be published.
- There is existing ad-hoc validation that covers some schema compliance, but not all (there are logical errors not identifiable by schema validation and that are not covered by the existing ad hoc validation).
- Many servers that do pass validation do not represent best-practices for published servers.

This design implements a three-tier validation system: Schema Validation, Semantic Validation, and Linter Validation.

Current State

Problems with Current Validation


- No schema validation: Servers are published without validating against the published schema (and many violate it)
- Incomplete validation: Ad hoc validation covers only some schema constraints (many published servers have additional logical errors)
- Best Practices not indicated: Many servers that would pass schema and semantic validation do not represent best practices
- Fail-fast behavior: Legacy ValidateServerJSON() stopped at first error (now replaced with exhaustive validation)
- No path information: Errors don't specify where in JSON the problem occurs

Three-Tier Validation System

Schema Validation (Primary)


- Validates against published schema: Ensures servers comply with the official server.json schema
- Exhaustive coverage: Catches all structural and format violations defined in the schema
- Detailed error references: Shows exact schema rule locations with specific constraint and full path to constraint

Semantic Validation (Secondary)


- Business logic validation: Validates only constraints not expressible in JSON Schema
- Registry validation: Enforce validitiy of registry references (as current)
- Logical Errors: Enforce logical consistency: format, choices, variable usage, etc

Linter Validation (Tertiary)


- Best practice recommendations: Security concerns, style guidelines, naming conventions
- Non-blocking: Warnings and suggestions, not errors
- Quality improvements: Helps developers create better servers
- Educational: Teaches best practices for MCP server development

Implementation Approach

The enhanced validation will be implemented in stages to minimize risk and allow for review and experimentation:

Stage 1: Schema Validation and Exhaustive Validation Results (Current)


- Convert existing validators to use and track context and to return exhaustive results
- Add mcp-publisher validate command that performs exhaustive validation with full schema validation
- Implement schema validation with configurable policy for non-current schemas
- Schema version validation consolidated in schema.go with policy support (Allow/Warn/Error)
- mcp-publisher publish command validates schema version (rejects empty and non-current schemas) but does not perform full schema validation
- API /v0/publish endpoint uses ValidatePublishRequest which validates schema version and semantic validation, but not full schema validation
- All callers migrated: All code now uses ValidateServerJSON() with ValidationOptions directly; legacy wrapper removed
- ValidationResult.FirstError(): Backward compatibility maintained via FirstError() method for code expecting error return type
- This allows experimentation and validation of the new model (including schema validation) without impacting production API

Future Stages


- Enable full schema validation in mcp-publisher publish command (currently only validates schema version)
- Enable full schema validation in the /v0/publish API endpoint (currently only validates schema version via ValidatePublishRequest)
- Add /v0/validate API endpoint for programmatic validation without publishing (see Validate API Endpoint section below)
- Enhance production code to use full validation results: Update importer.go and validate-examples/main.go to log all issues instead of just first error
- Build out comprehensive semantic and linter validation rules (with tests)
- Remove redundant manual validators that duplicate schema constraints
- Consider migrating tests to check all validation issues instead of just first error (where appropriate)

Proposed Design

Design Goals

1. Exhaustive Feedback: Collect all validation issues in a single pass, not just the first error
2. Precise Location: Provide exact JSON paths for every validation issue
3. Structured Output: Return machine-readable validation results with consistent format
4. Backward Compatibility: Use ValidationResult.FirstError() for code expecting error return type
5. Extensible: Support different validation types (json, schema, semantic, linter) and severity levels


Core Types

text
/ Detailed source-code truncated for AI context efficiency. /

Validation Types

The Type field categorizes validation issues by their source:

- ValidationIssueTypeJSON: JSON parsing errors (malformed JSON syntax)
- ValidationIssueTypeSchema: JSON Schema validation errors (structural/format violations)
- ValidationIssueTypeSemantic: Logical validation errors not enforceable in schema (business rules)
- ValidationIssueTypeLinter: Best practice recommendations, security concerns, style guidelines

The Severity field indicates the impact level:

- ValidationIssueSeverityError: Critical issues that must be fixed
- ValidationIssueSeverityWarning: Issues that should be addressed
- ValidationIssueSeverityInfo: Suggestions and recommendations

The Reference field provides context about what triggered the validation issue:

- Schema validation: Contains the resolved schema path with $ref resolution (e.g., "#/definitions/SseTransport/properties/url/format from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/transport/properties/url/format")
- Semantic validation: Contains rule names for business logic (e.g., "invalid-server-name", "missing-transport-url")
- Linter validation: Contains rule names for best practices (e.g., "descriptive-naming", "security-recommendation")
- JSON validation: Contains error type identifiers (e.g., "json-syntax-error", "invalid-json-format")

ValidationContext

The ValidationContext tracks the current JSON path during validation, allowing validators to report issues with precise location information. This is essential for providing users with exact paths to problematic fields.

#### Purpose
- Path tracking: Builds JSON paths like "packages[0].transport.url" as validation traverses nested structures
- Precise error location: Users can see exactly where validation issues occur
- Immutable building: Each method returns a new context, preventing accidental mutations

#### Usage Example

go
// Navigate to packages array, first item, transport field
pkgCtx := ctx.Field("packages").Index(0).Field("transport")
// Validate transport - any issues will be reported at "packages[0].transport"

Backward Compatibility Strategy

The design maintains perfect backward compatibility by leveraging Go's existing error handling patterns:

#### Error Message Preservation
- Current validators use fmt.Errorf("%w: %s", ErrInvalidRepositoryURL, obj.URL)
- New validators use NewValidationIssueFromError() which extracts err.Error()
- Result: Identical error messages, ensuring all existing tests pass

#### Constructor Pattern
Following Go conventions used throughout the project:

go
// Standard constructor for manual field setting
issue := NewValidationIssue(ValidationIssueTypeLinter, "name", "message", ValidationIssueSeverityWarning, "rule-name")

// Constructor that preserves existing error formatting
issue := NewValidationIssueFromError(ValidationIssueTypeSemantic, "path", err, "rule-name")

#### Error Interface Compatibility

For code that needs an error return type, use ValidationResult.FirstError():

go
result := ValidateServerJSON(serverJSON, ValidationSchemaVersionAndSemantic)
if err := result.FirstError(); err != nil {
return err // Returns first error-level issue as error
}

This maintains compatibility with existing error handling code while providing access to all validation issues.

New Validation Architecture

#### All Validators Use Context and Return ValidationResult

All existing validators are converted to use ValidationContext for precise error location tracking and return ValidationResult for comprehensive error collection:

go
func ValidateServerJSON(serverJSON apiv0.ServerJSON, opts ValidationOptions) ValidationResult {
result := &ValidationResult{Valid: true, Issues: []ValidationIssue{}}

// Schema validation based on options
if opts.ValidateSchemaVersion || opts.ValidateSchema {
schemaResult := validateServerJSONSchema(serverJSON, opts.ValidateSchema, opts.NonCurrentSchemaPolicy)
result.Merge(schemaResult)
}

// Semantic validation (if requested)
if opts.ValidateSemantic {
// Validate server name - using existing error logic
if _, err := parseServerName(*serverJSON); err != nil {
issue := NewValidationIssueFromError(
ValidationIssueTypeSemantic,
"name",
err,
"invalid-server-name",
)
result.AddIssue(issue)
}

// Validate repository with context
if repoResult := validateRepository(&ValidationContext{}, &serverJSON.Repository); !repoResult.Valid {
result.Merge(repoResult)
}

// ... more semantic validation ...
}

return result
}

For backward compatibility with code that expects an error return type, ValidationResult.FirstError() can be used:

go
result := ValidateServerJSON(serverJSON, ValidationSchemaVersionAndSemantic)
if err := result.FirstError(); err != nil {
return err
}

Schema Validation

The project uses github.com/santhosh-tekuri/jsonschema/v5 for schema validation with an embedded schema approach. The schema is embedded at compile time using Go's //go:embed directive, eliminating the need for file system access and ensuring the schema is always available.

Schema-First Validation Strategy

The enhanced validation system adopts a schema-first approach where JSON Schema validation serves as the primary and first validator. This strategy addresses the current duplication between manual/semantic validators and schema constraints.

#### Current Problem: Validation Duplication

The existing system has both:
- Manual/semantic validators: Custom Go code validating server name format, URL patterns, etc.
- JSON Schema validation: Structural validation of the same constraints

This creates redundancy and potential inconsistencies where:
- Manual validators provide friendly error messages
- Schema validation provides technical error messages
- Both validate the same underlying constraints

#### Proposed Solution: Schema-First with Friendly Error Mapping

1. Schema validation runs first and catches all structural/format issues
2. Manual validators are eliminated for constraints already specified in the schema
3. Schema error messages are mapped to friendly messages using deterministic schema rule references (if needed)

Embedded Schema Benefits

#### No File System Dependencies
- Embedded at compile time: Schema is included in the binary using //go:embed schema/*.json
- No external files: Eliminates dependency on schema files being present at runtime
- Portable: Binary contains everything needed for validation

#### Version Consistency
- Schema version tracking: model.CurrentSchemaURL provides compile-time constant for current schema version
- Version validation: Schema version validation consolidated in schema.go with policy support (Allow/Warn/Error)
- Empty schema handling: Empty/missing schema fields always generate errors during validation
- Compile-time validation: Schema is validated when the binary is built
- No version drift: Schema version is locked to the binary version

#### Performance Benefits
- No I/O operations: Schema is already in memory
- Faster startup: No need to read schema files
- Reduced complexity: No file path resolution or error handling for missing files

Rich Error Information

The jsonschema.ValidationError provides:
- InstanceLocation: JSON Pointer format (RFC 6901) path to the invalid field (e.g., "/packages/0/transport/url")
- Error: Detailed error message from schema
- KeywordLocation: Schema path with $ref segments (e.g., "/$ref/properties/transport/$ref/properties/url/format")
- AbsoluteKeywordLocation: Resolved schema path (e.g., "file:///server.schema.json#/definitions/SseTransport/properties/url/format")

Path Format Conversion: JSON Pointer format paths from InstanceLocation are converted to bracket notation format to match semantic validation paths. The conversion transforms JSON Pointer paths like "/packages/0/transport/url" into bracket notation like "packages[0].transport.url". This ensures consistent path formatting across all validation types (schema, semantic, and linter).

#### Current Error Reference Format

Schema validation errors now include detailed reference information:

text
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format

This format provides:
- Absolute location: #/definitions/Repository/properties/url/format - the final resolved schema location
- Resolved path: Shows the complete path with $ref segments replaced by their resolved values in square brackets
- Full context: Users can see exactly which schema rule triggered the error and how it was reached

#### Error Message Quality

The current schema validation errors are generally quite readable:

text
[error] repository.url (schema)
'' has invalid format 'uri'
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format

#### Future Error Message Enhancement

If we encounter situations where schema validation errors need to be more user-friendly, we have full access to:

- KeywordLocation: The schema path to the validating rule
- AbsoluteKeywordLocation: The absolute schema location after $ref resolution
- InstanceLocation: The JSON Pointer format path (e.g., "/packages/0/transport/url") which is converted to bracket notation (e.g., "packages[0].transport.url") for consistency with semantic validation
- Message: The original schema validation error message
- Complete reference stack: The entire resolved path showing how the error was reached

This allows us to build better, more descriptive error messages if needed, while maintaining the current high-quality error references.

Integration with ValidateServerJSON

text
/ Detailed source-code truncated for AI context efficiency. /

Schema Version Validation

Schema version validation is consolidated in validateServerJSONSchema() (now private) in schema.go:

- Empty schema check: Always performed when schema validation is requested, always generates an error
- Schema file existence check: Always performed when schema validation is requested - verifies the schema file exists in embedded schemas, even when not performing full validation
- Schema version policy: Controls how non-current schemas are handled (via ValidationOptions.NonCurrentSchemaPolicy):
- SchemaVersionPolicyAllow: Non-current schemas are allowed with no warning
- SchemaVersionPolicyWarn: Non-current schemas are allowed but generate a warning
- SchemaVersionPolicyError: Non-current schemas are rejected with an error
- Full schema validation: Only performed if performValidation is true

The mcp-publisher publish command validates schema version (rejects empty, non-existent, and non-current schemas) but does not perform full schema validation. The mcp-publisher validate command performs full schema validation with SchemaVersionPolicyWarn (warns about non-current schemas but doesn't error).

Request Validation Functions

Two consolidated validation functions in validators package handle publish and update requests:

- ValidatePublishRequest(): Validates publisher extensions, server JSON structure (via ValidateServerJSON), and registry ownership (if enabled)
- ValidateUpdateRequest(): Validates server JSON structure (via ValidateServerJSON) and registry ownership (if enabled), with option to skip registry validation for deleted servers

Both functions use ValidateServerJSON() with ValidationSchemaVersionAndSemantic and FirstError() for backward-compatible error handling. Registry ownership validation is extracted into a shared validateRegistryOwnership() helper function.

Testing with Draft or Custom Schemas

The validation system supports testing against draft schemas or custom schema versions by embedding them in the validators package.

#### Setup Steps

1. Copy the schema file: Copy your schema file (e.g., docs/reference/server-json/draft/server.schema.json) to internal/validators/schemas/{version}.json
- Example: Copy to internal/validators/schemas/draft.json for draft schema testing
- Ensure the schema file's $id field matches: https://static.modelcontextprotocol.io/schemas/{version}/server.schema.json
- For draft schema, the $id should be: https://raw.githubusercontent.com/modelcontextprotocol/registry/main/docs/reference/server-json/draft/server.schema.json

2. Rebuild: Recompile the Go binary to embed the new schema file (schemas are embedded at compile time)

3. Use in server.json: Reference the schema version in your server.json file:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/draft/server.schema.json",
...
}

#### Schema Version Identifier Rules

Schema version identifiers can contain:
- Letters: A-Z, a-z
- Digits: 0-9
- Special characters: Hyphen (-), underscore (_), tilde (~), period (.)

Examples of valid identifiers: 2025-10-17, draft, test-v1.0, custom_schema~1.2.3

#### Non-Current Schema Policy

When testing with draft or custom schemas, they will be treated as non-current schemas (since they don't match model.CurrentSchemaURL), which triggers the NonCurrentSchemaPolicy behavior:

- SchemaVersionPolicyAllow: Draft schemas are allowed with no warning
- SchemaVersionPolicyWarn: Draft schemas are allowed but generate a warning (default for ValidationAll and ValidationSchemaVersionAndSemantic)
- SchemaVersionPolicyError: Draft schemas are rejected with an error (default for ValidationSchemaVersionOnly)

#### Treating Draft as Current Schema

To test with a draft schema as if it were the current schema (no warnings/errors about non-current version):

1. Temporarily update model.CurrentSchemaVersion in pkg/model/constants.go:

go
const (
CurrentSchemaVersion = "draft" // Temporarily set for testing
CurrentSchemaURL = "https://static.modelcontextprotocol.io/schemas/" + CurrentSchemaVersion + "/server.schema.json"
)

2. Rebuild and test

3. Important: Revert the change before committing - model.CurrentSchemaVersion should always point to the latest official schema version

#### Example: Testing with Draft Schema

bash

1. Copy draft schema


cp docs/reference/server-json/draft/server.schema.json internal/validators/schemas/draft.json

2. Verify the $id field in draft.json is correct


Should be: "https://raw.githubusercontent.com/modelcontextprotocol/registry/main/docs/reference/server-json/draft/server.schema.json"

3. Rebuild


go build ./...

4. Use in server.json


Set "$schema": "https://static.modelcontextprotocol.io/schemas/draft/server.schema.json"

5. Validate


mcp-publisher validate server.json

Note: The draft schema will be validated successfully, but you may see a warning about it not being the current schema version unless you temporarily update model.CurrentSchemaVersion as described above.

Discriminated Union Error Consolidation

The schema uses anyOf for discriminated unions (transport, argument, remote), which causes noisy error messages when validation fails. When a transport/argument/remote doesn't match its specified type, anyOf validation tries all variants and reports errors for each one that doesn't match.

Problem Example: If you have an "sse" transport with no url, you get errors for all transport types:

1. [error] packages[0].transport.type (schema)
value must be "stdio"
Reference: #/definitions/StdioTransport/properties/type/enum

2. [error] packages[0].transport (schema)
missing required fields: 'url'
Reference: #/definitions/StreamableHttpTransport/required

3. [error] packages[0].transport.type (schema)
value must be "streamable-http"
Reference: #/definitions/StreamableHttpTransport/properties/type/enum

4. [error] packages[0].transport (schema)
missing required fields: 'url'
Reference: #/definitions/SseTransport/required

Solution Strategy: Since we cannot modify the schema (it's managed in the static repository), we'll detect and consolidate these anyOf error patterns in the validation error processing code (addDetailedErrors in schema.go).

Detection Strategy:
- Identify groups of errors at the same JSON path (e.g., packages[0].transport)
- Detect pattern of multiple "type must be X" errors or multiple "missing required fields" errors from different schema definitions
- Extract the actual type value from the JSON being validated
- Filter out errors from non-matching transport/argument/remote definitions
- Consolidate remaining errors into a single, actionable error message

Implementation Approach:
- Add logic in addDetailedErrors() or a post-processing function to detect anyOf error clusters
- Group errors by instance location and analyze error patterns
- Identify the intended type from the JSON data
- Filter/consolidate errors to only show relevant issues for the actual type specified
- Preserve all other validation errors unchanged

This approach allows us to provide clearer error messages without modifying the schema, and can be applied to transport, argument, and remote validation.

Future Enhancement: If the schema is updated to use if/then/else discriminated unions in the future, this consolidation logic can be removed, but it provides immediate value without requiring schema changes.

Implementation Status

✅ Completed Features

#### Core Validation System
- [x] ValidationIssue and ValidationResult types: Complete with all required fields
- [x] ValidationContext: Immutable context building for JSON path tracking
- [x] Constructor functions: NewValidationIssue() and NewValidationIssueFromError() with consistent parameter naming
- [x] Helper methods: Context building, result merging, and path construction

#### Schema Validation Integration
- [x] JSON Schema validation: Using existing jsonschema/v5 library
- [x] Error conversion: Schema errors converted to ValidationIssue format
- [x] $ref resolution: Sophisticated resolution showing complete schema path with resolved references
- [x] Comprehensive testing: Full test coverage for schema validation scenarios
- [x] Embedded schema: Schema embedded at compile time using //go:embed directive
- [x] Path format normalization: JSON Pointer paths converted to bracket notation to match semantic validation format (e.g., /packages/0/transportpackages[0].transport)

#### Enhanced Error References
- [x] Resolved schema paths: Shows complete path with $ref segments replaced by resolved values
- [x] Incremental resolution: Each $ref resolved in context of previous resolution
- [x] Human-readable format: Clear indication of schema rule location and resolution chain
- [x] Consistent output: All schema errors use the same reference format

#### Testing and Quality
- [x] Unit tests: Comprehensive test coverage for all new functionality
- [x] Integration tests: End-to-end validation testing
- [x] Backward compatibility: Existing validation continues to work

#### Caller Migration
- [x] Function rename: ValidateServerJSONExhaustive renamed to ValidateServerJSON (now takes ValidationOptions parameter)
- [x] Legacy wrapper removed: Old ValidateServerJSON() wrapper that returned error removed
- [x] All callers migrated: All production code and tests now use ValidateServerJSON() with ValidationOptions directly
- [x] FirstError() helper: ValidationResult.FirstError() method added for backward compatibility with error return types
- [x] Request validators consolidated: ValidatePublishRequest and ValidateUpdateRequest moved to validators package with shared validateRegistryOwnership helper

🔄 In Progress

#### Schema-First Validation Strategy
- [x] Schema validation integration: ValidateServerJSON() runs schema validation first
- [x] CLI integration: Schema validation enabled in mcp-publisher validate command
- [x] Schema version validation: Consolidated in schema.go with policy support (Allow/Warn/Error)
- [x] Schema file existence check: Schema version validation verifies schema file exists in embedded schemas
- [x] Publish command schema checks: mcp-publisher publish validates schema version (rejects empty, non-existent, and non-current schemas)
- [x] API endpoint validation: /v0/publish uses ValidatePublishRequest which validates schema version and semantic validation
- [ ] Full schema validation in publish: Enable full schema validation in mcp-publisher publish command
- [ ] Full schema validation in API: Enable full schema validation in /v0/publish API endpoint
- [ ] Discriminated union error consolidation: Detect and filter/consolidate noisy anyOf errors for transport, argument, and remote validation to show only relevant errors for the actual type
- [ ] Error message mapping: Map technical schema errors to user-friendly messages (if needed)
- [ ] Validator migration: Move from manual validators to schema-first approach

📋 Pending

#### Migration Strategy
- [ ] Phase 1: Identify Schema Coverage: Audit existing manual validators against schema constraints
- [ ] Phase 2: Implement Error Mapping (Optional): Create mapping function for schema error messages (only if current messages are insufficient)
- [ ] Phase 3: Error Consolidation: Implement logic to detect and consolidate noisy anyOf errors from discriminated unions (transport, argument, remote)
- [ ] Phase 4: Enable Schema-First Validation: Update tests to expect schema validation errors instead of semantic errors; Enable schema validation in publish API
- [ ] Phase 5: Clean Up Redundant Validators: Remove manual validators that duplicate schema constraints
- [ ] Phase 6: Add Enhanced Semantic and Linter Rules: Review and implement specific rules from MCP Registry Validator linter guidelines

#### Command Integration
- [x] CLI updates: mcp-publisher validate command uses detailed validation with full schema validation
- [x] Publish command: mcp-publisher publish validates schema version (rejects empty, non-existent, and non-current schemas)
- [x] Shared validation logic: Both commands use runValidationAndPrintIssues to eliminate duplication
- [x] Caller migration: All callers migrated to use ValidateServerJSON() with ValidationOptions directly
- [x] Request validation consolidation: ValidatePublishRequest and ValidateUpdateRequest consolidated in validators package
- [ ] Enhanced error reporting: Update production code (importer, validate-examples tool) to log all issues instead of just first error
- [ ] Output formatting: Add JSON output format options
- [ ] Filtering options: Add severity and type filtering

#### Validate API Endpoint
- [ ] POST /v0/validate endpoint: API endpoint for validating server.json without publishing

#### Documentation and Polish
- [ ] API documentation: Update API documentation with new validation types

🎯 Key Achievements

1. Comprehensive Error Collection: All validation issues collected in single pass
2. Precise Error Location: Exact JSON paths for every validation issue
3. Schema Integration: Full JSON Schema validation with detailed error references
4. Backward Compatibility: Existing validation continues to work unchanged
5. Type Safety: Constrained types prevent invalid validation issue creation
6. Extensible Architecture: Easy to add new validation types and severity levels

The enhanced validation system is now production-ready with comprehensive schema validation, detailed error references, and full backward compatibility.


Example Usage

JSON Output Format


json
{
"valid": false,
"issues": [
{
"type": "json",
"path": "",
"message": "invalid JSON syntax at line 5, column 12",
"severity": "error",
"reference": "json-syntax-error"
},
{
"type": "semantic",
"path": "name",
"message": "server name must be in format 'dns-namespace/name'",
"severity": "error",
"reference": "invalid-server-name"
},
{
"type": "semantic",
"path": "packages[0].transport.url",
"message": "url is required for streamable-http transport type",
"severity": "error",
"reference": "missing-transport-url"
},
{
"type": "schema",
"path": "packages[1].environmentVariables[0].name",
"message": "string does not match required pattern",
"severity": "error",
"reference": "#/definitions/EnvironmentVariable/properties/name/pattern from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/environmentVariables/items/[#/definitions/EnvironmentVariable]/properties/name/pattern"
},
{
"type": "linter",
"path": "packages[1].description",
"message": "consider adding a more descriptive package description",
"severity": "warning",
"reference": "descriptive-package-description"
}
]
}

Note: The JSON output still uses string values for type and severity fields for JSON serialization compatibility, but the Go code uses the typed constants for type safety.

CLI Usage


bash

Basic validation


mcp-publisher validate server.json

JSON output format


mcp-publisher validate --format json server.json

Filter by severity


mcp-publisher validate --severity error server.json

Include schema validation


mcp-publisher validate --schema server.json

Benefits and Achievements

✅ Comprehensive Feedback


- Exhaustive error collection: See all validation issues at once, not just the first error
- Better developer experience: No need to fix errors one by one
- Precise error location: JSON paths show exactly where issues occur in large JSON files
- Structured output: JSON format for tooling integration and machine-readable error information

✅ Schema-First Validation


- Primary validator: Schema validation catches all structural and format violations defined in the schema
- Semantic validation only for gaps: Covers business logic that cannot be expressed in JSON Schema
- Standards compliance: Ensures server.json follows the official schema
- Detailed error messages: Exact JSON paths and resolved schema references

✅ Backward Compatibility


- Backward compatibility: Use ValidationResult.FirstError() for code expecting error return type
- Error interface compatibility: Leverages Go's error interface and existing error constants
- Constructor pattern: Follows established project conventions
- No breaking changes: All error handling code remains functional

✅ Extensible Architecture


- Easy to add new validation types: Schema, semantic, linter validation
- Easy to add new severity levels: Error, warning, info
- Easy to add filtering and formatting options: By type, severity, path pattern
- Type safety: Constrained types prevent invalid validation issue creation

✅ Schema-First Strategy Benefits


- Eliminates duplication: Single source of truth for structural constraints
- Better error messages: Schema validation provides precise JSON paths with deterministic mapping
- Maintainability: Schema changes automatically update validation
- Standards compliance: Ensures validation matches official schema exactly

Technical Design

Architecture Overview

The enhanced validation system uses a schema-first approach with comprehensive error collection and precise location tracking. The system is designed for maximum backward compatibility while providing extensive new capabilities.

#### Error Interface Compatibility
- Leverages existing error constants: ErrInvalidRepositoryURL, ErrVersionLooksLikeRange, etc.
- Preserves error wrapping: Uses fmt.Errorf("%w: %s", err, context) pattern
- Maintains error.Is() compatibility: Existing error checking continues to work
- No breaking changes: All error handling code remains functional

#### Constructor Pattern
Following established Go conventions in the project:
- NewValidationIssue(): Standard constructor following NewXxx() pattern
- NewValidationIssueFromError(): Specialized constructor for error conversion
- Consistent with project: Matches patterns used in NewConfig(), NewServer(), etc.
- Type safety: Compile-time validation of required fields

#### Context Passing Architecture
- Immutable context building: ctx.Field("name").Index(0) pattern
- Clean composition: Validators focus on validation, not path building
- Reusable validators: Same validator can be called with different contexts
- No global state: Thread-safe validation with explicit context

#### Type Safety with Constrained Values
Following Go best practices used throughout the project:
- Typed string constants: ValidationIssueType, ValidationIssueSeverity prevent invalid values
- Compile-time validation: IDE autocomplete and error checking
- JSON compatibility: Still serializes as strings for API compatibility
- Refactoring safety: Rename constants without breaking code
- Consistent with project: Matches patterns used in Status, Format, ArgumentType

Performance Considerations


- Slightly slower than fail-fast validation: Acceptable trade-off for better user experience
- Memory usage increases with error collection: Manageable for typical server.json files
- Schema validation performance: Embedded schema eliminates I/O operations

Testing Strategy


- Unit tests: Each validator with context
- Integration tests: End-to-end validation testing
- Backward compatibility tests: Ensure existing code continues to work
- Performance benchmarks: Validate acceptable performance characteristics

---

Appendix: Future Enhancements

Additional Validation Types


- Linter rules: Best practices and style guidelines
- Warning level: Non-critical issues
- Info level: Suggestions and improvements

Advanced Features


- Error filtering: By type, severity, path pattern
- Output formatting: Human-readable, JSON, XML
- Configuration: Custom validation rules
- IDE integration: Real-time validation feedback

Tooling Integration


- WASM package: Browser-based validation
- VS Code extension: Real-time validation
- CI/CD integration: Automated validation in pipelines
- API endpoint: Validation as a service (see Validate API Endpoint section below)

Validate API Endpoint

Overview

A REST API endpoint (POST /v0/validate) that validates server.json files without publishing them to the registry. This endpoint provides programmatic access to the same validation logic used by the CLI commands, returning structured validation results in JSON format.

Use Cases

- CI/CD Pipelines: Validate server.json files before attempting to publish
- Editor/IDE Integrations: Real-time validation feedback in development tools
- Web UIs: Validate files in browser-based interfaces
- Pre-publish Checks: Validate before authentication/publishing workflow
- Validation as a Service: Allow external tools to validate server.json format

Implementation

#### Endpoint Specification

Endpoint: POST /v0/validate
Authentication: None required (read-only operation)
Content-Type: application/json

#### Request

Request body should be a valid ServerJSON object:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
"name": "io.example/server",
"version": "1.0.0",
...
}

#### Response

Returns a ValidationResult in JSON format:

json
{
"valid": false,
"issues": [
{
"type": "schema",
"path": "packages[0].transport.url",
"message": "missing required field: 'url'",
"severity": "error",
"reference": "#/definitions/SseTransport/required"
},
{
"type": "semantic",
"path": "name",
"message": "server name must be in format 'dns-namespace/name'",
"severity": "error",
"reference": "invalid-server-name"
}
]
}

HTTP Status Codes:
- 200 OK: Validation completed successfully (regardless of whether valid or invalid)
- 400 Bad Request: Malformed JSON or invalid request format

Note: A 200 OK status does not mean the server.json is valid - check the valid field in the response body.

#### Implementation Details

Location: internal/api/handlers/v0/validate.go

Handler Function:
- Accepts ServerJSON in request body
- Calls validators.ValidateServerJSON(serverJSON, validators.ValidationAll)
- Returns ValidationResult as JSON response
- Uses Huma framework (same as publish endpoint) for request/response handling

Key Differences from Publish Endpoint:
- No authentication required (read-only)
- Does not save to database
- Returns structured validation results instead of published server response
- Returns warnings, not just errors (useful for comprehensive feedback)

Reuses Existing Infrastructure:
- Same validation functions as CLI commands
- Same ValidationResult type
- Same issue types and severity levels
- Consistent validation behavior across CLI and API

Testing Strategy

#### Unit Tests

Test handler function with mocked dependencies:
- Valid server.json → valid: true, issues: []
- Invalid server.json → valid: false with specific issues
- Schema errors → issues with type: "schema"
- Semantic errors → issues with type: "semantic"
- Empty schema → schema-field-required issue
- Non-current schema → schema-version-deprecated issue
- Multiple issues → all issues returned in response
- Malformed JSON → proper error handling

#### Integration Tests

Follow patterns from publish_integration_test.go:
- Start test server
- Send HTTP POST requests with various server.json payloads
- Assert response JSON matches expected ValidationResult structure
- Verify HTTP status codes (200 for valid requests, 400 for malformed)
- Test both valid and invalid inputs
- Reuse test fixtures from validation_detailed_test.go

#### Test Infrastructure

- Reuse existing test server setup
- Use same patterns as test_endpoints.sh for manual testing
- Leverage existing validation test cases

Future Enhancements

- Query Parameters: Optional parameters to filter by issue type or severity
- Partial Validation: Validate specific sections (e.g., only schema, only semantic)
- Format Options: Request different output formats (detailed vs. summary)
- Batch Validation: Validate multiple server.json files in one request

---

Design/Roadmap

MCP Registry Roadmap

This is a high-level roadmap for the MCP Registry. It is subject to change and not exhaustive, but it outlines the general thinking of the sequencing and scope of our work in this repository.

This roadmap may occasionally drift out of date. Please review Issues (and corresponding Labels) for the most current work in progress.

Current Status

NOTE

The phase labelling below is out of date as of 2026-08-10. The registry launched in preview on


2025-09-08 and the v0.1 API entered a freeze on 2025-10-24, so "Go-Live" has already happened. The

phases are retained for historical context. See

Issues for current work.

The initial version of the MCP Registry is actively being developed. The initial focus is on delivering a REST API to which server creators can publish, and aggregator/marketplace consumers can ETL.

Phase 1: MVP/Go-Live

See the go-live blocker issues.

Backlog (Future Work, may be moved to out of scope)

- [ ] UI implementation
- [ ] Store and surface other data besides servers (e.g. clients, resources)
- [ ] Download count tracking
- [ ] Internationalization (i18n)

Out of Scope (Not Planned)

- Source code hosting: The registry will never host actual server code
- Quality rankings: No built-in server quality assessments or rankings
- Curation: No editorial decisions about which servers are "better"
- Unified runtime: Not solving how servers are executed
- Server hosting: The registry does not provide hosting for servers
- Search engine: The registry will not provide a commercial grade search engine for servers
- Server tags or categories: Not supported, to reduce moderation burden
- Server rankings: The registry will not rank servers by subjective measures of quality

---

Design/Tech Architecture

MCP Registry Technical Architecture

WARNING

This document has drifted from the shipped system and is kept as a design record.


For the architecture as deployed, see deploy/README.md; for the API as

implemented, see the API reference.

> Known inaccuracies below, as of 2026-08-10:

- The registry is not deployed via a Helm chart. It is a plain Kubernetes Deployment created by

Pulumi; Helm is used only for third-party components (ingress-nginx, cert-manager,

cloudnative-pg, k8up, monitoring).

- The database diagram shows a StatefulSet on port 27017 (MongoDB). The database is PostgreSQL

on 5432, provisioned by the CloudNativePG operator.

- The DNS verification sequence (mcp verify-domain, POST /verify-domain,

POST /verify-domain/check, server-issued challenge tokens) describes a design that was never

built. The shipped flow signs a timestamp with the operator's key and posts it to

POST /v0/auth/dns, with the public key published in a v=MCPv1; k=...; p=... TXT record.

- No /admin/* routes exist. Admin actions use the regular server endpoints with an

admin-permissioned token.

- The CLI is mcp-publisher, not mcp, and publishing posts to /v0/publish.

- The database stores servers and server extensions only; it holds no user authentication state

and no DNS verification records.

This document describes the technical architecture of the MCP Registry, including system components, deployment strategies, and data flows.

System Overview

The MCP Registry is designed as a lightweight metadata service that bridges MCP server creators with consumers (MCP clients and aggregators).

Core Components

REST API (Go)

The main application server implemented in Go, providing:
- Public read endpoints for server discovery
- Authenticated write endpoints for server publication
- GitHub OAuth integration (extensible to other providers)
- DNS verification system (optional for custom namespaces)

Database (PostgreSQL)

Primary data store for:
- Versioned server metadata (server.json contents)
- User authentication state
- DNS verification records

CDN Layer

Critical for scalability:
- Caches all public read endpoints
- Reduces load on origin servers
- Enables global distribution
- Designed for daily consumer polling patterns

CLI Tool

Developer interface for:
- Server publication workflow
- GitHub OAuth flow
- DNS verification

Deployment Architecture

Kubernetes Deployment (Helm)

The registry is designed to run on Kubernetes using Helm charts:

mermaid
graph TB
subgraph "Kubernetes Cluster"
subgraph "Namespace: mcp-registry"
subgraph "Registry Service"
LB[Load Balancer<br/>:80]
RS[Registry Service<br/>:8080]
RP1[Registry Pod 1]
RP2[Registry Pod 2]
RP3[Registry Pod N]
end

subgraph "Database Service"
DBS[DB Service<br/>:27017]
SS[StatefulSet]
PV[Persistent Volume]
end

subgraph "Secrets"
GHS[GitHub OAuth Secret]
end
end
end

LB --> RS
RS --> RP1
RS --> RP2
RS --> RP3
RP1 --> DBS
RP2 --> DBS
RP3 --> DBS
DBS --> SS
SS --> PV
RP1 -.-> GHS
RP2 -.-> GHS
RP3 -.-> GHS

Data Flow Patterns

1. Server Publication Flow

mermaid
sequenceDiagram
participant Dev as Developer
participant CLI as CLI Tool
participant API as Registry API
participant DB as Database
participant GH as GitHub
participant DNS as DNS Provider

Dev->>CLI: mcp publish server.json
CLI->>CLI: Validate server.json
CLI->>GH: OAuth flow
GH-->>CLI: Access token
CLI->>API: POST /servers
API->>GH: Verify token
API->>DNS: Verify domain (if applicable)
API->>DB: Store metadata
API-->>CLI: Success
CLI-->>Dev: Published!

2. Consumer Discovery Flow

mermaid
sequenceDiagram
participant Client as MCP Client Host App
participant INT as Intermediary<br/>(Marketplace/Aggregator)
participant CDN as CDN Cache
participant API as Registry API
participant DB as Database

Note over INT,CDN: Daily ETL Process
INT->>CDN: GET /servers
alt Cache Hit
CDN-->>INT: Cached response
else Cache Miss
CDN->>API: GET /servers
API->>DB: Query servers
DB-->>API: Server list
API-->>CDN: Response + cache headers
CDN-->>INT: Response
end
INT->>INT: Process & enhance data
INT->>INT: Store in local cache

Note over Client,INT: Real-time Client Access
Client->>INT: Request server list
INT-->>Client: Curated/enhanced data

3. DNS Verification Flow

mermaid
sequenceDiagram
participant User as User
participant CLI as CLI Tool
participant API as Registry API
participant DNS as DNS Provider
participant DB as Database

User->>CLI: mcp verify-domain example.com
CLI->>API: POST /verify-domain
API->>API: Generate verification token
API->>DB: Store pending verification
API-->>CLI: TXT record: mcp-verify=abc123
CLI-->>User: Add TXT record to DNS
User->>DNS: Configure TXT record
User->>CLI: Confirm added
CLI->>API: POST /verify-domain/check
API->>DNS: Query TXT records
DNS-->>API: TXT records
API->>API: Validate token
API->>DB: Store verification
API-->>CLI: Domain verified
CLI-->>User: Success!

4. Admin OIDC Authentication Flow

For registry administration, users with @modelcontextprotocol.io Google Cloud Identity accounts can authenticate using OIDC:

mermaid
sequenceDiagram
participant Admin as Admin User
participant CLI as Admin CLI
participant GCP as Google Cloud Identity
participant API as Registry API

Admin->>CLI: Request admin token
CLI->>GCP: gcloud auth print-identity-token --audiences=mcp-registry
GCP-->>CLI: ID Token (with hd: "modelcontextprotocol.io")
CLI->>API: POST /v0.1/auth/oidc {"oidc_token": "eyJ..."}
API->>GCP: Verify token signature (JWKS)
API->>API: Validate claims (issuer, audience, hd)
API->>API: Grant admin permissions (edit: , publish: )
API-->>CLI: Registry JWT Token
CLI->>API: POST /admin/* (with Registry JWT)
API->>API: Validate JWT + permissions
API-->>CLI: Admin operation success

Usage:

bash

Get Google Cloud Identity token


ID_TOKEN=$(gcloud auth print-identity-token)

Exchange for Registry JWT token


REGISTRY_TOKEN=$(curl -X POST /v0.1/auth/oidc \
-H "Content-Type: application/json" \
-d '{"oidc_token": "'$ID_TOKEN'"}' | jq -r .registry_token)

Use for admin operations


curl -H "Authorization: Bearer $REGISTRY_TOKEN" /v0.1/...

---

Modelcontextprotocol Io/About

---
title: The MCP Registry
sidebarTitle: About
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

The MCP Registry is the official centralized metadata repository for publicly accessible MCP servers, backed by major trusted contributors to the MCP ecosystem such as Anthropic, GitHub, PulseMCP, and Microsoft.

The MCP Registry provides:

- A single place for server creators to publish metadata about their servers
- Namespace management through DNS verification
- A REST API for MCP clients and aggregators to discover available servers
- Standardized installation and configuration information

Server metadata is stored in a standardized server.json format, which contains:

- The server's unique name (e.g., io.github.user/server-name)
- Where to locate the server (e.g., npm package name, remote server URL)
- Execution instructions (e.g., command-line args, env vars)
- Other discovery data (e.g., description, server capabilities)

The MCP Registry Ecosystem

The MCP Registry is part of an ecosystem that looks something like:

Relationship with Package Registries

Package registries — such as npm, PyPI, and Docker Hub — host packages with code and binaries.

The MCP Registry hosts metadata that points to those packages.

For example, a weather-mcp package could be hosted on npm, and metadata in the MCP Registry could map the "weather v1.2.0" server to npm:weather-mcp.

The Package Types guide lists the supported package types and registries. More package registries may be supported in the future based on community demand. If you are interested in building support for a package registry, please open an issue.

Relationship with Server Developers

The MCP Registry supports both open-source and closed-source servers. Server developers can publish their server's metadata to the registry as long as the server's installation method is publicly available (e.g., an npm package or a Docker image on a public registry) _or_ the server itself is publicly accessible (e.g., a remote server that is not restricted to private networks).

The MCP Registry does not support private servers. Private servers are those that are only accessible to a narrow set of users. For example, servers published on a private network (like mcp.acme-corp.internal) or on private package registries (e.g. npx -y @acme/mcp --registry https://artifactory.acme-corp.internal/npm). If you want to publish private servers, we recommend that you host your own private MCP registry and add them there.

Relationship with Downstream Aggregators

The MCP Registry is intended to be consumed primarily by downstream aggregators, such as MCP server marketplaces.

The metadata hosted by the MCP Registry is deliberately unopinionated. Downstream aggregators can provide curation or additional metadata such as community ratings.

We expect that downstream aggregators will use the MCP Registry API to pull new metadata on a regular but infrequent basis (for example, once per hour). See the MCP Registry Aggregators guide for more information.

Relationship with Other MCP Registries

In addition to a public REST API, the MCP Registry defines an OpenAPI spec that other MCP registries can implement in order to provide a standardized interface for MCP host applications.

We expect that many downstream aggregators will implement this interface. Private MCP registries can implement it as well to benefit from existing host application support.

Note that the official MCP Registry codebase is not designed for self-hosting, and the registry maintainers cannot provide support for this use case. If you choose to fork it, you would need to maintain and operate it independently.

Relationship with MCP Host Applications

The MCP Registry is not intended to be directly consumed by host applications. Instead, host applications should consume other MCP registries, such as downstream marketplaces, via a REST API conforming to the official MCP Registry's OpenAPI spec.

Trust and Security

Verifying Server Authenticity

The MCP Registry uses namespace authentication to ensure that servers come from their claimed sources. Server names follow a reverse DNS format (like io.github.username/server or com.example/server) that ties them to verified GitHub accounts or domains.

This namespace system ensures that only the legitimate owner of a GitHub account or domain can publish servers under that namespace, providing trust and accountability in the ecosystem. For details on authentication methods, see the Authentication guide.

Security Scanning

The MCP Registry delegates security scanning to:

- Underlying package registries — npm, PyPI, Docker Hub, and other package registries perform their own security scanning and vulnerability detection.
- Downstream aggregators — MCP Registry aggregators and marketplaces can implement additional security checks, ratings, or curation.

The MCP Registry focuses on namespace authentication and metadata hosting, while relying on the broader ecosystem for security scanning of actual server code.

Spam Prevention

The MCP Registry uses multiple mechanisms to prevent spam:

- Namespace authentication requirements — Publishers must verify ownership of their namespace through GitHub, DNS, or HTTP challenges, preventing arbitrary spam submissions.
- Character limits and validation — Free-form fields have strict character limits and regex validation to prevent abuse.
- Manual takedown — The registry maintainers can manually remove spam or malicious servers. See the Moderation Policy for details on what content is removed.

Future spam prevention measures under consideration include stricter rate limiting, AI-based spam detection, and community reporting capabilities.

---

Modelcontextprotocol Io/Authentication

---
title: How to Authenticate When Publishing to the Official MCP Registry
sidebarTitle: Authentication
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

You must authenticate before publishing to the official MCP Registry. The MCP Registry supports different authentication methods. Which authentication method you choose determines the namespace of your server's name.

If you choose GitHub-based authentication, your server's name in server.json MUST be of the form io.github.username/ (or io.github.orgname/). For example, io.github.alice/weather-server.

If you choose domain-based authentication, your server's name in server.json MUST be of the form com.example./, where com.example is the reverse-DNS form of your domain name. For example, io.modelcontextprotocol/everything.

| Authentication | Name Format | Example Name |
| -------------- | ----------------------------------------------- | ------------------------------------ |
| GitHub-based | io.github.username/ or io.github.orgname/ | io.github.alice/weather-server |
| domain-based | com.example./ | io.modelcontextprotocol/everything |

GitHub Authentication

GitHub authentication uses an OAuth flow initiated by the mcp-publisher CLI tool.

To perform GitHub authentication, navigate to your server project directory and run:

bash
mcp-publisher login github

You should see output like:

``text Output
Logging in with github...

To authenticate, please:
1. Go to: https://github.com/login/device
2. Enter code: ABCD-1234
3. Authorize this application
Waiting for authorization...

text
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g., ABCD-1234 in the above output). Once complete, go back to the terminal, and you should see output like:
text Output
Successfully authenticated!
✓ Successfully logged in
text

Personal vs. organization namespaces

GitHub authentication always grants your personal namespace, io.github.<your-username>/*.

To publish under an organization namespace (io.github.<orgname>/*), you must be an Owner of that organization. Ordinary org membership is no longer sufficient: the registry checks your membership role and only grants the org namespace to admins. This prevents anyone who merely belongs to an org from publishing — or overwriting — servers under the org's name.

If you authenticate with a Personal Access Token (for example in CI), the token must let the registry read your organization role. A token that can't will still publish to your personal namespace, but org publishing will be silently unavailable. The exact requirement depends on the token type:

- Classic PAT: grant the read:org scope.
- Fine-grained PAT: grant the Organization permissions → Members → Read-only permission (the fine-grained equivalent of
read:org). Without it, GitHub returns no organization membership for the token and you'll get your personal namespace only. Note that a fine-grained PAT is bound to a single resource owner, so it can only see the organization it was created for.

Either way the token needs no repository scopes — the registry never reads or writes your code.

DNS Authentication

DNS authentication is a domain-based authentication method that relies on a DNS TXT record.

<Warning>
The TXT record must be placed on the apex of your domain (e.g.
example.com), not under a selector like _mcp-auth.example.com or _mcp-registry.example.com. MCP DNS auth follows SPF-style placement (apex), not DKIM-style (selector). If you put the record under a selector, the registry will not see it and authentication will fail with a generic signature error.

If you rotate keys, also remember to remove the previous TXT record from the apex — a stale record left behind will be tried first and cause verification to fail.
</Warning>

To perform DNS authentication using the mcp-publisher CLI tool, run the following commands in your server project directory to generate a TXT record based on a public/private key pair:

<Note>
The Ed25519 codepath requires OpenSSL 3.0 or later. macOS ships with LibreSSL by default (the system
openssl binary), which does not implement Ed25519 in genpkey and fails with Algorithm Ed25519 not found. On macOS, install OpenSSL 3 (brew install openssl@3) and invoke it explicitly — for example, replace openssl with /opt/homebrew/opt/openssl@3/bin/openssl (Apple Silicon) or /usr/local/opt/openssl@3/bin/openssl (Intel) in the commands below. The ECDSA P-384 codepath works on LibreSSL.
</Note>

<CodeGroup>

bash Ed25519
MY_DOMAIN="example.com"

Generate public/private key pair using Ed25519


openssl genpkey -algorithm Ed25519 -out key.pem

Generate TXT record


PUBLIC_KEY="$(openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64)"
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ed25519; p=${PUBLIC_KEY}\""
text
bash ECDSA P-384
MY_DOMAIN="example.com"

Generate public/private key pair using ECDSA P-384


openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem

Generate TXT record


PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}\""
text
bash Google KMS
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"

Log in using gcloud CLI (https://cloud.google.com/sdk/docs/install)


gcloud auth login

Set default project


gcloud config set project "${MY_PROJECT}"

Create a keyring in your project


gcloud kms keyrings create "${MY_KEYRING}" --location global

Create an Ed25519 signing key


gcloud kms keys create "${MY_KEY_NAME}" --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring="${MY_KEYRING}" --location=global

Enable Application Default Credentials (ADC) so the publisher tool can sign


gcloud auth application-default login

Attempt login to show the public key


mcp-publisher login dns google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"

Copy the "Expected proof record":


${MY_DOMAIN}. IN TXT "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}"


text
bash Azure Key Vault
MY_DOMAIN="example.com"
MY_SUBSCRIPTION="subscription name or ID"
MY_RESOURCE_GROUP="MyResourceGroup"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"

Log in using Azure CLI (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)


az login

Set default subscription


az account set --subscription "${MY_SUBSCRIPTION}"

Create a resource group


az group create --location westus --resource-group "${MY_RESOURCE_GROUP}"

Create a key vault


az keyvault create --name "${MY_KEY_VAULT}" --location westus --resource-group "${MY_RESOURCE_GROUP}"

Create an ECDSA P-384 signing key


az keyvault key create --name "${MY_KEY_NAME}" --vault-name "${MY_KEY_VAULT}" --curve P-384

Attempt login to show the public key


mcp-publisher login dns azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"

Copy the "Expected proof record":


${MY_DOMAIN}. IN TXT "v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}"


text
</CodeGroup>

Then add the TXT record using your DNS provider's control panel. It may take several minutes for the TXT record to propagate. After the TXT record has propagated, log in using the mcp-publisher login command:

<CodeGroup>

bash Ed25519
MY_DOMAIN="example.com"

PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login dns --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"

text
bash ECDSA P-384
MY_DOMAIN="example.com"

PRIVATE_KEY="$(openssl ec -in key.pem -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login dns --algorithm ecdsap384 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"

text
bash Google KMS
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"

mcp-publisher login dns google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"

text
bash Azure Key Vault
MY_DOMAIN="example.com"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"

mcp-publisher login dns azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"

text
</CodeGroup>

HTTP Authentication

HTTP authentication is a domain-based authentication method that relies on a /.well-known/mcp-registry-auth file hosted on your domain. For example, https://example.com/.well-known/mcp-registry-auth.

To perform HTTP authentication using the mcp-publisher CLI tool, run the following commands in your server project directory to generate an mcp-registry-auth file based on a public/private key pair:

<Note>
As with DNS authentication, the Ed25519 codepath requires OpenSSL 3.0 or later. macOS's system LibreSSL does not support Ed25519 in
genpkey. See the note in the DNS Authentication section for the macOS workaround.
</Note>

<CodeGroup>

bash Ed25519

Generate public/private key pair using Ed25519


openssl genpkey -algorithm Ed25519 -out key.pem

Generate mcp-registry-auth file


PUBLIC_KEY="$(openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64)"
echo "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}" > mcp-registry-auth
text
bash ECDSA P-384

Generate public/private key pair using ECDSA P-384


openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem

Generate mcp-registry-auth file


PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
echo "v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}" > mcp-registry-auth
text
bash Google KMS
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"

Log in using gcloud CLI (https://cloud.google.com/sdk/docs/install)


gcloud auth login

Set default project


gcloud config set project "${MY_PROJECT}"

Create a keyring in your project


gcloud kms keyrings create "${MY_KEYRING}" --location global

Create an Ed25519 signing key


gcloud kms keys create "${MY_KEY_NAME}" --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring="${MY_KEYRING}" --location=global

Enable Application Default Credentials (ADC) so the publisher tool can sign


gcloud auth application-default login

Attempt login to show the public key


mcp-publisher login http google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"

Copy the "Expected proof record" to ./mcp-registry-auth:


v=MCPv1; k=ed25519; p=${PUBLIC_KEY}


text
bash Azure Key Vault
MY_DOMAIN="example.com"
MY_SUBSCRIPTION="subscription name or ID"
MY_RESOURCE_GROUP="MyResourceGroup"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"

Log in using Azure CLI (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)


az login

Set default subscription


az account set --subscription "${MY_SUBSCRIPTION}"

Create a resource group


az group create --location westus --resource-group "${MY_RESOURCE_GROUP}"

Create a key vault


az keyvault create --name "${MY_KEY_VAULT}" --location westus --resource-group "${MY_RESOURCE_GROUP}"

Create an ECDSA P-384 signing key


az keyvault key create --name "${MY_KEY_NAME}" --vault-name "${MY_KEY_VAULT}" --curve P-384

Attempt login to show the public key


mcp-publisher login http azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"

Copy the "Expected proof record" to ./mcp-registry-auth:


v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}


text
</CodeGroup>

Then host the mcp-registry-auth file at /.well-known/mcp-registry-auth on your domain. After the file is hosted, log in using the mcp-publisher login command:

<CodeGroup>

bash Ed25519
MY_DOMAIN="example.com"
PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
text
bash ECDSA P-384
MY_DOMAIN="example.com"
PRIVATE_KEY="$(openssl ec -in key.pem -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --algorithm ecdsap384 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
text
bash Google KMS
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"

mcp-publisher login http google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"

text
bash Azure Key Vault
MY_DOMAIN="example.com"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"

mcp-publisher login http azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"

text
</CodeGroup>

---

Modelcontextprotocol Io/Faq

---
title: Frequently Asked Questions
sidebarTitle: FAQ
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

General

What is the difference between "Official MCP Registry", "MCP Registry", "MCP registry", "MCP Registry API", etc?

- "MCP Registry API" — An API that implements the OpenAPI spec defined by the MCP Registry.
- "Official MCP Registry API" — The REST API served at
https://registry.modelcontextprotocol.io, which is a superset of the MCP Registry API. Its OpenAPI spec can be downloaded from https://registry.modelcontextprotocol.io/openapi.yaml.
- "MCP registry" — A third-party service that provides an MCP Registry API.
- "Official MCP Registry" (or "The MCP Registry") — The service that lives at
https://registry.modelcontextprotocol.io.

Can I delete/unpublish my server?

Yes, you can change your server's status to deleted using the mcp-publisher status command:

bash

Delete a specific version


mcp-publisher status --status deleted --message "No longer maintained" \
io.github.my-username/my-server 1.0.0

Delete all versions


mcp-publisher status --status deleted --all-versions --message "Project archived" \
io.github.my-username/my-server
text
Deleted servers are hidden from default API listings but can still be retrieved with include_deleted=true. You can restore a deleted server by setting its status back to active.

Note: Server metadata is never permanently removed from the registry. The deleted status hides the server from discovery but preserves the historical record.

How do I update my server metadata?

Submit a new server.json with a unique version string. Once published, version metadata is immutable (similar to npm).

Can I add custom metadata when publishing?

Yes, custom metadata under _meta.io.modelcontextprotocol.registry/publisher-provided is preserved when publishing to the registry. This allows you to include custom metadata specific to your publishing process.

<Warning>

There is a 4KB size limit (4096 bytes of JSON). Publishing will fail if this limit is exceeded.

</Warning>

Reporting Issues

What if I need to report a spam or malicious server?

1. Report it as abuse to the underlying package registry (e.g. NPM, PyPi, DockerHub, etc.); and
2. Raise a GitHub issue on the registry repo with a title beginning
Abuse report:

What if I need to report a security vulnerability in the registry itself?

Follow the MCP community SECURITY.md.

---

Modelcontextprotocol Io/Github Actions

---
title: How to Automate Publishing with GitHub Actions
sidebarTitle: GitHub Actions
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

Step 1: Create a Workflow File

In your server project directory, create a .github/workflows/publish-mcp.yml file. Here is an example for npm-based local server, but the MCP Registry publishing steps are the same for all package types:

<CodeGroup>


/ Detailed source-code truncated for AI context efficiency. /
text

/ Detailed source-code truncated for AI context efficiency. /
text

/ Detailed source-code truncated for AI context efficiency. /
text
</CodeGroup>

Step 2: Add Secrets

You may need to add a secret depending on which authentication method you choose:

- GitHub OIDC Authentication: No dedicated secret necessary.
- GitHub PAT Authentication: Add a
MCP_GITHUB_TOKEN secret with a GitHub Personal Access Token. The registry only needs to read your organization role to confirm you are an Owner before granting its namespace, so grant the minimum for that and no repository scopes (the registry never reads or writes your code):
- Classic PAT: the
read:org scope (read:user is not needed; a bare token still authenticates and publishes to your personal namespace).
- Fine-grained PAT: the Organization permissions → Members → Read-only permission (the equivalent of
read:org). Without it you'll get your personal namespace only. A fine-grained PAT is scoped to one resource owner, so create it for the organization you're publishing to.

For an organization token, store it as an Environment secret on the mcp-registry-publish environment rather than a plain repository secret — see "Securing your registry token in CI" below for why this matters and how to configure the environment.
- DNS Authentication: Add a
MCP_PRIVATE_KEY secret with your Ed25519 private key.

You may also need to add secrets for your package registry. For example, the workflow above needs an NPM_TOKEN secret with your npm token.

For information about how to add secrets to a repository, see Using secrets in GitHub Actions.

Securing your registry token in CI

Whichever token you use to authenticate, treat it as a high-value credential. When you authenticate to an organization namespace, the resulting registry token can publish — and overwrite — any server under io.github.<org>/, not just the one in this repository. The relevant question for your threat model is therefore: who can cause code to run in a job where this secret is exposed?* By default that is every repository writer (via branch or tag pushes), not only org Owners — so a plain repo secret quietly widens publish access to everyone with write access.

GitHub gives you the tools to close that gap. We recommend, in increasing order of strength:

1. Store the token as an Environment secret, not a repository or organization secret. A job can only read an environment secret when it declares environment: (as the PAT example above does). Note that the environment: key alone protects nothing: if the environment does not exist yet, GitHub auto-creates it with no protection rules on first run, and a job that declares environment: still also receives ordinary repository and organization secrets. The protection comes from storing the token as a secret on that environment and configuring the environment (steps 2–3 below) — not from the environment: key by itself.
2. Restrict that environment to your default branch and/or release tags with a deployment branch rule, and protect that branch with required pull request reviews. Now only reviewed, maintainer-approved code can ever reach the token.
3. (Strongest) Add a required reviewer to the environment so each publish pauses for an explicit human approval.

Two pitfalls to avoid regardless of the above:

- Never publish from a pull_request_target workflow that checks out PR-head code. That trigger runs with your secrets in the base-repo context, so an untrusted fork PR could exfiltrate the token. Environment branch rules do not protect you here (the ref is still your base branch) — only required reviewers do.
- Avoid self-hosted runners for the publish job on public repositories, where fork PRs may be able to schedule jobs onto them.

By default, pull requests from forks receive no secrets and cannot push branches, so external contributors cannot reach the token without one of the misconfigurations above.

Step 3: Tag and Release

Create and push a version tag to trigger the workflow:

bash
git tag v1.0.0
git push origin v1.0.0
text
The workflow will run tests, build the package, publish the package to npm, and publish the server to the MCP Registry.

Troubleshooting

{/ prettier-ignore-start /}

| Error Message | Action |
| --- | --- |
| "Authentication failed" | Ensure
id-token: write permission is set for OIDC, or check secrets. |
| "invalid audience" | Your
mcp-publisher binary is too old for this registry deployment. Re-run the install step shown above so you pick up the latest release. |
| "Package validation failed" | Verify your package successfully published to the package registry (e.g., npm, PyPI), and that your package has the necessary verification information. |

{/ prettier-ignore-end /}

---

Modelcontextprotocol Io/Moderation Policy

---
title: The MCP Registry Moderation Policy
sidebarTitle: Moderation Policy
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

TL;DR: The MCP Registry is quite permissive! We only remove illegal content, malware, spam, and completely broken servers.

Scope

This policy applies to the official MCP Registry at registry.modelcontextprotocol.io.

Subregistries may have their own moderation policies. If you have questions about content on a specific subregistry, please contact them directly.

Disclaimer

The MCP Registry does not make guarantees about moderation, and consumers should assume minimal-to-no moderation.

The MCP Registry is a community supported project, and we have limited active moderation capabilities. We largely rely on upstream package registries (like NPM, PyPI, and Docker) or downstream subregistries (like the GitHub MCP Registry) to do more in-depth moderation.

This means there may be content in the MCP Registry that should be removed under this policy, but which we haven't yet removed. Consumers should treat scraped data accordingly.

What We Remove

We will remove servers that contain:

- Illegal content, which includes obscene content, copyright violations, and hacking tools
- Malware, regardless of intentions
- Spam, especially mass-created servers that disrupt the registry. Examples:
- The same server being submitted multiple times under different names
- A server that doesn't do anything but provide a fixed response with some marketing copy
- A server with a description stuffed with marketing copy and an unrelated implementation
- Non-functioning servers

What We Don't Remove

Generally, we believe in keeping the registry open and pushing moderation to subregistries. We therefore won't remove:

- Low-quality or buggy servers
- Servers with security vulnerabilities
- Servers that do the same thing as other servers
- Servers that provide or contain adult content

How Removal Works

When we remove a server, we set the server's status to "deleted", but the server's metadata remains accessible via the MCP Registry API. Aggregators may then remove the server from their indexes.

In extreme cases, we may overwrite or erase the server's metadata. For example, if the metadata itself is unlawful.

Appeals

Think we made a mistake? Open an issue on our GitHub repository with:

- The name of the server
- Why you believe the server doesn't meet the above criteria for removal

Changes to This Policy

We're still learning how best to run the MCP Registry! As such, we might end up changing this policy in the future.

---

Modelcontextprotocol Io/Package Types

---
title: MCP Registry Supported Package Types
sidebarTitle: Package Types
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

Package Types

The MCP Registry supports several different package types, and each package type has its own verification method.

npm Packages

For npm packages, the MCP Registry currently supports the npm public registry (https://registry.npmjs.org) only.

npm packages use "registryType": "npm" in server.json. For example:

json server.json highlight={9}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text

Ownership Verification

The MCP Registry verifies ownership of npm packages by checking mcpName in package.json. The mcpName property MUST match the server name from server.json. For example:

json package.json
{
"name": "@username/email-integration-mcp",
"version": "1.0.0",
"mcpName": "io.github.username/email-integration-mcp"
}
text

PyPI Packages

For PyPI packages, the MCP Registry currently supports the official PyPI registry (https://pypi.org) only.

PyPI packages use "registryType": "pypi" in server.json. For example:

json server.json highlight={9}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/database-query-mcp",
"title": "Database Query",
"description": "Execute SQL queries and manage database connections",
"version": "1.0.0",
"packages": [
{
"registryType": "pypi",
"identifier": "database-query-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text

Ownership Verification

The MCP Registry verifies ownership of PyPI packages by checking for the existence of an mcp-name: $SERVER_NAME string in the package README (which becomes the package description on PyPI). The string may be hidden in a comment, but the $SERVER_NAME portion MUST match the server name from server.json. For example:

markdown README.md highlight={5}

Database Query MCP Server

This MCP server executes SQL queries and manages database connections.


text
The mcp-name: token must be followed by a boundary — a newline, whitespace, an HTML tag, or the comment close -->. Keep it on its own line or inside ; do not glue it directly to trailing characters such as a sentence-ending period (…/database-query-mcp.), which prevents the match.

NuGet Packages

For NuGet packages, the MCP Registry currently supports the official NuGet registry (https://api.nuget.org/v3/index.json) only.

NuGet packages use "registryType": "nuget" in server.json. For example:

json server.json highlight={9}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/azure-devops-mcp",
"title": "Azure DevOps",
"description": "Manage Azure DevOps work items and pipelines",
"version": "1.0.0",
"packages": [
{
"registryType": "nuget",
"identifier": "Username.AzureDevOpsMcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text

Ownership Verification

The MCP Registry verifies ownership of NuGet packages by checking for the existence of an mcp-name: $SERVER_NAME string in the package README. The string may be hidden in a comment, but the $SERVER_NAME portion MUST match the server name from server.json. For example:

markdown README.md highlight={5}

Azure DevOps MCP Server

This MCP server manages Azure DevOps work items and pipelines.


text
The mcp-name: token must be followed by a boundary — a newline, whitespace, an HTML tag, or the comment close -->. Keep it on its own line or inside ; do not glue it directly to trailing characters such as a sentence-ending period (…/azure-devops-mcp.), which prevents the match.

Cargo (Rust) Packages

For Cargo packages, the MCP Registry currently supports the official crates.io registry (https://crates.io) only.

Cargo packages use "registryType": "cargo" in server.json. For example:

json server.json highlight={9}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/widget-mcp",
"title": "Widget",
"description": "Rust-native MCP server",
"version": "0.3.0",
"packages": [
{
"registryType": "cargo",
"identifier": "widget-mcp",
"version": "0.3.0",
"transport": {
"type": "stdio"
}
}
]
}
text

Runtime Model

Cargo's runtime model differs from npm/PyPI/NuGet. cargo install <crate> places the compiled binary on PATH at ~/.cargo/bin, after which MCP clients invoke it directly by name. There is no per-invocation runner equivalent to npx (npm), uvx (PyPI), or dnx (NuGet, .NET 10 SDK Preview 6+) — install is one-time, execution is by binary name. The Cargo example above intentionally omits runtimeHint for this reason.

Rust MCP authors have two first-class distribution paths:

- Cargo (registryType: cargo) — source-distributed via crates.io. End users need the Rust toolchain (rustup) to run cargo install. Idiomatic for the Rust ecosystem and consistent with how Rust CLIs are typically published.
- MCPB (
registryType: mcpb) — prebuilt binary distributed via GitHub or GitLab Releases. End users need no toolchain. Right choice if the priority is "no Rust toolchain required."

Both paths are supported; the choice is the author's. Cargo native support exists so Rust authors who prefer source distribution are not forced into the MCPB binary-packaging workaround.

Ownership Verification

The MCP Registry verifies ownership of Cargo packages by checking for the existence of an mcp-name: $SERVER_NAME string in the package README (which is rendered to HTML and served by crates.io's static CDN). The $SERVER_NAME portion MUST match the server name from server.json. For example:

markdown README.md highlight={5}

Widget MCP Server

A Rust-native MCP server for widget operations.

- MCP Registry name: mcp-name: io.github.username/widget-mcp

text
Cargo-specific gotcha: Unlike PyPI and NuGet (which preserve HTML comments in their README rendering), crates.io strips HTML comments during markdown→HTML conversion. The  hidden-comment form that works for PyPI/NuGet does not work for cargo — the token will not appear in the rendered HTML the validator inspects. Cargo authors must include the mcp-name: token as visible markdown text. A simple bullet in the Links section is the recommended pattern.

Docker/OCI Images

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)

Docker/OCI images use "registryType": "oci" in server.json. For example:

json server.json highlight={9}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/kubernetes-manager-mcp",
"title": "Kubernetes Manager",
"description": "Deploy and manage Kubernetes resources",
"version": "1.0.0",
"packages": [
{
"registryType": "oci",
"identifier": "docker.io/yourusername/kubernetes-manager-mcp:1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text
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

The MCP Registry verifies ownership of Docker/OCI images by checking for an io.modelcontextprotocol.server.name annotation. The value of the io.modelcontextprotocol.server.name annotation MUST match the server name from server.json. For example:

dockerfile Dockerfile
LABEL io.modelcontextprotocol.server.name="io.github.username/kubernetes-manager-mcp"
text

MCPB Packages

For MCPB packages, the MCP Registry currently supports MCPB artifacts hosted via GitHub or GitLab releases.

MCPB packages use "registryType": "mcpb" in server.json. For example:

json server.json highlight={9}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/image-processor-mcp",
"title": "Image Processor",
"description": "Process and transform images with various filters",
"version": "1.0.0",
"packages": [
{
"registryType": "mcpb",
"identifier": "https://github.com/username/image-processor-mcp/releases/download/v1.0.0/image-processor.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}
]
}
text

Verification

The MCPB package URL (identifier in server.json) MUST contain the string "mcp". That can be as part of the .mcpb file extension or in the name of the repository.

The package metadata in server.json MUST include a fileSha256 property with a SHA-256 hash of the MCPB artifact, which can be computed using the openssl command:

bash
openssl dgst -sha256 image-processor.mcpb
text
The MCP Registry does not validate this hash; however, MCP clients do validate the hash before installation to ensure file integrity. Downstream registries may also implement their own validation.

---

Modelcontextprotocol Io/Quickstart

---
title: "Quickstart: Publish an MCP Server to the MCP Registry"
sidebarTitle: "Quickstart: Publish a Server"
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

This tutorial will show you how to publish an MCP server written in TypeScript to the MCP Registry using the official mcp-publisher CLI tool.

<Note>
If you are publishing a non-npm package (PyPI, NuGet, OCI, MCPB), the overall flow is identical, but the ownership-verification step in Step 1 is different per package type. See Package Types for the verification mechanism that applies to your package, then return here and follow the remaining steps.
</Note>

Prerequisites

- Node.js — This tutorial assumes the MCP server is written in TypeScript.
- npm account — The MCP Registry only hosts metadata, not artifacts. Before publishing to the MCP Registry, we will publish the MCP server's package to npm, so you will need an npm account.
- GitHub account — The MCP Registry supports multiple authentication methods. For simplicity, this tutorial will use GitHub-based authentication, so you will need a GitHub account.

If you do not have an MCP server written in TypeScript, you can copy the weather-server-typescript server from the modelcontextprotocol/quickstart-resources repository to follow along with this tutorial:

bash
git clone --depth 1 [email protected]:modelcontextprotocol/quickstart-resources.git
cp -r quickstart-resources/weather-server-typescript .
rm -rf quickstart-resources
cd weather-server-typescript
text
And edit package.json to reflect your information:
diff package.json
{
- "name": "mcp-quickstart-ts",
- "version": "1.0.0",
+ "name": "@my-username/mcp-weather-server",
+ "version": "1.0.1",
"main": "index.js",
text
diff package.json
"license": "ISC",
- "description": "",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/my-username/mcp-weather-server.git"
+ },
+ "description": "An MCP server for weather information.",
"devDependencies": {
text

Step 1: Add verification information to the package

The MCP Registry verifies that a server's underlying package matches its metadata. For npm packages, this requires adding an mcpName property to package.json:

diff package.json
{
"name": "@my-username/mcp-weather-server",
"version": "1.0.1",
+ "mcpName": "io.github.my-username/weather",
"main": "index.js",
text
The value of mcpName will be your server's name in the MCP Registry.

Because we will be using GitHub-based authentication, mcpName must start with io.github.my-username/.

Step 2: Publish the package

The MCP Registry only hosts metadata, not artifacts, so we must publish the package to npm before publishing the server to the MCP Registry.

Ensure the distribution files are built:

bash

Navigate to project directory


cd weather-server-typescript

Install dependencies


npm install

Build the distribution files


npm run build
text
Then follow npm's publishing guide. In particular, you will probably need to run the following commands:
bash

If necessary, authenticate to npm


npm adduser

Publish the package


npm publish --access public
text
You can verify your package is published by visiting its npm URL, such as https://www.npmjs.com/package/@my-username/mcp-weather-server.

Step 3: Install mcp-publisher

Install the mcp-publisher CLI tool using a pre-built binary or Homebrew:

<CodeGroup>

bash macOS/Linux
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher && sudo mv mcp-publisher /usr/local/bin/
text
powershell Windows
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq "Arm64") { "arm64" } else { "amd64" }; Invoke-WebRequest -Uri "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_windows_$arch.tar.gz" -OutFile "mcp-publisher.tar.gz"; tar xf mcp-publisher.tar.gz mcp-publisher.exe; rm mcp-publisher.tar.gz

Move mcp-publisher.exe to a directory in your PATH


text
bash
brew install mcp-publisher
text
</CodeGroup>

Verify that mcp-publisher is correctly installed by running:

bash
mcp-publisher --help
text
You should see output like:
text Output
MCP Registry Publisher Tool

Usage:
mcp-publisher <command> [arguments]

Commands:
init Create a server.json file template
login Authenticate with the registry
logout Clear saved authentication
publish Publish server.json to the registry
status Update the status of a server version
validate Validate server.json without publishing

Use 'mcp-publisher <command> --help' for more information about a command.

text

Step 4: Create server.json

The mcp-publisher init command can generate a server.json template file with some information derived from your project.

In your server project directory, run mcp-publisher init:

bash
mcp-publisher init
text
Open the generated server.json file, and you should see contents like:
json server.json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.my-username/weather",
"description": "An MCP server for weather information.",
"repository": {
"url": "https://github.com/my-username/mcp-weather-server",
"source": "github"
},
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/mcp-weather-server",
"version": "1.0.0",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"description": "Your API key for the service",
"isRequired": true,
"format": "string",
"isSecret": true,
"name": "YOUR_API_KEY"
}
]
}
]
}
text
Edit the contents as necessary:
diff server.json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.my-username/weather",
"description": "An MCP server for weather information.",
"repository": {
"url": "https://github.com/my-username/mcp-weather-server",
"source": "github"
},
- "version": "1.0.0",
+ "version": "1.0.1",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/mcp-weather-server",
- "version": "1.0.0",
+ "version": "1.0.1",
"transport": {
"type": "stdio"
- },
- "environmentVariables": [
- {
- "description": "Your API key for the service",
- "isRequired": true,
- "format": "string",
- "isSecret": true,
- "name": "YOUR_API_KEY"
- }
- ]
+ }
}
]
}
text
The name property in server.json must match the mcpName property in package.json.

Step 5: Authenticate with the MCP Registry

For this tutorial, we will authenticate with the MCP Registry using GitHub-based authentication.

Run the mcp-publisher login command to initiate authentication:

bash
mcp-publisher login github
text
You should see output like:
text Output
Logging in with github...

To authenticate, please:
1. Go to: https://github.com/login/device
2. Enter code: ABCD-1234
3. Authorize this application
Waiting for authorization...

text
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g., ABCD-1234 in the above output). Once complete, go back to the terminal, and you should see output like:
text Output
Successfully authenticated!
✓ Successfully logged in
text

Step 6: Publish to the MCP Registry

Finally, publish your server to the MCP Registry using the mcp-publisher publish command:

bash
mcp-publisher publish
text
You should see output like:
text Output
Publishing to https://registry.modelcontextprotocol.io...
✓ Successfully published
✓ Server io.github.my-username/weather version 1.0.1
text
You can verify that your server is published by searching for it using the MCP Registry API:
bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"
text
You should see your server's metadata in the search results JSON:
text Output
{"servers":[{ ... "name":"io.github.my-username/weather" ... }]}
text

Troubleshooting

{/ prettier-ignore-start /}

| Error Message | Action |
| --- | --- |
|"Registry validation failed for package"|Ensure your package includes the required ownership-verification marker for its package type. For npm this is
mcpName in package.json; for PyPI and NuGet it is an mcp-name: <server-name> line (or HTML comment) in the package README; for other types see Package Types.|
|"Invalid or expired Registry JWT token"|Re-authenticate by running
mcp-publisher login github.|
|"You do not have permission to publish this server"|Your authentication method doesn't match your server's namespace format. With GitHub auth, your server name must start with
io.github.your-username/.|

{/ prettier-ignore-end /}

Next Steps

- Learn about support for other package types.
- Learn about support for remote servers.
- Learn how to use other authentication methods, such as DNS authentication which enables custom domains for server name prefixes.
- Learn how to automate publishing with GitHub Actions.

---

Modelcontextprotocol Io/Registry Aggregators

---
title: MCP Registry Aggregators
sidebarTitle: Registry Aggregators
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

Aggregators are downstream consumers of the MCP Registry that provide additional value. For example, a server marketplace that provides user ratings and security scanning.

The MCP Registry provides an unauthenticated read-only REST API that aggregators can use to populate their data stores. Aggregators are expected to scrape data on a regular but infrequent basis (e.g., once per hour), and persist the data in their own data store. The MCP Registry does not provide uptime or data durability guarantees.

Consuming the MCP Registry REST API

The base URL for the MCP Registry REST API is https://registry.modelcontextprotocol.io. It supports the following endpoints:

- GET /v0.1/servers — List all servers.
-
GET /v0.1/servers/{serverName}/versions — List all versions of a server.
-
GET /v0.1/servers/{serverName}/versions/{version} — Get a specific version of a server. Use the special version latest to get the latest version of the server.

<Warning>

URL path parameters such as serverName and version must be URL-encoded. For example, io.modelcontextprotocol/everything must be encoded as io.modelcontextprotocol%2Feverything.

</Warning>

Aggregators will most likely scrape the GET /v0.1/servers endpoint.

Pagination

The GET /v0.1/servers endpoint supports cursor-based pagination.

For example, the first page can be fetched using a limit query parameter:

bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?limit=100"
text
jsonc Output highlight={5}
{
"servers": [
/ ... /
],
"metadata": {
"count": 100,
"nextCursor": "com.example/my-server:1.0.0",
},
}
text
Then subsequent pages can be fetched by passing the nextCursor value as the cursor query parameter:
bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?limit=100&cursor=com.example/my-server:1.0.0"
text

Filtering Since

The GET /v0.1/servers endpoint supports filtering servers that have been updated since a given timestamp.

For example, servers that have been updated since 2025-10-23 can be fetched using an updated_since query parameter in RFC 3339 date-time format:

bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?updated_since=2025-10-23T00:00:00.000Z"
text

Server Status

Server metadata is generally immutable, except for the status field which may be updated to, e.g., "deprecated" or "deleted". We recommend that aggregators keep their copy of each server's status up to date.

The "deleted" status typically indicates that a server has violated our permissive moderation policy, suggesting the server might be spam, malware, or illegal. Aggregators may prefer to remove these servers from their index.

Acting as a Subregistry

A subregistry is an aggregator that also implements the OpenAPI spec defined by the MCP Registry. This allows clients, such as MCP host applications, to consume server metadata via a standardized interface.

The subregistry OpenAPI spec allows subregistries to inject custom metadata via the _meta field. For example, a subregistry could inject user ratings, download counts, and security scan results:

json server.json highlight={17-26}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
],
"_meta": {
"com.example.subregistry/custom": {
"user_rating": 4.5,
"download_count": 12345,
"security_scan": {
"last_scanned": "2025-10-23T12:00:00Z",
"vulnerabilities_found": 0
}
}
}
}
text
We recommend that custom metadata be put under a key that reflects the subregistry (e.g., "com.example.subregistry/custom" in the above example).

---

Modelcontextprotocol Io/Remote Servers

---
title: Publishing Remote Servers
sidebarTitle: Remote Servers
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

The MCP Registry supports remote MCP servers via the remotes property in server.json:

json server.json highlight={7-12}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://analytics.example.com/mcp"
}
]
}
text
A remote server MUST be publicly accessible at its specified URL.

Transport Type

Remote servers can use the Streamable HTTP transport (recommended) or the SSE transport. Remote servers can also support both transports simultaneously at different URLs.

Specify the transport by setting the type property of the remotes entry to either "streamable-http" or "sse":

json server.json highlight={9,13}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://analytics.example.com/mcp"
},
{
"type": "sse",
"url": "https://analytics.example.com/sse"
}
]
}
text

URL Template Variables

Remote servers can define URL template variables using {curly_braces} notation. This enables multi-tenant deployments where a single server definition can support multiple endpoints with configurable values:

json server.json highlight={10-17}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://{tenant_id}.analytics.example.com/mcp",
"variables": {
"tenant_id": {
"description": "Your tenant identifier (e.g., 'us-cell1', 'emea-cell1')",
"isRequired": true
}
}
}
]
}
text
When configuring this server, users provide their tenant_id value, and the URL template gets resolved to the appropriate endpoint (e.g., https://us-cell1.analytics.example.com/mcp).

Variables support additional properties like default, choices, and isSecret:

json server.json highlight={12-22}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/multi-region-mcp",
"title": "Multi-Region MCP",
"description": "MCP server with regional endpoints",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.example.com/{region}/mcp",
"variables": {
"region": {
"description": "Deployment region",
"isRequired": true,
"choices": [
"us-east-1",
"eu-west-1",
"ap-southeast-1"
],
"default": "us-east-1"
}
}
}
]
}
text

HTTP Headers

MCP clients can be instructed to send specific HTTP headers by adding the headers property to the remotes entry:

json server.json highlight={11-18}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://analytics.example.com/mcp",
"headers": [
{
"name": "X-API-Key",
"description": "API key for authentication",
"isRequired": true,
"isSecret": true
}
]
}
]
}
text

Supporting Remote and Non-remote Installation

The remotes property can coexist with the packages property in server.json in order to allow MCP host applications to choose the preferred method of installation.

json server.json highlight={7-22}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://email.example.com/mcp"
}
],
"packages": [
{
"registryType": "npm",
"identifier": "@example/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text
---

Modelcontextprotocol Io/Terms Of Service

---
title: Official MCP Registry Terms of Service
sidebarTitle: Terms of Service
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

Effective date: 2025-09-02

Overview

These terms (“Terms”) govern your access to and use of the official MCP Registry (the service hosted at https://registry.modelcontextprotocol.io/ or a successor location) (“Registry”), including submissions or publications of MCP servers, references to MCP servers or to data about such servers and/or their developers (“Registry Data”), and related conduct. The Registry is intended to be a centralized repository of MCP servers developed by community members to facilitate easy access by AI applications.

These terms are governed by the laws of the State of California.

For All Users

1. No Warranties. The Registry is provided “as is” with no warranties of any kind. That means we don't guarantee the accuracy, completeness, safety, durability, or availability of the Registry, servers included in the registry, or Registry Data. In short, we’re also not responsible for any MCP servers or Registry Data, and we highly recommend that you evaluate each MCP server and its suitability for your intended use case(s) before deciding whether to use it.

2. Access and Use Requirements. To access or use the Registry, you must:
1. Be at least 18 years old.
2. Use the Registry, MCP servers in the Registry, and Registry Data only in ways that are legal under the applicable laws of the United States or other countries including the country in which you are a resident or from which you access and use the Registry, and not be barred from accessing or using the Registry under such laws. You will comply with all applicable law, regulation, and third party rights (including, without limitation, laws regarding the import or export of data or software, privacy, intellectual property, and local laws). You will not use the Registry, MCP servers, or Registry Data to encourage or promote illegal activity or the violation of third party rights or terms of service.
3. Log in via method(s) approved by the Registry maintainers, which may involve using applications or other software owned by third parties.

3. Entity Use. If you are accessing or using the Registry on behalf of an entity, you represent and warrant that you have authority to bind that entity to these Terms. By accepting these Terms, you are doing so on behalf of that entity (and all references to “you” in these Terms refer to that entity).

4. Account Information. In order to access or use the Registry, you may be required to provide certain information (such as identification or contact details) as part of a registration process or in connection with your access or use of the Registry or MCP servers therein. Any information you give must be accurate and up-to-date, and you agree to inform us promptly of any updates. You understand that your use of the Registry may be monitored to ensure quality and verify your compliance with these Terms.

5. Feedback. You are under no obligation to provide feedback or suggestions. If you provide feedback or suggestions about the Registry or the Model Context Protocol, then we (and those we allow) may use such information without obligation to you.

6. Branding. Only use the term “Official MCP Registry” where it is clear it refers to the Registry, and does not imply affiliation, endorsement, or sponsorship. For example, you can permissibly say “Acme Inc. keeps its data up to date by automatically pulling data from the Official MCP Registry” or “This data comes from the Official MCP Registry,” but cannot say “This is the website for the Official MCP Registry,” “We’re the premier destination to view Official MCP Registry data,” or “We’ve partnered with the Official MCP Registry to provide this data.”

7. Modification. We may modify the Terms or any portion to, for example, reflect changes to the law or changes to the Model Context Protocol. We’ll post notice of modifications to the Terms to this website or a successor location. If you do not agree to the modified Terms, you should discontinue your access to and/or use of the Registry. Your continued access to and/or use of the Registry constitutes your acceptance of any modified Terms.

8. Additional Terms. Depending on your intended use case(s), you must also abide by applicable terms below.

For MCP Developers

9. Prohibitions. By accessing and using the Registry, including by submitting MCP servers and/or Registry Data, you agree not to:
1. Share malicious or harmful content, such as malware, even in good faith or for research purposes, or perform any action with the intent of introducing any viruses, worms, defects, Trojan horses, malware, or any items of a destructive nature;
2. Defame, abuse, harass, stalk, or threaten others;
3. Interfere with or disrupt the Registry or any associated servers or networks;
4. Submit data with the intent of confusing or misleading others, including but not limited to via spam, posting off-topic marketing content, posting MCP servers in a way that falsely implies affiliation with or endorsement by a third party, or repeatedly posting the same or similar MCP servers under different names;
5. Promote or facilitate unlawful online gambling or disruptive commercial messages or advertisements;
6. Use the Registry for any activities where the use or failure of the Registry could lead to death, personal injury, or environmental damage;
7. Use the Registry to process or store any data that is subject to the International Traffic in Arms Regulations maintained by the U.S. Department of State.

10. License. You agree that metadata about MCP servers you submit (e.g., schema name and description, URLs, identifiers) and other Registry Data is intended to be public, and will be dedicated to the public domain under CC0 1.0 Universal. By submitting such data, you agree that you have the legal right to make this dedication (i.e., you own the copyright to these submissions or have permission from the copyright owner(s) to do so) and intend to do so. You understand that this dedication is perpetual, irrevocable, and worldwide, and you waive any moral rights you may have in your contributions to the fullest extent permitted by law. This dedication applies only to Registry Data and not to packages in third party registries that you might point to.

11. Privacy and Publicity. You understand that any MCP server metadata you publish may be made public. This includes personal data such as your GitHub username, domain name, or details from your server description. Moreover, you understand that others may process personal information included in your MCP server metadata. For example, subregistries might enrich this data by adding how many stars your GitHub repository has, or perform automated security scanning on your code. By publishing a server, you agree that others may engage in this sort of processing, and you waive rights you might have in some jurisdictions to access, rectify, erase, restrict, or object to such processing.

---

Modelcontextprotocol Io/Versioning

---
title: Versioning Published MCP Servers
sidebarTitle: Versioning
---

<Note>
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
</Note>

MCP servers MUST define a version string in server.json. For example:

json server.json highlight={6}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text
The version string MUST be unique for each publication of the server. Once published, the version string (and other metadata) cannot be changed.

Version Format

The MCP Registry recommends semantic versioning, but supports any version string format. When a server is published, the MCP Registry will attempt to parse its version as a semantic version string for sorting purposes, and will mark the version as "latest" if appropriate. If parsing fails, the version will always be marked as "latest".

<Warning>

If a server uses semantic version strings but publishes a new version that does _not_ conform to semantic versioning, the new version will be marked as "latest" even if it would otherwise be sorted before the semantic version strings.

</Warning>

As an error prevention mechanism, the MCP Registry prohibits version strings that appear to refer to ranges of versions.

| Example | Type | Guidance |
| -------------- | ------------------- | ------------------------------ |
|
1.0.0 | semantic version | Recommended |
|
2.1.3-alpha | semantic prerelease | Recommended |
|
1.0.0-beta.1 | semantic prerelease | Recommended |
|
3.0.0-rc.2 | semantic prerelease | Recommended |
|
2025.11.25 | semantic date | Recommended |
|
2025.6.18 | semantic date | Recommended (⚠️Caution!⚠️) |
|
2025.06.18 | non-semantic date | Allowed (⚠️Caution!⚠️) |
|
2025-06-18 | non-semantic date | Allowed |
|
v1.0 | prefixed version | Allowed |
|
^1.2.3 | version range | Prohibited |
|
~1.2.3 | version range | Prohibited |
|
>=1.2.3 | version range | Prohibited |
|
<=1.2.3 | version range | Prohibited |
|
>1.2.3 | version range | Prohibited |
|
<1.2.3 | version range | Prohibited |
|
1.x | version range | Prohibited |
|
1.2.* | version range | Prohibited |
|
1 - 2 | version range | Prohibited |
|
1.2 \|\| 1.3 | version range | Prohibited |

Best Practices

Use Semantic Versioning

Use semantic versioning for version strings.

Align Server Version with Package Version

For local servers, align the server version with the underlying package version in order to prevent confusion:

json server.json highlight={2,7}
{
"version": "1.2.3",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.2.3",
"transport": {
"type": "stdio"
}
}
]
}
text
If there are multiple underlying packages, use the server version to indicate the overall release version:
json server.json highlight={2,7,15}
{
"version": "1.3.0",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.3.0",
"transport": {
"type": "stdio"
}
},
{
"registryType": "nuget",
"identifier": "MyUsername.MyServer",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
text

Align Server Version with Remote API Version

For remote servers with an API version, the server version should align with the API version:

json server.json highlight={2,6}
{
"version": "2.1.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.myservice.com/mcp/v2.1"
}
]
}
text

Use Prerelease Versions for Registry-only Updates

If you anticipate publishing a server multiple times _without_ changing the underlying package or remote URL — for example, to update other parts of the metadata — use semantic prerelease versions:

json server.json highlight={2}
{
"version": "1.2.3-1",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.2.3",
"transport": {
"type": "stdio"
}
}
]
}
text
<Warning>

According to semantic versioning, prerelease versions such as 1.2.3-1 are sorted before regular semantic versions such as 1.2.3. Therefore, if you publish a prerelease version _after_ its corresponding regular version, the prerelease version will not be marked as "latest".

</Warning>

Aggregator Recommendations

MCP Registry aggregators SHOULD:

1. Attempt to interpret versions as semantic versions when possible
2. Use the following version comparison rules:
- If one version is marked as "latest", treat it as later
- If both versions are valid semantic versions, use semantic versioning comparison rules
- If neither versions are valid semantic versions, compare published timestamp
- If one version is a valid semantic version and the other is not, treat the semantic version as later

---

Reference/Cli/Commands

Publisher CLI Commands Reference

Complete command reference for the mcp-publisher CLI tool.

See the publishing guide for a walkthrough of using the CLI to publish a server.

Installation

Install via Homebrew (macOS/Linux):

bash
$ brew install mcp-publisher
text

Global Options

All commands support:
-
--help, -h - Show command help

--registry is a flag on login only (default: https://registry.modelcontextprotocol.io). The
other commands read the registry URL from the stored login token, so passing
--registry to
publish would be interpreted as the server.json path.

Commands

mcp-publisher init

Generate a server.json template with automatic detection.

Usage:

bash
mcp-publisher init
text
Behavior:
- Creates
server.json in current directory
- Auto-detects package managers (
package.json, setup.py, etc.)
- Pre-fills fields where possible
- Writes
TODO: placeholders for fields it cannot detect — it is non-interactive and takes no flags

Example output:

json
{
"name": "io.github.username/server-name",
"description": "TODO: Add server description",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "detected-package-name",
"version": "1.0.0"
}
]
}
text

mcp-publisher login <method>

Authenticate with the registry.

Authentication Methods:

#### GitHub Interactive

bash
mcp-publisher login github [--token=PAT] [--registry=URL]
text
- Opens browser for GitHub OAuth flow
- Grants access to
io.github.{username}/ and io.github.{org}/ namespaces
-
--token supplies a GitHub Personal Access Token instead of the interactive flow, which is how
publishing from GitHub Actions authenticates
without a browser. This flag is accepted by
login github only.

#### GitHub OIDC (CI/CD)

bash
mcp-publisher login github-oidc [--registry=URL]
text
- Uses GitHub Actions OIDC tokens automatically
- Requires
id-token: write permission in workflow
- No browser interaction needed

The CLI derives the OIDC aud claim from --registry (scheme + host, e.g.
https://registry.modelcontextprotocol.io) so tokens are bound to the specific
deployment they were minted for. Self-hosters must set
MCP_REGISTRY_GITHUB_OIDC_AUDIENCE on the registry to the matching value;
publishers running an older
mcp-publisher will fail with invalid audience
and need to upgrade.

Also see the guide to publishing from GitHub Actions.

#### DNS Verification

bash
mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--algorithm=ed25519|ecdsap384] [--registry=URL]
text
- Verifies domain ownership via DNS TXT record
- Grants access to
com.example.* namespaces
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
-
--algorithm defaults to ed25519. For an ECDSA P-384 key you must pass --algorithm ecdsap384, otherwise the key is rejected with invalid seed length: expected 32 bytes, got 48.
- The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault. Cloud providers derive the algorithm from the key itself, so
--algorithm does not apply to them.

Setup: (for Ed25519, recommended)

bash

Generate keypair


openssl genpkey -algorithm Ed25519 -out key.pem

Get public key for DNS record


openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64

Add DNS TXT record:


example.com. IN TXT "v=MCPv1; k=ed25519; p=PUBLIC_KEY"

Extract private key for login


openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n'
text
Setup: (for ECDSA P-384)
bash

Generate keypair


openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem

Get public key for DNS record


openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64

Add DNS TXT record:


example.com. IN TXT "v=MCPv1; k=ecdsap384; p=PUBLIC_KEY"

Extract private key for login


openssl ec -in <pem path> -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n'

Log in, selecting the ECDSA P-384 algorithm explicitly


mcp-publisher login dns --algorithm ecdsap384 --domain=example.com --private-key=HEX_KEY
text
Setup: (for Google KMS signing)

This requires the gcloud CLI.

bash

log in and set default project


gcloud auth login
gcloud config set project myproject

Create a keyring in your project


gcloud kms keyrings create mykeyring --location global

Create an Ed25519 signing key


gcloud kms keys create mykey --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring=mykeyring --location=global

Enable Application Default Credentials (ADC) so the publisher tool can sign


gcloud auth application-default login

Attempt login to show the public key


mcp-publisher login dns google-kms --domain=example.com --resource=projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey/cryptoKeyVersions/1

Copy the "Expected proof record" and add the TXT record


example.com. IN TXT "v=MCPv1; k=ed25519; p=PUBLIC_KEY"

Re-run the login command


mcp-publisher login dns google-kms --domain=example.com --resource=projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey/cryptoKeyVersions/1
text
Setup: (for Azure Key Vault signing)

This requires the Azure CLI.

bash

log in and set default subscription


az login
az account set --subscription "My Subscription (name or ID)"

Create a resource group


az group create --location westus --resource-group MyResourceGroup

Create a Key Vault


az keyvault create --name MyKeyVault --location westus --resource-group MyResourceGroup

Create an ECDSA P-384 signing key


az keyvault key create --name MyKey --vault-name MyKeyVault --curve P-384

Attempt login to show the public key


mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault --key MyKey

Copy the "Expected proof record" and add the TXT record


example.com. IN TXT "v=MCPv1; k=ecdsap384; p=PUBLIC_KEY"

Re-run the login command


mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault --key MyKey
text
#### HTTP Verification
bash
mcp-publisher login http --domain=example.com --private-key=HEX_KEY [--algorithm=ed25519|ecdsap384] [--registry=URL]
text
- Verifies domain ownership via HTTPS endpoint  
- Grants access to
com.example.* namespaces
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
-
--algorithm defaults to ed25519. For an ECDSA P-384 key you must pass --algorithm ecdsap384.
- The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault. Cloud providers derive the algorithm from the key itself, so
--algorithm does not apply to them.

Setup: (for Ed25519, recommended)

bash

Generate keypair (same as DNS)


openssl genpkey -algorithm Ed25519 -out key.pem

Host public key at:


https://example.com/.well-known/mcp-registry-auth


Content: v=MCPv1; k=ed25519; p=PUBLIC_KEY


text
Setup: (for ECDSA P-384)
bash

Generate keypair (same as DNS)


openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem

Host public key at:


https://example.com/.well-known/mcp-registry-auth


Content: v=MCPv1; k=ecdsap384; p=PUBLIC_KEY

Log in, selecting the ECDSA P-384 algorithm explicitly


mcp-publisher login http --algorithm ecdsap384 --domain=example.com --private-key=HEX_KEY
text
Cloud signing is also supported for HTTP authentication, similar to the DNS examples above. Just swap out the dns positional argument for http.

#### Anonymous (Testing)

bash
mcp-publisher login none [--registry=URL]
text
- No authentication - for local testing only
- Only works with local registry instances

mcp-publisher validate

Validate a server.json file without publishing.

Usage:

bash
mcp-publisher validate [file]
text
Arguments:
-
file - Path to server.json file (default: ./server.json)

Behavior:
- Performs exhaustive validation, reporting all issues at once (not just the first error)
- Validates JSON syntax and schema compliance
- Runs semantic validation (business logic checks)
- Checks for deprecated schema versions and provides migration guidance
- Includes detailed error locations with JSON paths (e.g.,
packages[0].transport.url)
- Shows validation issue type (json, schema, semantic, linter)
- Displays severity level (error, warning, info)
- Provides schema references showing which validation rule triggered each error

Example output:

bash
$ mcp-publisher validate
✅ server.json is valid

$ mcp-publisher validate custom-server.json
❌ Validation failed with 2 issue(s):

1. [error] repository.url (schema)
'' has invalid format 'uri'
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format

2. [error] name (semantic)
server name must be in format 'dns-namespace/name'
Reference: invalid-server-name

text

mcp-publisher publish

Publish server to the registry.

For detailed guidance on the publishing process, see the publishing guide.

Usage:

bash
mcp-publisher publish [PATH]
text
Options:
-
PATH - Path to server.json (default: ./server.json)

Process:
1. Validates
server.json against schema
2. Publishes the
server.json to the registry server URL specified in the login token
3. Server: Verifies package ownership (see Official Registry Requirements)
4. Server: Checks namespace authentication
5. Server: Publishes to registry

Example:

bash

Basic publish


mcp-publisher publish

Custom file location


mcp-publisher publish ./config/server.json
text

mcp-publisher status

Update the lifecycle status of a published server.

Usage:

bash
mcp-publisher status --status <active|deprecated|deleted> [flags] <server-name> [version]
text
Flags:
-
--status (required) - New status: active, deprecated, or deleted
-
--message - Optional message explaining the status change (not allowed when status is active)
-
--all-versions - Apply status change to all versions of the server
-
--yes, -y - Skip confirmation prompt (only applies when using --all-versions)

Arguments:
-
server-name - Full server name (e.g., io.github.user/my-server)
-
version - Server version to update (required unless --all-versions is set)

Status Values:
-
active - Server is active and visible in default listings
-
deprecated - Server is deprecated but still visible with a warning message
-
deleted - Server is hidden from default listings

Examples:

bash

Deprecate a specific version


mcp-publisher status --status deprecated --message "Please upgrade to 2.0.0" \
io.github.user/my-server 1.0.0

Delete a version with security issues


mcp-publisher status --status deleted --message "Critical security vulnerability" \
io.github.user/my-server 1.0.0

Restore a version to active


mcp-publisher status --status active io.github.user/my-server 1.0.0

Deprecate all versions at once


mcp-publisher status --status deprecated --all-versions --message "Project archived" \
io.github.user/my-server
text
Requirements:
- Must be logged in with
publish or edit permission for the server namespace

mcp-publisher logout

Clear stored authentication credentials.

Usage:

bash
mcp-publisher logout
text
Behavior:
- Removes
~/.config/mcp-publisher/token.json
- Also cleans up legacy token files (
~/.mcp_publisher_token, .mcpregistry_*)
- Does not revoke tokens on server side

Configuration

Token Storage


Authentication tokens are stored in
~/.config/mcp-publisher/token.json as JSON:
json
{
"token": "jwt-token-here",
"method": "github",
"registry": "https://registry.modelcontextprotocol.io"
}
text
Note: Tokens were previously stored in ~/.mcp_publisher_token. If you are upgrading, run mcp-publisher logout followed by mcp-publisher login to migrate to the new location.

---

Reference/Api/CHANGELOG

Registry API Changelog

Changes to the REST API endpoints and responses.

Unreleased

Added

#### Server Status Management Endpoints

New endpoints for managing server lifecycle status:

- PATCH /v0/servers/{serverName}/versions/{version}/status - Update status of a specific server version
-
PATCH /v0/servers/{serverName}/status - Update status of all versions of a server in a single transaction

Status values:
-
active - Server is active and visible in default listings
-
deprecated - Server is deprecated but still visible with a warning message
-
deleted - Server is hidden from default listings

Authentication: Requires publish or edit permission for the server namespace.

#### New Response Fields

New fields added to _meta["io.modelcontextprotocol.registry/official"] (RegistryExtensions):

- statusChangedAt - Timestamp when the server status was last changed
-
statusMessage - Optional message explaining status change (e.g., deprecation reason, migration guidance)

#### Server Filtering Enhancements

New include_deleted query parameter added to multiple endpoints:

- GET /v0/servers - Include deleted servers in list results (default: false, automatically true when updated_since is provided)
-
GET /v0/servers/{serverName}/versions/{version} - Include deleted servers in detail results (default: false)
-
GET /v0/servers/{serverName}/versions - Include deleted servers in version history (default: false)

2025-10-17

Added

#### API Versioning - v0.1 Introduction

Introduced /v0.1/ as a stable API version while /v0/ continues as the development version.

New version paths:
- All
/v0/ endpoints are now also available at /v0.1/
- Both versions currently share identical behavior
-
/v0/ will continue to evolve with additive changes (new optional fields, new endpoints)
-
/v0.1/ will remain stable with only additive, backward-compatible changes
- Both versions will be maintained until a future v1.0 release

Migration guidance:
- Production applications should consider using
/v0.1/ for stability
- Development and testing can continue using
/v0/ for latest features
- No immediate action required -
/v0/ remains fully supported

⚠️ BREAKING CHANGES

#### Endpoint Simplification

Removed redundant endpoint to simplify API surface and reduce implementation burden for subregistries.

Removed endpoints:
-
GET /v0/servers/{serverName} - Use GET /v0/servers/{serverName}/versions/latest instead

2025-09-29

⚠️ BREAKING CHANGES

#### API Endpoint Restructuring

API endpoints updated to use server names instead of server IDs for better usability.

Changed endpoints:
-
GET /v0/servers/{server_id}GET /v0/servers/{serverName}
-
GET /v0/servers/{server_id}/versionsGET /v0/servers/{serverName}/versions

New endpoints:
-
GET /v0/servers/{serverName}/versions/{version} - Get specific server version
-
PUT /v0/servers/{serverName}/versions/{version} - Edit server version (admin only)

Response format changes:
- Introduced
ServerResponse schema separating server data from registry metadata
- Moved
status field from server data to _meta.io.modelcontextprotocol.registry/official
- Removed
io.modelcontextprotocol.registry/official metadata from ServerDetail schema

Changed


- OpenAPI spec version:
2025-09-162025-09-29

2025-09-16

⚠️ BREAKING CHANGES

#### Server ID Endpoints (#396)

API endpoints updated for consistent server identification across versions.

Problem: Each server version had a unique ID, preventing version history tracking and server renaming.

Solution: Introduced consistent server identification across versions.

Changed endpoints:
-
GET /v0/servers/{id}GET /v0/servers/{server_id}

New endpoints:
-
GET /v0/servers/{server_id}/versions - List all versions of a server
-
GET /v0/servers/{server_id}?version=1.0.0 - Get specific version

Changed response metadata:
-
_meta..id_meta..serverId
- Added:
_meta.*.versionId

#### Migration Examples

Old Structure:

json
{
"_meta": {
"io.modelcontextprotocol.registry/official": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"published_at": "2024-01-01T00:00:00Z",
"is_latest": true
}
}
}
text
New Structure:
json
{
"_meta": {
"io.modelcontextprotocol.registry/official": {
"serverId": "550e8400-e29b-41d4-a716-446655440000",
"versionId": "773f9b2e-1a47-4c8d-b5e6-2f8d9c4a7b3e",
"published_at": "2024-01-01T00:00:00Z",
"is_latest": true
}
}
}
text
#### Migration Checklist for API Consumers

- [ ] Update API endpoint URLs from /v0/servers/{id} to /v0/servers/{server_id}
- [ ] Update code reading registry metadata from
id to serverId/versionId
- [ ] Add support for new
/v0/servers/{server_id}/versions endpoint if needed
- [ ] Update JSON parsing to expect camelCase field names
- [ ] Test with new API responses

Changed


- OpenAPI spec version:
2025-07-092025-09-16

2025-07-09

Initial release of the Registry API.

---

Reference/Api/Extensions

Registry Extensions Specification

A standardized way for registries to provide experimental or community-driven features without committing them to the core API specification.

Motivation

The core generic registry API intentionally stays minimal to ensure stability and broad adoption. Extensions provide a path for:

- Experimentation: Try new features without core API changes
- Community innovation: Anyone can implement custom extensions
- Gradual adoption: Popular extensions may inform future core API features
- Avoiding breaking changes: Failed experiments can be deprecated without API versioning churn

URL Structure

Extensions live under the /v0.1/x/ prefix:


/v0.1/x/<namespace>/<extension>[/<path>]
text
Components:
-
<namespace>: Reverse domain ownership (e.g., com.example, io.github.username)
-
<extension>: Extension name (lowercase, hyphens for word separation)
-
<path>: Extension-specific path structure (optional)

Examples:


/v0.1/x/com.example/search?q=database
/v0.1/x/com.example/stats
/v0.1/x/io.github.username/custom-feature
text

Conventions

Where possible:
- Follow standard REST conventions, return simple JSON responses, and avoid special headers
- For list endpoints, use cursor-based pagination matching the core API
- Extensions requiring authentication SHOULD follow the Registry Authorization Specification
- Build open-source implementations in a composable way on top of the core APIs (e.g. as opposed to via custom database integration)

Implementation Requirements

Registries implementing extensions SHOULD namespace extensions properly to avoid conflicts.

Clients consuming extensions MUST gracefully handle missing extensions.

Example

A simple server stats extension:

bash
GET /v0.1/x/com.example/stats
text
json
{
"totalServers": 1234,
"totalVersions": 5678,
"recentPublishes": 42
}
text

Future Considerations

- Extension discovery: A potential /v0.1/x endpoint to list available extensions
- Extension metadata: Standardized metadata format for extension capabilities
- Defining common extensions: Like semantic conventions from OpenTelemetry, develop common extensions that registries can adopt (possibly under an experimental namespace)
- Search extension for free-text search across server metadata (#389)
- MCP server extension to expose the registry itself as an MCP server for programmatic access (#24)

---

Reference/Api/Generic Registry Api

Generic Registry API Specification

A standardized RESTful HTTP API for MCP registries to provide consistent endpoints for discovering and retrieving MCP servers.

Also see:
- For authentication and authorization, see the registry authorization specification.

Browse the Complete API Specification

📋 View the full API specification interactively: Open openapi.yaml in an OpenAPI viewer like Stoplight Elements.

The official registry has some more endpoints and restrictions on top of this. See the official registry API spec for details.

Quick Reference

Core Endpoints


-
GET /v0.1/servers - List all servers with pagination
-
GET /v0.1/servers/{serverName}/versions - List all versions of a server
-
GET /v0.1/servers/{serverName}/versions/{version} - Get specific version of server. Use the special version latest to get the latest version.
-
POST /v0.1/publish - Publish new server (optional, registry-specific authentication)
-
PUT /v0.1/servers/{serverName}/versions/{version} - Update specific server version (optional; the official registry implements this as an admin endpoint)
-
DELETE /v0.1/servers/{serverName}/versions/{version} - Delete specific server version (optional, not implemented by official registry)
-
PATCH /v0.1/servers/{serverName}/versions/{version}/status - Update server version status (optional)
-
PATCH /v0.1/servers/{serverName}/status - Update status for all versions (optional)

Server names and version strings should be URL-encoded in paths.

Authentication

No authentication required by default. Subregistries may optionally require authentication following the registry authorization specification.

Content Type


All requests and responses use
application/json

Pagination


List endpoints use cursor-based pagination for efficient, stable results.

#### Usage
1. Initial request: Omit the
cursor parameter
2. Subsequent requests: Use the
nextCursor value from the previous response
3. End of results: When
nextCursor is null or empty, there are no more results

Important: Always treat cursors as opaque strings. Never manually construct or modify cursor values.

Basic Example: List Servers

bash
curl https://registry.example.com/v0.1/servers?limit=10
text
json
{
"servers": [
{
"server": {
"name": "io.modelcontextprotocol/filesystem",
"description": "Filesystem operations server",
"version": "1.0.2"
},
"_meta": {
"io.modelcontextprotocol.registry/official": {
"status": "active",
"publishedAt": "2025-01-01T10:30:00Z",
"isLatest": true
}
}
}
],
"metadata": {
"count": 10,
"nextCursor": "com.example/my-server:1.0.0"
}
}
text
For complete endpoint documentation, view the OpenAPI specification in a schema viewer.

---

Reference/Api/Official Registry Api

Official MCP Registry API

This document describes the API for the official MCP Registry hosted at registry.modelcontextprotocol.io.

This API is based on the generic registry API with additional endpoints and authentication. For publishing servers using the API, see the publishing guide.

Base URLs

- Production: https://registry.modelcontextprotocol.io
- Staging:
https://staging.registry.modelcontextprotocol.io

Interactive Documentation

- Live API Docs - Stoplight elements with try-it-now functionality
- OpenAPI Spec - Complete machine-readable specification

Extensions

The official registry implements the Generic Registry API with the following specific configurations and extensions:

Authentication

Publishing requires namespace-based authentication:

- GitHub OAuth - For io.github.* namespaces
- GitHub OIDC - For publishing from GitHub Actions
- DNS verification - For domain-based namespaces (
com.example.*)
- HTTP verification - For domain-based namespaces (
com.example.*)

See Publisher Commands for authentication setup.

Package Validation

The official registry enforces additional package validation requirements when publishing.

Server List Filtering

The official registry extends the GET /v0.1/servers endpoint with additional query parameters for improved discovery and synchronization:

- updated_since - Filter servers updated after RFC3339 timestamp (e.g., 2025-08-07T13:15:04.280Z)
-
search - Case-insensitive substring search on server names (e.g., filesystem)
- This is intentionally simple. For more advanced searching and filtering, use a subregistry.
-
version - Filter by version (currently supports latest for latest versions only)
-
include_deleted - Include deleted servers in results (default: false, but automatically true when updated_since is provided for incremental sync)

These extensions enable efficient incremental synchronization for downstream registries and improved server discovery. Parameters can be combined and work with standard cursor-based pagination.

Example: GET /v0.1/servers?search=filesystem&updated_since=2025-08-01T00:00:00Z&version=latest

Server Detail

The GET /v0.1/servers/{serverName}/versions/{version} endpoint returns detailed information about a specific server version.

Path parameters:
-
serverName - URL-encoded server name (e.g., io.github.user%2Fmy-server)
-
version - Server version or latest for the most recent version

Query parameters:
-
include_deleted - Include deleted servers in results (default: false)

Server Version History

The GET /v0.1/servers/{serverName}/versions endpoint returns all versions of a server.

Path parameters:
-
serverName - URL-encoded server name (e.g., io.github.user%2Fmy-server)

Query parameters:
-
include_deleted - Include deleted servers in results (default: false)

Additional endpoints

#### Auth endpoints
- POST
/v0.1/auth/dns - Exchange signed DNS challenge for auth token
- POST
/v0.1/auth/http - Exchange signed HTTP challenge for auth token
- POST
/v0.1/auth/github-at - Exchange GitHub access token for auth token
- POST
/v0.1/auth/github-oidc - Exchange GitHub OIDC token for auth token
- POST
/v0.1/auth/oidc - Exchange Google OIDC token for auth token (for admins)

#### Status endpoints

##### Update Single Version Status

PATCH /v0.1/servers/{serverName}/versions/{version}/status - Update status of a specific server version.

Path parameters:
-
serverName - URL-encoded server name (e.g., io.github.user%2Fmy-server)
-
version - Server version to update

Request body:
-
status (required) - New status: active, deprecated, or deleted
-
statusMessage (optional) - Message explaining the status change (max 500 characters, not allowed when status is active)

##### Update All Versions Status

PATCH /v0.1/servers/{serverName}/status - Update status of all versions of a server in a single transaction.

Path parameters:
-
serverName - URL-encoded server name (e.g., io.github.user%2Fmy-server)

Request body:
-
status (required) - New status: active, deprecated, or deleted
-
statusMessage (optional) - Message explaining the status change (max 500 characters, not allowed when status is active)

Status values:
-
active - Server is active and visible in default listings
-
deprecated - Server is deprecated but still visible with a warning message
-
deleted - Server is hidden from default listings (use include_deleted=true to show)

Authentication: Requires publish or edit permission for the server namespace.

#### Validation endpoint
- POST
/v0.1/validate - Validate a server.json without publishing it. Used by mcp-publisher validate.

#### Service endpoints
- GET
/v0.1/ping - Liveness check
- GET
/v0.1/version - Registry version information

#### Admin endpoints
- GET
/metrics - Prometheus metrics endpoint
- GET
/v0.1/health - Basic health check endpoint
- PUT
/v0.1/servers/{serverName}/versions/{version} - Edit specific server version

Note that /v0.1/validate, /v0.1/ping, /v0.1/version and /v0.1/health are not described in
openapi.yaml, which covers the server and publish endpoints only.

---

Reference/Api/Registry Authorization

Registry Authorization

MCP registries wishing to implement authentication SHOULD follow the MCP Authorization Specification.

How it works

The registry acts as an OAuth 2.1 Resource Server, identical to how MCP servers work. This means:

- MCP clients can reuse their existing MCP authorization implementation without any changes
- Registries validate access tokens the same way MCP servers do
- Users get a consistent login experience across MCP servers and registries

Registry-Specific Scopes

Registries MAY use these scopes:

- mcp-registry:read - List and read server metadata
-
mcp-registry:write - Publish, update, and delete servers

These are recommendations - registries may use any set of scopes they deem sensible.

Note that scopes only control what types of operations a user can perform. Registries should still apply user-level authorization to control which specific resources a user can access. For example, a user with mcp-registry:write might only be able to publish servers to namespaces they own, and may not have permissions to edit servers if the registry treats servers as immutable.

Official Registry Authentication

The official modelcontextprotocol.io registry remains public for reading. For publishing servers, it uses a custom JWT-based authentication system for legacy reasons - see its API spec. This may change in future to align with the MCP Authorization Specification.

---

Reference/Server Json/CHANGELOG

Server JSON Schema Changelog

Changes to the server.json schema and format.

Draft (Unreleased)

This section tracks changes that are in development and not yet released. The draft schema is available at server.schema.json in this repository.

Changed

#### Transport URL Pattern Now Accepts Template Variables

The url field in StreamableHttpTransport and SseTransport now accepts URLs that start with a template variable (e.g., {baseUrl}), in addition to the existing http:// and https:// prefixes.

Example:

json
{
"remotes": [{
"type": "streamable-http",
"url": "{baseUrl}/mcp",
"variables": {
"baseUrl": {
"description": "Base URL for the MCP server",
"isRequired": true
}
}
}]
}
text
Migration: No changes required. Existing servers continue to work unchanged.

Notes

When ready for release, changes in this section will be moved to a dated version section (e.g., ## 2025-XX-XX) and the schema will be published to a versioned URL.

---

2025-12-11

Changed

#### URL Template Variables for Remote Servers (#570)

Remote servers can now define URL template variables using {curly_braces} notation. This enables multi-tenant deployments where a single server definition can support multiple endpoints with configurable values.

Example:

json
{
"remotes": [{
"type": "streamable-http",
"url": "https://{tenant_id}.api.example.com/mcp",
"variables": {
"tenant_id": {
"description": "Your tenant identifier",
"isRequired": true
}
}
}]
}
text
Migration: No changes required. Existing servers continue to work unchanged.

---

2025-10-17

Changed

The version field is now optional for MCPB packages, providing flexibility for publishers.

Key Changes:

- MCPB packages can now include an optional version field - Previously rejected by validation, MCPB packages can now optionally specify a version field for clarity and metadata purposes.
- Both formats are valid:
- MCPB packages with version field: Provides explicit version metadata
- MCPB packages without version field: Version information is embedded in the download URL (as before)

Migration:

Publishers using MCPB packages can optionally add a version field to their package configuration. This is particularly useful when:
- The version information is not clearly visible in the download URL
- You want to provide explicit version metadata for tooling and clients
- You need consistent version tracking across different package types

Existing MCPB packages without the version field continue to work without any changes.

Example - MCPB Package with optional version:

json
{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"version": "1.0.0",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
text
Example - MCPB Package without version (still valid):
json
{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
text

Schema Version


- Schema version:
2025-10-112025-10-17

2025-10-11

Changed

#### Package Format Enhancements (#634)

The Package schema has been refactored to better support different package types with dedicated handling per registry type.

Key Changes:

- version field is now optional - Previously required for all packages, now only used by npm, pypi, and nuget. OCI packages include version in the identifier (e.g., ghcr.io/owner/repo:v1.0.0), and MCPB packages use direct download URLs.

- Enhanced documentation - Added detailed comments explaining which fields are relevant for each registryType:
- NPM/PyPI/NuGet: Use
registryType, identifier (package name), version, optional registryBaseUrl
- OCI: Use
registryType, identifier (full image reference with tag)
- MCPB: Use
registryType, identifier (download URL), fileSha256 (required)

- Field clarifications:
-
identifier: Now clearly documented as package name for registries, full image reference for OCI, or download URL for MCPB
-
fileSha256: Clarified as required for MCPB packages and optional for other types
-
registryBaseUrl: Clarified as used by npm/pypi/nuget but not by oci/mcpb

Migration:

Publishers using OCI or MCPB packages can now omit the version field, as it's either embedded in the identifier (OCI) or not applicable (MCPB direct downloads). Publishers using npm, pypi, or nuget should continue to provide the version field as before.

Example - OCI Package (version in identifier):

json
{
"packages": [{
"registryType": "oci",
"identifier": "ghcr.io/modelcontextprotocol/server-example:v1.2.3",
"transport": {
"type": "stdio"
}
}]
}
text
Example - MCPB Package (no version field):
json
{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
text

Schema Version


- Schema version:
2025-09-292025-10-11

2025-09-29

⚠️ BREAKING CHANGES

#### Schema Simplification

Removed registry-managed fields from publisher-controlled server.json schema.

Removed fields:
-
status field from Server object (now managed by registry in API responses)
-
io.modelcontextprotocol.registry/official from _meta (read-only, added by registry)

Migration:
Publishers should remove these fields from their
server.json files. The registry will manage server status and official metadata separately.

Changed


- Schema version:
2025-09-162025-09-29

2025-09-16

⚠️ BREAKING CHANGES

#### Field Names: snake_case → camelCase (#428)

All JSON field names standardized to camelCase. All existing server.json files must be updated.

Changed fields:
-
registry_typeregistryType
-
registry_base_urlregistryBaseUrl
-
file_sha256fileSha256
-
runtime_hintruntimeHint
-
runtime_argumentsruntimeArguments
-
package_argumentspackageArguments
-
environment_variablesenvironmentVariables
-
is_requiredisRequired
-
is_secretisSecret
-
value_hintvalueHint
-
is_repeatedisRepeated
-
website_urlwebsiteUrl

#### Migration Examples

Package Configuration:

json
// OLD - Will be rejected
{
"packages": [{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"file_sha256": "abc123...",
"runtime_hint": "node",
"runtime_arguments": [...],
"package_arguments": [...],
"environment_variables": [...]
}]
}

// NEW - Required format
{
"packages": [{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"fileSha256": "abc123...",
"runtimeHint": "node",
"runtimeArguments": [...],
"packageArguments": [...],
"environmentVariables": [...]
}]
}

text
Arguments Configuration:
json
// OLD - Will be rejected
{
"runtime_arguments": [
{
"name": "port",
"is_required": true,
"is_repeated": false,
"value_hint": "8080"
}
]
}

// NEW - Required format
{
"runtimeArguments": [
{
"name": "port",
"isRequired": true,
"isRepeated": false,
"valueHint": "8080"
}
]
}

text
Environment Variables:
json
// OLD - Will be rejected
{
"environment_variables": [
{
"name": "API_KEY",
"is_required": true,
"is_secret": true
}
]
}

// NEW - Required format
{
"environmentVariables": [
{
"name": "API_KEY",
"isRequired": true,
"isSecret": true
}
]
}

text
#### Migration Checklist for Publishers

- [ ] Update your server.json files to use camelCase field names
- [ ] Test server publishing with new CLI version
- [ ] Update any automation scripts that reference old field names
- [ ] Update documentation referencing old field names

#### Updated Schema Reference

🔗 Current schema: https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json

Changed


- Schema version:
2025-07-092025-09-16

2025-07-09

Initial release of the server.json schema.

---

Reference/Server Json/CONTRIBUTING

Contributing to server.json Schema

This document describes the process for making and releasing changes to the server.json schema.

Making Changes

1. Modify the OpenAPI spec: Edit docs/reference/api/openapi.yaml with your schema changes. The ServerDetail component defines the server.json structure.

2. Regenerate the schema: Run make generate-schema to update server.schema.json from the OpenAPI spec.

3. Update the changelog: Add your changes to the "Draft (Unreleased)" section in CHANGELOG.md.

4. Open a PR: Submit a pull request to this repository for review.

Releasing Changes

When the draft changes are ready for release:

1. Update the changelog: Move changes from "Draft (Unreleased)" to a new dated section (e.g., ## 2025-XX-XX).

2. Update the schema URL: Change the $id in the schema and the example URL in openapi.yaml from draft to the release date (e.g., 2025-XX-XX).

3. Merge the PR: Get approval and merge the changes to main.

4. Publish to static hosting: Open a PR on modelcontextprotocol/static to add the new versioned schema file. This "locks in" the released schema at its versioned URL.

Schema Versioning

- Draft schema: https://raw.githubusercontent.com/modelcontextprotocol/registry/main/docs/reference/server-json/draft/server.schema.json - For in-progress changes, may change without notice.
- Released schemas:
https://static.modelcontextprotocol.io/schemas/YYYY-MM-DD/server.schema.json - Stable, versioned by release date.

---

Reference/Server Json/Generic Server Json

server.json Format Specification

A server.json file is a standardized way to describe MCP servers for registry publishing, client discovery, and package management.

Also see:
- For step-by-step instructions on creating and using server.json files, see the publishing guide.
- For understanding the validation requirements when publishing to the official registry, see official registry requirements.

Browse the Complete Schema

📋 View the full specification interactively: Open server.schema.json in a schema viewer like json-schema.app.

The schema contains all field definitions, validation rules, examples, and detailed descriptions.

The official registry has some more restrictions on top of this. See the official registry requirements for details.

Extension Metadata with _meta

The optional _meta field allows publishers to include custom metadata alongside their server definitions using reverse DNS namespacing.

jsonc
{
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
// Your custom metadata here
}
}
}
text
When publishing to the official registry, custom metadata must be placed under the key io.modelcontextprotocol.registry/publisher-provided. See the official registry requirements for detailed restrictions and examples.

Examples

Basic Server with NPM Package

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/brave-search",
"description": "MCP server for Brave Search API integration",
"title": "Brave Search",
"websiteUrl": "https://anonymous.modelcontextprotocol.io/examples",
"repository": {
"url": "https://github.com/modelcontextprotocol/servers",
"source": "github"
},
"version": "1.0.2",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "@modelcontextprotocol/server-brave-search",
"version": "1.0.2",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"name": "BRAVE_API_KEY",
"description": "Brave Search API Key",
"isRequired": true,
"isSecret": true
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "npm-publisher",
"version": "1.0.1",
"build_info": {
"timestamp": "2023-12-01T10:30:00Z"
}
}
}
}
text

Server in a Monorepo with Subfolder

For MCP servers located within a subdirectory of a larger repository (monorepo structure), use the subfolder field to specify the relative path:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol/everything",
"description": "MCP server that exercises all the features of the MCP protocol",
"title": "Everything",
"repository": {
"url": "https://github.com/modelcontextprotocol/servers",
"source": "github",
"subfolder": "src/everything"
},
"version": "0.6.2",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "@modelcontextprotocol/everything",
"version": "0.6.2",
"transport": {
"type": "stdio"
}
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "npm-publisher",
"version": "1.0.1",
"build_info": {
"timestamp": "2023-12-01T10:30:00Z"
}
}
}
}
text

Constant (fixed) arguments needed to start the MCP server

Suppose your MCP server application requires a mcp start CLI arguments to start in MCP server mode. Express these as positional arguments like this:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.joelverhagen/knapcode-samplemcpserver",
"description": "Sample NuGet MCP server for a random number and random weather",
"version": "0.4.0-beta",
"packages": [
{
"registryType": "nuget",
"registryBaseUrl": "https://api.nuget.org/v3/index.json",
"identifier": "Knapcode.SampleMcpServer",
"version": "0.4.0-beta",
"transport": {
"type": "stdio"
},
"packageArguments": [
{
"type": "positional",
"value": "mcp"
},
{
"type": "positional",
"value": "start"
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "nuget-publisher",
"version": "2.1.0",
"build_info": {
"timestamp": "2023-11-15T14:22:00Z",
"pipeline_id": "nuget-build-456"
}
}
}
}
text
This will essentially instruct the MCP client to execute dnx [email protected] -- mcp start instead of the default dnx [email protected] (when no packageArguments are provided).

Filesystem Server with Multiple Packages


/ Detailed source-code truncated for AI context efficiency. /
text
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"
}
}
]
}
text

Remote Server Example

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/mcp-fs",
"description": "Cloud-hosted MCP filesystem server",
"repository": {
"url": "https://github.com/example/remote-fs",
"source": "github",
"id": "xyz789ab-cdef-0123-4567-890ghijklmno"
},
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://mcp-fs.anonymous.modelcontextprotocol.io/http"
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "cloud-deployer",
"version": "2.4.0",
"build_info": {
"commit": "f7e8d9c2b1a0",
"timestamp": "2023-12-05T08:45:00Z",
"deployment_id": "remote-fs-deploy-456",
"region": "us-west-2"
}
}
}
}
text

Python Package Example

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.example/weather-mcp",
"description": "Python MCP server for weather data access",
"title": "Weather",
"repository": {
"url": "https://github.com/example/weather-mcp",
"source": "github",
"id": "def456gh-ijkl-7890-mnop-qrstuvwxyz12"
},
"version": "0.5.0",
"packages": [
{
"registryType": "pypi",
"registryBaseUrl": "https://pypi.org",
"identifier": "weather-mcp-server",
"version": "0.5.0",
"runtimeHint": "uvx",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"name": "WEATHER_API_KEY",
"description": "API key for weather service",
"isRequired": true,
"isSecret": true
},
{
"name": "WEATHER_UNITS",
"description": "Temperature units (celsius, fahrenheit)",
"default": "celsius"
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "poetry-publisher",
"version": "1.8.3",
"build_info": {
"python_version": "3.11.5",
"timestamp": "2023-11-28T16:20:00Z",
"build_id": "pypi-weather-123",
"dependencies_hash": "sha256:a9b8c7d6e5f4"
}
}
}
}
text

Cargo (Rust) Package Example

cargo install <crate> places the binary on PATH (via ~/.cargo/bin); MCP clients invoke it directly by name. There is no single-shot equivalent of npx (npm), uvx (PyPI), or dnx (NuGet, .NET 10 SDK) for cargo — install once, run by name.

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.example/widget-mcp",
"description": "Rust-native MCP server",
"title": "Widget",
"repository": {
"url": "https://github.com/example/widget-mcp",
"source": "github"
},
"version": "0.3.0",
"packages": [
{
"registryType": "cargo",
"registryBaseUrl": "https://crates.io",
"identifier": "widget-mcp",
"version": "0.3.0",
"transport": {
"type": "stdio"
}
}
]
}
text

NuGet (.NET) Package Example

The dnx tool ships with the .NET 10 SDK, starting with Preview 6.

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.joelverhagen/knapcode-samplemcpserver",
"description": "Sample NuGet MCP server for a random number and random weather",
"repository": {
"url": "https://github.com/joelverhagen/Knapcode.SampleMcpServer",
"source": "github",
"id": "example-nuget-id-0000-1111-222222222222"
},
"version": "0.5.0",
"packages": [
{
"registryType": "nuget",
"registryBaseUrl": "https://api.nuget.org/v3/index.json",
"identifier": "Knapcode.SampleMcpServer",
"version": "0.5.0",
"runtimeHint": "dnx",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"name": "WEATHER_CHOICES",
"description": "Comma separated list of weather descriptions to randomly select.",
"isRequired": true,
"isSecret": false
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "dotnet-publisher",
"version": "8.0.100",
"build_info": {
"dotnet_version": "8.0.0",
"timestamp": "2023-12-10T12:15:00Z",
"configuration": "Release",
"target_framework": "net8.0",
"build_number": "20231210.1"
}
}
}
}
text

Complex Docker Server with Multiple Arguments


/ Detailed source-code truncated for AI context efficiency. /
text

Server with Remote and Package Options


/ Detailed source-code truncated for AI context efficiency. /
text

MCP Bundle (MCPB) Package Example

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol/text-editor",
"description": "MCP Bundle server for advanced text editing capabilities",
"title": "Text Editor",
"repository": {
"url": "https://github.com/modelcontextprotocol/text-editor-mcpb",
"source": "github"
},
"version": "1.0.2",
"packages": [
{
"registryType": "mcpb",
"identifier": "https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "mcpb-publisher",
"version": "1.0.0",
"build_info": {
"timestamp": "2023-12-02T09:15:00Z",
"bundle_format": "mcpb-v1"
}
}
}
}
text
This example shows an MCPB (MCP Bundle) package that:
- Is hosted on GitHub Releases (an allowlisted provider)
- Includes a SHA-256 hash for integrity verification
- Can be downloaded and executed directly by MCP clients that support MCPB

Embedded MCP inside a CLI tool

Some CLI tools bundle an MCP server, without a standalone MCP package or a public repository. In these cases, reuse the existing packages shape by pointing at the host CLI package and supplying the packageArguments and runtimeHint if needed to start the MCP server.

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.snyk/cli-mcp",
"description": "MCP server provided by the Snyk CLI",
"title": "Snyk",
"version": "1.1298.0",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "snyk",
"version": "1.1298.0",
"transport": {
"type": "stdio"
},
"packageArguments": [
{ "type": "positional", "value": "mcp" },
{
"type": "named",
"name": "-t",
"description": "Transport type for MCP server",
"default": "stdio",
"choices": ["stdio", "sse"]
}
]
}
]
}
text

Server with Custom Installation Path

For MCP servers that follow a custom installation path or are embedded in applications without standalone packages, use the websiteUrl field to direct users to setup documentation:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/embedded-mcp",
"description": "MCP server embedded in a Desktop app",
"websiteUrl": "https://anonymous.modelcontextprotocol.io/embedded-mcp-guide",
"version": "0.1.0"
}
text

Remote Server with URL Templating

This example demonstrates URL templating for remote servers, useful for multi-tenant deployments where each instance has its own endpoint. Unlike Package transports (which reference parent arguments/environment variables), Remote transports define their own variables:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/multi-tenant-server",
"description": "MCP server with configurable remote endpoint",
"title": "Multi-Tenant Server",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://anonymous.modelcontextprotocol.io/mcp/{tenant_id}",
"variables": {
"tenant_id": {
"description": "Tenant identifier (e.g., 'us-cell1', 'emea-cell1')",
"isRequired": true
}
}
}
]
}
text
Clients configure the tenant identifier, and the {tenant_id} variable in the URL gets replaced with the provided variable value to connect to the appropriate tenant endpoint (e.g., https://anonymous.modelcontextprotocol.io/mcp/us-cell1 or https://anonymous.modelcontextprotocol.io/mcp/emea-cell1).

The same URL templating works with SSE transport:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/events-server",
"description": "MCP server using SSE with tenant-specific endpoints",
"version": "1.0.0",
"remotes": [
{
"type": "sse",
"url": "https://events.anonymous.modelcontextprotocol.io/sse/{tenant_id}",
"variables": {
"tenant_id": {
"description": "Tenant identifier",
"isRequired": true
}
}
}
]
}
text

Local Server with URL Templating

This example demonstrates URL templating for local/package servers, where variables reference parent Package arguments or environment variables:

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.example/configurable-server",
"description": "Local MCP server with configurable port",
"title": "Configurable Server",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "@example/mcp-server",
"version": "1.0.0",
"transport": {
"type": "streamable-http",
"url": "http://localhost:{--port}/mcp"
},
"packageArguments": [
{
"type": "named",
"name": "--port",
"description": "Port for the server to listen on",
"default": "3000"
}
]
}
]
}
`

The {--port} variable in the URL references the --port argument name from packageArguments. For positional arguments, an argument with the valueHint of port could similarly be referenced as {port}. When the package runs with --port 8080, the URL becomes http://localhost:8080/mcp`.

---