---
title: "Publishing to the MCP Registry"
type: entity
tags: [registry, publishing, mcp-publisher, ci-cd, versioning]
created: 2026-07-07
updated: 2026-07-07
sources: ["raw/llms_txt_doc-quickstart-publish-an-mcp-server-to-the-mcp-registry.md", "raw/llms_txt_doc-publishing-remote-servers.md", "raw/llms_txt_doc-how-to-automate-publishing-with-github-actions.md", "raw/llms_txt_doc-how-to-authenticate-when-publishing-to-the-official-mcp-regi.md", "raw/llms_txt_doc-versioning-published-mcp-servers.md"]
source_url: "https://modelcontextprotocol.io/registry/quickstart"
confidence: high
---

## Overview

Publishing to the [[entities/mcp-registry|MCP Registry]] means pushing a `server.json` describing your server, using the official **`mcp-publisher`** CLI. Because the registry only hosts metadata, you first publish the underlying package to its package registry (npm, PyPI, etc.), then authenticate (which determines your namespace), then publish the `server.json`. This page covers the operational how-to: install, authenticate, publish, automate in CI, and version.

## Characteristics

- **Two publishes, in order:** package to npm/PyPI/etc. **first**, then `server.json` to the MCP Registry.
- **Namespace is set by your auth method.** GitHub auth → name must be `io.github.username/*` (or `io.github.orgname/*`). Domain auth → `com.example.*/*` (reverse-DNS of your domain).
- **Name must match everywhere.** The `server.json` `name` must equal the package's ownership marker (`mcpName` in `package.json` for npm; an `mcp-name: $SERVER_NAME` string in the README for PyPI/NuGet).
- **`mcp-publisher` commands:** `init` (create a `server.json` template), `login` (authenticate), `logout` (clear auth), `publish` (push `server.json`).

## How to Use

**Install `mcp-publisher`** (pre-built binary or Homebrew):

```bash
# macOS/Linux
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 && sudo mv mcp-publisher /usr/local/bin/
```

```bash
brew install mcp-publisher
```

**Publish an npm-based server (end to end):**

```bash
# 1. Add the ownership marker to package.json: "mcpName": "io.github.my-username/weather"

# 2. Build and publish the package to npm first
npm install
npm run build
npm adduser              # if not authenticated
npm publish --access public

# 3. Generate and edit server.json
mcp-publisher init

# 4. Authenticate (GitHub device flow) — sets your namespace
mcp-publisher login github

# 5. Publish the server metadata
mcp-publisher publish
```

`mcp-publisher init` produces a template like:

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

The `name` in `server.json` **must** match `mcpName` in `package.json`. Verify with `curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"`.

### Authentication methods

`mcp-publisher login` supports several methods; each maps to a namespace:

- **GitHub (device OAuth):** `mcp-publisher login github` → `io.github.username/*`.
- **GitHub OIDC (for CI):** `mcp-publisher login github-oidc` (no dedicated secret needed).
- **GitHub PAT (for CI):** `mcp-publisher login github --token $TOKEN` (PAT needs `read:org` and `read:user` scopes).
- **DNS:** `mcp-publisher login dns --domain example.com --private-key $KEY` — proves domain ownership via a `v=MCPv1; k=ed25519; p=...` TXT record (Ed25519 or ECDSA P-384; Google KMS / Azure Key Vault supported).
- **HTTP:** `mcp-publisher login http --domain example.com --private-key $KEY` — proves ownership via a `/.well-known/mcp-registry-auth` file hosting the same `v=MCPv1;...` record.

### Publishing a remote (hosted) server

Use the `remotes` property instead of (or alongside) `packages`. A remote server **MUST** be publicly accessible at its URL:

```json
{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "name": "com.example/acme-analytics",
  "version": "2.0.0",
  "remotes": [
    { "type": "streamable-http", "url": "https://analytics.example.com/mcp" }
  ]
}
```

`type` is `"streamable-http"` (recommended) or `"sse"`; a server can offer both at different URLs. `remotes` supports `{curly_brace}` URL template `variables` (with `description`, `isRequired`, `choices`, `default`, `isSecret`) for multi-tenant/regional endpoints, and a `headers` array to instruct clients to send specific HTTP headers (e.g. `X-API-Key`). `remotes` may coexist with `packages` so hosts can pick an install method. See [[syntheses/local-vs-remote-servers]].

### Automating with GitHub Actions

Create `.github/workflows/publish-mcp.yml` triggered on version tags. The recommended OIDC variant:

```yaml
name: Publish to MCP Registry

on:
  push:
    tags: ["v*"] # Triggers on version tags like v1.0.0

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write # Required for OIDC authentication
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v5
      - name: Set up Node.js
        uses: actions/setup-node@v5
        with:
          node-version: "lts/*"
      - name: Install dependencies
        run: npm ci
      - name: Build package
        run: npm run build --if-present
      - name: Publish package to npm
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
      - name: Install mcp-publisher
        run: |
          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
      - name: Authenticate to MCP Registry
        run: ./mcp-publisher login github-oidc
      - name: Publish server to MCP Registry
        run: ./mcp-publisher publish
```

Secrets by method: **OIDC** — none (needs `id-token: write`); **PAT** — `MCP_GITHUB_TOKEN` (a PAT with `read:org`, `read:user`), authenticate with `./mcp-publisher login github --token ${{ secrets.MCP_GITHUB_TOKEN }}`; **DNS** — `MCP_PRIVATE_KEY` (Ed25519 private key). Also add `NPM_TOKEN` for the npm publish. Trigger by pushing a tag:

```bash
git tag v1.0.0
git push origin v1.0.0
```

### Versioning

The `version` string in `server.json` is **required**, **MUST be unique per publication**, and is **immutable once published**. [Semantic versioning](https://semver.org/) is recommended (any format is supported; unparseable versions are always marked "latest"). Version **ranges are prohibited** (`^1.2.3`, `~1.2.3`, `>=1.2.3`, `1.x`, `1.2.*`, `1 - 2`, etc.). Best practices: align the server version with the underlying package version (local servers) or the remote API version (remote servers); for registry-only metadata updates that don't change the package, use a semantic **prerelease** like `1.2.3-1` (note: prereleases sort *before* the matching release, so publishing one after the release won't be marked "latest").

## Related Entities

- [[entities/mcp-registry]] — what you are publishing to (metadata store, package types, aggregators)
- [[entities/typescript-sdk]] · [[entities/python-sdk]] — the servers you package and publish
- [[concepts/versioning]] — the protocol/version-awareness concept
- [[concepts/authorization]] — OAuth for the *served* remote server (distinct from publish-time auth)
- [[syntheses/local-vs-remote-servers]] — choosing `packages` vs. `remotes` in `server.json`
