How to publish an MCP server to the MCP Registry
To publish an MCP server to the official MCP Registry, you describe it in a server.json file,
prove that you own its namespace, and push the file with the mcp-publisher CLI. There is no form
and no review queue. This guide walks through every step with the exact commands from the
official docs, for package-based and remote
servers, and shows where MCP Tower helps: testing your server before you publish, and measuring the
traffic that registry catalogs send you afterwards.
This article is the long version of lesson 3, How to get listed in an MCP directory, of Growing Your MCP Server: A Crash Course, a free course on getting an MCP server listed, measured and used.
I highly recommend publishing. The registry is the upstream that Glama, PulseMCP and other catalogs pull from, so one publish reaches several listings. If you want the background first, read why the official MCP Registry is a new MCP growth channel.
The whole process, in order:
- Test your tools, sign-in and hostname in MCP Tower.
- Choose a namespace: GitHub or your own domain.
- Add an ownership marker to your package, or skip this for a remote-only server.
- Install
mcp-publisher. - Create and validate
server.json. - Log in to the registry.
- Run
mcp-publisher publish. - Check the listing through the registry API.
- Publish updates as new versions, and measure usage.
1. Prepare your MCP server before you publish
The registry checks your namespace and your package, not your tools. Catalogs downstream look closer: Glama’s methodology describes scoring tool definitions and watching tool descriptions for prompt-injection patterns. Clients that find you through a catalog will also connect and sign in the way ChatGPT and Claude do. Fix what they would trip on before the listing goes out.
- Tools and descriptions. When you add your server to MCP Tower, its Inspect module validates every tool and its description and gives you recommendations. Fix them on your server and scan again, or change a tool’s title and description with an override, without waiting for your developers.
- OAuth. If users sign in, run MCP Tower’s OAuth checks. There are 18 of them, and they sign in the way ChatGPT and Claude do and report the first place the flow breaks, with the fix.
- The hostname. A published version cannot be edited, and the registry allows each remote URL
for only one server name. Put MCP Tower in front of your server first
(onboarding) and use its public hostname, for example
https://mcp.example.com/, as the remote URL inserver.json. That way the URL you publish is the one you keep, and every call through it is measured.
2. Choose your namespace: GitHub or your domain
Every server in the registry has a unique name in reverse-DNS form, and the authentication method you use decides which names you may publish:
| Login method | Name format | Example |
|---|---|---|
| GitHub (OAuth or OIDC) | io.github.username/* or io.github.orgname/* | io.github.alice/weather-server |
| DNS or HTTP verification | com.example/* | com.example/weather |
GitHub login is the quickest start. If your remote server runs on your own domain, consider the
domain namespace: the registry checks the namespace, not the remote URL, so a com.example name
next to an example.com URL shows users that both belong to the same publisher
(ownership verification).
3. Add package ownership verification
The registry hosts metadata only, so the package must already be public on its own registry, and it
must name your server. Each package type
has its own marker, and the value must equal the name in server.json:
| Package type | Where the marker goes |
|---|---|
| npm | "mcpName": "io.github.alice/weather" in package.json |
| PyPI, NuGet | An mcp-name: io.github.alice/weather line in the package README |
| Cargo | The same mcp-name: line as visible text (crates.io strips comments) |
| Docker/OCI | LABEL io.modelcontextprotocol.server.name="io.github.alice/weather" |
| MCPB | A URL containing “mcp” and a fileSha256 hash in server.json |
For npm, the quickstart adds the field and publishes the package before anything else:
{ "name": "@alice/mcp-weather-server", "version": "1.0.1", "mcpName": "io.github.alice/weather"}npm publish --access publicOn PyPI and NuGet the line can sit inside an HTML comment in the README. Leave a space or a line break after the server name: a period glued to it stops the match.
Remote-only servers skip this step. A server with only a remotes entry needs no package, so
there is nothing to verify here.
4. Install the mcp-publisher CLI
Install mcp-publisher with Homebrew or a prebuilt binary from the
releases page:
brew install mcp-publishermcp-publisher --helpThe help lists six commands: init, login, logout, publish, status and validate.
5. Create and validate server.json
In your project directory, generate a template:
mcp-publisher initFor a package-based server, the result looks like this:
{ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.alice/weather", "description": "An MCP server for weather information.", "repository": { "url": "https://github.com/alice/mcp-weather-server", "source": "github" }, "version": "1.0.1", "packages": [ { "registryType": "npm", "identifier": "@alice/mcp-weather-server", "version": "1.0.1", "transport": { "type": "stdio" } } ]}For a remote server, init still writes a packages entry. Replace it with remotes, and use the
Streamable HTTP transport, since the SSE transport is deprecated
(remote servers):
{ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "com.example/weather", "title": "Weather", "description": "Forecasts and alerts for any city.", "version": "1.0.0", "remotes": [ { "type": "streamable-http", "url": "https://mcp.example.com/" } ]}Check three rules before you go on:
descriptionis limited to 100 characters. Say what the server does and stop.namemust match the package marker from step 3, character for character.- The remote URL must be publicly reachable. Remotes can also declare URL variables (for example a
{tenant_id}subdomain) and headers clients must send, such as an API key.
Then check the file. validate reports every issue at once, with the JSON path of each:
mcp-publisher validate6. Log in to the MCP Registry
Use the login that matches your namespace from step 2.
GitHub
mcp-publisher login githubThe CLI prints a code; open https://github.com/login/device, enter it and authorize. This grants
your personal namespace, io.github.<your-username>/*. The docs warn that the interactive login
cannot currently publish to an organization namespace (tracking issue
#1468). For an organization, where
you must be an Owner, use GitHub Actions with OIDC (mcp-publisher login github-oidc, with
id-token: write permission) or a personal access token with read:org:
mcp-publisher login github --token "$GITHUB_TOKEN"DNS
Generate an Ed25519 key pair and publish the public key as a TXT record on the apex of your
domain, not under a selector such as _mcp-auth. On macOS, use OpenSSL 3 (brew install openssl@3),
because the system LibreSSL cannot create Ed25519 keys:
MY_DOMAIN="example.com"openssl genpkey -algorithm Ed25519 -out key.pemPUBLIC_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}\""Add the record at your DNS provider, wait for it to propagate, then log in:
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 out of your repository; it proves you own the domain.
HTTP
Instead of DNS, you can serve the proof record v=MCPv1; k=ed25519; p=... at
https://example.com/.well-known/mcp-registry-auth and run mcp-publisher login http with the same
flags. The registry fetches the file from Google Cloud with User-Agent: mcp-registry/1.0, follows
no redirects and gives up after 10 seconds. A WAF or bot rule that challenges non-browser clients
makes the login fail with token exchange failed with status 401, even when the file opens fine in
your browser. If you are not sure what your edge does to such requests, use DNS.
7. Publish your MCP server to the MCP Registry
mcp-publisher publishThe CLI validates the file, then the registry checks the package marker and your namespace and stores the version. A successful run ends with:
✓ Successfully published✓ Server io.github.alice/weather version 1.0.1| Error | What to fix |
|---|---|
| “Registry validation failed for package” | The package marker from step 3 is missing or does not match |
| “Invalid or expired Registry JWT token” | Run the login command again |
| “You do not have permission to publish this server” | The name does not start with the namespace your login grants |
8. Check your listing with the registry API
The registry has a public, read-only REST API. Search for your server:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.alice/weather"Or fetch the latest version directly. Path parameters must be URL-encoded, so / becomes %2F:
curl "https://registry.modelcontextprotocol.io/v0.1/servers/io.github.alice%2Fweather/versions/latest"The response wraps your server.json with registry metadata under
_meta["io.modelcontextprotocol.registry/official"]: status, publishedAt, updatedAt and
isLatest. Catalogs that sync from the registry read the same record. The registry’s About page
expects them to pull “on a regular but infrequent basis (for example, once per hour)”, and each one
sets its own schedule, so your listing does not show up everywhere at the same moment.
9. Update, version and retire your listing
Published metadata is immutable. To change anything, even a typo in the description, publish a new
server.json with a new, unique version
(versioning):
- Use semantic versions. The registry sorts them to decide which version is latest, and rejects
ranges such as
^1.2.3or1.x. - Keep the server version equal to the package version, or to your API version for a remote server.
- For a metadata-only change, the docs suggest a prerelease such as
1.2.3-1. Semver sorts it before1.2.3, so a prerelease published after its regular version is not marked latest.
To take a version out of circulation, change its status. Nothing is ever deleted outright: a
deleted server is hidden from default listings but kept on record.
mcp-publisher status --status deprecated --message "Please upgrade to 2.0.0" \ io.github.alice/weather 1.0.0To publish on every release, the registry documents
GitHub Actions workflows that log in with
OIDC and run the same publish command.
After you publish: measure the traffic
The registry shows you none of the traffic it sends. Downstream catalogs may show their own numbers, each in its own place. This is the second place MCP Tower helps, as the first product built to show how AI agents use your MCP server.
If the remote URL you published is your MCP Tower hostname, MCP Tower records every call that passes through it: which agent made it and how far that label can be trusted, which tool it called, how long it took and what failed. It also tracks link clicks from tool results and interactions inside widgets, and checks your uptime even when nobody is calling. Compare the weeks before and after you publish to see what the registry and the catalogs pulling from it bring you, next to your ChatGPT and Claude traffic.
Where else to submit your MCP server
The registry feeds developer catalogs. The AI assistants have their own reviewed directories, each with a separate submission:
- How to submit a plugin to ChatGPT
- How to submit a connector to Claude
- How to publish an MCP to Muse
- How to publish an MCP in Perplexity
- How to submit an MCP to Gemini
Summary
- Validate tools, test OAuth and put MCP Tower in front of your server before you publish.
- Pick a namespace:
io.github.<you>/*with GitHub, orcom.example/*with DNS or HTTP. - Add the ownership marker to your package and publish the package, or use
remotesonly. - Install
mcp-publisher. - Run
mcp-publisher init, editserver.json, and runmcp-publisher validate. - Log in with
mcp-publisher login github,github-oidc,dnsorhttp. - Run
mcp-publisher publish. - Check the listing at
registry.modelcontextprotocol.io/v0.1/servers. - Publish every change as a new version, and measure what the listing brings.
Publishing to the official MCP Registry takes one config file and one CLI. Do it from behind MCP Tower so the URL you publish is tested, stays yours and is measured from the first call.
Open MCP Tower