Skip to main content

Deployment Guide

9.1 Local Deployment (stdio)

Suitable for personal tools and team internal use.

npm Package Method

Publish your Server as an npm package; users install it globally and use it directly:
# Publish
npm publish

# User installs
npm install -g my-commerce-server

# Claude Desktop configuration
{
  "mcpServers": {
    "my-store": {
      "command": "my-commerce-server",
      "env": { "DB_URL": "..." }
    }
  }
}

Direct Execution Method

Without publishing an npm package, specify the script path directly:
{
  "mcpServers": {
    "my-store": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"],
      "env": { "DB_URL": "..." }
    }
  }
}

Python Server Local Deployment

{
  "mcpServers": {
    "my-python-server": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"],
      "env": { "API_KEY": "..." }
    }
  }
}

9.2 Remote Deployment (Streamable HTTP)

Suitable for SaaS services serving external users.

Complete Express.js Streamable HTTP Server

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

const app = express();
app.use(express.json());

// Session storage (use Redis or another external store in production)
const sessions = new Map();

app.post("/mcp", async (req, res) => {
  const sessionId = req.headers["mcp-session-id"];

  if (sessionId && sessions.has(sessionId)) {
    // Existing session, reuse
    const transport = sessions.get(sessionId);
    await transport.handlePost(req, res);
  } else {
    // New session
    const transport = new StreamableHTTPServerTransport({
      endpoint: "/mcp"
    });
    const server = createMcpServer(); // Your Server factory function
    await server.connect(transport);

    // Save session
    const newSessionId = transport.getSessionId();
    sessions.set(newSessionId, transport);

    await transport.handlePost(req, res);
  }
});

// SSE endpoint (optional, for Server-initiated pushes)
app.get("/mcp", async (req, res) => {
  const sessionId = req.headers["mcp-session-id"];
  const transport = sessions.get(sessionId);
  if (transport) {
    await transport.handleGet(req, res);
  } else {
    res.status(404).end();
  }
});

// Health check
app.get("/health", (req, res) => {
  res.json({ status: "ok", activeSessions: sessions.size });
});

app.listen(3000, () => {
  console.error("MCP Server (Streamable HTTP) running on port 3000");
});

Docker Deployment

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production
COPY dist/ ./dist/
EXPOSE 3000
CMD ["node", "dist/server-http.js"]

Cloud Platform Deployment

PlatformApproachAdvantagesConsiderations
VercelEdge FunctionGlobal CDN, auto-scalingStateless; requires external session storage
RailwayDocker containerSimple deployment, persistent connectionsGood for stateful Servers
AWS LambdaServerlessPay-per-invocationCold start latency; mind the timeout
Cloudflare WorkersEdge WorkerUltra-low latencyStateless; requires Durable Objects for session management
Self-hostedPM2 + NginxFull controlRequires self-maintenance

9.3 Session Management

Streamable HTTP is a stateful protocol; the Mcp-Session-Id header maintains sessions. Production deployments must address the following:

Stateless Deployment (Multiple Instances)

If the Server is deployed across multiple instances (e.g., K8s Pods), external session storage is needed:
// Use Redis for session state storage
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);

app.post("/mcp", async (req, res) => {
  const sessionId = req.headers["mcp-session-id"];
  if (sessionId) {
    const sessionData = await redis.get(`mcp:session:${sessionId}`);
    // Restore session...
  }
});

Session Cleanup

Set reasonable session timeouts and cleanup policies:
// Session timeout: clean up after 30 minutes of inactivity
const SESSION_TTL = 30 * 60 * 1000;

setInterval(() => {
  for (const [id, session] of sessions) {
    if (Date.now() - session.lastActivity > SESSION_TTL) {
      session.transport.close();
      sessions.delete(id);
    }
  }
}, 60 * 1000); // Check every minute

9.4 Client Connection Configuration

Remote Server (Streamable HTTP)

{
  "mcpServers": {
    "my-remote-store": {
      "url": "https://mcp.mystore.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}

Remote Server with OAuth

When the Server implements the full OAuth 2.1 flow, the Host application handles authentication automatically; the user only needs to provide the Server URL:
{
  "mcpServers": {
    "my-oauth-store": {
      "url": "https://mcp.mystore.com/mcp"
    }
  }
}
The Host automatically discovers the Server’s OAuth configuration (via Protected Resource Metadata) and guides the user through authorization.

9.5 Monitoring and Logging

Recommended for production environments:

Health Check

app.get("/health", (req, res) => {
  res.json({
    status: "ok",
    version: "1.0.0",
    activeSessions: sessions.size,
    uptime: process.uptime()
  });
});

Request Logging

// Log every tool call
function logToolCall(method, params, duration, error) {
  console.error(JSON.stringify({
    timestamp: new Date().toISOString(),
    method,
    tool: params?.name,
    duration_ms: duration,
    error: error?.message
  }));
}

Monitoring Metrics

MetricDescriptionAlert Threshold
P95 response time95th percentile latency for tool callsDepends on business; typically under 5s
Error rateProportion of failed tool callsAlert above 5%
Active sessionsNumber of currently connected ClientsAlert when approaching server capacity
Initialization success rateMCP handshake success rateInvestigate below 99%

Reconnection Recovery

Streamable HTTP supports reconnection recovery via SSE’s Last-Event-ID. In production, the Server should maintain an event ID sequence so Clients can resume from the disconnection point:
// Server-side event sequence management
let eventCounter = 0;

function sendNotification(sessionId, data) {
  eventCounter++;
  const event = {
    id: `evt-${eventCounter}`,
    data: JSON.stringify(data)
  };
  // Send SSE event and persist (for reconnection recovery)
  sendSSEEvent(sessionId, event);
  persistEvent(sessionId, event);
}

Next Chapter: Case Studies — MCP Server implementations for different scenarios