MCP Deployment
Deploying MCP servers across different environments and transports. Covers local deployment via stdio, remote deployment with SSE and streamable HTTP, Docker containerization, cloud deployment on AWS/GCP/Vercel, npx and uvx distribution for zero-install usage, configuration management, and production hardening.
You are an AI assistant helping developers deploy MCP servers. Deployment strategy depends on the transport (stdio for local, HTTP for remote) and the audience (personal use, team, public). Your role is to guide developers through deployment options with practical configurations. ## Key Points - `--rm` — remove container when done. - `-i` — keep stdin open (required for stdio transport). - Do NOT use `-t` — TTY mode corrupts the JSON-RPC stream. - Use `host.docker.internal` to reach host services. - **Using `-t` (TTY) with Docker stdio** — this adds terminal control characters that corrupt JSON-RPC messages. - **Deploying on serverless for SSE** — serverless functions timeout and lack session persistence. MCP sessions are stateful. - **Hardcoding configuration** — always use environment variables so the same build works across environments. - **No health checks** — remote servers need health endpoints for load balancers and monitoring. - **Ignoring graceful shutdown** — servers that crash without cleanup can leave database connections or file locks open. - **Publishing `node_modules` or `src/`** — use the `files` field in package.json to publish only `dist/`. - **Missing error messages for missing config** — when a required environment variable is absent, tell the user what to set and where. - Start with stdio for local development. Add HTTP transport when you need remote access. ## Quick Example ```bash # Build and publish npm run build npm publish --access public ``` ```bash uv build uv publish ```
skilldb get mcp-server-skills/MCP DeploymentFull skill: 353 linesMCP Deployment
You are an AI assistant helping developers deploy MCP servers. Deployment strategy depends on the transport (stdio for local, HTTP for remote) and the audience (personal use, team, public). Your role is to guide developers through deployment options with practical configurations.
Philosophy
Start local, go remote only when needed. A stdio server running on the user's machine is the simplest deployment — no networking, no auth, no infrastructure. Move to remote deployment when you need shared access, centralized data, or when the server requires resources the client machine does not have. Choose the simplest deployment that meets your requirements.
Techniques
Local Deployment (stdio)
The default and simplest deployment. The AI client spawns the server as a child process:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/my-server/dist/index.js"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/mydb",
"API_KEY": "sk-..."
}
}
}
}
For Python:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["-m", "my_mcp_server"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/mydb"
}
}
}
}
The client manages the server lifecycle — starts it when needed, sends initialize, and terminates it when done.
npx Distribution (TypeScript)
Publish to npm so users can run without cloning or installing:
# Build and publish
npm run build
npm publish --access public
Users configure with:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@myorg/mcp-server-github"],
"env": { "GITHUB_TOKEN": "ghp_..." }
}
}
}
The -y flag auto-confirms the install prompt. npx caches the package after the first run.
Package.json essentials:
{
"name": "@myorg/mcp-server-github",
"version": "1.0.0",
"type": "module",
"bin": {
"mcp-server-github": "dist/index.js"
},
"files": ["dist"],
"engines": { "node": ">=18" }
}
Ensure dist/index.js starts with #!/usr/bin/env node and is listed in bin.
uvx Distribution (Python)
Publish to PyPI for zero-install Python distribution:
uv build
uv publish
Users configure with:
{
"mcpServers": {
"my-server": {
"command": "uvx",
"args": ["my-mcp-server"],
"env": { "API_KEY": "..." }
}
}
}
uvx creates an isolated environment, installs the package, and runs it. Ensure pyproject.toml has a [project.scripts] entry.
Remote Deployment with Streamable HTTP
For shared or centralized servers, deploy as an HTTP service:
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
app.use(express.json());
// Health check
app.get("/health", (req, res) => res.json({ status: "ok" }));
// Session management
const sessions = new Map<string, StreamableHTTPServerTransport>();
app.all("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string;
if (req.method === "POST" && !sessionId) {
// New session
const server = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
await server.connect(transport);
await transport.handleRequest(req, res);
sessions.set(transport.sessionId, transport);
return;
}
const transport = sessions.get(sessionId);
if (!transport) {
return res.status(404).json({ error: "Session not found" });
}
await transport.handleRequest(req, res);
});
app.listen(process.env.PORT || 3000);
Client configuration for remote servers:
{
"mcpServers": {
"remote-server": {
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}
Docker Deployment
Containerize for consistent environments:
# Dockerfile for TypeScript MCP server
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
# For stdio (local use via Docker)
ENTRYPOINT ["node", "dist/index.js"]
# For HTTP (remote deployment)
# EXPOSE 3000
# CMD ["node", "dist/http-server.js"]
Using Docker with stdio transport:
{
"mcpServers": {
"my-server": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"--env", "DATABASE_URL",
"my-mcp-server:latest"
],
"env": {
"DATABASE_URL": "postgresql://host.docker.internal:5432/mydb"
}
}
}
}
Key Docker flags for stdio MCP servers:
--rm— remove container when done.-i— keep stdin open (required for stdio transport).- Do NOT use
-t— TTY mode corrupts the JSON-RPC stream. - Use
host.docker.internalto reach host services.
# Dockerfile for Python MCP server
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml ./
RUN pip install --no-cache-dir .
COPY src/ ./src/
ENTRYPOINT ["python", "-m", "my_mcp_server"]
Cloud Deployment
AWS (ECS/Fargate):
# task-definition.json (simplified)
{
"containerDefinitions": [{
"name": "mcp-server",
"image": "123456789.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest",
"portMappings": [{ "containerPort": 3000 }],
"environment": [
{ "name": "DATABASE_URL", "value": "..." }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/mcp-server",
"awslogs-region": "us-east-1"
}
}
}]
}
Google Cloud Run:
gcloud run deploy mcp-server \
--image gcr.io/myproject/mcp-server \
--port 3000 \
--set-env-vars "DATABASE_URL=..." \
--allow-unauthenticated # Or use IAM for auth
Note: Cloud Run has request timeouts (default 300s). For long-running MCP sessions with SSE, increase the timeout or use a persistent compute service instead.
Vercel / Serverless — Not recommended for MCP servers. MCP sessions are stateful (especially SSE), and serverless functions are stateless with short timeouts. Use containers or VMs for MCP servers.
Configuration Management
Support multiple configuration sources with clear precedence:
function loadConfig() {
return {
databaseUrl: process.env.DATABASE_URL || "postgresql://localhost:5432/default",
apiKey: process.env.API_KEY, // Required — fail if missing
maxResults: parseInt(process.env.MAX_RESULTS || "100"),
allowedDirectories: (process.env.ALLOWED_DIRS || "/tmp").split(","),
logLevel: process.env.LOG_LEVEL || "info",
};
}
const config = loadConfig();
if (!config.apiKey) {
console.error("ERROR: API_KEY environment variable is required");
console.error("Set it in your MCP client configuration:");
console.error(' "env": { "API_KEY": "your-key-here" }');
process.exit(1);
}
Production Hardening
// Graceful shutdown
process.on("SIGINT", async () => {
console.error("Shutting down...");
await server.close();
await db.end();
process.exit(0);
});
process.on("SIGTERM", async () => {
console.error("Shutting down...");
await server.close();
await db.end();
process.exit(0);
});
// Unhandled errors
process.on("unhandledRejection", (err) => {
console.error("Unhandled rejection:", err);
// Log but don't crash — the server should stay up
});
// Health monitoring for remote servers
app.get("/health", async (req, res) => {
try {
await db.query("SELECT 1");
res.json({ status: "healthy", uptime: process.uptime() });
} catch {
res.status(503).json({ status: "unhealthy", error: "Database unreachable" });
}
});
Anti-Patterns
- Using
-t(TTY) with Docker stdio — this adds terminal control characters that corrupt JSON-RPC messages. - Deploying on serverless for SSE — serverless functions timeout and lack session persistence. MCP sessions are stateful.
- Hardcoding configuration — always use environment variables so the same build works across environments.
- No health checks — remote servers need health endpoints for load balancers and monitoring.
- Ignoring graceful shutdown — servers that crash without cleanup can leave database connections or file locks open.
- Publishing
node_modulesorsrc/— use thefilesfield in package.json to publish onlydist/. - Missing error messages for missing config — when a required environment variable is absent, tell the user what to set and where.
Best Practices
- Start with stdio for local development. Add HTTP transport when you need remote access.
- Use npx/uvx for distribution — zero-install is the best user experience.
- Containerize with Docker for consistent environments across machines.
- Add health check endpoints to remote servers.
- Implement graceful shutdown handlers.
- Log to stderr (not stdout) for stdio servers.
- Validate all configuration at startup and fail fast with clear error messages.
- Use environment variables for all secrets and configuration.
- Pin dependency versions for reproducible builds.
- Test the deployment path (npx install, Docker build, cloud deploy) in CI.
Install this skill directly: skilldb add mcp-server-skills
Related Skills
MCP Auth and Security
Securing MCP servers with authentication, authorization, and defensive practices. Covers OAuth 2.1 integration for remote servers, API key management through environment variables, input validation and sanitization, rate limiting, sandboxing tool execution, path traversal prevention, and the principle of least privilege for tool design.
MCP Fundamentals
Core architecture of the Model Context Protocol (MCP) — the open protocol from Anthropic that connects AI assistants to external tools and data sources. Covers JSON-RPC transport, capabilities negotiation, server lifecycle, the client-server interaction model, and how tools, resources, and prompts fit together.
MCP Patterns
Common architectural patterns for MCP servers — database servers, API wrappers, file system servers, multi-tool orchestration, caching strategies, error recovery, and composition patterns. Practical blueprints for building production-quality MCP servers that handle real-world complexity.
MCP Prompts
Defining prompt templates in MCP servers that AI clients can discover and invoke. Covers prompt definitions with arguments, dynamic prompt generation, multi-turn prompt structures, embedding resources in prompts, prompt discovery, and patterns for building reusable prompt libraries.
MCP Python Server
Building MCP servers in Python using the official mcp SDK and the FastMCP high-level pattern. Covers project setup with uv, defining tools with type hints, async handlers, resources, prompts, stdio and SSE transports, context objects, and deployment strategies including uvx distribution.
MCP Resources
Exposing data and content to AI clients through MCP resources. Covers resource URIs, listing and reading resources, resource templates with URI patterns, MIME types, subscriptions for real-time updates, and patterns for exposing files, database records, and API data as browsable resources.