Publish a Remote MCP Server to the Official Registry

The complete 2026 walkthrough — no npm package, no npx, no install step for your users. Your hosted URL is the package. Written from a server that is actually listed.

Why this page exists. Nearly every MCP tutorial tells you to publish an npm package and have users run npx -y your-server. That is the hard path: it needs an npm account, it ships your code to the client, and you can never meter or update it. The official registry also accepts remote servers — you point it at a URL you already host. This guide is that path, end to end.

1. Remote vs. local: pick remote

Local (npm / npx)Remote (hosted URL)
Accounts needednpm account (often CAPTCHA-walled)None beyond a GitHub token
User install stepnpx -y your-serverPaste a URL
Ship updatesPublish a version, users must upgradeRedeploy, everyone gets it
Secrets / API keysLive on the user's machineStay on your server
Usage metering & billingEffectively impossibleServer-side, trivial
Cold startPackage download per runAlready warm

If you ever want to charge for your tool, remote is the only shape that works — the paywall has to live somewhere you control. The same is true for rate limits and abuse handling.

2. What a remote MCP server actually has to answer

Streamable HTTP is one POST endpoint speaking JSON-RPC 2.0. You need to handle four things. That is the whole protocol surface for a tools-only server:

Three things that will bite you. (1) Reject JSON-RPC batch arrays explicitly — the current spec drops them, and silently mishandling one looks like a hang. (2) Bound your request body (256 KB is plenty). An unbounded read on a public endpoint is a free memory-exhaustion attack. (3) Answer ping with an empty result object; some clients health-check with it before ever calling tools/list.

3. Publishing to the official registry

The registry is registry.modelcontextprotocol.io. Authentication is the part everyone gets stuck on: you do not need a registry account. You exchange a GitHub token for a short-lived registry JWT, which grants you the namespace io.github.<your-github-username>/*.

Step 1 — trade your GitHub token for a registry JWT

curl -s -X POST https://registry.modelcontextprotocol.io/v0/auth/github-at \
  -H 'Content-Type: application/json' \
  -d '{"github_token":"ghp_your_token_here"}'
# -> {"token":"eyJ...","expires_at":...}

A classic PAT or a fine-grained token both work. The registry only verifies who owns the token; it never fetches your public profile, so this works even for accounts with no public repos at all.

Step 2 — write server.json

{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
  "name": "io.github.yourname/yourserver",
  "description": "One line, 100 characters maximum.",
  "version": "1.0.0",
  "repository": { "url": "https://github.com/yourname/yourrepo", "source": "github" },
  "remotes": [
    { "type": "streamable-http", "url": "https://your.domain/yourserver/mcp" }
  ]
}

Step 3 — validate free, then publish

# dry run, costs nothing and never publishes
curl -s -X POST https://registry.modelcontextprotocol.io/v0/validate \
  -H 'Content-Type: application/json' -d @server.json

curl -s -X POST https://registry.modelcontextprotocol.io/v0/publish \
  -H "Authorization: Bearer $REGISTRY_JWT" \
  -H 'Content-Type: application/json' -d @server.json
The four gotchas that cost real time:
  1. The body is the server object at the TOP LEVEL. Wrapping it as {"server": {...}} returns a confusing 422 that does not tell you this. This is the single most common failure.
  2. description must be ≤ 100 characters. Longer descriptions 422 as a validation error that reads like a schema problem.
  3. The JWT expires in about five minutes. Mint it in the same script that publishes; do not paste it in by hand.
  4. Your namespace is fixed to your GitHub username. name must start io.github.<username>/ exactly, or you get a permission error rather than a naming error.

Step 4 — confirm it is really live (unauthenticated)

curl -s 'https://registry.modelcontextprotocol.io/v0/servers/io.github.yourname%2Fyourserver/versions'
# want: "status":"active" and "isLatest":true

Always check this with no credentials attached. Authenticated requests can show you a version of the world only you can see — an unauthenticated curl is the only honest test that a stranger can find your server.

4. What listing actually gets you

The official registry is the upstream source for the places people and agents actually browse — mcp.so, PulseMCP, Glama, and the server pickers built into VS Code and Cursor. One publish propagates to all of them, which is the real reason to do this rather than only posting a link somewhere.

Two things worth doing the same day: serve a /llms.txt at your origin describing your tools in plain text, and keep your root path returning a real HTML page. Both are cheap and both are crawled.

5. Hosting notes that save a redeploy

6. The starter kit

Everything above is free and complete — follow it and you will get listed. The kit is for skipping the two hours of assembly and the gotchas above:

MIT licensed — use it in commercial work, no attribution required.

$9 Remote MCP Server Starter Kit — working code, publish script, metering hook. One-time, MIT. Buy the kit

How delivery works: the kit is sent to the email address on your Stripe receipt, by hand, normally within a few hours and always within 24. This is a one-person shop and that is the honest timing — if you need it this minute, §2, §3 and §5 above contain every step and every gotcha, and cost nothing. Any problem, reply to your Stripe receipt and you get a refund, no questions.

A server built exactly this way

Peek API is listed in the official registry as io.github.smeltworks-labs/peek, serving peek_preview and peek_markdown over remote MCP at https://smeltworks.com/peek/mcp. Every claim on this page came from getting that one live — including all four publish gotchas, each of which cost a real debugging session.