Shipping an MCP server is a distribution problem — the Registry gives you one discovery surface, but the packaging choice (one of six registry types) determines who can actually install it.
Building the server is one problem; getting it in front of users is another. The MCP Registry — the closest thing MCP has to a package index, and still explicitly in preview — lets you list a server; the package type determines who can install it and how; and server/discover, mandatory since 2026-07-28, means a host that already has your URL never needs the registry at all. This is a short essay because the topic is small; skipping it means users can't find your server.
The MCP Registry: what it is now.
The MCP Registry is a central index of published servers — name, description, repository, version, the package or packages it ships as, the remote endpoints it answers on, and the environment variables it expects. A host that wants to let a user "add an MCP server" queries the Registry, filters, and hands the resulting launch command or HTTP endpoint to its transport layer. Read the status line before you build against it: the Registry's own about page carries the banner "The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub." There is no announced GA date. The deployed service reports version 1.8.1, built 2026-08-06, from GET /v0/version. Two API prefixes are live — /v0/ and /v0.1/ — and the docs now steer integrators to the latter: "Production applications should consider using /v0.1/ for stability." There is no /v1/. The failure mode is the same as npm's — an unlisted server is invisible to any host that only looks in the Registry, and a listed server with a stale metadata block gets picked up for the wrong reasons.
The Registry is not a runtime — it does not proxy tool calls, front the transport, or sign responses. Its only jobs are discovery and metadata. Everything building an MCP server teaches — tool granularity, resource vs prompt split, annotations — is invisible to the Registry beyond what you write into the description field. That field is where selection actually happens, and treating it as a marketing slug instead of a capability sentence is the fastest way to publish a server nobody installs.
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "dev.acme/search",
"description": "Search Acme's docs, then fetch full articles by id.",
"version": "1.4.2",
"repository": { "url": "https://github.com/acme/mcp-search", "source": "github" },
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "@acme/mcp-search",
"version": "1.4.2",
"runtimeHint": "npx",
"transport": { "type": "stdio" },
"environmentVariables": [
{ "name": "ACME_API_KEY", "description": "Acme API key", "isRequired": true, "isSecret": true }
]
}
],
"remotes": [
{ "type": "streamable-http", "url": "https://mcp.acme.dev/mcp" }
]
}
That file is server.json, and the tool that pushes it is mcp-publisher. The working loop is init → login → publish, with validate to check a manifest against the 2025-12-11 schema before you send it, status to move a published version between active, deprecated and deleted, and logout to drop the stored token. login takes a method, and the method decides which namespace you are allowed to publish into: GitHub OAuth device flow and GitHub Actions OIDC for io.github.*, generic OIDC for other CI, DNS TXT or an HTTP /.well-known/mcp-registry-auth document for a domain namespace such as dev.acme/*, and none for a local registry you are testing against. Both domain methods can keep the signing key in Google KMS or Azure Key Vault instead of on disk, which is the difference between a domain namespace one engineer can publish under and one a team can.
mcp-publisher init # scaffold server.json, autodetecting the package manager mcp-publisher validate # exhaustive check against the 2025-12-11 schema mcp-publisher login github # or: github-oidc | oidc | dns | http | none mcp-publisher publish # server verifies package ownership + namespace auth mcp-publisher status --status deprecated dev.acme/search 1.4.1
Two 2026 changes break publishers that worked before, and both fail in ways that read like an auth bug. Publishing under an organisation namespace now requires the GitHub Owner role — being a member of the org is no longer enough, so a release job that has worked for a year starts failing the moment the account behind it is anything less than an owner. And version 1.8.1 rejects github.io domains in DNS and HTTP token exchange, which retires the cheapest way people were claiming a domain namespace. If a publish suddenly stops working, check the role and the domain before you debug the manifest.
Package types: npm, PyPI, OCI — and three more.
Three package types cover the three realistic delivery stories, and three more exist for the ecosystems that need them. An npm package (JS/TS servers) installs with npx for one-shot execution or npm i -g for persistence; the host launches it as a subprocess, which puts it on the stdio path by default. A PyPI package (Python servers) installs with pip, pipx, or uv; uvx has become the FastMCP-idiomatic one-shot launcher, playing the role npx does for Node. An OCI image is heavier but runtime-independent — the host pulls it, runs it, and talks to it over Streamable HTTP rather than stdio, because binding a container's stdio to a host's tool loop is more friction than a network endpoint. The trade-off is not subtle: npm and PyPI depend on the host having a working Node or Python runtime; OCI ships a self-contained thing that runs identically everywhere. Pick per audience — hobbyists and small teams take the npm/PyPI hit, enterprise deploys take the OCI hit.
The full set of registryType values the Registry accepts today is npm, pypi, nuget, cargo, oci, and mcpb — .NET and Rust servers are first-class, and mcpb covers MCP's own bundle format. One wrinkle worth knowing before you waste an afternoon: the published 2025-12-11 schema's registryType examples still omit cargo even though the validator accepts it. Trust the validator, not the examples. Each type is also pinned to one trusted base URL — npm to registry.npmjs.org, PyPI to pypi.org, Cargo to crates.io, and OCI to a short allowlist including Docker Hub, ghcr.io and Quay — so a private mirror is not a publishable source.
# npm — one-shot, Node runtime npx -y @acme/mcp-search --stdio # PyPI — one-shot, Python runtime uvx acme-mcp-search --stdio # OCI — self-contained image, Streamable HTTP docker run -p 3333:3333 ghcr.io/acme/mcp-search:1.4.2 # registryType values the validator accepts today npm pypi nuget cargo oci mcpb
Versioning: SemVer with spec-version pinning.
Two version numbers travel together, and confusing them is where compatibility bugs come from. The server's own version is SemVer — 1.4.2 — with the usual contract: patch for bugfixes, minor for additive tool changes, major for breaking schema changes. That is the number the Registry indexes, and the one mcp-publisher publish refuses to reuse. The other number is the MCP protocol version, a dated spec revision — currently 2026-07-28, which succeeded 2025-11-25. As of that revision it is no longer settled once in an initialize handshake, because there is no handshake any more: the version travels on every request, and a client that wants to know what a server speaks before committing calls server/discover, which every server must implement and which answers with the list of supported versions. Both sides must handle the "host newer than server" and "server newer than host" cases without crashing. A server that pins to exactly one spec version turns every spec bump into a forced upgrade for its clients — the transition from HTTP+SSE to Streamable HTTP took months precisely because servers pinned narrowly.
What does not connect the two numbers is the Registry. server.json has no protocol-version field at all: the manifest describes the package, the transports and the environment, and says nothing about which spec revisions the server speaks. A host therefore cannot filter the Registry by spec compatibility before installing — it finds out at the first request, or by calling server/discover once it is connected. Document your supported revisions in the description or the repository README, because right now that is the only place a prospective user can read them.
Runtime discovery now exists, and the roadmap is going somewhere else.
A host that already knows a server's URL — because it is enterprise-hosted, local, or bookmarked — no longer needs the Registry to learn what the server does. The 2026-07-28 revision made server/discover mandatory, and it answers with supportedVersions, capabilities, optional instructions, and the caching hints ttlMs and cacheScope. That is the runtime discovery path, and it arrived inside the protocol rather than beside it at a well-known URL. The general pattern is covered in the capability discovery essay.
What the roadmap actually prioritises next is a different problem: not "how does a host find out what a server offers" but "how does a host avoid ingesting a catalogue it cannot afford". The Core Primitives working group is chartered on progressive discovery — clients learning a server's tools and resources as they need them instead of taking the full list up front — explicitly tied to the caching work that added ttlMs and cacheScope, and extending it toward ETags. Capability scoping for tool lists is named as follow-on work from the stateless revision. Design your server metadata so a host can take it in pieces, and so a list result is worth caching; that is the axis the protocol is moving along.