Run and distribute an MCP server for real users. The failures this skill fixes: in-memory session state that breaks behind a load balancer, servers with no observability into per-tool behavior, big-bang deploys of a protocol surface agents depend on, and "distribution" that ends at a git clone.
Registry/packaging facts verified 2026-07-20 (registry is in preview — re-verify at https://modelcontextprotocol.io/registry/ before publishing).
When NOT to use
- Writing/fixing server code or transports →
mcp-server-implementation(incl. SSE→Streamable HTTP migration). - OAuth/token wiring →
mcp-server-auth. Hardening/sandboxing →mcp-server-security. Evals/load-test design →mcp-server-testing. - Generic web-app deployment with no MCP server involved.
Workflow
1. Choose the delivery modes
| Audience | Ship as |
|---|---|
| Technical users, local | npm/PyPI package run over stdio (pinned install command; never npx -y pkg@latest in docs) |
| Non-technical users, local | MCPB bundle (.mcpb, ex-.dxt): zip + manifest, one-click install; mark secrets "sensitive": true → OS keychain |
| Multi-user / SaaS | hosted Streamable HTTP service |
| Both local + remote | one core tool layer, thin per-transport entrypoints |
2. Architect the remote service stateless
- App nodes hold no cross-request state: sessions/workflow state live in Redis/Postgres keyed by session id (bound to user identity), so any node serves any request behind a plain load balancer. Sticky sessions are a liability, not a strategy — and the 2026-07-28 spec revision removes protocol sessions entirely, so statelessness is also future-proofing.
- Modular monolith first; queue/workers behind slow or fan-out tools to keep p95 tool latency flat; microservices only for concrete org/scale boundaries.
- Serverless caveats: ~30s execution ceilings kill long tool calls (use
progress + async patterns from
mcp-server-implementation), and long-lived SSE streams need platform support. Managed MCP platforms and gateways (auth/rate-limit/audit termination) are legitimate shortcuts — the auth requirements move there, they don't disappear.
3. Health and rollout
- Liveness = process wedged; readiness = real dependency health (DB, cache, downstream APIs, auth discovery) — not "port open".
- Progressive delivery: canary or blue/green; at each step verify
initialize, auth discovery, and one canonicaltools/callbefore shifting more traffic. The per-request nature of Streamable HTTP makes canaries cheap — use them. - Containers: slim base, non-root, pinned image tags, resource limits.
4. Observability per tool call
- One structured log line + one trace span per tool invocation. Span
names like
tools.call.<tool_name>; include identity/session as trace attributes and log fields — never as metric labels (cardinality). - Metrics that matter: per-tool call count, latency percentiles, error rate,
isErrorrate, active streams/sessions, downstream latency, token/cost counters where billable. - Alert on: sustained 5xx, auth-discovery failures, tool timeout spikes,
readiness drops, per-caller cost anomalies. Behavioral anomalies (guardrail
blocks, weird session lengths) are the security signal —
mcp-server-securityconsumes these. - OpenTelemetry end-to-end; logs redact secrets/PII (audit-store rules live
in
mcp-server-security).
5. Package for local install
MCPB: manifest + server files zipped; supports Node/Python/binary; Claude
Desktop bundles a Node runtime; secrets marked "sensitive" are stored in
the OS keychain. Keep the manifest's env/config schema minimal and validated
at startup. For npm/PyPI: ship an executable entrypoint (npx your-server /
uvx your-server), document the exact pinned client-config JSON, and keep
tool code per-call self-contained so a misconfigured install can still start
and list tools (users see the config error at call time, not a dead server).
Server docs ship with the package: every tool documented with at least a few
working invocation examples, required permissions/scopes, and rate limits.
6. Publish to the MCP Registry
brew install mcp-publisher # or the release binary
mcp-publisher init # scaffolds server.json
mcp-publisher login github # verifies the io.github.* namespace
mcp-publisher publish
server.json (schema: static.modelcontextprotocol.io) points at your
packages (npm/PyPI/Docker) and/or remotes URL; npm packages must carry a
matching mcpName field in package.json for verification; custom-domain
namespaces verify via DNS/HTTP challenge. The registry hosts metadata
only, is consumed by downstream sub-registries, does not security-review
listings, and is in preview — expect possible breaking changes.
python3 "${CLAUDE_SKILL_DIR}/scripts/check_server_json.py" server.json
gates the manifest before publishing.
7. Version without breaking clients
- Server/package: semver — breaking tool-interface change = major.
- Protocol: date-based revisions negotiated per session; support the current
revision plus what your real clients need; send/expect
MCP-Protocol-Versioncorrectly (mechanics inmcp-server-implementation). - Tool surface stability: additive fields over renames; deprecate with a
notice window in tool descriptions (agents read them) before removal;
schema-snapshot diff in CI (
mcp-server-testing) catches accidents. - Keep a README compatibility note: protocol revisions supported, clients tested, SDK version.
Output spec
A production deployment: stateless nodes with externalized state; readiness
gated on real dependencies; canary rollout verifying init/auth/tool-call;
per-tool logs+spans+metrics with low-cardinality labels and actionable
alerts; local packages (npm/PyPI and/or MCPB) with pinned install docs;
registry entry published from a validated server.json under a verified
namespace; semver + protocol compatibility note in the README.
Gotchas
- In-memory session state is the #1 horizontal-scaling breaker — externalize before adding replicas, or go stateless and skip sessions entirely.
- Client configs pinned to
@latestre-introduce the supply-chain risk your pinning removed — pin in docs and MCPB manifests too. - Registry preview status is real: entries/data may reset before GA; don't make the registry your only distribution channel.
- Corporate proxies and some LBs buffer or kill long-lived SSE — test the GET stream through the real ingress path, enable heartbeats, and prefer JSON response mode where streams aren't needed.
- A gateway that terminates auth/ratelimits does not exempt the server from validating what reaches it — defense in depth, not delegation.
Pointers
references/operations.md— reference architecture, probe/rollout/OTel examples, MCPB manifest anatomy, server.json anatomy, gateway pattern, versioning matrix template.scripts/check_server_json.py— deterministic registry-manifest gate.