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
Run this, then run the export command it outputs
./tools/admin/auth.shEdit 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
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.jsonOr get the latest version (use the special version "latest")
curl -s "https://registry.modelcontextprotocol.io/v0/servers/${ENCODED_SERVER_NAME}/versions/latest" > server.jsonStep 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
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:
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.
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
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
Extract all versions from the server
jq -r '.servers[].server.version' all_versions.json > versions.txt#### Step 3: Apply Changes to Each Version
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.jsonQuick Operations
Get Latest Version of a Server
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
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 eitherVERSION or ALL_VERSIONS to be set explicitly, so a forgotten VERSION cannot take down a whole
server by accident.
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
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
REGISTRY_TOKEN="$REGISTRY_TOKEN" SERVER_NAME="$SERVER_NAME" ALL_VERSIONS=true \
STATUS_MESSAGE="Removed per moderation policy" ./tools/admin/takedown.shConnecting 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:
gcloud components install gke-gcloud-auth-pluginConnect
Get cluster credentials
gcloud container clusters get-credentials mcp-registry-prod --zone us-central1-b --project mcp-registry-prodGet the database password
kubectl get secret registry-pg-app -o jsonpath='{.data.password}' | base64 -dPort-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 appRead-Only Access
To prevent accidental writes, set your session to read-only after connecting:
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
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
/ 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
// 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:
// 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():
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:
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:
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:
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/formatThis 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:
[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
/ 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:
{
"$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:
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
1. Copy draft schema
cp docs/reference/server-json/draft/server.schema.json internal/validators/schemas/draft.json2. 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.jsonNote: 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/transport → packages[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
{
"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
Basic validation
mcp-publisher validate server.jsonJSON output format
mcp-publisher validate --format json server.jsonFilter by severity
mcp-publisher validate --severity error server.jsonInclude schema validation
mcp-publisher validate --schema server.jsonBenefits 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:
{
"$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:
{
"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
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
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 byPulumi; Helm is used only for third-party components (ingress-nginx, cert-manager,
cloudnative-pg, k8up, monitoring).
- The database diagram shows aStatefulSeton port27017(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 neverbuilt. 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 av=MCPv1; k=...; p=...TXT record.
- No /admin/* routes exist. Admin actions use the regular server endpoints with anadmin-permissioned token.
- The CLI ismcp-publisher, notmcp, 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:
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 -.-> GHSData Flow Patterns
1. Server Publication Flow
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
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 data3. DNS Verification Flow
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:
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 successUsage:
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:
mcp-publisher login githubYou 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...
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g.,ABCD-1234in the above output). Once complete, go back to the terminal, and you should see output like:
Successfully authenticated!
✓ Successfully logged in
io.github.<your-username>/*Personal vs. organization namespaces
GitHub authentication always grants your personal namespace,
.io.github.<orgname>/*To publish under an organization namespace (
), 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.read:orgIf 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
scope.read:org
- Fine-grained PAT: grant the Organization permissions → Members → Read-only permission (the fine-grained equivalent of). 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.example.comEither 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.), not under a selector like_mcp-auth.example.comor_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.mcp-publisherIf 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
CLI tool, run the following commands in your server project directory to generate a TXT record based on a public/private key pair:openssl<Note>
The Ed25519 codepath requires OpenSSL 3.0 or later. macOS ships with LibreSSL by default (the systembinary), which does not implement Ed25519 ingenpkeyand fails withAlgorithm Ed25519 not found. On macOS, install OpenSSL 3 (brew install openssl@3) and invoke it explicitly — for example, replaceopensslwith/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>
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}\""
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}\""
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}"
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}"
</CodeGroup>mcp-publisher loginThen 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
command:<CodeGroup>
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}"
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}"
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"
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}"
</CodeGroup>/.well-known/mcp-registry-authHTTP Authentication
HTTP authentication is a domain-based authentication method that relies on a
file hosted on your domain. For example,https://example.com/.well-known/mcp-registry-auth.mcp-publisherTo perform HTTP authentication using the
CLI tool, run the following commands in your server project directory to generate anmcp-registry-authfile based on a public/private key pair:genpkey<Note>
As with DNS authentication, the Ed25519 codepath requires OpenSSL 3.0 or later. macOS's system LibreSSL does not support Ed25519 in. See the note in the DNS Authentication section for the macOS workaround.
</Note><CodeGroup>
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
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
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}
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}
</CodeGroup>mcp-registry-authThen host the
file at/.well-known/mcp-registry-authon your domain. After the file is hosted, log in using themcp-publisher logincommand:<CodeGroup>
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}"
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}"
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"
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}"
</CodeGroup>https://registry.modelcontextprotocol.io---
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, which is a superset of the MCP Registry API. Its OpenAPI spec can be downloaded from https://registry.modelcontextprotocol.io/openapi.yaml.https://registry.modelcontextprotocol.io
- "MCP registry" — A third-party service that provides an MCP Registry API.
- "Official MCP Registry" (or "The MCP Registry") — The service that lives at.deletedCan I delete/unpublish my server?
Yes, you can change your server's status to
using themcp-publisher statuscommand:
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
Deleted servers are hidden from default API listings but can still be retrieved withinclude_deleted=true. You can restore a deleted server by setting its status back toactive.deletedNote: Server metadata is never permanently removed from the registry. The
status hides the server from discovery but preserves the historical record.server.jsonHow do I update my server metadata?
Submit a new
with a unique version string. Once published, version metadata is immutable (similar to npm)._meta.io.modelcontextprotocol.registry/publisher-providedCan I add custom metadata when publishing?
Yes, custom metadata under
is preserved when publishing to the registry. This allows you to include custom metadata specific to your publishing process.Abuse report:<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.github/workflows/publish-mcp.ymlWhat 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
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. /
/ Detailed source-code truncated for AI context efficiency. /
/ Detailed source-code truncated for AI context efficiency. /
</CodeGroup>MCP_GITHUB_TOKENStep 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 asecret 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):read:org
- Classic PAT: thescope (read:useris not needed; a bare token still authenticates and publishes to your personal namespace).read:org
- Fine-grained PAT: the Organization permissions → Members → Read-only permission (the equivalent of). 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.mcp-registry-publishFor an organization token, store it as an Environment secret on the
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.MCP_PRIVATE_KEY
- DNS Authentication: Add asecret with your Ed25519 private key.NPM_TOKENYou may also need to add secrets for your package registry. For example, the workflow above needs an
secret with your npm token.io.github.<org>/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
, 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 theenvironment: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 declaresenvironment: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 theenvironment: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:
git tag v1.0.0
git push origin v1.0.0
The workflow will run tests, build the package, publish the package to npm, and publish the server to the MCP Registry.id-token: writeTroubleshooting
{/ prettier-ignore-start /}
| Error Message | Action |
| --- | --- |
| "Authentication failed" | Ensurepermission is set for OIDC, or check secrets. |mcp-publisher
| "invalid audience" | Yourbinary is too old for this registry deployment. Re-run the install step shown above so you pick up the latest release. |registry.modelcontextprotocol.io
| "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
.statusSubregistries 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 serversWhat 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 contentHow Removal Works
When we remove a server, we set the server's
to"deleted", but the server's metadata remains accessible via the MCP Registry API. Aggregators may then remove the server from their indexes.https://registry.npmjs.orgIn 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 removalChanges 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 (
) only."registryType": "npm"npm packages use
inserver.json. For example:
{
"$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"
}
}
]
}
mcpNameOwnership Verification
The MCP Registry verifies ownership of npm packages by checking
inpackage.json. ThemcpNameproperty MUST match the server name fromserver.json. For example:
{
"name": "@username/email-integration-mcp",
"version": "1.0.0",
"mcpName": "io.github.username/email-integration-mcp"
}
https://pypi.orgPyPI Packages
For PyPI packages, the MCP Registry currently supports the official PyPI registry (
) only."registryType": "pypi"PyPI packages use
inserver.json. For example:
{
"$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"
}
}
]
}
mcp-name: $SERVER_NAMEOwnership Verification
The MCP Registry verifies ownership of PyPI packages by checking for the existence of an
string in the package README (which becomes the package description on PyPI). The string may be hidden in a comment, but the$SERVER_NAMEportion MUST match the server name fromserver.json. For example:
Database Query MCP Server
This MCP server executes SQL queries and manages database connections.
Themcp-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.https://api.nuget.org/v3/index.jsonNuGet Packages
For NuGet packages, the MCP Registry currently supports the official NuGet registry (
) only."registryType": "nuget"NuGet packages use
inserver.json. For example:
{
"$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"
}
}
]
}
mcp-name: $SERVER_NAMEOwnership Verification
The MCP Registry verifies ownership of NuGet packages by checking for the existence of an
string in the package README. The string may be hidden in a comment, but the$SERVER_NAMEportion MUST match the server name fromserver.json. For example:
Azure DevOps MCP Server
This MCP server manages Azure DevOps work items and pipelines.
Themcp-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.https://crates.ioCargo (Rust) Packages
For Cargo packages, the MCP Registry currently supports the official crates.io registry (
) only."registryType": "cargo"Cargo packages use
inserver.json. For example:
{
"$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"
}
}
]
}
cargo install <crate>Runtime Model
Cargo's runtime model differs from npm/PyPI/NuGet.
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 tonpx(npm),uvx(PyPI), ordnx(NuGet, .NET 10 SDK Preview 6+) — install is one-time, execution is by binary name. The Cargo example above intentionally omitsruntimeHintfor this reason.registryType: cargoRust MCP authors have two first-class distribution paths:
- Cargo (
) — source-distributed via crates.io. End users need the Rust toolchain (rustup) to runcargo install. Idiomatic for the Rust ecosystem and consistent with how Rust CLIs are typically published.registryType: mcpb
- MCPB () — prebuilt binary distributed via GitHub or GitLab Releases. End users need no toolchain. Right choice if the priority is "no Rust toolchain required."mcp-name: $SERVER_NAMEBoth 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
string in the package README (which is rendered to HTML and served by crates.io's static CDN). The$SERVER_NAMEportion MUST match the server name fromserver.json. For example:
Widget MCP Server
A Rust-native MCP server for widget operations.
- MCP Registry name: mcp-name: io.github.username/widget-mcp For Docker/OCI images, the MCP Registry currently supports: - Docker Hub ( Docker/OCI images use The MCP Registry verifies ownership of Docker/OCI images by checking for an For MCPB packages, the MCP Registry currently supports MCPB artifacts hosted via GitHub or GitLab releases. MCPB packages use The MCPB package URL ( The package metadata in --- --- <Note> This tutorial will show you how to publish an MCP server written in TypeScript to the MCP Registry using the official <Note> - Node.js — This tutorial assumes the MCP server is written in TypeScript. If you do not have an MCP server written in TypeScript, you can copy the The MCP Registry verifies that a server's underlying package matches its metadata. For npm packages, this requires adding an Because we will be using GitHub-based authentication, 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: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.ioDocker/OCI Images
)ghcr.io
- GitHub Container Registry ()quay.io
- Quay.io ()*.pkg.dev
- Google Artifact Registry (any domain)*.azurecr.io
- Azure Container Registry ()mcr.microsoft.com
- Microsoft Container Registry ()"registryType": "oci" in server.json. For example:
{
"$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"
}
}
]
}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.io.modelcontextprotocol.server.nameOwnership Verification
annotation. The value of the io.modelcontextprotocol.server.name annotation MUST match the server name from server.json. For example:
LABEL io.modelcontextprotocol.server.name="io.github.username/kubernetes-manager-mcp""registryType": "mcpb"MCPB Packages
in server.json. For example:
{
"$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"
}
}
]
}identifierVerification
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.server.json MUST include a fileSha256 property with a SHA-256 hash of the MCPB artifact, which can be computed using the openssl command:
openssl dgst -sha256 image-processor.mcpbThe 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.mcp-publisherModelcontextprotocol Io/Quickstart
title: "Quickstart: Publish an MCP Server to the MCP Registry"
sidebarTitle: "Quickstart: Publish a Server"
---
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> CLI tool.weather-server-typescript
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
- 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. server from the modelcontextprotocol/quickstart-resources repository to follow along with this tutorial:
git clone --depth 1 [email protected]:modelcontextprotocol/quickstart-resources.git
cp -r quickstart-resources/weather-server-typescript .
rm -rf quickstart-resources
cd weather-server-typescriptAnd edit package.json to reflect your information:
{
- "name": "mcp-quickstart-ts",
- "version": "1.0.0",
+ "name": "@my-username/mcp-weather-server",
+ "version": "1.0.1",
"main": "index.js",
"license": "ISC",
- "description": "",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/my-username/mcp-weather-server.git"
+ },
+ "description": "An MCP server for weather information.",
"devDependencies": {mcpNameStep 1: Add verification information to the package
property to package.json:
{
"name": "@my-username/mcp-weather-server",
"version": "1.0.1",
+ "mcpName": "io.github.my-username/weather",
"main": "index.js",The value of mcpName will be your server's name in the MCP Registry.mcpName must start with io.github.my-username/.Step 2: Publish the package
Navigate to project directory
cd weather-server-typescript
Install dependencies
npm install
Build the distribution files
npm run build
Then follow npm's publishing guide. In particular, you will probably need to run the following commands:If necessary, authenticate to npm
npm adduser
Publish the package
npm publish --access public
You can verify your package is published by visiting its npm URL, such as https://www.npmjs.com/package/@my-username/mcp-weather-server.mcp-publisherStep 3: Install
mcp-publisherInstall the
CLI tool using a pre-built binary or Homebrew:<CodeGroup>
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/
$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
brew install mcp-publisher
</CodeGroup>mcp-publisherVerify that
is correctly installed by running:
mcp-publisher --help
You should see output like: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.
server.jsonStep 4: Create
mcp-publisher initThe
command can generate aserver.jsontemplate file with some information derived from your project.mcp-publisher initIn your server project directory, run
:
mcp-publisher init
Open the generatedserver.jsonfile, and you should see contents like:
{
"$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"
}
]
}
]
}
Edit the contents as necessary:{
"$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"
- }
- ]
+ }
}
]
}
Thenameproperty inserver.jsonmust match themcpNameproperty inpackage.json.mcp-publisher loginStep 5: Authenticate with the MCP Registry
For this tutorial, we will authenticate with the MCP Registry using GitHub-based authentication.
Run the
command to initiate authentication:
mcp-publisher login github
You should see output like: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...
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g.,ABCD-1234in the above output). Once complete, go back to the terminal, and you should see output like:
Successfully authenticated!
✓ Successfully logged in
mcp-publisher publishStep 6: Publish to the MCP Registry
Finally, publish your server to the MCP Registry using the
command:
mcp-publisher publish
You should see output like:Publishing to https://registry.modelcontextprotocol.io...
✓ Successfully published
✓ Server io.github.my-username/weather version 1.0.1
You can verify that your server is published by searching for it using the MCP Registry API:curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"
You should see your server's metadata in the search results JSON:{"servers":[{ ... "name":"io.github.my-username/weather" ... }]}
mcpNameTroubleshooting
{/ 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 isinpackage.json; for PyPI and NuGet it is anmcp-name: <server-name>line (or HTML comment) in the package README; for other types see Package Types.|mcp-publisher login github
|"Invalid or expired Registry JWT token"|Re-authenticate by running.|io.github.your-username/
|"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.|https://registry.modelcontextprotocol.io{/ 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
. 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 versionlatestto get the latest version of the server.serverName<Warning>
URL path parameters such as
andversionmust be URL-encoded. For example,io.modelcontextprotocol/everythingmust be encoded asio.modelcontextprotocol%2Feverything.GET /v0.1/servers</Warning>
Aggregators will most likely scrape the
endpoint.GET /v0.1/serversPagination
The
endpoint supports cursor-based pagination.limitFor example, the first page can be fetched using a
query parameter:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?limit=100"
{
"servers": [
/ ... /
],
"metadata": {
"count": 100,
"nextCursor": "com.example/my-server:1.0.0",
},
}
Then subsequent pages can be fetched by passing thenextCursorvalue as thecursorquery parameter:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?limit=100&cursor=com.example/my-server:1.0.0"
GET /v0.1/serversFiltering Since
The
endpoint supports filtering servers that have been updated since a given timestamp.updated_sinceFor example, servers that have been updated since 2025-10-23 can be fetched using an
query parameter in RFC 3339 date-time format:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?updated_since=2025-10-23T00:00:00.000Z"
statusServer Status
Server metadata is generally immutable, except for the
field which may be updated to, e.g.,"deprecated"or"deleted". We recommend that aggregators keep their copy of each server'sstatusup to date."deleted"The
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._metaActing 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
field. For example, a subregistry could inject user ratings, download counts, and security scan results:
{
"$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
}
}
}
}
We recommend that custom metadata be put under a key that reflects the subregistry (e.g.,"com.example.subregistry/custom"in the above example).remotes---
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
property inserver.json:
{
"$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"
}
]
}
A remote server MUST be publicly accessible at its specified URL.typeTransport 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
property of theremotesentry to either"streamable-http"or"sse":
{
"$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"
}
]
}
{curly_braces}URL Template Variables
Remote servers can define URL template variables using
notation. This enables multi-tenant deployments where a single server definition can support multiple endpoints with configurable values:
{
"$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
}
}
}
]
}
When configuring this server, users provide theirtenant_idvalue, and the URL template gets resolved to the appropriate endpoint (e.g.,https://us-cell1.analytics.example.com/mcp).defaultVariables support additional properties like
,choices, andisSecret:
{
"$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"
}
}
}
]
}
headersHTTP Headers
MCP clients can be instructed to send specific HTTP headers by adding the
property to theremotesentry:
{
"$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
}
]
}
]
}
remotesSupporting Remote and Non-remote Installation
The
property can coexist with thepackagesproperty inserver.jsonin order to allow MCP host applications to choose the preferred method of installation.
{
"$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"
}
}
]
}
---server.jsonModelcontextprotocol 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
. For example:
{
"$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"
}
}
]
}
The version string MUST be unique for each publication of the server. Once published, the version string (and other metadata) cannot be changed.1.0.0Version 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 |
| -------------- | ------------------- | ------------------------------ |
|| 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:
{
"version": "1.2.3",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.2.3",
"transport": {
"type": "stdio"
}
}
]
}
If there are multiple underlying packages, use the server version to indicate the overall release version:{
"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"
}
}
]
}
Align Server Version with Remote API Version
For remote servers with an API version, the server version should align with the API version:
{
"version": "2.1.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.myservice.com/mcp/v2.1"
}
]
}
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:
{
"version": "1.2.3-1",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.2.3",
"transport": {
"type": "stdio"
}
}
]
}
<Warning>1.2.3-1According to semantic versioning, prerelease versions such as
are sorted before regular semantic versions such as1.2.3. Therefore, if you publish a prerelease version _after_ its corresponding regular version, the prerelease version will not be marked as "latest".mcp-publisher</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
CLI tool.See the publishing guide for a walkthrough of using the CLI to publish a server.
Installation
Install via Homebrew (macOS/Linux):
$ brew install mcp-publisher
--helpGlobal Options
All commands support:
-,-h- Show command help--registryis a flag onloginonly (default:https://registry.modelcontextprotocol.io). The--registry
other commands read the registry URL from the stored login token, so passingtopublishwould be interpreted as theserver.jsonpath.mcp-publisher initCommands
server.jsonGenerate a
template with automatic detection.Usage:
mcp-publisher init
Behavior:server.json
- Createsin current directorypackage.json
- Auto-detects package managers (,setup.py, etc.)TODO:
- Pre-fills fields where possible
- Writesplaceholders for fields it cannot detect — it is non-interactive and takes no flagsExample output:
{
"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"
}
]
}
mcp-publisher login <method>Authenticate with the registry.
Authentication Methods:
#### GitHub Interactive
mcp-publisher login github [--token=PAT] [--registry=URL]
- Opens browser for GitHub OAuth flowio.github.{username}/
- Grants access toandio.github.{org}/namespaces--token
-supplies a GitHub Personal Access Token instead of the interactive flow, which is howlogin github
publishing from GitHub Actions authenticates
without a browser. This flag is accepted byonly.#### GitHub OIDC (CI/CD)
mcp-publisher login github-oidc [--registry=URL]
- Uses GitHub Actions OIDC tokens automaticallyid-token: write
- Requirespermission in workflowaud
- No browser interaction neededThe CLI derives the OIDC
claim from--registry(scheme + host, e.g.https://registry.modelcontextprotocol.io) so tokens are bound to the specificMCP_REGISTRY_GITHUB_OIDC_AUDIENCE
deployment they were minted for. Self-hosters must seton the registry to the matching value;mcp-publisher
publishers running an olderwill fail withinvalid audience
and need to upgrade.Also see the guide to publishing from GitHub Actions.
#### DNS Verification
mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--algorithm=ed25519|ecdsap384] [--registry=URL]
- Verifies domain ownership via DNS TXT recordcom.example.*
- Grants access tonamespaces--algorithm
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
-defaults toed25519. For an ECDSA P-384 key you must pass--algorithm ecdsap384, otherwise the key is rejected withinvalid seed length: expected 32 bytes, got 48.--algorithm
- 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, sodoes not apply to them.Setup: (for Ed25519, recommended)
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'
Setup: (for ECDSA P-384)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
Setup: (for Google KMS signing)This requires the gcloud CLI.
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
Setup: (for Azure Key Vault signing)This requires the Azure CLI.
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
#### HTTP Verificationmcp-publisher login http --domain=example.com --private-key=HEX_KEY [--algorithm=ed25519|ecdsap384] [--registry=URL]
- Verifies domain ownership via HTTPS endpointcom.example.*
- Grants access tonamespaces--algorithm
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
-defaults toed25519. For an ECDSA P-384 key you must pass--algorithm ecdsap384.--algorithm
- 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, sodoes not apply to them.Setup: (for Ed25519, recommended)
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
Setup: (for ECDSA P-384)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
Cloud signing is also supported for HTTP authentication, similar to the DNS examples above. Just swap out thednspositional argument forhttp.#### Anonymous (Testing)
mcp-publisher login none [--registry=URL]
- No authentication - for local testing onlymcp-publisher validate
- Only works with local registry instancesserver.jsonValidate a
file without publishing.Usage:
mcp-publisher validate [file]
Arguments:file
-- Path to server.json file (default:./server.json)packages[0].transport.urlBehavior:
- 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.,)
- Shows validation issue type (json, schema, semantic, linter)
- Displays severity level (error, warning, info)
- Provides schema references showing which validation rule triggered each errorExample output:
$ 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
mcp-publisher publishPublish server to the registry.
For detailed guidance on the publishing process, see the publishing guide.
Usage:
mcp-publisher publish [PATH]
Options:PATH
-- Path to server.json (default:./server.json)server.jsonProcess:
1. Validatesagainst schemaserver.json
2. Publishes theto 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 registryExample:
Basic publish
mcp-publisher publish
Custom file location
mcp-publisher publish ./config/server.json
mcp-publisher statusUpdate the lifecycle status of a published server.
Usage:
mcp-publisher status --status <active|deprecated|deleted> [flags] <server-name> [version]
Flags:--status
-(required) - New status:active,deprecated, ordeleted--message
-- Optional message explaining the status change (not allowed when status isactive)--all-versions
-- Apply status change to all versions of the server--yes
-,-y- Skip confirmation prompt (only applies when using--all-versions)server-nameArguments:
-- Full server name (e.g.,io.github.user/my-server)version
-- Server version to update (required unless--all-versionsis set)activeStatus Values:
-- Server is active and visible in default listingsdeprecated
-- Server is deprecated but still visible with a warning messagedeleted
-- Server is hidden from default listingsExamples:
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
Requirements:publish
- Must be logged in withoreditpermission for the server namespacemcp-publisher logoutClear stored authentication credentials.
Usage:
mcp-publisher logout
Behavior:~/.config/mcp-publisher/token.json
- Removes~/.mcp_publisher_token
- Also cleans up legacy token files (,.mcpregistry_*)~/.config/mcp-publisher/token.json
- Does not revoke tokens on server sideConfiguration
Token Storage
Authentication tokens are stored inas JSON:
{
"token": "jwt-token-here",
"method": "github",
"registry": "https://registry.modelcontextprotocol.io"
}
~/.mcp_publisher_tokenNote: Tokens were previously stored in. If you are upgrading, runmcp-publisher logoutfollowed bymcp-publisher loginto migrate to the new location.PATCH /v0/servers/{serverName}/versions/{version}/status---
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:
-
- Update status of a specific server versionPATCH /v0/servers/{serverName}/status
-- Update status of all versions of a server in a single transactionactiveStatus values:
-- Server is active and visible in default listingsdeprecated
-- Server is deprecated but still visible with a warning messagedeleted
-- Server is hidden from default listingspublishAuthentication: Requires
oreditpermission for the server namespace._meta["io.modelcontextprotocol.registry/official"]#### New Response Fields
New fields added to
(RegistryExtensions):statusChangedAt-
- Timestamp when the server status was last changedstatusMessage
-- Optional message explaining status change (e.g., deprecation reason, migration guidance)include_deleted#### Server Filtering Enhancements
New
query parameter added to multiple endpoints:GET /v0/servers-
- Include deleted servers in list results (default:false, automaticallytruewhenupdated_sinceis 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)/v0.1/2025-10-17
Added
#### API Versioning - v0.1 Introduction
Introduced
as a stable API version while/v0/continues as the development version./v0/New version paths:
- Allendpoints are now also available at/v0.1//v0/
- Both versions currently share identical behavior
-will continue to evolve with additive changes (new optional fields, new endpoints)/v0.1/
-will remain stable with only additive, backward-compatible changes/v0.1/
- Both versions will be maintained until a future v1.0 releaseMigration guidance:
- Production applications should consider usingfor stability/v0/
- Development and testing can continue usingfor latest features/v0/
- No immediate action required -remains fully supportedGET /v0/servers/{serverName}⚠️ BREAKING CHANGES
#### Endpoint Simplification
Removed redundant endpoint to simplify API surface and reduce implementation burden for subregistries.
Removed endpoints:
-- UseGET /v0/servers/{serverName}/versions/latestinsteadGET /v0/servers/{server_id}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/{serverName}GET /v0/servers/{server_id}/versions
-→GET /v0/servers/{serverName}/versionsGET /v0/servers/{serverName}/versions/{version}New endpoints:
-- Get specific server versionPUT /v0/servers/{serverName}/versions/{version}
-- Edit server version (admin only)ServerResponseResponse format changes:
- Introducedschema separating server data from registry metadatastatus
- Movedfield from server data to_meta.io.modelcontextprotocol.registry/officialio.modelcontextprotocol.registry/official
- Removedmetadata fromServerDetailschema2025-09-16Changed
- OpenAPI spec version:→2025-09-29GET /v0/servers/{id}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/{server_id}GET /v0/servers/{server_id}/versionsNew endpoints:
-- List all versions of a serverGET /v0/servers/{server_id}?version=1.0.0
-- Get specific version_meta..idChanged response metadata:
-→_meta..serverId_meta.*.versionId
- Added:#### Migration Examples
Old Structure:
{
"_meta": {
"io.modelcontextprotocol.registry/official": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"published_at": "2024-01-01T00:00:00Z",
"is_latest": true
}
}
}
New Structure:{
"_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
}
}
}
#### Migration Checklist for API Consumers/v0/servers/{id}- [ ] Update API endpoint URLs from
to/v0/servers/{server_id}id
- [ ] Update code reading registry metadata fromtoserverId/versionId/v0/servers/{server_id}/versions
- [ ] Add support for newendpoint if needed2025-07-09
- [ ] Update JSON parsing to expect camelCase field names
- [ ] Test with new API responsesChanged
- OpenAPI spec version:→2025-09-16/v0.1/x/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 churnURL Structure
Extensions live under the
prefix:
/v0.1/x/<namespace>/<extension>[/<path>]
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
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:
GET /v0.1/x/com.example/stats
{
"totalServers": 1234,
"totalVersions": 5678,
"recentPublishes": 42
}
/v0.1/xFuture Considerations
- Extension discovery: A potential
endpoint to list available extensionsGET /v0.1/servers
- 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
-- List all servers with paginationGET /v0.1/servers/{serverName}/versions
-- List all versions of a serverGET /v0.1/servers/{serverName}/versions/{version}
-- Get specific version of server. Use the special versionlatestto 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)application/jsonServer 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 usecursorPagination
List endpoints use cursor-based pagination for efficient, stable results.#### Usage
1. Initial request: Omit theparameternextCursor
2. Subsequent requests: Use thevalue from the previous responsenextCursor
3. End of results: Whenis null or empty, there are no more resultsImportant: Always treat cursors as opaque strings. Never manually construct or modify cursor values.
Basic Example: List Servers
curl https://registry.example.com/v0.1/servers?limit=10
{
"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"
}
}
For complete endpoint documentation, view the OpenAPI specification in a schema viewer.registry.modelcontextprotocol.io---
Reference/Api/Official Registry Api
Official MCP Registry API
This document describes the API for the official MCP Registry hosted at
.https://registry.modelcontextprotocol.ioThis 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://staging.registry.modelcontextprotocol.io
- Staging:io.github.*Interactive Documentation
- Live API Docs - Stoplight elements with try-it-now functionality
- OpenAPI Spec - Complete machine-readable specificationExtensions
The official registry implements the Generic Registry API with the following specific configurations and extensions:
Authentication
Publishing requires namespace-based authentication:
- GitHub OAuth - For
namespacescom.example.*
- GitHub OIDC - For publishing from GitHub Actions
- DNS verification - For domain-based namespaces ()com.example.*
- HTTP verification - For domain-based namespaces ()GET /v0.1/serversSee 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
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)version
- This is intentionally simple. For more advanced searching and filtering, use a subregistry.
-- Filter by version (currently supportslatestfor latest versions only)include_deleted
-- Include deleted servers in results (default:false, but automaticallytruewhenupdated_sinceis provided for incremental sync)GET /v0.1/servers?search=filesystem&updated_since=2025-08-01T00:00:00Z&version=latestThese 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/{serverName}/versions/{version}Server Detail
The
endpoint returns detailed information about a specific server version.serverNamePath parameters:
-- URL-encoded server name (e.g.,io.github.user%2Fmy-server)version
-- Server version orlatestfor the most recent versioninclude_deletedQuery parameters:
-- Include deleted servers in results (default:false)GET /v0.1/servers/{serverName}/versionsServer Version History
The
endpoint returns all versions of a server.serverNamePath parameters:
-- URL-encoded server name (e.g.,io.github.user%2Fmy-server)include_deletedQuery parameters:
-- Include deleted servers in results (default:false)/v0.1/auth/dnsAdditional endpoints
#### Auth endpoints
- POST- Exchange signed DNS challenge for auth token/v0.1/auth/http
- POST- Exchange signed HTTP challenge for auth token/v0.1/auth/github-at
- POST- Exchange GitHub access token for auth token/v0.1/auth/github-oidc
- POST- Exchange GitHub OIDC token for auth token/v0.1/auth/oidc
- POST- Exchange Google OIDC token for auth token (for admins)PATCH /v0.1/servers/{serverName}/versions/{version}/status#### Status endpoints
##### Update Single Version Status
- Update status of a specific server version.serverNamePath parameters:
-- URL-encoded server name (e.g.,io.github.user%2Fmy-server)version
-- Server version to updatestatusRequest body:
-(required) - New status:active,deprecated, ordeletedstatusMessage
-(optional) - Message explaining the status change (max 500 characters, not allowed when status isactive)PATCH /v0.1/servers/{serverName}/status##### Update All Versions Status
- Update status of all versions of a server in a single transaction.serverNamePath parameters:
-- URL-encoded server name (e.g.,io.github.user%2Fmy-server)statusRequest body:
-(required) - New status:active,deprecated, ordeletedstatusMessage
-(optional) - Message explaining the status change (max 500 characters, not allowed when status isactive)activeStatus values:
-- Server is active and visible in default listingsdeprecated
-- Server is deprecated but still visible with a warning messagedeleted
-- Server is hidden from default listings (useinclude_deleted=trueto show)publishAuthentication: Requires
oreditpermission for the server namespace./v0.1/validate#### Validation endpoint
- POST- Validate aserver.jsonwithout publishing it. Used bymcp-publisher validate./v0.1/ping#### Service endpoints
- GET- Liveness check/v0.1/version
- GET- Registry version information/metrics#### Admin endpoints
- GET- Prometheus metrics endpoint/v0.1/health
- GET- Basic health check endpoint/v0.1/servers/{serverName}/versions/{version}
- PUT- Edit specific server version/v0.1/validateNote that
,/v0.1/ping,/v0.1/versionand/v0.1/healthare not described inmcp-registry:read
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 registriesRegistry-Specific Scopes
Registries MAY use these scopes:
-
- List and read server metadatamcp-registry:write
-- Publish, update, and delete serversmcp-registry:writeThese 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
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.server.schema.jsonOfficial 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
in this repository.urlChanged
#### Transport URL Pattern Now Accepts Template Variables
The
field inStreamableHttpTransportandSseTransportnow accepts URLs that start with a template variable (e.g.,{baseUrl}), in addition to the existinghttp://andhttps://prefixes.Example:
{
"remotes": [{
"type": "streamable-http",
"url": "{baseUrl}/mcp",
"variables": {
"baseUrl": {
"description": "Base URL for the MCP server",
"isRequired": true
}
}
}]
}
Migration: No changes required. Existing servers continue to work unchanged.## 2025-XX-XXNotes
When ready for release, changes in this section will be moved to a dated version section (e.g.,
) and the schema will be published to a versioned URL.{curly_braces}---
2025-12-11
Changed
#### URL Template Variables for Remote Servers (#570)
Remote servers can now define URL template variables using
notation. This enables multi-tenant deployments where a single server definition can support multiple endpoints with configurable values.Example:
{
"remotes": [{
"type": "streamable-http",
"url": "https://{tenant_id}.api.example.com/mcp",
"variables": {
"tenant_id": {
"description": "Your tenant identifier",
"isRequired": true
}
}
}]
}
Migration: No changes required. Existing servers continue to work unchanged.version---
2025-10-17
Changed
The
field is now optional for MCPB packages, providing flexibility for publishers.versionKey Changes:
- MCPB packages can now include an optional
field - Previously rejected by validation, MCPB packages can now optionally specify a version field for clarity and metadata purposes.version
- 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
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 typesExisting MCPB packages without the version field continue to work without any changes.
Example - MCPB Package with optional version:
{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"version": "1.0.0",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
Example - MCPB Package without version (still valid):{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
2025-10-11Schema Version
- Schema version:→2025-10-17Package2025-10-11
Changed
#### Package Format Enhancements (#634)
The
schema has been refactored to better support different package types with dedicated handling per registry type.versionKey Changes:
-
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.registryType- Enhanced documentation - Added detailed comments explaining which fields are relevant for each
:registryType
- NPM/PyPI/NuGet: Use,identifier(package name),version, optionalregistryBaseUrlregistryType
- OCI: Use,identifier(full image reference with tag)registryType
- MCPB: Use,identifier(download URL),fileSha256(required)identifier- Field clarifications:
-: Now clearly documented as package name for registries, full image reference for OCI, or download URL for MCPBfileSha256
-: Clarified as required for MCPB packages and optional for other typesregistryBaseUrl
-: Clarified as used by npm/pypi/nuget but not by oci/mcpbversionMigration:
Publishers using OCI or MCPB packages can now omit the
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 theversionfield as before.Example - OCI Package (version in identifier):
{
"packages": [{
"registryType": "oci",
"identifier": "ghcr.io/modelcontextprotocol/server-example:v1.2.3",
"transport": {
"type": "stdio"
}
}]
}
Example - MCPB Package (no version field):{
"packages": [{
"registryType": "mcpb",
"identifier": "https://github.com/example/releases/download/v1.0.0/package.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}]
}
2025-09-29Schema Version
- Schema version:→2025-10-11status2025-09-29
⚠️ BREAKING CHANGES
#### Schema Simplification
Removed registry-managed fields from publisher-controlled server.json schema.
Removed fields:
-field from Server object (now managed by registry in API responses)io.modelcontextprotocol.registry/official
-from_meta(read-only, added by registry)server.jsonMigration:
Publishers should remove these fields from theirfiles. The registry will manage server status and official metadata separately.2025-09-16Changed
- Schema version:→2025-09-29server.json2025-09-16
⚠️ BREAKING CHANGES
#### Field Names: snake_case → camelCase (#428)
All JSON field names standardized to camelCase. All existing
files must be updated.registry_typeChanged fields:
-→registryTyperegistry_base_url
-→registryBaseUrlfile_sha256
-→fileSha256runtime_hint
-→runtimeHintruntime_arguments
-→runtimeArgumentspackage_arguments
-→packageArgumentsenvironment_variables
-→environmentVariablesis_required
-→isRequiredis_secret
-→isSecretvalue_hint
-→valueHintis_repeated
-→isRepeatedwebsite_url
-→websiteUrl#### Migration Examples
Package Configuration:
// 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": [...]
}]
}
Arguments Configuration:// 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"
}
]
}
Environment Variables:// 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
}
]
}
#### Migration Checklist for Publishersserver.json- [ ] Update your
files to use camelCase field names2025-07-09
- [ ] 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-09-16server.json2025-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
schema.docs/reference/api/openapi.yamlMaking Changes
1. Modify the OpenAPI spec: Edit
with your schema changes. TheServerDetailcomponent defines the server.json structure.make generate-schema2. Regenerate the schema: Run
to updateserver.schema.jsonfrom the OpenAPI spec.CHANGELOG.md3. Update the changelog: Add your changes to the "Draft (Unreleased)" section in
.## 2025-XX-XX4. 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.,
).$id2. Update the schema URL: Change the
in the schema and the example URL inopenapi.yamlfromdraftto the release date (e.g.,2025-XX-XX).https://raw.githubusercontent.com/modelcontextprotocol/registry/main/docs/reference/server-json/draft/server.schema.json3. 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:
- For in-progress changes, may change without notice.https://static.modelcontextprotocol.io/schemas/YYYY-MM-DD/server.schema.json
- Released schemas:- Stable, versioned by release date.server.json---
Reference/Server Json/Generic Server Json
server.json Format Specification
A
file is a standardized way to describe MCP servers for registry publishing, client discovery, and package management._metaAlso 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
_metaThe optional
field allows publishers to include custom metadata alongside their server definitions using reverse DNS namespacing.
{
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
// Your custom metadata here
}
}
}
When publishing to the official registry, custom metadata must be placed under the keyio.modelcontextprotocol.registry/publisher-provided. See the official registry requirements for detailed restrictions and examples.Examples
Basic Server with NPM Package
{
"$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"
}
}
}
}
subfolderServer in a Monorepo with Subfolder
For MCP servers located within a subdirectory of a larger repository (monorepo structure), use the
field to specify the relative path:
{
"$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"
}
}
}
}
mcp startConstant (fixed) arguments needed to start the MCP server
Suppose your MCP server application requires a
CLI arguments to start in MCP server mode. Express these as positional arguments like this:
{
"$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"
}
}
}
}
This will essentially instruct the MCP client to executednx [email protected] -- mcp startinstead of the defaultdnx [email protected](when nopackageArgumentsare provided).Filesystem Server with Multiple Packages
/ Detailed source-code truncated for AI context efficiency. /
The sameregistryType/identifierpattern works for other supported OCI hosts. For example, an image on Quay.io:
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.example/quay-sample-mcp",
"description": "Example MCP server distributed as an OCI image on Quay.io",
"version": "1.0.0",
"packages": [
{
"registryType": "oci",
"identifier": "quay.io/myorg/my-mcp-server:1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
Remote Server Example
{
"$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"
}
}
}
}
Python Package Example
{
"$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"
}
}
}
}
cargo install <crate>Cargo (Rust) Package Example
places the binary on PATH (via~/.cargo/bin); MCP clients invoke it directly by name. There is no single-shot equivalent ofnpx(npm),uvx(PyPI), ordnx(NuGet, .NET 10 SDK) for cargo — install once, run by name.
{
"$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"
}
}
]
}
dnxNuGet (.NET) Package Example
The
tool ships with the .NET 10 SDK, starting with Preview 6.
{
"$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"
}
}
}
}
Complex Docker Server with Multiple Arguments
/ Detailed source-code truncated for AI context efficiency. /
Server with Remote and Package Options
/ Detailed source-code truncated for AI context efficiency. /
MCP Bundle (MCPB) Package Example
{
"$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"
}
}
}
}
This example shows an MCPB (MCP Bundle) package that:packages
- 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 MCPBEmbedded 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
shape by pointing at the host CLI package and supplying thepackageArgumentsandruntimeHintif needed to start the MCP server.
{
"$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"]
}
]
}
]
}
websiteUrlServer with Custom Installation Path
For MCP servers that follow a custom installation path or are embedded in applications without standalone packages, use the
field to direct users to setup documentation:
{
"$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"
}
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:
{
"$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
}
}
}
]
}
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-cell1orhttps://anonymous.modelcontextprotocol.io/mcp/emea-cell1).The same URL templating works with SSE transport:
{
"$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
}
}
}
]
}
Local Server with URL Templating
This example demonstrates URL templating for local/package servers, where variables reference parent Package arguments or environment variables:
{
"$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`.---