# The MCP Registry — publishing a server so people can find it

**Load this when:** you are publishing an MCP server, building something that consumes the
registry, or deciding whether to run a private one.

**Spec pinned:** MCP Registry (preview), `server.json` schema `2025-12-11`, API `v0.1` · read 2026-08-13

Schema: `https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json`.
API: `https://registry.modelcontextprotocol.io`.

**It says preview and means it.** The documentation warns of breaking changes and data resets
before general availability. The API is under a declared freeze at **v0.1** with no breaking
changes expected during stabilization — that is a promise about a window, not about forever.

## Contents

- What it is, and what it is not
- The three refusals
- `server.json`
- Namespaces, and how ownership is proved
- The publish flow
- Consuming it
- Traps

## What it is, and what it is not

The official centralized **metadata** repository for publicly accessible MCP servers, backed
by Anthropic, GitHub, PulseMCP and Microsoft.

**The split that explains everything else:** package registries — npm, PyPI, Docker Hub —
host code and binaries. **The MCP Registry hosts metadata pointing at those packages.** A
`weather-mcp` package lives on npm; the registry maps "weather v1.2.0" to `npm:weather-mcp`.

It therefore also delegates **security scanning** downward: to the package registries, which
already scan, and to downstream aggregators, which may add curation and ratings. The registry
itself does namespace authentication and metadata hosting. Do not read presence in the
registry as a safety signal — it is an identity signal.

## The three refusals

Each of these is a design decision that will otherwise waste a day:

1. **No private servers.** A server qualifies if its installation method is publicly
   available (public npm package, public Docker image) *or* the server itself is publicly
   reachable. Something at `mcp.acme-corp.internal`, or behind a private Artifactory, does
   not belong here. **Run your own private registry for those.**
2. **Not for direct host consumption.** Host applications are told to consume *other*
   registries — downstream marketplaces — via a REST API conforming to the official
   registry's OpenAPI spec. Aggregators are expected to pull on a regular but infrequent
   basis, on the order of hourly.
3. **Not designed for self-hosting.** The official codebase is explicitly not built for it,
   and the maintainers do not support that use. Forking means operating it yourself,
   indefinitely. What *is* supported: implementing the published OpenAPI spec, so your
   private registry gets existing host support for free.

## `server.json`

Generated by `mcp-publisher init` and edited by hand:

```json
{
  "$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" }
    }
  ]
}
```

`environmentVariables` entries carry `name`, `description`, `isRequired`, `format` and
`isSecret` — the last one is how a host knows not to print it.

**The `$schema` date and your `version` are unrelated.** One is the registry's schema
revision; the other is your server's. They will never match and are not supposed to.

**Keep the registry version equal to the package version.** An npm package at 1.2.0 whose
registry entry says 1.0.0 makes every bug report unactionable, because nobody can tell which
artifact the reporter ran.

## Namespaces, and how ownership is proved

Names are **reverse-DNS**, and the prefix is a claim you must be able to prove:

| Name shape | Proved by |
|---|---|
| `io.github.username/server` | GitHub login as that user, or GitHub OIDC from that repo's Actions |
| `com.example/server` | DNS challenge, or HTTP challenge, on that domain |

Four authentication methods: **GitHub OAuth**, **GitHub OIDC** (for publishing from CI),
**DNS verification**, **HTTP verification**.

**GitHub is the shortest path** — `mcp-publisher login github`, device flow, done; the name
must start with your username.

**DNS and HTTP claim a domain with an Ed25519 key.** DNS puts the public key in a TXT
record; HTTP serves it at `https://<domain>/.well-known/mcp-registry-auth`. Use HTTP when
you control the web root but not the zone.

```bash
openssl genpkey -algorithm Ed25519 -out mcp-registry-key.pem
PUBLIC_KEY="$(openssl pkey -in mcp-registry-key.pem -pubout -outform DER | tail -c 32 | base64)"
# DNS:  <domain>. IN TXT "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}"
# HTTP: echo "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}" > mcp-registry-auth
```

**`mcp-registry-key.pem` is a publishing credential** — it is the thing that lets someone
replace your server entry. Secret store, not the repository, and the CI job that uses it
should be its only consumer.

**Package-side verification is separate and easy to forget.** For npm, `package.json` must
carry an `mcpName` property, and it must equal the `name` in `server.json`. This is what ties
the metadata to the artifact; without it the registry rejects the publish with *"Registry
validation failed for package"*.

## The publish flow

Metadata points at an artifact, so **the artifact ships first**:

```bash
npm publish --access public                    # 1. the package must exist

brew install mcp-publisher                     # 2. or download the release binary
mcp-publisher init                             # 3. writes server.json from your project
mcp-publisher login github                     # 4. device-code flow, prints a code
mcp-publisher publish                          # 5.
```

Verify it landed, rather than trusting the success line:

```bash
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"
```

For CI, GitHub OIDC removes the interactive device-code step — that is the whole reason it
exists as a separate method. Install the CLI in CI from the released binary:

```bash
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
```

**Two rules for an automated publish**, both learned the expensive way:

- **Gate the publish on the validator, in the same job.** A registry entry pointing at a
  broken build is worse than no entry, because clients cache it.
- **Fail on a version already published** rather than skipping quietly. A silent skip is how
  a tag goes green while the registry still serves the previous version.

**Failure modes worth recognizing on sight:**

| Message | Cause |
|---|---|
| `Registry validation failed for package` | `mcpName` missing from `package.json`, or not equal to `server.json`'s `name` |
| `Invalid or expired Registry JWT token` | re-run `mcp-publisher login github` |
| `You do not have permission to publish this server` | the namespace does not match the auth method — GitHub auth requires `io.github.<your-user>/` |

## Consuming it

If you are building a marketplace or an aggregator: pull on a schedule, not per request; the
metadata is **deliberately unopinionated**, so ratings, curation and trust signals are yours
to add. If you are building a host application: consume an aggregator that implements the
OpenAPI spec rather than the official registry directly — that is the stated topology.

## Traps

- **Publishing metadata before the artifact.** The registry validates against a package that
  must already exist.
- **A namespace you cannot prove.** Pick it after you know which auth method you will use,
  not before.
- **Version drift between `package.json`, `server.json` and the published package.** Three
  places, one number.
- **Reading registry presence as vetting.** It proves who published, not that it is safe.
- **Planning to self-host the official codebase.** Implement the OpenAPI spec instead.
- **Building on preview guarantees.** Data resets are explicitly on the table.
