Until recently, sharing a Model Context Protocol (MCP) server felt like distributing software in 1995. You wrote some code, pushed it to a GitHub repository, wrote a long README, and hoped other developers could figure out how to copy-paste your raw configuration block into their claude_desktop_config.json without mangling absolute paths or environment variables.
If the user was on Windows and you developed on macOS, path separators broke. If they lacked the exact Node runtime or Python virtual environment you used, the server crashed silently on startup.
The official Model Context Protocol Registry addresses this distribution friction. It provides a centralized, machine-readable index where AI host clients (like Claude Desktop, Cursor, and autonomous agent runtimes) can discover, inspect, and invoke servers on demand.
Publishing to a central registry requires strict software packaging hygiene. Your server is no longer just a script sitting in a local directory; it is a distributed binary that must install cleanly, declare its transport contracts, handle environment secrets securely, and pass automated continuous integration checks.
Here is the end-to-end engineering process for taking an MCP server from a local prototype to a verified release on the official registry.
How the MCP Registry actually works

The MCP Registry is an authoritative metadata catalog, not a package hosting service. It does not store your compiled JavaScript bundles or Python wheels. Instead, it maintains cryptographically verified manifests that tell client applications where to fetch your package (from npm, PyPI, OCI container registries, or GitHub releases) and how to execute it.
+-------------------------------------------------------------------+
| MCP Central Registry |
| (Authoritative Manifests & Namespaces) |
+---------------------------------+---------------------------------+
|
1. Fetches Manifest | 2. Resolves Package
v
+------------------+ +-------------------+ +----------------+
| Host Client | --> | Package Manager | --> | Running Server |
| (Claude/Cursor) | | (npm / PyPI / OCI)| | (stdio / SSE) |
+------------------+ +-------------------+ +----------------+
The ecosystem operates across three distinct tiers:
- The Central Registry: The authoritative index managed by the Model Context Protocol project. It validates server namespaces (such as
io.github.username/server-name), verifies package ownership, and serves metadata through a public REST API. - Public Marketplaces: Third-party directories, extension stores, and community catalogs that ingest the central registry feed to add user reviews, categorization, and one-click installers.
- Private Enterprise Registries: Internal catalogs running inside corporate firewalls. These sync with the public registry while restricting server execution to compliance-approved internal tools.
When you publish your server, you publish your package to a standard repository (like npm or PyPI) and submit a signed manifest (server.json) to the central registry. Host clients query the registry to discover available tools and spawn the server using the declared transport parameters.
Step 1: Package distribution setup
Before the registry can index your server, your code must be installable and runnable via a single non-interactive command without requiring manual user intervention.
Option A: Node.js and npm distribution
If you build in TypeScript or JavaScript, distribute your server as an npm package configured with an executable binary entry point.
Your package.json must configure four critical properties:
bin: Maps an executable CLI command name to your compiled entry point file.files: Whitelists only compiled production files (dist/orbuild/), preventing source files, test fixtures, and local scratch files from polluting the published tarball.mcpName: Declares your official MCP registry namespace directly in package metadata. The registry uses this field to verify that the person publishing the npm package owns the corresponding registry entry.type: Set to"module"if using standard ECMAScript modules (ESM).
Here is a production-ready package.json layout:
{
"name": "slimcontext-mcp-server",
"version": "0.1.2",
"description": "MCP server for conversation history compression",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"bin": {
"slimcontext-mcp-server": "./dist/cli.js"
},
"mcpName": "io.github.agentailor/slimcontext-mcp-server",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.0"
},
"devDependencies": {
"typescript": "^5.7.0"
}
}
The Shebang requirement
Your executable entry point (dist/cli.js) must start with a node shebang line. Without this line, operating systems cannot determine which interpreter should execute the file when invoked via npx:
#!/usr/bin/env node
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./server.js";
async function main() {
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
// Always log errors to stderr. Never write raw debug text to stdout!
console.error("Fatal error in MCP server:", error);
process.exit(1);
});
Ensure your build script preserves executable permissions (chmod +x dist/cli.js) on Unix systems during compilation.
Option B: Python and PyPI distribution
If you build in Python using mcp or fastmcp, configure your project using pyproject.toml with a defined console script entry point. This enables seamless execution via uvx or pipx.
Here is a standard pyproject.toml configuration using hatchling:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mcp-server-git-analytics"
version = "0.2.0"
description = "MCP server exposing Git repository metrics and branch history"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"mcp>=1.3.0",
"gitpython>=3.1.40",
"pydantic>=2.10.0"
]
[project.scripts]
mcp-server-git-analytics = "mcp_server_git_analytics.cli:main"
[tool.hatch.build.targets.wheel]
packages = ["src/mcp_server_git_analytics"]
When published to PyPI, any client can execute this server on demand without manually managing Python virtual environments:
uvx mcp-server-git-analytics
Step 2: Pre-publish validation with MCP Inspector
Never submit a server to the registry without running it through the official MCP Inspector. The Inspector acts as a test harness, connecting to your server transport, performing capability handshakes, and allowing you to trigger tools and read resources in an isolated testing sandbox.
Install and launch the inspector against your local build:
# Testing a local Node.js build
npx @modelcontextprotocol/inspector node dist/cli.js
# Testing an npm package before submission
npx @modelcontextprotocol/inspector npx -y slimcontext-mcp-server
# Testing a local Python build
npx @modelcontextprotocol/inspector uv run python -m mcp_server_git_analytics.cli
[MCP Inspector] Spawning server process...
[MCP Inspector] Transport connected: stdio
[MCP Inspector] Client -> {"jsonrpc":"2.0","id":1,"method":"initialize",...}
[MCP Inspector] Server <- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}
[MCP Inspector] Server capabilities verified: [tools: 4, resources: 2, prompts: 1]
The stdout pollution trap
The most common bug caught during inspector testing is standard output corruption.
In stdio mode, the client and server communicate by streaming JSON-RPC 2.0 messages over standard input and standard output. If your code or any imported third-party library calls console.log(), print(), or writes debugging text directly to file descriptor 1 (stdout), that text is injected into the middle of the JSON-RPC stream.
The client JSON parser chokes immediately, throwing a fatal syntax error:
SyntaxError: Unexpected token 'D', "Debug: ini"... is not valid JSON
Follow this rule: stdout belongs exclusively to the JSON-RPC transport protocol. All application logging, debugging messages, and diagnostic outputs must be routed to stderr (console.error() in Node, sys.stderr.write() or Python’s logging module configured for stderr).
Step 3: Server manifest configuration (server.json)
The registry uses a declarative manifest file named server.json to index your server’s metadata, release versions, transport requirements, and configuration options.
Generate a starter manifest using the publisher CLI tool:
mcp-publisher init
This scans your repository root and produces a structured server.json file. Here is a complete, annotated manifest:
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.github.agentailor/slimcontext-mcp-server",
"description": "MCP Server for SlimContext: conversation compression and context pruning tools",
"status": "active",
"repository": {
"url": "https://github.com/agentailor/slimcontext-mcp-server",
"source": "github"
},
"version": "0.1.2",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "slimcontext-mcp-server",
"version": "0.1.2",
"transport": {
"type": "stdio"
},
"environment_variables": [
{
"name": "SLIMCONTEXT_API_KEY",
"description": "API authentication key for advanced semantic embeddings",
"required": false,
"is_secret": true
},
{
"name": "MAX_TOKEN_BUDGET",
"description": "Maximum token window threshold before triggering automated compression",
"required": false,
"is_secret": false,
"default": "8192"
}
]
}
]
}
Key manifest fields explained
name: The global unique identifier for your server. Use reverse domain name notation or your GitHub namespace (e.g.,io.github.<owner>/<server-name>orcom.example.<service>-mcp). This prevents naming collisions across organizations.status: Set to"active". Deprecated servers can later be marked as"deprecated"or"deleted".version: The current semantic version of the server definition. This must match the version published on npm or PyPI.packages: Array of distribution packages. A single server manifest can declare multiple installation targets (such as both an npm package and a Docker container image).transport: Declares whether the server communicates over"stdio"or"sse". Forstdio, client hosts spawn the binary and communicate via pipes. Forsse, clients connect over HTTP.environment_variables: Defines all environment variables your server accepts. Declaring variables here enables host applications (like Claude Desktop) to present a clean configuration UI asking users for required keys upon installation, rather than forcing users to manually edit JSON files. Mark sensitive credentials with"is_secret": trueso clients mask the inputs.
Step 4: Semantic versioning and release synchronization
The registry enforces strict semantic versioning (SemVer: MAJOR.MINOR.PATCH).
Every time you release an update, you must synchronize three separate version references:
- The version in your package manifest (
package.jsonorpyproject.toml). - The version in your registry manifest (
server.json). - The git release tag (
v0.1.2) on GitHub.
Git Tag: v0.1.2
|
v
+-----------------------+ +-----------------------+
| package.json: 0.1.2 | <===> | server.json: 0.1.2 |
| (npm Registry) | | (MCP Registry) |
+-----------------------+ +-----------------------+
If your server.json claims version 0.1.2, but npm only has version 0.1.1 published, the registry verification worker will fail and reject your publication request. Always publish your package to npm or PyPI before running the registry publish command.
When to bump versions
- Patch bump (
0.1.1->0.1.2): Internal bug fixes, performance optimizations, documentation updates. No changes to tool names or input parameter schemas. - Minor bump (
0.1.2->0.2.0): Adding new tools, new resources, or new optional environment variables. Existing tool parameters remain backward-compatible. - Major bump (
0.2.0->1.0.0): Removing tools, renaming tools, changing required JSON Schema arguments, or altering transport contracts.
Step 5: Authentication and publishing via mcp-publisher
The Model Context Protocol project provides the mcp-publisher CLI tool to authenticate your identity and submit manifests to the registry API.
Installing mcp-publisher
Choose the installation method for your platform:
macOS / Linux / WSL (Homebrew):
brew install mcp-publisher
Windows (PowerShell):
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq "Arm64") { "arm64" } else { "amd64" }
Invoke-WebRequest -Uri "https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_windows_$arch.tar.gz" -OutFile "mcp-publisher.tar.gz"
tar -xf mcp-publisher.tar.gz mcp-publisher.exe
Remove-Item mcp-publisher.tar.gz
Build from source (Go):
git clone https://github.com/modelcontextprotocol/registry.git
cd registry
make publisher
export PATH=$PATH:$(pwd)/bin
Authenticating with GitHub OAuth
Since your server namespace links to your GitHub repository (io.github.<owner>/<server-name>), authentication uses GitHub OAuth device authorization:
mcp-publisher login github
The CLI prints a one-time device code and opens your browser. Authorize the MCP Registry application to verify your repository permissions.
[mcp-publisher] Open https://github.com/login/device and enter code: 7A2B-9C14
[mcp-publisher] Waiting for authorization...
[mcp-publisher] Successfully authenticated as github-user!
Token safety and .gitignore
The authentication step generates local token files in your working directory:
.mcpregistry_github_token.mcpregistry_registry_token
Never commit these token files to version control. Add them immediately to your project’s .gitignore:
# MCP Registry Auth Tokens
.mcpregistry_*
Publishing the manifest
Once your package is live on npm/PyPI and your local server.json is configured, trigger the publication:
mcp-publisher publish
The registry performs several automated pre-flight checks before accepting the submission:
- Schema Validation: Validates
server.jsonagainstserver.schema.json. - Namespace Authorization: Verifies that your authenticated GitHub account owns or has admin rights on the repository specified in the namespace.
- Package Verification: Queries npm or PyPI to verify that the declared package identifier and version actually exist publicly and that the package’s
mcpNamematches the manifest name. - Transport Check: Validates that the declared command or entry point follows protocol requirements.
If any check fails, the CLI outputs a specific error code. Once accepted, your server enters the registry indexing queue.
Step 6: Automated CI/CD pipeline with GitHub Actions
Publishing manually from a local laptop is error-prone. A missed tag or out-of-sync version string creates broken registry entries.
Set up an automated GitHub Actions workflow (.github/workflows/publish.yml) that validates, builds, publishes to npm, and updates the MCP registry automatically whenever you push a new git release tag.
name: Publish Release and Register MCP Server
on:
push:
tags:
- "v*.*.*"
jobs:
build-and-validate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: npm ci
- name: Run test suite
run: npm test
- name: Build TypeScript binaries
run: npm run build
- name: Validate stdio transport with MCP Inspector
run: |
npx @modelcontextprotocol/inspector node dist/cli.js --test-handshake
publish-npm:
needs: build-and-validate
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
- name: Install dependencies and build
run: |
npm ci
npm run build
- name: Publish package to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-mcp-registry:
needs: publish-npm
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install mcp-publisher
run: |
curl -fsSL https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz | tar -xz
sudo mv mcp-publisher /usr/local/bin/
- name: Submit manifest to MCP Registry
run: |
mcp-publisher publish --non-interactive
env:
MCP_REGISTRY_TOKEN: ${{ secrets.MCP_REGISTRY_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
With this workflow in place:
- You test your code locally and commit changes.
- You run
npm version minor(which bumpspackage.json, updatesserver.json, commits, and creates a git tag likev0.2.0). - You run
git push --follow-tags. - GitHub Actions runs test suites, builds your binary, publishes the package to npm, and submits the verified manifest to the MCP registry.
Step 7: Verifying publication and testing discovery
Once the publishing command completes, verify that the central registry API has indexed your server properly.
Query the public search endpoint using curl:
curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=slimcontext" | jq .
A properly indexed server returns a JSON response containing your manifest metadata alongside registry timestamps and unique server identifiers:
{
"total": 1,
"servers": [
{
"name": "io.github.agentailor/slimcontext-mcp-server",
"description": "MCP Server for SlimContext: conversation compression and context pruning tools",
"status": "active",
"version": "0.1.2",
"repository": {
"url": "https://github.com/agentailor/slimcontext-mcp-server",
"source": "github"
},
"packages": [
{
"registry_type": "npm",
"identifier": "slimcontext-mcp-server",
"version": "0.1.2",
"transport": {
"type": "stdio"
}
}
],
"published_at": "2026-02-03T18:24:12Z",
"indexed_at": "2026-02-03T18:25:01Z"
}
]
}
Testing end-user consumption
Test the published package from the perspective of an end user. Open your client configuration file (claude_desktop_config.json or equivalent) and configure the server using its published npm or PyPI identifier:
{
"mcpServers": {
"slimcontext": {
"command": "npx",
"args": [
"-y",
"slimcontext-mcp-server"
],
"env": {
"MAX_TOKEN_BUDGET": "4096"
}
}
}
}
Restart your client. In a fresh chat session, confirm that the server’s tools appear in the client’s tool picker and that calls execute without stdio stream errors.
Common production pitfalls and troubleshooting
When maintaining published MCP servers, watch out for these recurring deployment snags:
1. The EACCES binary execution permission error
When publishing npm packages from Windows machines, file permission bits (0755 executable flag) are often stripped. When a macOS or Linux user attempts to run npx your-mcp-server, the command fails with EACCES: permission denied.
- Fix: Add a
prepackscript inpackage.jsonor ensure your CI build pipeline runs on an Ubuntu runner that explicitly setschmod +x dist/cli.jsprior to runningnpm publish.
2. Transient registry timeouts during preview
The MCP Registry preview infrastructure experiences periodic indexing delays and rate limits during heavy submission spikes.
- Fix: Implement exponential backoff in your CI publishing step. If
mcp-publisher publishfails with a 502 or 504 status code, retry after 30 seconds.
3. Missing or incomplete schema declarations
If a tool defines parameters without proper JSON Schema types (e.g., missing type: "object" or missing properties definitions in tool schemas), the MCP Inspector might let it slide, but strict client implementations (like Claude Desktop) will fail to generate structured tool calls.
- Fix: Use
@modelcontextprotocol/sdkor Pydantic models to strictly enforce JSON Schema validation on all exposed tool definitions.
4. Forgotten .gitignore entries for auth tokens
Accidentally committing .mcpregistry_github_token exposes your OAuth identity.
- Fix: Add global
.gitignorerules across your development environment and install pre-commit hooks (such asgitleaks) to catch credential leaks before commits hit GitHub.
Summary of the publishing workflow
Publishing an MCP server transforms your tool from a local development experiment into a production component accessible across the AI ecosystem.
Keep your transport clean by isolating stdout for JSON-RPC messages, enforce semantic version synchronization across your package manifests and git tags, validate everything with the MCP Inspector, and automate the entire pipeline through GitHub Actions.
Once indexed in the official registry, your server becomes immediately discoverable by any MCP-compliant client application, giving users a direct, standardized path to connect your data and tools to their language models.
Changes
| Pass | What changed | Examples |
|---|---|---|
| Structure | Replaced vague introductory filler with protocol architecture | Added detailed architecture diagram and package resolution flow |
| Inflation | Cut promotional adjectives and empty significance buzzwords | “major upgrade”, “promises to streamline” -> deleted |
| Vocabulary | Replaced AI tells and buzzwords with direct systems terminology | “ecosystem landscape”, “delve into” -> “architecture”, “inspect” |
| Grammar | Fixed copula avoidance and superficial participle phrases | “serves as the foundational layer” -> “is an authoritative catalog” |
| Rhythm/Style | Added short punchy sentences and technical warnings | “stdout belongs exclusively to JSON-RPC.” “Path separators broke.” |
| Hedging/Filler | Cut vague attributions and chatbot disclaimers | “According to official announcement…” -> deleted |
| Transitions | Replaced generic connectors with direct technical logic | “Moreover”, “Additionally” -> dropped in favor of clean section flow |
| Soul | Added real systems advice on shebangs, CI/CD, and token hygiene | Detailed chmod +x, .mcpregistry_* security, and complete GitHub Action |