Guide
How to publish an MCP server to the official registry
Last verified: 10 August 2026 · spec 2026-07-28 · ruleset 1.4.2
mcp-publisher, add an ownership marker to your package, generate server.json, authenticate for the namespace you want, publish. Two of those five are where publishes actually fail — the ownership marker and the namespace match — so they get the most room below.The whole flow, in one block
# 1. Install
brew install mcp-publisher
# 2. In package.json — must match server.json's "name"
# "mcpName": "io.github.you/weather"
npm publish
# 3. Generate and edit server.json
mcp-publisher init
# 4. Authenticate for your namespace
mcp-publisher login github # io.github.you/*
# or: mcp-publisher login dns --domain example.com --private-key …
# 5. Publish
mcp-publisher publishmcpName in your package must match server.json's name exactly, and the package must already be published where the registry can read it. Step 4 — the namespace you authenticate for must contain the name you are publishing. Everything else is mechanical.1. Install mcp-publisher
brew install mcp-publishercurl -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.gzmcp-publisher --help2. Prove you own the package
This is the step people skip, and it is the one that makes a publish fail with an ownership error that reads like an authentication problem. The registry has to confirm that whoever is registering io.github.you/weather also controls the npm package that entry points at — otherwise anyone could publish a registry entry aimed at someone else's code.
The proof is a marker inside the package itself:
{
"name": "@my-username/mcp-weather-server",
"mcpName": "io.github.my-username/weather"
}For PyPI and NuGet, the equivalent marker goes in the package README as mcp-name: <server-name>, or the same string inside an HTML comment.
name property in server.json must match the mcpName property in package.json. Not similar — identical. And the package must already be published, because the registry reads the marker from the published artifact rather than from your working directory.# 1. Add mcpName to package.json
# 2. npm publish ← the marker becomes readable
# 3. mcp-publisher publish ← the registry can now verify it
# Publishing to the MCP registry before npm fails:
# the registry fetches your package and finds no marker.Only official package registries are accepted, at fixed URLs — npm at registry.npmjs.org, PyPI at pypi.org, NuGet at api.nuget.org/v3/index.json, Cargo at crates.io, plus specific container registries. Private registries are explicitly disallowed, for the same reason: the registry cannot verify a marker it cannot read.
server.json declares remotes rather than packages, there is no package to own and no marker to add — the endpoint URL and your namespace are the whole claim.3. Generate and edit server.json
MCP-REG-001mcp-publisher initThen fill it in. A minimal, complete file for an npm-packaged server:
{
"$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" }
}
]
}$schema, name, description and version are required, and at least one of packages or remotes must be present. Every field is covered in the server.json reference.
mcp-publisher validate“The MCP Registry recommends semantic versioning, but supports any version string format.”
4. Authenticate for the namespace you want
MCP-REG-002The registry grants namespaces, not blanket write access. Which command you run determines which names you may publish:
| Command | Namespace | Use when |
|---|---|---|
| login github | io.github.name/* | Publishing by hand |
| login github-oidc | io.github.name/* | GitHub Actions — no secret needed |
| login dns | com.example.*/* | Your own domain |
| login http | com.example.*/* | Own the domain, not the DNS |
“Publishers must verify ownership of their namespace through GitHub, DNS, or HTTP challenges, preventing arbitrary spam submissions.”
io.github.orgname/* requires Owner role in that organization. Ordinary membership is not sufficient — the registry checks your role, so that anyone who merely belongs to an org cannot publish or overwrite servers under the org's name.For a custom domain, the full walkthrough is verify a DNS namespace. If login fails, every known cause is here.
5. Publish, then verify it landed
MCP-REG-004mcp-publisher publishcurl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"One more thing the registry checks that is easy to miss: if you declared a remote endpoint, it has to actually be there.
“A remote server MUST be publicly accessible at its specified URL.”
version in server.json, the version in package.json, and the tag you cut should move together — a registry entry pointing at a package version that was never published is a broken install for anyone who finds you.Updating, deprecating and removing
Publishing a new version is the same command with a bumped version. Changing a server's lifecycle state is a separate subcommand:
# New version — bump "version" in server.json first
mcp-publisher publish
# Mark deprecated, with a note for anyone who finds it
mcp-publisher status --status deprecated \
--message "Superseded by io.github.you/weather-v2" \
io.github.you/weather
# Remove
mcp-publisher status --status deleted --all-versions --yes io.github.you/weatherdeprecated with a message over deleted. A deleted entry leaves anyone who already depends on you with nothing to follow; a deprecated one with a pointer is the difference between a migration and a dead end.What we check about a published server
Publishing successfully means the registry accepted your metadata. It says nothing about whether the server behind it conforms to the protocol. These four registry rules are what we check from the outside:
| Rule | What we check | Level | If it fails |
|---|---|---|---|
| MCP-REG-001 | server.json present and valid against the registry schema | MAY | warn |
| MCP-REG-002 | Namespace ownership is verifiable (GitHub or DNS TXT) | MAY | warn |
| MCP-REG-003 | version is present and valid semver | MAY | warn |
| MCP-REG-004 | Declared remote endpoint matches the probed URL | MAY | warn |
All four carry verbatim quotes from registry documentation.
Check the server, not just the listing
A published entry is discoverable. Whether it works for the clients that find it is a different question — run the endpoint through the validator before you announce it.
Validate a server →Frequently asked
How do I publish an MCP server to the official registry?
Install the mcp-publisher CLI, add an ownership marker to your package (mcpName in package.json for npm), run mcp-publisher init to generate server.json, authenticate with mcp-publisher login github or login dns for a custom domain, then run mcp-publisher publish. The whole flow is five commands once the markers are in place.
What is mcpName in package.json?
An ownership marker proving the same person controls both the npm package and the registry namespace. The mcpName value in package.json must match the name property in server.json exactly — that pairing is what stops someone publishing a registry entry that points at a package they do not own.
Do I need to publish to npm before the MCP registry?
If your server.json declares an npm package, yes — the registry verifies the ownership marker by reading the published package. Publish to npm first with mcpName already in package.json, then publish to the MCP registry. A remote-only server declaring remotes rather than packages does not need a package registry at all.
Which package registries does the MCP registry accept?
Only official ones, at fixed URLs: npm at registry.npmjs.org, PyPI at pypi.org, NuGet at api.nuget.org/v3/index.json, and Cargo at crates.io, plus specific container registries. Private registries are explicitly disallowed, because the registry cannot verify ownership on a host it cannot read.
Can I publish under my organization's namespace?
Yes, but publishing under io.github.orgname/* requires Owner role in that organization — ordinary membership is not enough. The registry checks your membership role so that anyone who merely belongs to an org cannot publish or overwrite servers under the org's name.
How do I update or unpublish a server?
Publish a new version by bumping the version field in server.json and running mcp-publisher publish again. To change a server's lifecycle state, use mcp-publisher status --status with active, deprecated or deleted, optionally with --message and --all-versions.
Sources
Verified on 10 August 2026 against:
- registry/docs/modelcontextprotocol-io/quickstart.mdx — install commands, ownership markers, the worked server.json
- official-registry-requirements.md — namespace ownership, the allowed registry list
- cli/commands.md — every subcommand and flag