How to publish an MCP server to the official registry without GitHub OAuth
If you have built an MCP server and want it discoverable by Claude Desktop, Cursor, and every other MCP-aware tool, you need to publish it to registry.modelcontextprotocol.io. Most documentation walks you through GitHub OAuth, where the server name ends up under io.github.<your-username>/....
That works. But there is a second path most people miss: HTTP domain-ownership authentication. If you control a domain, you can claim a namespace under it (we use com.astranl/...) without any browser OAuth dance. This matters when you want a corporate-shaped namespace, when GitHub OAuth is friction, or when you are running fully autonomous publishing from a server with no browser available.
The flow at a glance
- Generate an ed25519 keypair on your server.
- Publish the public key as a TXT-style record at
https://<your-domain>/.well-known/mcp-registry-auth(HTTP, not DNS). - Run
mcp-publisher login http --domain=<your-domain>. The CLI signs a challenge with your private key, the registry fetches the well-known URL, verifies the signature against the public key, and issues you a token. - Edit your
server.jsonsonameuses your reverse-domain namespace (e.g.com.astranl/mcp). mcp-publisher publish.
Concrete commands
Generate the keypair:
openssl genpkey -algorithm Ed25519 -out key.pem
openssl pkey -in key.pem -outform DER 2>/dev/null \
| tail -c 32 | xxd -p -c 32 > priv.hex
Extract the public key in the exact format the registry expects (base64, not hex — this caught us with a 401 the first time):
PUB_B64=$(openssl pkey -in key.pem -pubout -outform DER 2>/dev/null \
| tail -c 32 | base64)
echo "v=MCPv1; k=ed25519; p=$PUB_B64" \
> /var/www/.well-known/mcp-registry-auth
Verify it is reachable on HTTPS, then login:
curl https://your-domain.com/.well-known/mcp-registry-auth
# v=MCPv1; k=ed25519; p=U+fQxRqnI1WX0zs/...
PRIV=$(cat priv.hex)
mcp-publisher login http --domain=your-domain.com --private-key="$PRIV"
# ✓ Successfully logged in
The two namespace gotchas
Format of the public key. The registry accepts only base64 in the well-known record, even though many tutorials show hex. If you get "invalid Ed25519 public key size", you have hex where base64 belongs.
Namespace shape. HTTP auth gives you com.<domain>/..., not io.github.<user>/.... If you ported a server.json from a GitHub-OAuth setup, you will need to rewrite the name field. Description must also be ≤100 characters — the registry rejects longer ones with an HTTP 400 you will only see with verbose curl.
Why this matters operationally
Once you have HTTP-auth login working, your publishing flow is fully scriptable. No browser. No interactive consent. We run our publish step from a systemd unit on the same VPS that hosts the MCP server itself. New version of the protocol ships, the unit runs mcp-publisher publish server.json, the registry updates within seconds, and every Claude Desktop user who looks us up tomorrow sees the new version.
If your MCP server is a side project tied to your personal GitHub account, OAuth is fine. If it is a product or a piece of infrastructure with its own domain, HTTP auth is the cleaner ground.
References
- modelcontextprotocol/registry — source of the CLI
- CLI reference (commands.md)
- Our live entry: com.astranl/mcp
—
AstraNL