Skip to main content

MCP Registry and Distributing Servers

This guide explains how to publish and distribute MCP servers, and how to manage which servers an organization allows. The publishing side registers metadata in the official MCP Registry so that packages and remote URLs can be discovered. The consuming side (an organization) does not use the official Registry directly as its entry point, and instead keeps its own mechanism that provides only the servers it has approved. For implementing a server, see Building MCP Servers - SDKs with Go and C#; for operations, see Deploying and Operating MCP Servers; for the checks to make when adopting a local server, see Operating Local MCP Servers Safely.

Status at the time of writing

The official MCP Registry is in preview at the time of writing. Breaking changes or data resets may occur before general availability. Before you publish, check the official documentation for the latest commands, server.json schema, and supported package types.

1. What This Document Covers​

  • The official MCP Registry
  • Quickstart for publishing
  • Supported package types
  • Publishing remote servers
  • Authentication when publishing
  • Versioning
  • Automated publishing with GitHub Actions
  • Server Cards
  • Moderation policy
  • Approval and governance inside an organization

The official MCP Registry​

The MCP Registry is the official central repository of metadata for publicly accessible MCP servers. It is backed by contributors such as Anthropic, GitHub, PulseMCP, and Microsoft. It provides the following.

  • A single place where server authors publish metadata
  • Namespace management, including DNS verification
  • A REST API for MCP clients and aggregators to discover servers
  • Standardized installation and configuration information

The metadata is in a standard format called server.json, and carries the server's unique name (for example io.github.user/server-name), where to find it (an npm package name, a remote URL), execution instructions (command-line arguments, environment variables), and discovery data such as a description.

The Registry does not host artifacts. The package itself lives in a package registry such as npm, PyPI, or Docker Hub, and the MCP Registry holds metadata that points to it. For example, when weather-mcp is on npm, the Registry's metadata maps "weather v1.2.0" to npm:weather-mcp.

Here is how each party relates to it.

  • Server developers: can publish open-source or closed-source servers. The conditions are that the installation method is publicly available (an npm package or Docker image on a public registry) or that the server itself is publicly accessible (a remote server not restricted to a private network). Private servers (those on an internal network or a private package registry) are not supported. For such servers, hosting your own private registry is recommended
  • Aggregators (downstream consumers): the Registry is mainly expected to be consumed by downstream aggregators such as MCP server marketplaces. The Registry's metadata is deliberately unopinionated, and aggregators add value such as curation and community ratings. Aggregators are expected to pull metadata from the Registry API regularly (for example, about once an hour)
  • Other registries (subregistries): in addition to its public REST API, the official Registry defines an OpenAPI specification that other registries can implement. Aggregators and private registries that conform to it can benefit from existing host application support. The official Registry's codebase, however, is not designed for self-hosting, and no support is provided for that
  • MCP host applications: they are not expected to use the official Registry directly. They use the REST API of a registry that conforms to the OpenAPI specification, such as a downstream marketplace

On trust and security, the Registry is responsible for namespace authentication. Server names are in reverse-DNS form (io.github.username/server, com.example/server) and are tied to verified GitHub accounts or domains, so only the legitimate owner can publish in that namespace. Security scanning of a server's code, on the other hand, is not the Registry's responsibility; it is left to the package registries (npm, PyPI, Docker Hub) and to downstream aggregators. Being listed in the Registry is not a guarantee of safety or quality.

Quickstart for publishing​

Following the official quickstart, here is the flow for publishing a TypeScript npm package. The Registry publishing part is the same for the other package types. You need an npm account and a GitHub account beforehand (this example uses GitHub authentication).

Step 1: Add verification information to the package. The Registry verifies that a server's package matches its metadata. For npm, add mcpName to package.json. This value becomes the name in the Registry, and if you use GitHub authentication it must start with io.github.<username>/.

{
"name": "@my-username/mcp-weather-server",
"version": "1.0.1",
"mcpName": "io.github.my-username/weather"
}

Step 2: Publish the package. The Registry holds only metadata, so publish to npm first (npm publish --access public).

Step 3: Install mcp-publisher. This is the official CLI. Use the prebuilt binary or Homebrew (brew install mcp-publisher) on macOS and Linux, and the prebuilt binary on Windows. Verify with mcp-publisher --help.

Step 4: Create server.json. mcp-publisher init generates a template from the project's information. name must match mcpName in package.json.

Step 5: Authenticate. mcp-publisher login github starts the device authorization flow (you approve on the GitHub side with the URL and code shown).

Step 6: Publish. Run mcp-publisher publish. You can confirm it was published by searching the Registry API.

mcp-publisher init
mcp-publisher login github
mcp-publisher publish

curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"

The typical errors are as follows.

ErrorAction
"Registry validation failed for package"Check that the package has the verification information (for npm, mcpName in package.json)
"Invalid or expired Registry JWT token"Re-authenticate with mcp-publisher login github
"You do not have permission to publish this server"Your authentication method does not match the server name's namespace. With GitHub authentication, the name must start with io.github.<your-username>/

Supported package types​

Each package type has its own registryType in server.json and its own way to verify ownership. In every case, the server name in server.json must match what the package states.

TyperegistryTypeSupported registryOwnership verification
npmnpmThe npm public registry onlymcpName in package.json
PyPIpypiThe official PyPI onlyA string mcp-name: <server name> in the README (a comment is fine)
NuGetnugetThe official NuGet onlyA string mcp-name: <server name> in the README (a comment is fine)
Cargo (Rust)cargocrates.io onlyA string mcp-name: <server name> in the README. crates.io strips HTML comments, so write it as visible text
Docker / OCIociDocker Hub, ghcr.io, Google Artifact Registry, Azure Container Registry, Microsoft Container RegistryThe io.modelcontextprotocol.server.name label (annotation) on the image
MCPBmcpbArtifacts hosted through GitHub or GitLab releasesThe URL contains "mcp". fileSha256 is required

The identifier of an OCI package has the form registry/namespace/repository:tag, and a digest can be given instead of a tag. fileSha256 for MCPB is the SHA-256 of the artifact, computed with openssl dgst -sha256 <file>. The Registry does not validate this hash, but MCP clients validate it before installation.

Cargo also differs in its runtime model. cargo install places the binary on PATH, and the client launches it directly by name. There is no per-invocation runner like npx or uvx. Rust authors have two choices: Cargo if you distribute source, or MCPB, which distributes a prebuilt binary, if you do not want to require users to have a Rust toolchain.

Publishing remote servers​

A remote MCP server is published with remotes in server.json. A remote server must be publicly accessible at the URL you specify.

{
"$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"
}
]
}

Specify the transport by setting type to "streamable-http" or "sse". SSE is deprecated, so publish it only to support existing clients. You can also offer both at the same time, at different URLs.

  • URL template variables: you can define variables in curly braces such as {tenant_id}. For multi-tenant deployments, one definition can describe many endpoints. A variable can carry description, isRequired, default, choices, and isSecret
  • HTTP headers: headers instructs the client to send particular headers (for example X-API-Key), with isRequired and isSecret
  • Remote and packages together: remotes and packages can coexist in the same server.json, so a host application can choose its preferred installation method

Note that remotes in server.json is an instruction about where to connect and which headers to send. The authorization method itself (OAuth metadata, scopes) is conveyed at runtime through the server's WWW-Authenticate and its protected resource metadata (MCP Authorization - OAuth 2.1 and Metadata Discovery). Mark headers that need a secret with isSecret: true, but never write the value into server.json.

Authentication when publishing​

Publishing to the official Registry requires authentication. The authentication method you choose determines the namespace of the server name.

MethodServer name formatExample
GitHubio.github.<username or orgname>/*io.github.alice/weather-server
Domain (DNS or HTTP)Reverse-DNS form com.example.*/*com.example/acme-analytics

GitHub authentication: mcp-publisher login github uses the OAuth device flow.

DNS authentication: proves domain ownership with a DNS TXT record. You create a public and private key pair (Ed25519 or ECDSA P-384), add a TXT record built from the public key (v=MCPv1; k=ed25519; p=<public key>) to the domain, and after it propagates run mcp-publisher login dns --domain example.com --private-key ... with the private key. If you do not want the key on your own machine, you can also sign with a key in Google Cloud KMS or Azure Key Vault.

HTTP authentication: with the same key scheme, you place a proof string (v=MCPv1; k=ed25519; p=<public key>) in the file /.well-known/mcp-registry-auth on the domain. After hosting the file, run mcp-publisher login http --domain ... --private-key ....

For an organization's servers, publishing under the organization's domain namespace (DNS or HTTP authentication) or the organization's GitHub namespace, rather than under an individual's GitHub account, is more resilient to changes of owner and departures. Keep the private key out of the repository, in a secret management mechanism or KMS.

Versioning​

version in server.json is required, must be unique for each publication, and cannot be changed after publishing (as with the other metadata).

  • Semantic versioning is recommended as the format, but any string can be used. The Registry tries to parse it as a semantic version for sorting and marks it "latest" when appropriate. If you later publish a format that cannot be parsed, it is always marked latest
  • Strings that look like version ranges (^1.2.3, ~1.2.3, >=1.2.3, 1.x, 1.2.*, and so on) are prohibited, to prevent mistakes
  • For local servers, align the server.json version with the package version. For remote servers that have an API version, align with that
  • To republish only to update metadata without changing the package or URL, use a semantic prerelease version such as 1.2.3-1. A prerelease sorts before the corresponding regular version, however, so a prerelease published after the regular version is not marked "latest"

If you align version in server.json and packages[].version by hand, they drift. In release automation, derive the value from the tag and apply it to both.

Automated publishing with GitHub Actions​

Triggered by a release tag (v*), a single workflow can publish the package and publish to the Registry. You can choose GitHub OIDC (recommended), a GitHub personal access token (PAT), or DNS for authentication. With OIDC, no dedicated secret is needed.

name: Publish to MCP Registry

on:
push:
tags: ["v*"]

jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC authentication
contents: read
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: "lts/*"
- run: npm ci
- run: npm run build --if-present
- name: Publish package to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Install mcp-publisher
run: |
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
- name: Authenticate to MCP Registry
run: ./mcp-publisher login github-oidc
- name: Publish server to MCP Registry
run: ./mcp-publisher publish

If you use a PAT, put a token with the read:org and read:user scopes in MCP_GITHUB_TOKEN and use mcp-publisher login github --token .... For DNS, put the private key in the MCP_PRIVATE_KEY secret. The official example lists, as an optional step, rewriting version in server.json from the tag (overwriting with jq). Pushing a tag with git push origin v1.0.0 runs tests, build, the npm publish, and the Registry publish.

As a caution, fetching mcp-publisher as the latest binary leaves room, from a supply chain perspective, to consider pinning the version and hash.

Server Cards​

Server Cards are metadata documents for discovering a remote MCP server before connecting (SEP-2127, Extensions Track, Final). The runtime server/discover can be used only once you know where to connect. A Server Card serves the stage before that, so that a client can learn the available transports, protocol versions, and connection guidance.

  • Uses: an IDE extension configures itself automatically when pointed at a domain; a Registry or crawler scans domains and indexes servers; information about a server can be shown without connecting to each one
  • Content: server identity (name, version, description, and optionally title, icons, repository, websiteUrl), remote endpoints (URLs, headers, variable templates, supported protocol versions), and namespaced _meta. It carries neither definitions of tools, resources, or prompts, nor capabilities. These are dynamic and vary by user and configuration, so a static document cannot represent them accurately
  • Location: a card can be hosted at any unreserved URI. The recommended location is <Streamable HTTP URL>/server-card. For domain-level discovery, an AI Catalog placed at /.well-known/ai-catalog.json links to cards or embeds them
  • Relationship to server/discover: keep information that overlaps (identity, endpoint, protocol versions) aligned. When they disagree, a client prefers the values from the runtime server/discover
  • Relationship to the Registry's server.json: a Server Card covers remote connectivity only. Locally installable packages and their runtime configuration are the domain of server.json. Concepts that correspond (identity, documentation, remote endpoints) are expressed in compatible forms

A Server Card is a public, read-only document. It must not include credentials, internal network structure, or data specific to a user or session (MUST NOT). Serve it over HTTPS and rate limit public endpoints. A card's contents are advisory and clients verify them against runtime metadata, so even if one is tampered with, the impact is mostly limited to how discoverable the server is.

The body of the specification (the schema, discovery details, CORS and caching requirements) is maintained not in the SEP but in the extension repository (modelcontextprotocol/experimental-ext-server-card), and may change. Before implementing, check that repository's latest schema and docs/discovery.md.

Moderation policy​

Moderation in the official Registry is quite permissive. In summary: "we remove only illegal content, malware, spam, and completely non-functioning servers."

  • Removed: illegal content (obscene content, copyright violations, hacking tools), malware regardless of intent, spam (the same server submitted repeatedly under different names, a server that returns only a fixed response with marketing copy, a server whose description is stuffed with marketing copy and whose implementation is unrelated), and non-functioning servers
  • Not removed: low-quality or buggy servers, servers with security vulnerabilities, servers that do the same thing as others, and servers that contain adult content
  • How removal works: the server's status is set to "deleted". The metadata remains available through the API, and aggregators remove it from their own indexes. In extreme cases, such as unlawful metadata, it can also be overwritten or erased
  • Appeals: open an issue in the GitHub repository with the server name and why you believe it does not meet the criteria for removal

This policy applies only to the official Registry, and subregistries may have their own policies. In addition, consumers must assume minimal-to-no moderation. The official Registry has limited capacity for active moderation and relies on upstream package registries and downstream subregistries for deeper review. Vulnerable servers are not removed either, so do not treat being listed in the Registry as grounds for using a server.

Approval and governance inside an organization​

When an organization uses MCP servers, making the official Registry the employees' entry point as is would be dangerous. Moderation is minimal, and vulnerable servers and look-alike servers are listed. The official Registry also does not support private servers. Make the organization's entry point its own mechanism that provides only approved servers.

  • A private registry: if the organization runs a registry that conforms to the official Registry's OpenAPI specification, it can use existing host applications that support that specification. The official Registry's codebase is not designed for self-hosting, so pick a conformant implementation or build your own. Register only the server.json of approved servers
  • An allowlist: make only the permitted servers (name, version, origin) usable in clients and gateways. Pin versions, and put updates through review. Block connections to servers outside the allowlist with gateway or network controls. For the configuration of a gateway that aggregates many servers in the organization (such as MCP Server Portals), see MCP Authorization Extensions and Enterprise Adoption
  • Pre-adoption review: check from these angles. Is the publisher and namespace a verified organization's? Can you read the source, and is it maintained? Are the permissions it requests (environment variables, files, network, OAuth scopes) minimal for the purpose? Does it need to run locally, or would a remote one do (Operating Local MCP Servers Safely)? For a remote server, where is the data sent and how is it stored?
  • Tool poisoning and supply chain checks: do the tool descriptions hide instructions for the model (read the list with Inspector, without going through a model)? To detect a rug pull, which rewrites definitions after approval, record the tool definitions and require re-approval when they change (AI Agent-Specific Threats - Prompt Injection and Tool Poisoning). Pin package versions, and for containers pin by digest. For MCPB, check fileSha256. If signatures, attestations, or an SBOM exist, verify them and scan dependencies for vulnerabilities
  • Continuous inventory: safety at the time of approval does not last. Monitor usage, version updates, changes of publisher, and disclosed vulnerabilities, and re-review. Remove servers that are no longer used from the allowlist
  • Publishing your own servers: whether to publish a server you built yourselves externally is also a matter of governance. A private internal server cannot be listed in the official Registry (it is out of scope). Register servers you do publish under the organization's domain namespace, and limit the private key and publishing authority to a few people

Review lowers the probability of a bad trust decision, and isolation and minimal privilege determine the cost of a bad decision. Do not depend on only one; combine them.

2. Distribution Methods​

MethodTargetNotes
Packages (npm / PyPI / NuGet / Docker)Local stdio serversPinning versions and checking signatures recommended
Remote URLStreamable HTTP serversState the authorization method in metadata
Official RegistryDiscoverability of public serversProof of namespace ownership required
Server CardsSelf-description of HTTP serversPlaced at <URL>/server-card. Domain-level discovery uses an AI Catalog (/.well-known/ai-catalog.json)
Internal registryOnly servers the organization approvedAllowlist and review process

3. Common Pitfalls for Developers​

mcpName and name in server.json do not match​

mcpName in npm's package.json and name in server.json must be the same string. For PyPI, NuGet, and Cargo it is mcp-name: in the README, and for OCI it is the label. What tends to be a nuisance is that you may have to republish the package first, before publishing.

Writing mcp-name in an HTML comment in a Cargo README​

On PyPI and NuGet you can hide it in an HTML comment, but crates.io strips HTML comments, so verification fails. For Cargo, write it as visible text.

Trying to publish in another namespace with GitHub authentication​

With GitHub authentication you can publish only names that start with io.github.<you>/. To use a name under your own domain, you need DNS or HTTP authentication.

Fixing only metadata and republishing at the same version​

A version must be unique for each publication and cannot be changed after publishing. For a metadata-only update, use a prerelease such as 1.2.3-1. A prerelease published after the regular version does not become "latest", though.

Assuming a listing in the official Registry means it is safe​

The Registry authenticates namespace ownership, but does not guarantee the safety of code. Vulnerable servers are not removed either. An organization keeps its own review and allowlist.

Trying to list an internal server in the official Registry​

Servers on private networks or private package registries are not supported. Set up a private registry.

Putting a tool list or credentials in a Server Card​

A Server Card is a public, read-only document and carries no definitions of tools, resources, or capabilities. Do not include credentials or user-specific data. Get the tool list at runtime by calling tools/list under the user's authorization.

Thinking a Server Card lives at a fixed .well-known path​

In the SEP's text, the recommended location for a card is <Streamable HTTP URL>/server-card, and the fixed .well-known path belongs to the AI Catalog (/.well-known/ai-catalog.json). Before implementing, check the latest specification in the extension repository.

4. Code Examples​

server.json for an npm package (stdio)​

{
"$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.1",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/mcp-weather-server",
"version": "1.0.1",
"transport": { "type": "stdio" }
}
]
}

The corresponding package.json needs "mcpName": "io.github.my-username/weather".

A remote server, with template variables and headers​

{
"$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
}
},
"headers": [
{
"name": "X-API-Key",
"description": "API key for authentication",
"isRequired": true,
"isSecret": true
}
]
}
]
}

An OCI image and MCPB​

{
"packages": [
{
"registryType": "oci",
"identifier": "docker.io/yourusername/kubernetes-manager-mcp:1.0.0",
"transport": { "type": "stdio" }
},
{
"registryType": "mcpb",
"identifier": "https://github.com/username/image-processor-mcp/releases/download/v1.0.0/image-processor.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": { "type": "stdio" }
}
]
}

Add LABEL io.modelcontextprotocol.server.name="<server name>" to the OCI image.

Creating the TXT record for DNS authentication (Ed25519)​

MY_DOMAIN="example.com"

# Create a key pair
openssl genpkey -algorithm Ed25519 -out key.pem

# Create the 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}\""

# Log in after the TXT record has propagated
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}"

Keep key.pem and PRIVATE_KEY out of repositories and shell history, in a secret management mechanism.

An organization's allowlist (a design example)​

The format of an allowlist depends on the client or gateway you use. This is an example to show what to manage.

{
"allowedServers": [
{
"name": "io.github.my-org/internal-tools",
"version": "1.4.2",
"source": "npm:@my-org/[email protected]",
"approvedBy": "security-review-2026-10",
"toolsDigest": "sha256:…",
"scopes": ["tools:read"]
}
]
}

toolsDigest is a hash of the tools/list result at approval time, used to detect that definitions changed and to trigger re-review.

5. Summary​

  • The official MCP Registry is a central repository of metadata (server.json) for public servers, and does not host artifacts. It is currently in preview, so check the latest procedure and schema before publishing. It does not support private servers, and host applications use downstream registries
  • To publish, add verification information to the package, publish the package, and then publish server.json with mcp-publisher. The server name shows namespace ownership
  • Package types are npm, PyPI, NuGet, Cargo, OCI, and MCPB. Each has its own way to verify ownership (mcpName, mcp-name: in the README, an OCI label)
  • A remote server is published with remotes, which can instruct template variables and headers. The authorization method itself is conveyed in runtime metadata
  • Authentication is by GitHub or by domain (DNS, HTTP), and it determines the namespace. Publish an organization's servers under the organization's domain or GitHub namespace
  • A version must be unique per publication and cannot be changed after publishing. Use semantic versioning, and a prerelease for metadata-only updates
  • GitHub Actions can publish the package and the Registry entry on a tag. With OIDC, no dedicated secret is needed
  • Server Cards are public documents for discovering a remote server before connecting, and carry no tools or capabilities. The body of the specification lives in the extension repository and may change
  • Moderation in the official Registry is permissive and does not remove vulnerable servers. Being listed is not grounds for safety. An organization combines a private registry, an allowlist, pre-adoption review, pinned tool definitions, and continuous inventory

References​