CLAUDE LABJP
CODE — Claude Code ships a broad quality and reliability update with /rewind, stronger MCP resilience, and steadier OAuth handlingCODE — CPU and memory use drops during streaming and long sessions, keeping always-on automation stableADMIN — New org model restrictions let administrators control which models are availableMCP — Structured output, remote MCP, and session resume all get more reliableMODEL — Claude Fable 5 is generally available, with a 1M-token context window, always-on adaptive thinking, and 128K outputLINEUP — Opus 4.8, Sonnet 4.6, and Haiku 4.5 lead the lineup; pick the right one per taskCODE — Claude Code ships a broad quality and reliability update with /rewind, stronger MCP resilience, and steadier OAuth handlingCODE — CPU and memory use drops during streaming and long sessions, keeping always-on automation stableADMIN — New org model restrictions let administrators control which models are availableMCP — Structured output, remote MCP, and session resume all get more reliableMODEL — Claude Fable 5 is generally available, with a 1M-token context window, always-on adaptive thinking, and 128K outputLINEUP — Opus 4.8, Sonnet 4.6, and Haiku 4.5 lead the lineup; pick the right one per task
Articles/Claude Code
Claude Code/2026-05-05Advanced

Claude Code MCP Connection Troubleshooting: A Complete Diagnostic Guide

A systematic approach to diagnosing MCP server failures in Claude Code—from config file validation to transport-layer debugging, crash recovery, and multi-server production patterns.

claude-code123mcp13troubleshooting86debug4server

People who run into MCP server problems in Claude Code tend to describe three scenarios: the server never starts, it starts but disconnects after a few minutes, or it's running fine but specific tools don't show up. In most cases, the root cause is something different from what the error message suggests.

This guide walks through a systematic diagnostic process—from configuration validation all the way to multi-server production patterns.

Understanding How MCP Communication Works

Before diving into diagnostics, a quick orientation on how Claude Code and MCP servers actually talk to each other.

MCP (Model Context Protocol) is the protocol Claude uses to communicate with external tools and data sources. There are two transport types:

stdio transport (standard I/O)

The default for locally running servers. Claude Code spawns the server process as a child process and exchanges JSON-RPC messages over stdin/stdout.

Claude Code
    ↓ spawn
[MCP server process]
    ↑↓ stdin/stdout (JSON-RPC)

HTTP+SSE transport

For remote servers or long-running processes. You specify a url in the config file and Claude communicates over HTTP.

Knowing this upfront lets you immediately ask: is the failure a process-launch problem or a network-layer problem? That one question narrows the diagnostic space significantly.

Step 1: Validate the Configuration File

Start here before anything else.

# macOS
cat ~/.claude/claude_desktop_config.json
 
# Windows (PowerShell)
Get-Content "$env:APPDATA\Claude\claude_desktop_config.json"
 
# Linux
cat ~/.config/claude/claude_desktop_config.json

Common mistake 1: JSON syntax errors

Even if the file looks fine visually, a trailing comma or mismatched quote will break the entire config.

# Validate with jq (install with: brew install jq)
cat ~/.claude/claude_desktop_config.json | jq .
 
# If you see: parse error (Expected separator between values) at line 8
# That line has a syntax problem

Paste the contents into jsonlint.com if you don't have jq handy.

Common mistake 2: Unresolvable command

{
  "mcpServers": {
    "my-server": {
      "command": "my-mcp-server"
    }
  }
}

Unless my-mcp-server is installed globally and on your PATH, this will fail silently. Claude Code can't start a process it can't find.

# Check if the command is reachable
which my-mcp-server || echo "not found"

The fix is usually one of these two:

// Option 1: Use npx for packages (no global install needed)
{
  "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/"]
}
 
// Option 2: Use node with an absolute path to the script
{
  "command": "node",
  "args": ["/absolute/path/to/server/index.js"]
}

Common mistake 3: Missing environment variables

MCP server processes don't inherit your shell environment. Any API key the server needs must be explicitly passed in env.

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "your_token_here"
      }
    }
  }
}

After any config change, restart Claude Code completely. The config is only read at startup.

Step 2: Start the Server Manually

Bypassing Claude Code and launching the server from your terminal isolates whether the problem is in the server itself or in Claude Code's launcher.

# Launch a stdio server directly
npx -y @modelcontextprotocol/server-filesystem /Users/username/projects
 
# A healthy server will wait on stdin (this is correct)
# An unhealthy server will print an error and exit immediately

If the server exits immediately, watch the error output. Three things cause this most often:

  • Node.js version too old — MCP servers generally require LTS 20+. Check with node --version.
  • Missing dependencies — run npm install in the server directory.
  • Target path doesn't exist — the filesystem server requires the specified path to actually exist.

Step 3: Read the MCP Logs

Claude Code records MCP communication in log files.

# macOS
tail -f ~/Library/Logs/Claude/mcp*.log
 
# Linux
tail -f ~/.local/share/claude/logs/mcp*.log

The logs use JSON-RPC format. Here's what to look for:

// A healthy initialization exchange
// → Claude sends:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}
 
// ← Server responds:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"filesystem","version":"0.6.2"}}}
 
// An error response
{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"Internal error: EACCES: permission denied, open '/restricted/path'"}}

If you see no response to the initialize request, the server isn't running. If you see an error field, that message is the real cause.

Step 4: Diagnose Tool Not Found Issues

If the server is running but Claude says it can't find a specific tool, the problem is in tool registration.

You can probe the tools list manually:

# Send JSON-RPC messages directly to a stdio server
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}\n{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}\n' \
  | npx -y @modelcontextprotocol/server-filesystem /Users/username/

If you're building a custom server, verify these implementation details:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
 
const server = new Server(
  { name: "my-server", version: "1.0.0" },
  { capabilities: { tools: {} } }  // ← tools must be declared here
);
 
// Must implement the tools/list handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "search_docs",
        description: "Search project documentation",
        inputSchema: {
          type: "object",
          properties: {
            query: { type: "string", description: "Search query" }
          },
          required: ["query"]
        }
      }
    ]
  };
});
 
// And the tools/call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  if (name === "search_docs") {
    const results = await performSearch(args.query);
    return { content: [{ type: "text", text: results }] };
  }
  throw new Error(`Unknown tool: ${name}`);
});

Missing the capabilities: { tools: {} } declaration is the most common reason tools don't appear.

Diagnosing Intermittent Disconnections

A server that starts correctly but disconnects after a few minutes is the trickiest category to debug.

Cause 1: Unhandled exceptions crashing the process

If an exception propagates out of a request handler, Node.js will terminate the process—and the connection drops.

// ❌ No error handling — any exception kills the process
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const result = await fetchFromDatabase(request.params.arguments.id);
  return { content: [{ type: "text", text: result }] };
});
 
// ✅ Catch and return errors as MCP error responses
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    const result = await fetchFromDatabase(request.params.arguments.id);
    return { content: [{ type: "text", text: result }] };
  } catch (error) {
    return {
      content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
      isError: true
    };
  }
});

Cause 2: Timeout

Some servers with slow operations get disconnected by the client's timeout before they can respond.

{
  "mcpServers": {
    "slow-server": {
      "command": "node",
      "args": ["server.js"],
      "timeout": 60000
    }
  }
}

Cause 3: Memory exhaustion

Long-running servers that accumulate data without cleanup will eventually crash. Set an explicit heap limit:

NODE_OPTIONS="--max-old-space-size=512" node server.js

Multi-Server Production Patterns

Once you're running three or more MCP servers for a project, reliability and maintainability start to matter.

Pattern 1: Separate servers by responsibility

Keep servers small and focused. A crash in one server then only affects that domain.

{
  "mcpServers": {
    "filesystem": { ... },
    "github": { ... },
    "database": { ... },
    "search": { ... }
  }
}

Pattern 2: Add a health-check tool to every server

A simple ping or health_check tool lets Claude verify a server is alive without needing external tooling.

// Lightweight health check
{
  name: "health_check",
  description: "Returns server status and version",
  inputSchema: { type: "object", properties: {} }
}
 
// Handler
if (name === "health_check") {
  return {
    content: [{
      type: "text",
      text: JSON.stringify({
        status: "ok",
        version: "1.2.0",
        uptime: process.uptime()
      })
    }]
  };
}

Pattern 3: Write logs to stderr

In stdio servers, console.log is forbidden—it goes to stdout and corrupts the JSON-RPC stream. Use process.stderr for all server-side logging.

// ✅ Safe logging in an MCP stdio server
const log = (level: string, message: string, data?: unknown) => {
  process.stderr.write(JSON.stringify({
    level,
    timestamp: new Date().toISOString(),
    message,
    ...(data ? { data } : {})
  }) + "\n");
};
 
log("info", "Server started");
log("error", "Database query failed", { error: error.message });

Claude Code captures stderr output and includes it in the MCP logs, so this is the right channel for debugging information that needs to survive process restarts.


Most MCP connection failures trace back to three root causes: a broken config file, a command that can't be found, or a server that crashes on unhandled exceptions. Check them in that order and you'll solve the vast majority of issues in under 30 minutes.

The investment in good stderr logging and per-handler error handling pays off quickly—once you have visibility into what the server is actually doing, debugging becomes a matter of reading logs rather than guessing.

Error Pattern 1: "MCP server not found" — Server Not Installed or Incorrect Path

This is the most common error you'll encounter. It means the MCP server either isn't installed on your system or the path configured in Claude is incorrect.

How to Diagnose

Start by checking which MCP servers are currently registered:

# List all available MCP servers
claude mcp list
 
# Example output:
# Available MCP servers:
#   - stdio: node /Users/user/.npm/_npx/mcp-server-stdio/bin/server.js
#   - jira: python /opt/mcp-servers/jira_server.py
#   - github: node /opt/mcp-servers/github/index.js

If the server you're trying to use doesn't appear in this list, you'll need to install it or fix its configuration.

Common Causes and Solutions

Cause 1: Server isn't installed

# For npm-based MCP servers
npm install -g @modelcontextprotocol/server-stdio
 
# For Python-based MCP servers
pip install mcp-server-jira
 
# After installation, verify the Claude config
cat ~/.claude-config.json

Cause 2: Path is incorrect

Check the Claude Desktop configuration file:

// ~/.claude-config.json — WRONG
{
  "mcpServers": {
    "jira": {
      "command": "python",
      "args": ["/wrong/path/to/jira_server.py"]
    }
  }
}
 
// CORRECT configuration
{
  "mcpServers": {
    "jira": {
      "command": "python",
      "args": ["/opt/homebrew/lib/python3.11/site-packages/mcp_server_jira/server.py"]
    }
  }
}

To find the correct path:

# For Python packages
python -c "import mcp_server_jira; print(mcp_server_jira.__file__)"
 
# For Node.js packages
npm root -g  # shows global package directory
which node-mcp-server  # shows command location

Verification Steps

# 1. Test if the server can be started directly
python /opt/homebrew/lib/python3.11/site-packages/mcp_server_jira/server.py
 
# 2. Look for output like this (success indicator)
# {"jsonrpc": "2.0", "id": 1, "method": "initialize", ...}
 
# 3. Restart Claude Desktop to reload configuration

Error Pattern 2: "Permission denied" — Insufficient Access Rights

This error commonly appears in headless environments like GitHub Actions or when running in VM mode (Cowork).

Fixing Permission Issues in GitHub Actions

When MCP servers run in GitHub Actions workflows, the current directory often lacks proper permissions:

# .github/workflows/claude-code.yml example
name: Claude Code Automation
 
on:
  push:
    branches: [main]
 
jobs:
  auto-generate-content:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      # Explicitly grant permissions
      - name: Fix permissions
        run: |
          chmod +x scripts/mcp-server.js
          chmod -R 755 /tmp/mcp-runtime
 
      - name: Run Claude Code with MCP
        env:
          CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
        run: |
          mkdir -p /tmp/mcp-runtime
          cp scripts/mcp-server.js /tmp/mcp-runtime/
          chmod +x /tmp/mcp-runtime/mcp-server.js
 
          claude code --mcp-server /tmp/mcp-runtime/mcp-server.js

Using the /permissions Command

You can check what permissions the current process has within Claude Desktop:

# Run directly in Claude Desktop input
/permissions
 
# Output example:
# Current working directory: /tmp/mcp-runtime
# Process UID: 501
# Process GID: 20
# Directory permissions: drwxr-xr-x
# File permissions: -rwxr-xr-x

If you see missing r (read) or x (execute) permissions, fix them:

# Grant read and execute permissions to directories
chmod -R u+rx /path/to/mcp-server
 
# Make scripts executable
chmod +x /path/to/mcp-server/bin/start.sh
 
# Verify
ls -la /path/to/mcp-server/

Fixing in Cowork VM Mode

If the filesystem isn't recognized correctly within Cowork:

# 1. Check mount status
mount | grep -E "mcp|workspace"
 
# 2. Re-select the workspace folder (from Cowork settings)
 
# 3. Restart the Cowork VM
 
# 4. Clear cache
rm -rf ~/.cowork/cache

Error Pattern 3: OAuth Token Expiration — External Service Authentication

MCP servers that integrate with external services like Jira or GitHub rely on OAuth tokens, which expire after a certain period.

Signs of Token Expiration

[ERROR] OAuth token expired
[ERROR] 401 Unauthorized: Invalid credentials
[ERROR] Authentication failed: Token refresh required

How to Refresh Tokens

# 1. Check current token status
cat ~/.mcp-tokens/jira.json
 
# 2. Trigger re-authentication
claude mcp auth jira
 
# 3. Your browser will open asking for authorization — approve it
 
# 4. Verify the token was updated
cat ~/.mcp-tokens/jira.json
 
# 5. Restart Claude Desktop

For a deeper dive into OAuth authentication with MCP servers, see the Claude Code MCP OAuth Authentication Guide.

Error Pattern 4: Tool Execution Timeout — Operation Takes Too Long

When an MCP tool takes longer to execute than Claude's timeout setting allows, you'll see a timeout error.

Symptoms

[ERROR] Tool execution timeout after 30s
[ERROR] Transport closed: Server did not respond in time

Adjusting Timeout Values

Edit the Claude Desktop configuration:

// ~/.claude-config.json
{
  "mcpServers": {
    "jira": {
      "command": "python",
      "args": ["/opt/homebrew/lib/python3.11/site-packages/mcp_server_jira/server.py"],
      "timeout": 120000  // milliseconds (default: 30000 = 30 seconds)
    }
  },
  "toolExecutionTimeout": 120000  // global timeout for all tools
}

Finding Performance Bottlenecks with Logs

# Enable debug logging to see execution times
CLAUDE_DEBUG=mcp claude code
 
# stderr output will include timing information:
# [MCP] Tool invocation: search_issues
# [MCP] Elapsed: 5234ms
# [MCP] Result size: 2.3 MB

If you notice results are very large, consider implementing pagination or filtering to reduce data volume.

Error Pattern 5: "Transport closed" — Server Process Crashes

When an MCP server crashes or runs out of memory, you'll see a transport closed error.

Symptoms

[ERROR] Transport closed
[ERROR] Connection lost to MCP server
[ERROR] Unexpected end of stream

Checking Server stderr for Clues

# 1. Run the MCP server in foreground to see errors directly
python /opt/homebrew/lib/python3.11/site-packages/mcp_server_jira/server.py 2>&1
 
# 2. Example of a memory exhaustion crash:
# Traceback (most recent call last):
#   File "/opt/...", line 45, in handle_request
#     result = process_large_dataset()
# MemoryError: Unable to allocate 2.5 GiB for array
 
# 3. Set memory limits before restarting
python -c "import resource; resource.setrlimit(resource.RLIMIT_AS, (4*1024*1024*1024, -1))" && python server.py

Process Management for Long-Running Servers

For production use, manage MCP servers with a process supervisor like supervisor:

# Install supervisor
brew install supervisor
 
# Create configuration file
cat > /opt/homebrew/etc/supervisor.d/mcp-jira.conf << 'EOF'
[program:mcp-jira]
command=python /opt/homebrew/lib/python3.11/site-packages/mcp_server_jira/server.py
autostart=true
autorestart=true
stderr_logfile=/var/log/mcp-jira.err.log
stdout_logfile=/var/log/mcp-jira.out.log
EOF
 
# Start supervisor
supervisord -c /opt/homebrew/etc/supervisord.conf

Error Pattern 6: Silent Failures — No Error Message Appears

The most frustrating case: MCP tools fail silently without any error output, and you simply get no result.

Debugging with --mcp-debug Flag

# Run with verbose MCP debugging enabled
claude code --mcp-debug

Example debug output:

[DEBUG] MCP Request: {
  "method": "call_tool",
  "params": {
    "name": "search_issues",
    "arguments": {
      "query": "project = MYPROJ",
      "limit": 100
    }
  }
}

[DEBUG] MCP Response (4234ms): {
  "result": null,
  "error": "JIRA API returned 403: Insufficient permissions"
}

Saving Logs to a File

# Capture all output to a log file
claude code --mcp-debug > /tmp/claude-mcp.log 2>&1
 
# Monitor logs in real-time
tail -f /tmp/claude-mcp.log

Verifying JSON Response Structure

Check that the server is returning valid JSON:

# Start the server and send a test request via stdin
echo '{"jsonrpc":"2.0","method":"call_tool","params":{"name":"search_issues","arguments":{"query":"key=MYPROJ-1"}},"id":1}' | \
python /opt/homebrew/lib/python3.11/site-packages/mcp_server_jira/server.py

Healthy response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{"type": "text", "text": "Issue: MYPROJ-1\nTitle: ..."}]
  }
}

Quick Reference: Connection Troubleshooting Checklist

Before diving into debugging, run through this checklist:

  • [ ] Server appears in claude mcp list output
  • [ ] Configuration file path is correct and file exists
  • [ ] File has execute permissions (run ls -la and look for x)
  • [ ] OAuth token is current (claude mcp auth {service} has been run recently)
  • [ ] Timeout settings are appropriate (use 60+ seconds for slow operations)
  • [ ] Debug logs show no errors (--mcp-debug output is clean)
  • [ ] Memory usage is within limits (check with top)

A Server Disappears in Another Directory — Scope Mismatch

A server that showed up in claude mcp list vanishes from the list the moment you open a different project. You didn't touch the config file. This is almost never a configuration error — it's the scope the server was registered under.

claude mcp add takes a -s (--scope) flag that decides where the registration lives, with three values:

  • local (default): usable only inside the project where you added it, only by you. Omitting -s lands here.
  • project: written to a .mcp.json at the repo root and shared with your team — anyone who clones the repo gets it.
  • user: available across all of your own projects.

So if you register with claude mcp add github -- npx -y @modelcontextprotocol/server-github and skip the scope, it becomes specific to that one directory. Open Claude Code in another folder and it's simply not there. That's the real story behind "it worked yesterday."

To isolate it, run claude mcp list in the directory where the problem occurs first. If the server is missing there, suspect a scope mismatch.

# Check which scope a server is registered under
claude mcp get github
 
# Re-add at user scope if you want it everywhere
claude mcp add -s user github -- npx -y @modelcontextprotocol/server-github
 
# Use project scope to share with the team (generates .mcp.json in the repo)
claude mcp add -s project github -- npx -y @modelcontextprotocol/server-github

Anyone who receives a .mcp.json via git clone is prompted to approve it on first use. When "the config file exists but the tool won't fire," check that you didn't dismiss that approval prompt.

As an indie developer I move between several repositories in a day, and for a while I kept re-registering the same MCP servers in each one. Once I started using the same tools across the Dolice Labs blogs, I settled on a simple rule: shared tools go in user scope, repo-specific ones in project scope. The "where did it go" panic stopped after that. Scope isn't a failure mode — it's a declaration of where a server is meant to be used, and reading it that way makes it much easier to handle.

Looking back

MCP connection errors fall into six categories: server discovery, permissions, token expiration, timeouts, crashes, and silent failures. When you hit a connection problem, start with claude mcp list to verify the server is recognized, then use --mcp-debug to see what's actually happening under the hood. And when a server you did register won't show up, don't forget to check the scope.

For deeper exploration of MCP implementation patterns, check out Claude Code MCP Server Practical Guide. For authorization and connector design, see Single-Source Authorization for MCP Connectors.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-05-05
5 Common Claude Code Errors and How to Fix Them
A practical guide to the five most common Claude Code errors—Permission denied, rate limits, MCP connection failures, context overflow, and bash failures—with real causes and fixes for each.
Claude Code2026-05-02
Claude Code Stops Mid-Execution: Complete Diagnostic Guide
Diagnose why Claude Code freezes or stops mid-task. Covers context budget exhaustion, unresponsive subagents, infinite loops, and tool timeouts with actionable fixes for each scenario.
Claude Code2026-04-24
Why Your Custom MCP Server's Logs Disappear — Writing to stderr in Claude Code
If you're building your own MCP server and your console.log lines never appear — while Claude Code silently reports the tool as unresponsive — you're probably polluting stdio. Here's how to spot it and rewrite your logs to keep the protocol intact.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →