Distribution: Registries and Discovery
Series
MCP Deep Dive← Part 9
Observability and Tracing
You have built an MCP server, tested it, and deployed it. Now your colleague in a different team wants to use it for their agent. They ask you for the endpoint URL. You send it in a Slack message. Three months later you change the deployment URL and six agents break silently. This is the distribution problem, and it is where most MCP efforts stall out at the organisation level.
Distribution is the set of practices that turn a single-team tool into a reusable platform capability: packaging, versioning, registering, and enabling discovery. Done well, another team finds your server, understands its capabilities, and integrates it in a day — without ever needing to message you.
The Distribution Stack
Each layer has concrete tooling choices. Let us go through them.
Packaging
npm for Node.js Servers
For TypeScript/Node MCP servers, npm is the natural packaging format. Structure your package so it exports both the server entrypoint and the type definitions:
{
"name": "@your-org/taskflow-mcp",
"version": "1.2.0",
"description": "MCP server for Taskflow project management",
"main": "dist/server.js",
"types": "dist/server.d.ts",
"bin": {
"taskflow-mcp": "dist/cli.js"
},
"files": ["dist/", "README.md"],
"engines": { "node": ">=20" }
}A bin entry lets teams install and run your server as a CLI:
npx @your-org/taskflow-mcp --port 3000This is exactly how stdio-based developer tools ship. Claude Desktop's MCP configuration references npm-packaged servers this way:
{
"mcpServers": {
"taskflow": {
"command": "npx",
"args": ["@your-org/taskflow-mcp"]
}
}
}Docker for Network Servers
Network-deployed servers should ship as Docker images. Tag with semantic versions:
docker build -t ghcr.io/your-org/taskflow-mcp:1.2.0 .
docker push ghcr.io/your-org/taskflow-mcp:1.2.0
docker tag ghcr.io/your-org/taskflow-mcp:1.2.0 ghcr.io/your-org/taskflow-mcp:latestUse a multi-stage build to keep the image lean:
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]Versioning
MCP uses semantic versioning for both the protocol and the server. Do not conflate them.
protocolVersionin theinitializehandshake is the MCP spec version (e.g."2024-11-05").- Your server's own version tracks its tool/resource/prompt API.
Version your tool schemas explicitly. When you remove or rename a tool, that is a breaking change — bump the major version.
// Communicate breaking changes in the server info
const server = new McpServer({
name: "taskflow-mcp",
version: "2.0.0" // breaking: removed deprecated `get_task_legacy` tool
});Deprecation Pattern
Never remove a tool silently. Add a deprecation notice in the description and keep the old name working for at least one major version:
{
name: "get_task_legacy", // old name
description: "DEPRECATED: Use `get_task` instead. Will be removed in v3.0.",
inputSchema: { /* ... */ }
}Registries
Public: MCP.run and the Ecosystem
The emerging public registry is mcp.run, which lets you publish a server manifest that describes your server's capabilities, connection details, and auth requirements. The format is a JSON document:
{
"name": "taskflow-mcp",
"version": "1.2.0",
"description": "MCP server for Taskflow project management",
"endpoint": "https://mcp.taskflow.example.com/mcp",
"transport": "streamable-http",
"auth": {
"type": "oauth2",
"authorizationUrl": "https://auth.taskflow.example.com/authorize",
"tokenUrl": "https://auth.taskflow.example.com/token",
"scopes": ["tickets:read", "tickets:write"]
},
"capabilities": {
"tools": true,
"resources": true,
"prompts": false
}
}Private: Internal Registry
For enterprise use, a private registry is typically a simple service that stores these manifests and exposes a search API. Even a Git repository with a registry.json file works at small scale:
{
"servers": [
{
"name": "taskflow-mcp",
"team": "platform",
"endpoint": "https://mcp-internal.example.com/taskflow/mcp",
"version": "1.2.0",
"tags": ["project-management", "tickets", "crm"]
},
{
"name": "analytics-mcp",
"team": "data",
"endpoint": "https://mcp-internal.example.com/analytics/mcp",
"version": "0.9.0",
"tags": ["analytics", "reporting", "sql"]
}
]
}A discovery API wrapping this:
# GET /registry/search?q=project
from fastapi import FastAPI
import json
app = FastAPI()
registry = json.load(open("registry.json"))
@app.get("/registry/search")
def search(q: str = ""):
results = [
s for s in registry["servers"]
if q.lower() in s["name"].lower()
or any(q.lower() in tag for tag in s.get("tags", []))
]
return {"results": results}Dynamic Discovery via /.well-known
A server can self-describe at a well-known URL, enabling clients to discover capabilities without a separate registry:
app.get("/.well-known/mcp-server", (_, res) => {
res.json({
name: "taskflow-mcp",
version: "1.2.0",
transport: "streamable-http",
endpoint: "/mcp",
capabilities: {
tools: true,
resources: true,
prompts: false
}
});
});An agent or platform tool can GET /.well-known/mcp-server on any suspected MCP endpoint and immediately know what it is dealing with.
Discovery Flow in Practice
Key Takeaways
- Package npm-based servers with a
binentry so teams can run them vianpxwithout any infrastructure. - Docker images should be tagged with semantic versions and pushed to a container registry alongside a
latesttag. - Version your tool schemas independently of the MCP protocol version; a renamed tool is a breaking change.
- Use a deprecation description field and a one-major-version grace period before removing any tool.
- A
/.well-known/mcp-serverendpoint lets clients self-discover capabilities without a central registry. - An internal registry — even a versioned JSON file in Git — dramatically reduces the friction for teams to find and adopt your server.
Series
MCP Deep Dive← Part 9
Observability and Tracing