← All articles

MCP Failed to Reconnect: Causes and Fixes

July 7, 2026·18 min read·MCPForge

Quick Answer

When an MCP client reports "failed to reconnect," work through this path first:

  1. Check whether the server process is still running (stdio) or responding to HTTP (remote).
  2. Read the disconnect reason — the root cause is almost always in the disconnect event, not the reconnect attempt.
  3. Determine whether the server is stateful — if it is, a reconnect that lands on a different instance or after a restart will always fail until the session is re-established from scratch.
  4. Check authentication — expired OAuth tokens silently convert working connections into 401 failures on reconnect.
  5. Look for retry storm signatures — rapid repeated reconnect attempts in logs indicate missing backoff, which can make recovery harder.

Reconnect failures are almost never about the reconnect mechanism itself. They are about a changed server state that the client does not know about.


MCP Failed to Reconnect: 60-Second Checklist

Work through these in order. Stop when you find the failure.

  • Server process alive? — For stdio, check ps aux | grep <server-name>. For HTTP, curl -v <server-url>/health.
  • Original disconnect logged? — Find the disconnect event in client or server logs before looking at reconnect errors.
  • Server restarted or redeployed recently? — Any restart invalidates in-memory session state.
  • Session ID still valid? — Check server logs for session lookup results on the reconnect attempt.
  • Auth token still valid? — Check token expiry time. Run the OAuth token endpoint manually if needed.
  • Reconnect hitting the same instance? — If behind a load balancer, verify session affinity (sticky sessions) is configured.
  • Reconnect timing out at the proxy? — Check Nginx/ALB idle timeout configuration.
  • Client re-running MCP initialization? — Verify the initialize handshake fires after transport reconnect, not just the transport layer.
  • Tools/list called after reconnect? — Absence of tool discovery after reconnect is a separate step from the transport reconnect.
  • Exponential backoff active? — Confirm the client is not hammering the server every 100ms.

Jump to the Right Fix

SymptomLikely CauseStart Here
Server process not found after restartstdio process crashed or binary path changedLocal stdio Reconnect Failures
HTTP 401 on reconnectExpired OAuth token, no refreshAuthentication and OAuth Expiry
HTTP 403 on reconnectToken still valid but scope changed or revokedAuthentication and OAuth Expiry
HTTP 404 on reconnectSession expired, endpoint moved, or deployment replaced sessionStreamable HTTP Session Troubleshooting
HTTP 429 on reconnectRetry storm hitting rate limitsReconnect Loops and Retry Storms
HTTP 5xx on reconnectServer crashed or unhealthyRemote MCP Reconnect Failures
Reconnect succeeds but no toolsMCP initialization skipped after transport reconnectMissing Tools After Reconnect
Reconnect succeeds but initialize failsServer state reset, capability mismatchInitialization Failures After Reconnect
Fresh connection works, reconnect failsStateful server lost sessionStreamable HTTP Session Troubleshooting
Reconnect worked before, broken after deployDeployment invalidated sessionsServer Restart and Deployment Failures
Reconnect loops endlesslyNo backoff, permanent underlying errorReconnect Loops and Retry Storms
Different behavior behind load balancerSession affinity missingLoad Balancer and Multi-Instance Problems

Want to analyze your API security?

Import your OpenAPI spec and generate a Security Report automatically.

Initial Connection vs. Disconnect vs. Reconnect Failure

These three failure types look similar in client logs but have completely different causes.

Initial connection failure — the client has never successfully connected. The server may be unreachable, the configuration is wrong, or authentication was never established. See MCP failed to connect for this scenario.

Disconnect — a working connection dropped. The transport closed cleanly or was interrupted. The disconnect itself is often not an error — what matters is why it happened.

Reconnect failure — the client attempts to restore the connection after a disconnect and cannot. This is the scenario this guide covers. The root cause is almost always in the state that existed at disconnect time, not in the reconnect logic itself.

This distinction matters because reconnect failures require diagnosing what changed between the disconnect and the reconnect attempt, not just what is broken now.


Finding the Original Disconnect Cause

Every reconnect failure investigation should start here, not at the reconnect error.

Where to Look

Client-side logs — MCP clients like Claude Desktop, Cursor, and custom SDK clients log disconnect events with reason codes. Find the log line that shows the connection closing, not the one showing reconnect failure.

Server-side logs — The server typically logs why it closed the connection: idle timeout, authentication failure, process signal, or unhandled exception.

Transport-level signals:

  • stdio: Did the child process exit? What was the exit code? A non-zero exit code means a crash. Exit code 0 means the process completed normally and was not designed to stay alive.
  • Streamable HTTP: What was the final HTTP status on the last request before disconnect? A 408 is a server-side timeout. A 503 is a server going away.

Correlating Disconnect to Reconnect Failure

Disconnect reason               → What it means for reconnect
─────────────────────────────────────────────────────────────
Process exited (exit code != 0) → Server crashed; may not restart cleanly
Idle timeout (proxy/server)     → Session may have been GC'd on server side
Network interruption            → Session may still exist if server is stateful
Server sent close frame         → Server intentionally closed; session likely invalid
Auth token expired              → New token required before any reconnect attempt
Server restarted                → All in-memory session state is gone

If you find the disconnect was caused by an expired token or a server restart, do not attempt a transport reconnect before fixing the underlying condition. The reconnect will fail for the same reason.


Local stdio Reconnect Failures

For stdio-based MCP servers, "reconnect" means spawning a new child process. There is no persistent session at the transport layer — each spawn is a fresh process.

The stdio Reconnect Model

When a stdio connection drops, the client kills the existing process (if any) and spawns a new one using the configured command. This is inherently a fresh start — there is no session resume, no handshake continuation, and no state transfer between the old and new process.

Reconnect failure in stdio almost always means the process is failing to start.

Diagnosing a Failed stdio Restart

Step 1: Run the server command manually

Copy the exact command from your MCP configuration and run it in a terminal:

bash
# Example for a Node.js MCP server
node /path/to/server/index.js

# Example for a Python MCP server
python -m my_mcp_server

# Example for a binary
/usr/local/bin/my-mcp-server

If the process exits immediately, you have a startup crash. Read stderr output.

Step 2: Check the binary path

After an update, the binary path may have changed. Verify:

bash
which my-mcp-server
ls -la /path/to/server

Step 3: Check environment variables

Many MCP server crashes at startup are missing API keys or configuration:

bash
# Run with the same environment your client would use
MY_API_KEY=... node /path/to/server/index.js

Step 4: Check Node.js / Python version compatibility

If a server update requires a newer runtime:

bash
node --version
python --version

Fixing stdio Reconnect Failures

FailureFix
ENOENT: no such file or directoryUpdate the binary path in client config
Error: Cannot find moduleRun npm install in the server directory
Missing required environment variableAdd the variable to client MCP config env block
Process exits with code 1 immediatelyCheck server logs for unhandled exception at startup
Permission deniedchmod +x the server binary

After fixing: Restart your MCP client (Claude Desktop, Cursor, etc.) to trigger a fresh process spawn. Do not just reload the connection — reload the full client if it caches the process state.


Remote MCP Reconnect Failures

For HTTP-based MCP servers, reconnect failures surface as HTTP errors. Each error has a distinct meaning.

HTTP Status Code Diagnostic Table

StatusMeaning in Reconnect ContextFix
401 UnauthorizedAuth token expired or missingRefresh or re-acquire the token
403 ForbiddenToken valid but permission revoked or scope changedRe-authorize with correct scopes
404 Not FoundSession endpoint gone, URL changed, or session expiredAttempt fresh initialization; verify endpoint URL
408 Request TimeoutServer timed out waiting for clientReduce latency, check network path
429 Too Many RequestsRetry storm hitting rate limitImplement exponential backoff with jitter
502 Bad GatewayUpstream server down, proxy can't reach itCheck server health, restart if needed
503 Service UnavailableServer intentionally unavailable (deploy, maintenance)Wait and retry with backoff
504 Gateway TimeoutUpstream server too slow to respondCheck server performance, connection pool

Verifying Server Health

bash
# Basic health check
curl -v https://your-mcp-server.example.com/health

# Test the MCP endpoint directly
curl -v -X POST https://your-mcp-server.example.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}'

If this succeeds but your client still fails to reconnect, the problem is in the client's reconnect logic, not the server.


Streamable HTTP Session Troubleshooting

Streamable HTTP is the current recommended transport for remote MCP servers. Understanding its session semantics is essential for diagnosing reconnect failures.

How Streamable HTTP Sessions Work

In the Streamable HTTP transport, the server may (but is not required to) assign a session ID after the initialize request. If a session ID is assigned:

  • The client includes it in subsequent requests via the Mcp-Session-Id header.
  • The server uses it to correlate requests to a logical session.
  • Session state may be stored in memory on the server.

Critical: Not all Streamable HTTP servers are stateful. A stateless server that ignores the session ID will happily accept any reconnect as a fresh initialization. A stateful server that stores session data in memory will reject reconnects to lost sessions.

Stale session ID

The client reconnects using a session ID from the previous connection, but that session no longer exists on the server.

Symptom: HTTP 404 on reconnect requests that include Mcp-Session-Id.

Verification:

bash
# Check server logs for a line like:
# "Session not found: sess_abc123"
# or
# "Unknown session ID"

Fix: The client should detect 404 responses to session-bearing requests and fall back to a fresh initialize without a session ID. If your client does not do this automatically, configure it to treat 404 as a session expiry and re-initialize.

Session ID format changed after server update

If a server update changes how session IDs are generated or validated, old session IDs become immediately invalid.

Fix: Treat any server update as a forced re-initialization. Clear cached session IDs on client restart.

In-memory session GC during idle

Servers that store session state in memory often garbage-collect idle sessions. A client that reconnects after a long idle period may find its session has been cleaned up.

Fix:

  • Configure longer session TTL on the server if you control it.
  • Design clients to handle 404 on session requests as a signal to re-initialize.
  • Use persistent session storage (Redis, database) if sessions must survive server restarts.

Session Verification Flow

Client sends reconnect request with Mcp-Session-Id
        │
        ▼
   Server receives request
        │
        ├─ Session found → Resume, return 200
        │
        └─ Session not found → Return 404
                │
                ▼
        Client should detect 404
                │
                ├─ Drop session ID
                ├─ Send fresh initialize (no Mcp-Session-Id)
                └─ Receive new session ID → Continue

Server Restart and Deployment Failures

A server restart is the most common cause of reconnect failures in production. It invalidates everything: in-memory session state, active transport connections, and any server-side context accumulated during the session.

Why Restarts Break Reconnects

  • In-memory session state is gone. Any session ID the client holds is now invalid.
  • Active SSE streams are closed. Clients connected via streaming will get a connection reset.
  • Process environment may have changed. New deployments may use different configuration.

Detecting a Restart-Caused Reconnect Failure

Look for these patterns in server logs:

# Server log: restart happened
[2025-01-28T10:00:00Z] Server starting up
[2025-01-28T10:00:01Z] Listening on :3000

# Client log: reconnect attempt after restart
[2025-01-28T10:00:05Z] Reconnecting with session: sess_abc123
[2025-01-28T10:00:05Z] 404 Not Found — session not found
[2025-01-28T10:00:05Z] Reconnect failed

The timestamp gap between server restart and client reconnect attempt confirms the session predates the restart.

Blue-Green and Rolling Deployments

Rolling deployments are particularly dangerous for stateful MCP servers because:

  • Old instances are gradually replaced with new ones.
  • In-flight sessions may be on instances that get shut down mid-session.
  • The client may reconnect to a new instance that has no knowledge of its session.

Fix options:

  1. Drain connections before terminating instances — configure your orchestrator (Kubernetes, ECS) to send SIGTERM, wait for in-flight requests to complete, then terminate.
  2. Externalize session state — store session data in Redis or a database so any instance can serve any session.
  3. Design for stateless reconnect — treat every reconnect as a fresh initialization. The server generates a new session ID and the client rebuilds state.

Load Balancer and Multi-Instance Problems

This is one of the most subtle categories of MCP reconnect failure because it works correctly in development (single instance) and fails unpredictably in production.

The Problem

  Client
    │
    ▼
Load Balancer
    │
    ├──→ Instance A  ← Session sess_abc123 stored here
    │
    └──→ Instance B  ← No session sess_abc123

Initial connection routed to Instance A creates session sess_abc123. After disconnect, the client reconnects. The load balancer routes the reconnect to Instance B. Instance B has no record of sess_abc123. Reconnect fails with 404.

Verifying Load Balancer Routing

bash
# Check which instance responds to successive requests
# Many servers expose instance ID in headers or response body
curl -v https://your-mcp-server.example.com/health | grep -i 'x-instance\|server-id'

# Or add instance identification to your server's health endpoint
# and call it multiple times to see if you get different instances
for i in {1..5}; do curl -s https://your-mcp-server.example.com/health; done

Fix 1: Sticky Sessions (Session Affinity)

Configure your load balancer to route requests with the same session identifier to the same backend instance.

Nginx upstream:

nginx
upstream mcp_backend {
    ip_hash;  # or use sticky cookie module
    server instance1:3000;
    server instance2:3000;
}

AWS ALB: Enable "Stickiness" on the target group with a duration that covers your longest expected session.

Kubernetes Ingress (nginx):

yaml
annotations:
  nginx.ingress.kubernetes.io/affinity: "cookie"
  nginx.ingress.kubernetes.io/session-cookie-name: "mcp-route"
  nginx.ingress.kubernetes.io/session-cookie-expires: "172800"

Fix 2: Shared External Session Store

If sticky sessions are not viable (e.g., active-active failover requirements), move session state out of process memory:

typescript
import { createClient } from 'redis';

// Instead of storing session state in a Map:
// const sessions = new Map<string, SessionState>();

// Store in Redis with TTL:
const redis = createClient({ url: process.env.REDIS_URL });

async function getSession(sessionId: string): Promise<SessionState | null> {
  const data = await redis.get(`mcp:session:${sessionId}`);
  return data ? JSON.parse(data) : null;
}

async function setSession(sessionId: string, state: SessionState): Promise<void> {
  await redis.setEx(
    `mcp:session:${sessionId}`,
    3600, // 1 hour TTL
    JSON.stringify(state)
  );
}

Fix 3: Stateless Server Design

If your server does not actually need session state between requests, remove session tracking entirely. Each request is self-contained. Reconnects always succeed because there is nothing to resume — every connection is treated as new.


Authentication and OAuth Expiry

Expired credentials are the second most common reconnect failure cause. OAuth access tokens typically expire in 1 hour, which means any MCP session lasting longer than the token lifetime will fail to reconnect if the token is not refreshed.

Identifying Token Expiry

Symptom: HTTP 401 on reconnect
Server log: "Token expired" or "Invalid token" or "JWT expired"
Client log: "Authentication failed" or "Unauthorized"

The Token Expiry Timeline Problem

T+0:00   Initial connection — token valid, expires at T+1:00
T+0:30   Session active — token still valid
T+0:55   Connection drops (network issue)
T+0:56   Client attempts reconnect — token expires in 4 minutes, reconnect may succeed
T+1:05   Client retries reconnect — token expired 5 minutes ago — reconnect fails with 401

If your client retries reconnects over a period that crosses the token expiry boundary, early retries may succeed while later retries fail. This produces inconsistent reconnect behavior that looks random but is actually deterministic.

Implementing Token Refresh Before Reconnect

typescript
async function reconnectWithTokenRefresh(client: MCPClient): Promise<void> {
  const token = await getStoredToken();
  
  // Refresh if token expires within 5 minutes
  const REFRESH_BUFFER_MS = 5 * 60 * 1000;
  if (Date.now() + REFRESH_BUFFER_MS >= token.expiresAt) {
    try {
      const refreshed = await refreshOAuthToken(token.refreshToken);
      await storeToken(refreshed);
      client.setAuthToken(refreshed.accessToken);
    } catch (refreshError) {
      // Refresh token itself may be expired
      // Trigger re-authorization flow
      await triggerReauthorization();
      return;
    }
  }
  
  await client.reconnect();
}

HTTP 403 vs 401

  • 401 means the token is invalid or expired. Fix: get a new token.
  • 403 means the token is valid but lacks permission. This can happen if scopes were revoked between the initial connection and the reconnect, or if the resource now requires elevated permissions. Fix: re-authorize with the required scopes.

Do not treat 403 as a transient error. It will not resolve with retries.


Reverse Proxy and Idle Timeout Problems

If your MCP server sits behind Nginx, HAProxy, AWS ALB, or a similar proxy, idle timeout configuration is a common silent killer of long-lived connections.

How It Happens

Client ←——————→ Nginx ←——————→ MCP Server
                 ↑
         proxy_read_timeout = 60s

If no data flows between client and server for 60 seconds, Nginx closes the connection. The client may not realize the connection was closed until it tries to send a message — at which point it attempts to reconnect. If the server treated the Nginx-side close as a session termination, the reconnect fails.

Nginx Configuration Fix

nginx
server {
    location /mcp {
        proxy_pass http://mcp_backend;
        
        # Increase timeouts for long-lived MCP sessions
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_connect_timeout 10s;
        
        # Keep the upstream connection alive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Forward session header
        proxy_set_header Mcp-Session-Id $http_mcp_session_id;
    }
}

AWS ALB Idle Timeout

AWS ALB has a default idle timeout of 60 seconds. For MCP servers with potentially long-lived sessions:

  1. Go to EC2 → Load Balancers → Select your ALB
  2. Attributes → Edit
  3. Set Idle timeout to match your session requirements (max 4000 seconds)

Application-Level Keepalive

Rather than relying on proxy timeout configuration (which you may not control), implement keepalive pings from the client side:

typescript
class MCPClientWithKeepalive {
  private keepaliveInterval: NodeJS.Timer | null = null;
  
  startKeepalive(intervalMs: number = 30000): void {
    this.keepaliveInterval = setInterval(async () => {
      try {
        // Send a lightweight ping that the server can respond to
        await this.client.ping();
      } catch (error) {
        // Connection dropped — trigger reconnect
        this.handleDisconnect();
      }
    }, intervalMs);
  }
  
  stopKeepalive(): void {
    if (this.keepaliveInterval) {
      clearInterval(this.keepaliveInterval);
      this.keepaliveInterval = null;
    }
  }
}

Reconnect Loops and Retry Storms

When an MCP client retries reconnects without backoff, a permanent failure becomes a thundering herd problem. The client hammers the server with requests it cannot handle, which can delay recovery or trigger rate limiting.

Identifying a Retry Storm

# Server logs showing a storm:
[10:00:00.001] POST /mcp — 503
[10:00:00.102] POST /mcp — 503
[10:00:00.203] POST /mcp — 503
[10:00:00.304] POST /mcp — 503
# 100ms intervals = no backoff

HTTP 429 responses from the server are the clearest signal. But some servers fail silently under load without returning 429.

Exponential Backoff with Jitter

MCP does not specify a reconnect retry policy. This is an application-level responsibility. Implement exponential backoff with full jitter:

typescript
interface RetryConfig {
  maxAttempts: number;
  baseDelayMs: number;
  maxDelayMs: number;
  jitterFactor: number; // 0–1
}

async function reconnectWithBackoff(
  connect: () => Promise<void>,
  config: RetryConfig
): Promise<void> {
  let attempt = 0;
  
  while (attempt < config.maxAttempts) {
    try {
      await connect();
      return; // Success
    } catch (error) {
      attempt++;
      
      // Do not retry on permanent errors
      if (isPermanentError(error)) {
        throw error; // 403, auth revoked, etc.
      }
      
      if (attempt >= config.maxAttempts) {
        throw new Error(`Failed to reconnect after ${config.maxAttempts} attempts: ${error}`);
      }
      
      // Exponential backoff with full jitter
      const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt - 1);
      const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
      const jitter = Math.random() * config.jitterFactor * cappedDelay;
      const delay = cappedDelay + jitter;
      
      console.log(`Reconnect attempt ${attempt} failed. Retrying in ${Math.round(delay)}ms`);
      await sleep(delay);
    }
  }
}

function isPermanentError(error: unknown): boolean {
  if (error instanceof HTTPError) {
    // Do not retry client errors except 429
    return error.status >= 400 && error.status < 500 && error.status !== 429;
  }
  return false;
}

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Reconnect Storm Prevention Checklist

  • Maximum retry count configured (not infinite)
  • Base delay >= 1 second
  • Exponential growth per attempt
  • Jitter applied to prevent synchronized retries from multiple clients
  • Permanent errors (403, 4xx non-429) do NOT trigger retry
  • User is notified after max retries, not silently looping

For more on error handling patterns, see our guide on MCP error -32000 connection closed which covers related transport error semantics.


Initialization Failures After Reconnect

The transport reconnected. The TCP/HTTP connection is alive. But the MCP initialize handshake fails. This is a distinct failure from transport reconnection.

Why This Happens

  • The server restarted and now has different capabilities than the client expects.
  • The server version changed and the protocol version is no longer compatible.
  • The server is returning an error during initialize because it requires a clean state the client is not providing.
  • The client is sending a cached initialize request that references a previous session context.

Verifying the Initialize Failure

bash
# Send a fresh initialize and check the response
curl -X POST https://your-mcp-server.example.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_VALID_TOKEN" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {
        "roots": { "listChanged": true },
        "sampling": {}
      },
      "clientInfo": {
        "name": "debug-client",
        "version": "1.0.0"
      }
    },
    "id": 1
  }'

A successful response returns serverInfo and capabilities. An error response will have a code and message explaining the failure.

Protocol Version Mismatch

If the server responds with a protocol version incompatibility error, check:

json
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32600,
    "message": "Unsupported protocol version: 2024-11-05"
  },
  "id": 1
}

This means the server updated to a newer protocol version. Update your client SDK to match.


Missing Tools After Reconnect

Your client reconnected, the initialize handshake completed, but the tools list is empty or incomplete. This is the most common post-reconnect confusion.

Why Tools Disappear

Tool discovery in MCP is not automatic after reconnect. It requires an explicit tools/list call. If the client's reconnect logic restores the transport connection and runs initialize but does not re-run tools/list, the local tool registry will be empty.

The Complete Reconnect Sequence

1. Transport reconnect (TCP/HTTP connection established)
        ↓
2. MCP initialize request/response (capabilities negotiated)
        ↓
3. initialized notification sent by client
        ↓
4. tools/list called (tools discovered)
        ↓
5. resources/list called (if applicable)
        ↓
6. prompts/list called (if applicable)
        ↓
7. Session fully restored — ready for use

Skipping step 4 is the most common cause of missing tools after reconnect. Verify your client's reconnect handler calls tools/list after initialized.

Server-Side Tool Changes During Reconnect

If the server was updated between the disconnect and the reconnect, the available tools may have changed. This is expected behavior, not a bug. The tools/list response after reconnect is the source of truth.

If a tool the user was relying on is no longer in tools/list after reconnect, the tool was removed from the server — not lost due to a reconnect failure.


MCP Inspector Testing

MCP Inspector is the fastest way to isolate whether a reconnect failure is a server issue or a client issue.

Reconnect Testing Procedure with MCP Inspector

bash
# Install MCP Inspector if needed
npx @modelcontextprotocol/inspector

# Or for a specific server
npx @modelcontextprotocol/inspector node /path/to/server/index.js

Testing reconnect behavior:

  1. Connect to your server in Inspector.
  2. Verify tools list is populated.
  3. Close the connection manually (Disconnect button).
  4. Wait 5 seconds.
  5. Reconnect.
  6. Observe: Does initialize succeed? Does tools/list return results?
  7. Check Inspector's network panel for the exact HTTP requests and responses during reconnect.

If Inspector reconnects successfully but your production client does not, the problem is in your client's reconnect logic, not the server.

If Inspector also fails to reconnect, the problem is server-side. You can also use the MCPForge server verification tool to run a conformance check against your server from an independent client.


Regression Troubleshooting: "It Used to Work"

When reconnect worked before and now fails after a change, narrow down what changed.

Regression Isolation Checklist

  • Server updated? — Check Git history for changes to session handling, authentication middleware, or startup sequence.
  • Client updated? — MCP client updates (Claude Desktop, Cursor, SDK version) may change reconnect behavior.
  • Infrastructure changed? — New load balancer, changed proxy timeout, added WAF, modified TLS configuration.
  • Auth provider changed? — Token expiry time shortened, scopes changed, new required claims.
  • Configuration changed? — Environment variables, feature flags, connection pool settings.

Binary Search for the Regression

If you have Git history:

bash
# Use git bisect to find when reconnect broke
git bisect start
git bisect bad HEAD
git bisect good <last-known-working-commit>

# Test each version
# git bisect good / git bisect bad
# Until git identifies the first bad commit

Minimal Reproduction Procedure

Before filing a bug report, create the smallest possible reproduction.

typescript
// minimal-reconnect-test.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

async function testReconnect() {
  const serverUrl = new URL('http://localhost:3000/mcp');
  
  // Step 1: Initial connection
  const transport1 = new StreamableHTTPClientTransport(serverUrl);
  const client = new Client(
    { name: 'reconnect-test', version: '1.0.0' },
    { capabilities: {} }
  );
  
  console.log('Connecting...');
  await client.connect(transport1);
  console.log('Connected. Tools:', await client.listTools());
  
  // Step 2: Simulate disconnect
  console.log('Disconnecting...');
  await client.close();
  
  // Step 3: Wait (simulate idle period or network interruption)
  await new Promise(r => setTimeout(r, 2000));
  
  // Step 4: Reconnect
  console.log('Reconnecting...');
  const transport2 = new StreamableHTTPClientTransport(serverUrl);
  
  try {
    await client.connect(transport2);
    const tools = await client.listTools();
    console.log('Reconnected. Tools:', tools);
    console.log('SUCCESS: Reconnect worked correctly');
  } catch (error) {
    console.error('FAILURE: Reconnect failed:', error);
    process.exit(1);
  } finally {
    await client.close();
  }
}

testReconnect();

Run this against your server. If it fails, you have a reproducible case you can debug or share.


Bug Report Checklist

If you need to report a reconnect failure to an SDK maintainer or server author, include:

  • MCP SDK version (npm list @modelcontextprotocol/sdk or pip show mcp)
  • Client name and version (Claude Desktop version, Cursor version, custom client)
  • Transport type (stdio, Streamable HTTP, SSE)
  • Server type (local process, remote HTTP, which framework)
  • Exact error message from client logs
  • Server logs from the moment of disconnect through the reconnect attempt
  • HTTP traffic — request/response headers and bodies if HTTP transport (redact tokens)
  • Reproduction steps — minimal script from the section above
  • What changed before the regression appeared
  • What works — does MCP Inspector reconnect? Does a fresh connection work?

Prevention Best Practices

Reconnect failures are largely preventable. These practices reduce their frequency and impact in production.

Server Design

  • Externalize session state — Never store session data only in process memory if you run multiple instances.
  • Handle SIGTERM gracefully — Drain connections before shutdown; do not kill active sessions.
  • Return meaningful errors on session miss — A clear 404 with {"error": "session_not_found"} is far easier to handle than a generic 500.
  • Implement session TTL — Set a reasonable expiry and communicate it to clients. Infinite session TTL is a memory leak.

Client Design

  • Implement full reconnect sequence — Transport → initialize → initialized → tools/list, not just transport reconnect.
  • Refresh tokens before expiry — Check token expiry before every reconnect attempt.
  • Use exponential backoff with jitter — Never retry without delay.
  • Treat permanent errors differently from transient ones — 403 should stop retrying immediately.
  • Detect and handle session invalidation — 404 on a session-bearing request should trigger fresh initialization.

Infrastructure

  • Configure proxy timeouts appropriately — Match idle timeouts to your session lifetime.
  • Use sticky sessions or shared state — Choose based on your failover requirements.
  • Monitor reconnect rate — A spike in reconnects indicates an upstream problem worth alerting on.
  • Test reconnects in staging — Include forced disconnect and reconnect scenarios in your integration tests.

For a full production deployment guide including monitoring, see running MCP in production.


Official Sources

Frequently Asked Questions

Does MCP define a universal reconnect operation? No. The MCP specification does not define a standard reconnect mechanism. Transport reconnection, session recovery, fresh MCP initialization, authentication refresh, and tool rediscovery are distinct concerns handled differently by each transport type and server implementation.

Why does a fresh connection work but reconnect always fails? Almost always means the server is stateful and the reconnect attempt is trying to resume a session that no longer exists — either because the server restarted, was redeployed, or the session expired.

Why do tools disappear after a successful MCP reconnect? A transport-level reconnect does not automatically re-run MCP initialization. Confirm the client issues a full initialization sequence including tools/list after reconnecting.

What causes an MCP reconnect loop? Reconnect loops happen when the client retries aggressively without exponential backoff, the underlying problem is permanent, and nothing breaks the retry cycle.

How does a load balancer cause MCP reconnect failures? If session state is stored in-memory on individual server instances, a reconnect routed to a different instance will not find the session. Fix with sticky sessions or a shared external session store.

Will an expired OAuth token cause a reconnect failure? Yes. HTTP 401 Unauthorized. The client must obtain a fresh token before attempting to reconnect.

What does HTTP 404 mean during an MCP reconnect? It can mean the session expired, the endpoint URL changed after a deployment, or the reverse proxy routing changed. Check server logs before assuming session expiry.

How do I prevent a reverse proxy from killing idle MCP connections? Configure the proxy's idle timeout to exceed your longest expected idle period, or implement application-level keepalive pings from the client.

What is the fastest way to test MCP server reconnect behavior? Use MCP Inspector — connect, disconnect manually, reconnect, and observe whether the server handles the reconnect cleanly. If Inspector reconnects but your client does not, the problem is in your client's reconnect logic.

Frequently Asked Questions

Why does a fresh MCP connection work but reconnect always fails?

This almost always means the server is stateful and the reconnect attempt is trying to resume a session that no longer exists — either because the server restarted, was redeployed, or the session expired. Try treating the reconnect as a fresh initialization instead of a session resume.

Does the MCP specification define a standard reconnect mechanism?

No. MCP does not define a universal reconnect operation. Transport reconnection, session recovery, fresh MCP initialization, and tool rediscovery are distinct concerns handled differently by each transport type and server implementation. Reconnect behavior is application-level, not protocol-level.

Why do tools disappear after a successful MCP reconnect?

A transport-level reconnect does not automatically re-run MCP initialization. If your client reconnected the transport but skipped the initialize handshake and tools/list call, the tool registry will be empty. Confirm the client issues a full initialization sequence after reconnecting.

What causes an MCP reconnect loop?

Reconnect loops happen when the client retries aggressively without exponential backoff, the underlying problem is permanent (expired credentials, missing session, crashed server), and nothing breaks the retry cycle. Add jitter and backoff, cap maximum retries, and surface the root error to the user rather than looping indefinitely.

How does a load balancer cause MCP reconnect failures?

If session state is stored in-memory on individual server instances, a reconnect routed to a different instance by the load balancer will not find the session. The fix is sticky sessions (session affinity), a shared external session store (Redis, database), or designing the server to be stateless.

Will an expired OAuth token cause a reconnect failure?

Yes. If the access token used during the initial connection has expired and the client does not refresh it before reconnecting, the server will return HTTP 401 Unauthorized. The client must obtain a fresh token — either via a refresh token grant or a new authorization flow — before attempting to reconnect.

What does HTTP 404 mean during an MCP reconnect?

HTTP 404 during reconnect is not always an expired session. It can also mean the endpoint URL changed after a deployment, the reverse proxy routing rule was updated, or the session endpoint simply never existed. Check the server logs and verify the endpoint URL before assuming session expiry.

How do I prevent a reverse proxy from killing idle MCP connections?

Configure the proxy's idle timeout to exceed the longest expected idle period in your MCP session. For Nginx, set proxy_read_timeout and proxy_send_timeout. For AWS ALB, adjust the idle timeout in the load balancer settings. Alternatively, implement application-level keepalives from the client side.

What is the fastest way to test whether my MCP server can handle reconnects?

Use MCP Inspector to open a connection, disconnect it manually, then reconnect. Check whether the server logs show session lookup or re-initialization. If the server errors on reconnect but not on first connect, you have a session state or initialization sequencing problem. See the MCPForge MCP Inspector guide for detailed steps.

My stdio MCP server stopped reconnecting after an update. What changed?

Check whether the update changed the server binary path, added a missing environment variable, changed the working directory, or introduced a startup crash. Run the server command manually in a terminal to see if it exits immediately. A process that exits at startup cannot be reconnected to by any client.

Check your MCP security posture

Generate a Security Score, detect risky tools, and review permissions before exposing APIs to AI agents.