Skip to main content
Back to Guides

Debugging MCP Servers

A practical guide to troubleshooting common issues when developing and running MCP servers.

Jordan Kim
Updated January 15, 2025
12 min read

Even the best-designed MCP servers can encounter issues. Whether you're building a new server or integrating an existing one, knowing how to debug effectively will save you hours of frustration. This guide covers everything from basic logging to advanced debugging techniques.

1. The MCP Inspector

The MCP Inspector is your best friend when debugging. It's an official tool that lets you interact with your server directly, bypassing the AI client entirely.

npx @modelcontextprotocol/inspector your-server-command

What You Can Do with the Inspector

  • View capabilities: See all tools, resources, and prompts your server exposes
  • Test tools: Manually invoke tools with custom arguments
  • Read resources: Fetch and view resource contents
  • Monitor messages: See raw JSON-RPC messages being exchanged
  • Inspect errors: View detailed error responses and stack traces

Pro Tip: Development Workflow

Run your server in watch mode alongside the Inspector. Use tsx watch src/index.ts or nodemon so changes automatically reload. This lets you iterate quickly without restarting the Inspector.

Inspector Examples

# TypeScript server with tsx
npx @modelcontextprotocol/inspector tsx src/index.ts

# Compiled JavaScript
npx @modelcontextprotocol/inspector node dist/index.js

# Python server
npx @modelcontextprotocol/inspector python server.py

# With environment variables
GITHUB_TOKEN=xxx npx @modelcontextprotocol/inspector npx @modelcontextprotocol/server-github

2. Logging Best Practices

Since MCP servers communicate over stdio, you can't use console.log in Node.js servers—it would corrupt the JSON-RPC stream. Here's how to log properly:

Use stderr for Debug Output

// ❌ Don't do this - corrupts stdio
console.log("Debug message");

// ✅ Do this instead - writes to stderr
console.error("[DEBUG] Tool called:", toolName);
process.stderr.write(`[INFO] Processing request\n`);

Implement a Logger

// src/logger.ts
type LogLevel = "debug" | "info" | "warn" | "error";

const LOG_LEVELS: Record<LogLevel, number> = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
};

const currentLevel = (process.env.LOG_LEVEL as LogLevel) || "info";

export function log(level: LogLevel, message: string, data?: unknown) {
  if (LOG_LEVELS[level] >= LOG_LEVELS[currentLevel]) {
    const timestamp = new Date().toISOString();
    const logLine = data
      ? `[${timestamp}] [${level.toUpperCase()}] ${message} ${JSON.stringify(data)}`
      : `[${timestamp}] [${level.toUpperCase()}] ${message}`;
    process.stderr.write(logLine + "\n");
  }
}

// Usage
log("debug", "Tool invoked", { name: "get_weather", args });
log("error", "Failed to fetch data", { error: err.message });

File-Based Logging

For persistent logs, write to a file:

import { appendFileSync } from "fs";

function logToFile(message: string) {
  const logPath = process.env.MCP_LOG_FILE || "/tmp/mcp-server.log";
  appendFileSync(logPath, `${new Date().toISOString()} ${message}\n`);
}

3. Common Issues and Solutions

Server Won't Start

If your server fails to launch, check these common causes:

  • Missing dependencies: Run npm install or pip install -r requirements.txt
  • Wrong Node/Python version: Check version requirements in package.json or pyproject.toml
  • Syntax errors: Run npx tsc --noEmit to check for TypeScript errors
  • Missing environment variables: Verify all required env vars are set

Tools Not Appearing

If your tools don't show up in the client:

  • Ensure capabilities.tools is set in server initialization
  • Verify ListToolsRequestSchema handler is implemented
  • Check that tool definitions include name, description, and inputSchema
  • Look for errors in the server startup logs
// Make sure capabilities include tools
const server = new Server(
  { name: "my-server", version: "1.0.0" },
  {
    capabilities: {
      tools: {},  // ← This must be present
    },
  }
);

4. Connection Errors

Common Mistake: Relative Paths

Many connection issues stem from incorrect paths in your config file. Always use absolute paths for the command field, or ensure the binary is in your system's PATH. Don't use ~ or relative paths.

Path Issues

// ❌ Won't work - relative path
{
  "command": "./dist/index.js"
}

// ❌ Won't work - tilde expansion
{
  "command": "~/projects/server/dist/index.js"
}

// ✅ Works - absolute path
{
  "command": "/Users/me/projects/server/dist/index.js"
}

// ✅ Works - command in PATH
{
  "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-filesystem"]
}

Permission Issues

On Unix systems, ensure your server script is executable:

chmod +x dist/index.js

5. Tool Execution Failures

When a tool fails during execution:

Add Detailed Logging

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  
  console.error(`[TOOL] Executing: ${name}`);
  console.error(`[TOOL] Arguments: ${JSON.stringify(args)}`);
  
  try {
    const result = await executeToolLogic(name, args);
    console.error(`[TOOL] Success: ${name}`);
    return result;
  } catch (error) {
    console.error(`[TOOL] Error in ${name}:`, error);
    throw error;
  }
});

Return Descriptive Errors

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";

// Instead of generic errors
throw new Error("Failed");

// Return descriptive MCP errors
throw new McpError(
  ErrorCode.InvalidParams,
  `City "${city}" not found. Available cities: ${cities.join(", ")}`
);

6. Reading Claude Desktop Logs

Claude Desktop logs MCP activity to help with debugging:

Log Locations

# macOS
~/Library/Logs/Claude/mcp.log
~/Library/Logs/Claude/mcp-server-*.log

# Windows
%APPDATA%\Claude\Logs\mcp.log

# Linux
~/.config/Claude/logs/mcp.log

Watching Logs in Real-Time

# macOS/Linux
tail -f ~/Library/Logs/Claude/mcp*.log

# Filter for specific server
tail -f ~/Library/Logs/Claude/mcp*.log | grep "my-server"

# Windows PowerShell
Get-Content -Path "$env:APPDATA\Claude\Logs\mcp.log" -Wait

What to Look For

  • Startup errors: Server failed to initialize
  • Connection timeouts: Server took too long to respond
  • JSON parse errors: Invalid output from server
  • Tool errors: Exceptions during tool execution

7. Validating Your Server

Before deploying, run through this checklist:

Pre-Deployment Checklist

  • ✓ All tools have clear descriptions and valid JSON schemas
  • ✓ Error cases return helpful, actionable messages
  • ✓ Server handles graceful shutdown (SIGTERM, SIGINT)
  • ✓ Sensitive data is not logged or exposed in errors
  • ✓ All tools work correctly in the MCP Inspector
  • ✓ Server starts within a reasonable time (<5 seconds)
  • ✓ Memory usage is stable over time (no leaks)

Automated Validation Script

// scripts/validate.ts
import { Client } from "@modelcontextprotocol/sdk/client";

async function validate() {
  const client = new Client({ name: "validator" });
  
  // Test tool listing
  const tools = await client.listTools();
  console.log(`✓ Found ${tools.tools.length} tools`);
  
  // Validate each tool has required fields
  for (const tool of tools.tools) {
    if (!tool.name || !tool.description || !tool.inputSchema) {
      throw new Error(`Tool ${tool.name} missing required fields`);
    }
    console.log(`✓ Tool "${tool.name}" is valid`);
  }
  
  console.log("\n✅ All validations passed!");
}

8. Advanced Debugging Techniques

Message Tracing

Log all JSON-RPC messages for detailed debugging:

// Wrap the transport to log messages
class LoggingTransport {
  constructor(private inner: Transport) {}
  
  async send(message: JSONRPCMessage) {
    console.error("[OUT]", JSON.stringify(message));
    return this.inner.send(message);
  }
  
  async receive(): Promise<JSONRPCMessage> {
    const message = await this.inner.receive();
    console.error("[IN]", JSON.stringify(message));
    return message;
  }
}

Memory Profiling

// Add memory logging
setInterval(() => {
  const usage = process.memoryUsage();
  console.error(`[MEM] Heap: ${Math.round(usage.heapUsed / 1024 / 1024)}MB`);
}, 30000);

Performance Timing

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const start = performance.now();
  try {
    return await handleTool(request);
  } finally {
    const duration = performance.now() - start;
    console.error(`[PERF] ${request.params.name}: ${duration.toFixed(2)}ms`);
  }
});

Conclusion

Debugging MCP servers requires a systematic approach. Start with the Inspector to isolate issues, add comprehensive logging, and use Claude Desktop logs for production debugging. With these techniques, you'll be able to quickly identify and fix problems, allowing you to focus on building amazing AI-powered integrations.

Needs Review

Last updated

January 12, 2025

374 days ago

This content may be outdated

This content may contain outdated information. Please verify details before use.