Lesson 11 - Step 5 of 5

Debugging Tips

Learn to diagnose and fix common issues with Claude Desktop integration.

Common Issues Overview

When things go wrong, systematic debugging helps. Here are the most common issues and how to solve them.

Issue 1: Server Not Appearing

Symptom: Your server doesn't show in Claude Desktop settings, or Claude says it doesn't have access to your tools.

text
Checklist:

1. Verify configuration file location
   □ macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
   □ Windows: %APPDATA%\Claude\claude_desktop_config.json
   □ Linux: ~/.config/claude/claude_desktop_config.json

2. Verify JSON syntax
   □ Run: python -m json.tool < claude_desktop_config.json
   □ Should print formatted JSON without errors

3. Verify paths are absolute
   □ "/Users/you/project/server.py" (correct)
   □ "./server.py" or "~/project/server.py" (incorrect)

4. Verify file exists
   □ ls -la /path/to/your/server.py
   □ Should show the file with appropriate permissions

5. Restart Claude Desktop completely
   □ Quit (not just close window)
   □ Reopen

Issue 2: Server Fails to Start

Symptom: Server shows in settings but with an error status, or Claude can't use your tools despite the server being configured.

bash
# Test server manually with exact command from config
/path/to/python /path/to/server.py

# Common errors:
# - ModuleNotFoundError: Wrong Python or missing dependencies
# - FileNotFoundError: Wrong path to server script
# - PermissionError: Script not executable
# - SyntaxError: Bug in server code
Virtual Environment Check

If using a virtual environment, make sure the config points to the venv's Python: /path/to/project/.venv/bin/python

Issue 3: Handshake Fails

Symptom: Server starts but tools don't appear. The handshake isn't completing successfully.

python
# Add debugging to your server's initialize handler

def handle_initialize(params: dict) -> dict:
    import sys
    print(f"Received initialize: {params}", file=sys.stderr)

    response = {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "tools": {}
        },
        "serverInfo": {
            "name": "my-server",
            "version": "1.0.0"
        }
    }

    print(f"Sending response: {response}", file=sys.stderr)
    return response

Common handshake issues:

  • Wrong protocolVersion (must be "2024-11-05" or compatible)
  • Missing required fields in response
  • Server outputting extra text to stdout (breaks JSON-RPC)
  • Server not sending notifications/initialized acknowledgment

Issue 4: Tools Not Working

Symptom: Tools appear but calls fail or return errors.

python
# Add comprehensive error handling

def handle_tool_call(name: str, arguments: dict) -> dict:
    import sys
    import traceback

    print(f"Tool call: {name}({arguments})", file=sys.stderr)

    try:
        result = execute_tool(name, arguments)
        print(f"Success: {result}", file=sys.stderr)
        return {
            "content": [{"type": "text", "text": str(result)}]
        }
    except Exception as e:
        error_msg = f"Error in {name}: {e}\n{traceback.format_exc()}"
        print(error_msg, file=sys.stderr)
        return {
            "content": [{"type": "text", "text": f"Error: {e}"}],
            "isError": True
        }

Issue 5: Stdout Corruption

Symptom: Intermittent failures, garbled responses, or "invalid JSON" errors.

Critical Rule

Never print to stdout except for JSON-RPC messages! All debug output, logs, and print statements must go to stderr.

python
import sys

# BAD - corrupts STDIO protocol
print("Starting server...")
print(f"Processing: {data}")

# GOOD - use stderr for all non-protocol output
print("Starting server...", file=sys.stderr)
print(f"Processing: {data}", file=sys.stderr)

# Or use logging configured for stderr
import logging
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)

logger.info("Starting server...")
logger.debug(f"Processing: {data}")

Viewing Claude Desktop Logs

Claude Desktop writes logs that can help diagnose issues:

bash
# macOS - View Claude Desktop logs
tail -f ~/Library/Logs/Claude/mcp*.log

# Or find all log files
ls -la ~/Library/Logs/Claude/

# Windows - Logs location
# %LOCALAPPDATA%\Claude\Logs\

# Linux - Logs location
# ~/.local/share/Claude/logs/

Creating a Debug Wrapper

Create a wrapper script that logs all communication:

python
#!/usr/bin/env python3
"""
debug_wrapper.py - Wraps your server and logs all I/O.
Usage: Configure Claude Desktop to run this instead of your server.
"""

import subprocess
import sys
import threading
from datetime import datetime

LOG_FILE = "/tmp/mcp_debug.log"

def log(direction: str, data: str):
    with open(LOG_FILE, "a") as f:
        timestamp = datetime.now().isoformat()
        f.write(f"[{timestamp}] {direction}: {data}\n")

def forward_stderr(process):
    """Forward server stderr to our stderr and log."""
    for line in process.stderr:
        log("STDERR", line)
        sys.stderr.write(line)
        sys.stderr.flush()

def main():
    # Start the actual server
    server_cmd = ["/path/to/python", "/path/to/server.py"]
    process = subprocess.Popen(
        server_cmd,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True
    )

    # Forward stderr in background
    stderr_thread = threading.Thread(
        target=forward_stderr,
        args=(process,),
        daemon=True
    )
    stderr_thread.start()

    # Forward stdin to server
    def forward_stdin():
        for line in sys.stdin:
            log("IN", line.strip())
            process.stdin.write(line)
            process.stdin.flush()

    stdin_thread = threading.Thread(target=forward_stdin, daemon=True)
    stdin_thread.start()

    # Forward stdout from server
    for line in process.stdout:
        log("OUT", line.strip())
        sys.stdout.write(line)
        sys.stdout.flush()

if __name__ == "__main__":
    main()

Testing with MCP Inspector

The MCP Inspector tool can help debug your server independently of Claude Desktop:

bash
# Install MCP Inspector (if available)
npm install -g @modelcontextprotocol/inspector

# Run your server with the inspector
mcp-inspector python /path/to/server.py

# This provides a web UI to:
# - Send requests manually
# - View responses
# - Test tools interactively
# - Debug protocol issues

Quick Reference: Debugging Checklist

text
When things don't work:

1. Configuration
   □ JSON syntax valid?
   □ Paths absolute?
   □ File permissions OK?
   □ Claude Desktop restarted?

2. Server Basics
   □ Server runs standalone?
   □ All dependencies installed?
   □ Correct Python version?
   □ Virtual env activated (in config)?

3. Protocol
   □ Initialize response correct?
   □ Tool definitions valid?
   □ No stdout pollution?
   □ Proper JSON-RPC format?

4. Runtime
   □ Error handling in place?
   □ Logging to stderr?
   □ No infinite loops?
   □ Graceful shutdown?

5. Environment
   □ Required env vars set?
   □ API keys valid?
   □ Network accessible (if needed)?
   □ File paths accessible?

Lesson Complete!

You've learned how to integrate your MCP server with Claude Desktop. This is a major milestone - your custom tools are now available to a production AI assistant!

Key Takeaways

Configuration: Use absolute paths, valid JSON, and specify the correct Python interpreter.

Debugging: Always log to stderr, never stdout. Use debug wrappers and log files to trace issues.

Testing: Test your server standalone before integrating with Claude Desktop.

What's Next?

In the final lesson, you'll build a capstone project that combines everything you've learned into a complete, production-ready MCP server.