Learn Build MCP Servers: Extend Claude with Custom Tools Publishing: npm, PyPI, and the MCP Registry

Publishing: npm, PyPI, and the MCP Registry

Advanced 🕐 22 min Lesson 15 of 15
What you'll learn
  • Publish a TypeScript MCP server to npm and a Python server to PyPI so users can install without cloning the repo
  • Submit a server to the Official MCP Registry by creating the required server.json manifest
  • Set up a GitHub Actions CI/CD pipeline that publishes on every tagged release
  • Apply semantic versioning correctly to avoid breaking cached client capability lists

Why Publish?

A server that lives only in your repo is useful to you. A published package is useful to everyone. Publishing to npm or PyPI means users can install your server with a single command, without cloning your code, installing dev dependencies, or figuring out how to run it. Adding it to the Official MCP Registry means it's discoverable by anyone browsing the MCP ecosystem — including Claude.ai's integration catalog.

This lesson walks through the full publication lifecycle: package preparation, registry submission, README conventions, versioning, and CI/CD automation.

Publishing a TypeScript Server to npm

A publishable npm package needs three things in package.json: a "bin" entry pointing to the compiled entry point, a "files" array including the build/ directory, and "type": "module" for ESM output.

{
  "name": "mcp-your-server",
  "version": "1.0.0",
  "type": "module",
  "bin": {
    "mcp-your-server": "./build/index.js"
  },
  "files": ["build"],
  "scripts": {
    "build": "tsc",
    "prepare": "npm run build"
  }
}

The publication steps are:

  1. Run npm run build to compile TypeScript to JavaScript.
  2. Run npm login and authenticate with your npm account.
  3. Run npm publish --access public to push the package.

Once published, users don't even need to install globally. They reference it in their MCP config with npx:

{
  "mcpServers": {
    "your-server": {
      "command": "npx",
      "args": ["-y", "mcp-your-server"]
    }
  }
}

The -y flag auto-confirms the install on first run. This is the recommended pattern — it always pulls the latest version without a manual update step.

Publishing a Python Server to PyPI

The modern Python workflow uses uv, the fast Rust-based package manager that's become the standard in the MCP Python ecosystem.

Make sure your pyproject.toml has a [project.scripts] entry:

[project]
name = "mcp-your-server"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = ["mcp[cli]>=1.0.0"]

[project.scripts]
mcp-your-server = "your_server.main:main"

Then publish:

  1. Run uv build to generate dist/*.whl and dist/*.tar.gz.
  2. Set your PyPI token: export UV_PUBLISH_TOKEN=pypi-...
  3. Run uv publish to upload both distribution files.

Users install with pip install mcp-your-server or run directly with uvx mcp-your-server (no install needed). In config files:

{
  "mcpServers": {
    "your-server": {
      "command": "uvx",
      "args": ["mcp-your-server"]
    }
  }
}

The traditional alternative — pip install build twine, then python -m build, then twine upload dist/* — still works, but uv is faster and handles virtual environments automatically.

The Official MCP Registry

The Official MCP Registry at modelcontextprotocol.io/registry is a metadata-only catalog. It doesn't host packages — it points to them. Submitting your server creates a server.json manifest that records the name, description, source URL, package coordinates, and supported transport. Claude.ai uses this registry as its primary discovery source for integrations.

To submit:

  1. Publish your package to npm or PyPI first — the registry won't accept submissions that don't point to a real installable package.
  2. Use the mcp-publisher CLI (npx mcp-publisher submit) or open a GitHub PR against the registry repository with your server.json.
  3. The manifest you submit includes: server name, short description, GitHub source URL, npm package name (or PyPI package name), and transport type (stdio or streamable-http).

Getting listed in the registry significantly boosts discoverability. It's worth the extra step even for modest tools.

README Conventions for Registry Approval

The registry maintainers review your README as part of the submission process. A README that passes review includes:

  • One-line description of what the server does at the top.
  • Quick install command: a working npx or uvx invocation users can copy immediately.
  • Exact config block: the JSON snippet for claude_desktop_config.json (or the equivalent for your target clients).
  • Tool list: each tool's name and a one-sentence description of what it does.
  • mcp-name marker (PyPI only): add mcp-name: your-server-name somewhere in the README body so the registry indexer can associate the PyPI package with the server name.

Don't underestimate the README. It's the first thing a user sees, and it's what the registry uses to populate the listing page.

Versioning

Use strict semantic versioning. This matters more for MCP servers than for typical libraries because clients may cache capability lists between sessions.

  • Patch (1.0.0 → 1.0.1): bug fixes, documentation updates, no tool schema changes.
  • Minor (1.0.0 → 1.1.0): new tools added; existing tools unchanged. Backwards compatible.
  • Major (1.0.0 → 2.0.0): a tool is removed, renamed, or its parameter schema changes in a breaking way. Clients that cached the old schema will break.

If you're unsure whether a change is breaking, it probably is. Bump the major version and document the migration path.

CI/CD: Automate Releases with GitHub Actions

Manual npm publish steps get forgotten or skipped under pressure. Automate them with a GitHub Actions workflow that triggers on a version tag push:

# .github/workflows/publish.yml
name: Publish to npm
on:
  push:
    tags: ["v*"]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: "https://registry.npmjs.org"
      - run: npm ci
      - run: npm run build
      - uses: JS-DevTools/npm-publish@v2
        with:
          token: ${{ secrets.NPM_TOKEN }}

For Python, replace the last step with:

      - run: pip install uv
      - run: uv build
      - run: uv publish
        env:
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}

Store NPM_TOKEN and PYPI_TOKEN as GitHub repository secrets (Settings > Secrets and variables > Actions). The workflow publishes automatically whenever you push a tag like v1.2.0.

What to Try Next

  • Publish a minimal server to npm under a scoped package name (@yourname/mcp-test-server) to practice the full flow in a low-stakes way.
  • Write a README that meets all five registry conventions listed above, then check it against the actual submission criteria in the registry GitHub repo.
  • Set up the GitHub Actions workflow on a test repo and verify it publishes correctly on a tag push before applying it to your real server.
  • You've now completed Track 11. You can build local stdio servers, remote HTTP servers, secure them with auth, and share them with the world — that's the full MCP server development lifecycle.
Key takeaways
  • npm users can run a published server with npx -y your-package — no global install needed; reference it in configs as 'command': 'npx', 'args': ['-y', 'your-package']
  • Python: uv build + uv publish is the modern workflow; add UV_PUBLISH_TOKEN to your environment and set mcp-name: your-server-name in your README for PyPI packages to be indexable by the MCP Registry
  • The Official MCP Registry is metadata-only — publish your package to npm or PyPI first, then submit a server.json manifest pointing to it
  • Use semantic versioning strictly: breaking tool schema changes require a major version bump because clients may cache capability lists between sessions
  • A good README is required for registry approval: include the exact config JSON block, list every tool with a one-line description, and provide a working quick-start install command