Advanced

Deploying MCP Servers

Deploy MCP servers locally, remotely, in Docker containers, and on cloud platforms with proper security and monitoring.

Local Deployment (stdio)

The simplest deployment model. The MCP client spawns the server as a local child process:

Local stdio Deployment
# TypeScript server - build and reference the output
npm run build
# Config points to: node /path/to/dist/index.js

# Python server - reference the script directly
# Config points to: python /path/to/server.py

# Or use npx for published packages
# Config points to: npx -y @your-org/mcp-server
Best for: Development, personal use, and accessing local resources (files, local databases). No network configuration needed.

Remote Deployment (HTTP/SSE)

For sharing MCP servers across a team or deploying as a service, use the HTTP/SSE transport:

TypeScript - HTTP/SSE Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const app = express();
const server = new McpServer({ name: "my-server", version: "1.0.0" });

// ... register tools, resources, prompts ...

// SSE endpoint for server-to-client messages
app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/message", res);
  await server.connect(transport);
});

// Message endpoint for client-to-server messages
app.post("/message", async (req, res) => {
  // Handle incoming JSON-RPC messages
  await transport.handlePostMessage(req, res);
});

app.listen(3001, () => {
  console.log("MCP server running on http://localhost:3001");
});

Docker Deployment

Containerize your MCP server for consistent deployments:

Dockerfile
FROM node:20-slim

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci --production

# Copy built server
COPY dist/ ./dist/

# For HTTP/SSE transport
EXPOSE 3001

# For stdio transport, use ENTRYPOINT
# ENTRYPOINT ["node", "dist/index.js"]

# For HTTP/SSE transport, use CMD
CMD ["node", "dist/index.js"]
Docker Commands
# Build the image
docker build -t my-mcp-server .

# Run with HTTP/SSE
docker run -p 3001:3001 --env-file .env my-mcp-server

# Run with stdio (for local use)
docker run -i my-mcp-server

Cloud Deployment

AWS (ECS / Fargate)

AWS ECS Task Definition (simplified)
{
  "family": "mcp-server",
  "containerDefinitions": [{
    "name": "mcp-server",
    "image": "your-ecr-repo/mcp-server:latest",
    "portMappings": [{
      "containerPort": 3001,
      "protocol": "tcp"
    }],
    "environment": [
      { "name": "NODE_ENV", "value": "production" }
    ],
    "secrets": [
      { "name": "API_KEY", "valueFrom": "arn:aws:ssm:..." }
    ]
  }]
}

Google Cloud Run

Terminal
# Deploy to Cloud Run
gcloud run deploy mcp-server \
  --image gcr.io/your-project/mcp-server \
  --port 3001 \
  --allow-unauthenticated \
  --set-env-vars "NODE_ENV=production"

Security

Authentication

For HTTP/SSE servers, implement authentication middleware:

TypeScript - Auth Middleware
// Simple API key authentication
function authMiddleware(req, res, next) {
  const apiKey = req.headers["x-api-key"];
  if (apiKey !== process.env.MCP_API_KEY) {
    return res.status(401).json({ error: "Unauthorized" });
  }
  next();
}

app.use("/sse", authMiddleware);
app.use("/message", authMiddleware);

Sandboxing

  • File access: Restrict to specific directories; validate all paths.
  • Database: Use read-only credentials when possible; limit query complexity.
  • Network: Restrict outbound connections; use allowlists for API endpoints.
  • Execution: Never allow arbitrary code execution; validate all inputs.

Monitoring and Logging

TypeScript - Structured Logging
import { createLogger } from "winston";

const logger = createLogger({
  level: process.env.LOG_LEVEL || "info",
  format: winston.format.json(),
  transports: [
    new winston.transports.Console({ stderrLevels: ["error"] })
  ]
});

// Log tool calls for monitoring
server.tool("my_tool", "description", schema, async (args) => {
  logger.info("Tool called", { tool: "my_tool", args });
  const start = Date.now();
  try {
    const result = await executeAction(args);
    logger.info("Tool succeeded", { tool: "my_tool", duration: Date.now() - start });
    return result;
  } catch (err) {
    logger.error("Tool failed", { tool: "my_tool", error: err.message });
    throw err;
  }
});
📚
Important: For stdio servers, always log to stderr (not stdout). Stdout is reserved for the JSON-RPC protocol. Writing non-protocol data to stdout will break the connection.

Scaling MCP Servers

For high-traffic MCP servers using HTTP/SSE transport:

  • Horizontal scaling: Run multiple server instances behind a load balancer. SSE connections are stateful, so use sticky sessions.
  • Connection pooling: Share database connections across requests to reduce overhead.
  • Caching: Cache frequently accessed resources to reduce backend load.
  • Rate limiting: Implement per-client rate limits to prevent abuse.

What's Next?

The final lesson covers best practices for designing, securing, testing, and maintaining MCP servers.