You wrote an MCP server. It works when you paste the config into your own client. But nobody else can find it — and the whole point of the Model Context Protocol is that any client can discover any server. The fix is publishing to the official MCP Registry at registry.modelcontextprotocol.io: the central metadata catalog that Claude, VS Code, and every downstream subregistry pull from. It's backed by Anthropic, GitHub, Microsoft, and PulseMCP, and it grew to roughly two thousand servers within months of its preview.
Here's the entire flow. It's three commands and one file — the file and the namespace are the only places people trip.
The three commands#
The publisher ships as a CLI. Build it from the registry repo, then it's three steps:
# build the CLI (or grab a released binary)
make publisher
./bin/mcp-publisher --help
# 1. scaffold a server.json in your project
mcp-publisher init
# 2. authenticate (proves you own the namespace)
mcp-publisher login github
# 3. submit
mcp-publisher publish
init writes a server.json template so you're editing, not authoring from a blank file. login github handles the common case. publish validates and submits. That's the happy path — now the two things that actually go wrong.
The file: server.json#
The registry stores metadata, not your code. So server.json describes your server and points at where the code already lives. A minimal one for an npm-distributed, locally-run 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" }
}
]
}
The packages array is the important part: registryType is npm, pypi, or oci; identifier is the package name in that registry; version is what to install; and transport.type is stdio when the client spawns your process locally.
If your server is hosted — a multi-tenant service, or one holding secrets you'd never ship to a laptop — drop packages and use remotes instead:
"remotes": [
{ "type": "streamable-http", "url": "https://mcp.yourcompany.com/v1" }
]
That's the whole decision: ship a package users run locally, or host it and register a URL.
The namespace: the part that gates you#
The registry won't let you publish a name you can't prove you own. Two paths:
io.github.<your-username>/<server>— authenticate as that GitHub user withmcp-publisher login githuband ownership is automatic. Zero setup. Use this for your first server.- A branded name like
com.yourcompany/billing— prove you controlyourcompany.comvia a DNS TXT record or an HTTP challenge.
One error trips almost everyone: the name in server.json must exactly match the mcpName field you declare in your package.json. The registry cross-checks them so nobody can register an entry claiming to be a package they don't control. Character-for-character identical, or publish fails. (This is also why a version bump means editing version in both files.) And if you see "Your authentication method doesn't match your server's namespace," your login provider doesn't line up with your name prefix — e.g., GitHub login but a custom-domain name.
Publish from CI, not your laptop#
For anything real, make publishing a release step. The CLI supports GitHub OIDC, so a GitHub Actions workflow authenticates with no stored token and no browser — same mcp-publisher publish, but the identity comes from the Action's OIDC token:
# .github/workflows/publish.yml (sketch)
permissions:
id-token: write # OIDC
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm publish # your package first
- run: mcp-publisher publish # then the registry entry
Bump the version, cut a tag, let CI publish. That's the discipline that keeps your registry entry and your npm/PyPI release from drifting apart.
That's the whole loop#
Three commands, one server.json, a namespace you prove once. Get listed and you stop emailing people your config — clients and subregistries find you. If you haven't built the server yet, start from a stateless server on the stable SDK so it's registry-ready from day one; if you're still deciding whether to build a server at all, the skill-or-MCP-server call is the piece to read first.



