MP-301c · Module 3
Blue-Green Deployment
3 min read
Blue-green deployment runs two identical MCP server environments — blue (current production) and green (new version). You deploy the new version to green, run the health check and a smoke test suite against it, and then switch traffic from blue to green by updating the load balancer or DNS. If the new version has problems, switch back to blue in seconds. The key benefit over rolling deployments is instant rollback: blue is still running with the old code, unchanged and ready to serve.
The challenge for MCP servers is session state. If a client has an active MCP session with the blue server (tracked by Mcp-Session-Id header), switching to green invalidates that session. The client gets a "session not found" error and must reconnect. For stateless tools, this is a minor hiccup — the client reconnects and retries. For stateful tools (e.g., multi-step wizards, document editing sessions), you need session draining: stop sending new sessions to blue, wait for active sessions to complete or expire, then switch. This requires the load balancer to route existing sessions to blue while sending new sessions to green during the transition.
// Blue-green deployment smoke test
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
async function smokeTest(serverUrl: string): Promise<boolean> {
const checks: { name: string; passed: boolean; error?: string }[] = [];
try {
// 1. Health check
const healthRes = await fetch(`${serverUrl}/health`);
checks.push({ name: "health", passed: healthRes.ok });
// 2. Tool listing via MCP protocol
const client = new Client(
{ name: "smoke-test", version: "1.0.0" },
{ capabilities: {} },
);
// Connect to the HTTP transport...
const { tools } = await client.listTools();
checks.push({
name: "tool_listing",
passed: tools.length > 0,
});
// 3. Call each tool with a known-good input
for (const tool of tools) {
if (tool.validExample) {
const result = await client.callTool({
name: tool.name,
arguments: tool.validExample,
});
checks.push({
name: `call_${tool.name}`,
passed: !result.isError,
});
}
}
await client.close();
} catch (err) {
checks.push({ name: "connection", passed: false, error: (err as Error).message });
}
const allPassed = checks.every(c => c.passed);
console.log(JSON.stringify({ smokeTest: allPassed, checks }));
return allPassed;
}
// Deployment flow:
// 1. Deploy to green
// 2. await smokeTest(greenUrl) — if false, abort
// 3. Switch load balancer to green
// 4. Monitor error rates for 5 minutes
// 5. If error rate spikes, switch back to blue
Do This
- Run automated smoke tests against the green environment before switching traffic
- Monitor error rates for 5 minutes after switching — a delayed spike means rollback
- Drain active sessions during the switch to prevent mid-conversation disruptions
- Keep the blue environment running for at least 1 hour after switch for instant rollback
Avoid This
- Switch traffic without smoke testing — the health check alone does not verify tool behavior
- Tear down blue immediately after switching — you need it for rollback
- Force-disconnect active sessions — let them complete or expire naturally
- Skip the monitoring window — some bugs only appear under real traffic patterns