How to Publish Your MCP Server to the Official MCP Registry

Introduction

You built an MCP server. It works on your machine, maybe on npm or PyPI too, but nobody can find it. The official MCP Registry at registry.modelcontextprotocol.io is where clients discover servers, and publishing to it is a short, scriptable procedure: declare your server in a server.json manifest, prove you own its name, and push the manifest with the mcp-publisher CLI. If you are new to the protocol itself, our MCP server tutorial covers the concepts this guide builds on. This guide walks you through the full flow, from manifest to a verified live listing, with the exact error messages you will hit if something is off and how to fix each one.

Quick answer

Publish your npm package with an mcpName field in package.json, install the mcp-publisher CLI, generate server.json with mcp-publisher init, authenticate with mcp-publisher login github, then run mcp-publisher publish. Verify the listing with the registry API.

Why the registry matters, in numbers

The registry project is one of the most-watched infrastructure repos in the AI tooling ecosystem: the official modelcontextprotocol/registry repository had 7,333 stars as of October 11, 2026 (checked via the GitHub API), which is strong evidence that tool builders treat the registry as the canonical discovery channel rather than any third-party directory. The registry is deliberately thin: as the official publishing guide states, “The MCP Registry only hosts metadata, not artifacts.” That single design decision drives the whole workflow, because it means your server must already be published to npm, PyPI, or a container registry before the MCP Registry will accept your entry. If you have not built a server yet, start with our Python MCP server tutorial and come back when you have a package to publish.

Prerequisites

  • A working MCP server published as a real package: an npm package, a PyPI package, or a container image on Docker Hub or GHCR. The registry will fetch the live package to verify ownership.
  • A GitHub account (for GitHub authentication) or control over a DNS zone (for DNS authentication).
  • The mcp-publisher CLI installed, either via Homebrew or a release binary.
  • Node.js if your server is a TypeScript package, per the official quickstart.

Step 1: Understand the two authentication paths

Before writing anything, decide how you will prove ownership of your server’s name, because the name format and the auth method must match.

GitHub authentication is the simple path. You run mcp-publisher login github, complete a device-flow prompt, and the registry grants you the io.github.<your-username>/ namespace automatically. No domain, no TXT records. If you publish under an organization, your GitHub identity must be a public member of that org for the io.github.<org>/ namespace to be granted.

DNS authentication is for custom domain namespaces like acme.com/my-server. You generate a keypair, publish a DNS TXT record at a challenge hostname in your zone, and run mcp-publisher login dns --domain acme.com --private-key "$MCP_PRIVATE_KEY". This is the right choice when your brand lives on a domain rather than a GitHub handle, and it is also the path that works headlessly in CI.

Step 2: Install mcp-publisher

The CLI is distributed as release binaries and via Homebrew:

# Homebrew (macOS or Linux)
brew install mcp-publisher

# Or download the latest release binary directly
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/

Verify the install:

mcp-publisher --help
# expect: init, login, logout, publish, validate, status

Step 3: Publish your package first, with the ownership marker

This is the step most people skip, and the registry rejects the publish if it is missing. The registry only stores metadata, so your actual artifact must exist on its home registry first: npm publish for TypeScript, twine upload for Python, or a pushed image for Docker.

For npm packages, add the mcpName field to package.json. Per the official quickstart, the value of mcpName will be your server’s name in the MCP Registry, and with GitHub authentication it must start with io.github.<your-username>/:

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

Publish the package normally (npm publish), and confirm the published package.json carries the mcpName field. When you later change the namespace, you must re-publish the npm package with a matching mcpName, because the registry fetches the live package and compares.

For PyPI packages, the equivalent marker is an mcp-name: line in the package description, which the registry also reads during verification.

Step 4: Write server.json

The mcp-publisher init command scaffolds a manifest from your project:

cd your-mcp-server
mcp-publisher init

Open the generated server.json and check the three required fields: name, description, and version. The official JSON Schema (version 2025-12-11) enforces two rules that bite newcomers: description must be 1 to 100 characters, and name must match the pattern ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$, which is the reverse-DNS or GitHub-scoped format. A complete minimal manifest for an npm stdio server looks like this:

{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "name": "io.github.my-username/mcp-weather-server",
  "title": "Weather Server",
  "description": "Current weather and forecasts for any city via MCP.",
  "version": "1.0.0",
  "repository": {
    "url": "https://github.com/my-username/mcp-weather-server",
    "source": "github"
  },
  "packages": [
    {
      "registryType": "npm",
      "identifier": "@my-username/mcp-weather-server",
      "version": "1.0.0",
      "transport": { "type": "stdio" }
    }
  ]
}

Note that the name in server.json must exactly match the mcpName in package.json. For a hosted server instead of a package install, use a remotes entry with a streamable-http transport pointing at your public /mcp endpoint.

Validate before you publish:

mcp-publisher validate server.json

Fix every schema error now. It is much faster than debugging a rejected publish.

Step 5: Authenticate

For the interactive GitHub path:

mcp-publisher login github

The CLI prints a URL and a device code like ABCD-1234. Open the URL, enter the code, authorize the registry app, and return to the terminal. The CLI stores a short-lived JWT locally. Authenticate immediately before publishing in the same session, because the token expires quickly and a stale token is the most common cause of mysterious auth failures.

For the DNS path:

mcp-publisher login dns --domain acme.com --private-key "$MCP_PRIVATE_KEY"

Add the TXT record the CLI prints to your zone first, then run the login. In GitHub Actions, prefer mcp-publisher login github-oidc, which mints the token from the workflow’s OIDC identity with no secrets to manage.

Step 6: Publish and verify

From the directory containing server.json:

mcp-publisher publish

The CLI validates the manifest, checks namespace ownership against your login, fetches the live package to confirm the ownership marker, and uploads. On success your server is listed immediately, there is no review queue. Verify with the registry API:

curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=mcp-weather-server" | jq '.servers[] | {name, version, description}'

Your canonical record lives at https://registry.modelcontextprotocol.io/v0/servers/io.github.my-username/mcp-weather-server. Clients that integrate the registry can now discover it by name; see our Claude Code MCP setup guide for how a client consumes a registered server in practice.

Publishing by hand works once. Every version bump afterwards needs the same three fields to stay in sync: the package version, server.json’s version, and the packages[0].version. Automate it with a GitHub Actions workflow that triggers on a version tag:

- name: Set version in server.json
  run: |
    VERSION="${GITHUB_REF_NAME#v}"
    jq --arg v "$VERSION" \
      '.version = $v | .packages[0].version = $v' \
      server.json > server.tmp && mv server.tmp server.json

- name: Publish to MCP Registry
  run: |
    mcp-publisher login github-oidc
    mcp-publisher publish

The OIDC login works without storing any token secret, and a tag push (git tag v1.1.0 && git push origin v1.1.0) becomes your release procedure. If you use the DNS namespace in CI, store the private key in a GitHub secret and pass it to login dns --domain.

Troubleshooting

“Registry validation failed for package” Your package is missing the ownership marker. For npm, confirm the published package.json contains mcpName (editing the local file without republishing does not count). For PyPI, confirm the description carries the mcp-name: marker. Then re-run mcp-publisher publish.

“Invalid or expired Registry JWT token” The login token expired. Re-run mcp-publisher login github immediately before mcp-publisher publish in the same session. Do not log in in the morning and publish in the afternoon.

“You do not have permission to publish this server” Your auth method does not match the namespace. With GitHub auth, the server name must start with io.github.<your-username>/, where the username is the GitHub account you authorized. Publishing under an org name requires your account to be a public member of that org: set membership to Public in the org’s People page, then log in again to mint a fresh token.

“package not found” or a 400 on the package check The registry could not fetch your package. Usual causes: the npm/PyPI package was never published, the version in server.json points at a version that does not exist yet, or you published under a different identifier than packages[0].identifier. Align all three version fields and the identifier, publish the artifact first, then publish to the registry.

422 on publish with a long description The description field allows at most 100 characters. Shorten it to one plain sentence. This also applies to title, which has the same 100-character limit.

FAQ

Do I need a domain to publish an MCP server to the registry? No. GitHub authentication gives you the io.github./ namespace automatically, with no domain verification. DNS authentication is only needed when you want a custom domain namespace like acme.com/server-name.

Does the MCP Registry host my server’s code? No. The official registry hosts only metadata, not artifacts. Your actual package must live on npm, PyPI, Docker Hub, or another registry, and your server.json points to it.

How long can the server description be? Between 1 and 100 characters. Anything longer is rejected with a validation error, so write one tight sentence that explains what the server does.

Can I update a server after publishing it? Yes. Bump the version in both server.json and the published package, then re-run mcp-publisher publish with a fresh login. The new version replaces the old listing.

How do clients discover my server once published? They can search the registry API at registry.modelcontextprotocol.io, and clients that integrate the registry, such as newer n8n versions, pick up the listing automatically. No client-side install changes are required beyond what the client already supports.

What is the difference between login github and login github-oidc? login github opens a device-flow prompt in your terminal for one-time interactive authentication. login github-oidc uses GitHub Actions OIDC tokens, so it only works inside a GitHub Actions workflow and is the right choice for automated CI publishing.

Conclusion

Publishing to the official MCP Registry is a five-minute job once the pieces are in order: package published with its ownership marker, a valid server.json, a fresh login, and one publish command. The registry does the boring verification work so clients can trust what they discover. If your server is not built yet, start with our Python MCP server tutorial, then use this guide to put it in front of every registry-aware client. For the bigger picture of how servers and clients fit together, our MCP server tutorial is the pillar to read next.

Frequently asked questions

TP
ToolPilot Team

We install, break, and fix automation tools so you don't have to. Every tutorial on ToolPilot is tested hands-on before publishing.

Keep reading