Files
gh-jezweb-claude-skills-ski…/references/debugging-guide.md
2025-11-30 08:24:23 +08:00

13 KiB

MCP Server Debugging Guide

Troubleshooting connection issues and common errors


Quick Diagnosis Flowchart

MCP Connection Failing?
         |
         v
[1] Can you curl the Worker?
    curl https://worker.dev/
         |
    NO ──┴─> Worker not deployed
         |   → Run: npx wrangler deploy
         |
    YES ──┴─> Continue
         |
         v
[2] Can you curl the MCP endpoint?
    curl https://worker.dev/sse
         |
    404 ──┴─> URL path mismatch (most common!)
         |   → Check: Client URL matches server base path
         |   → See: "URL Path Mismatch" section below
         |
    OK ──┴─> Continue
         |
         v
[3] Did you update config after deployment?
         |
    NO ──┴─> Update claude_desktop_config.json
         |   → Use deployed URL (not localhost)
         |   → Restart Claude Desktop
         |
    YES ──┴─> Continue
         |
         v
[4] Check Worker logs
    npx wrangler tail
         |
         v
    See errors? → Check "Common Errors" section below

Problem 1: URL Path Mismatch (Most Common!)

Symptoms

  • 404 Not Found
  • Connection failed
  • MCP Inspector shows "Failed to connect"
  • Claude Desktop doesn't show tools

Root Cause

Client URL doesn't match server base path configuration.

Debugging Steps

Step 1: Check what base path your server uses

Look at your src/index.ts:

// Option A: Serving at /sse
if (pathname.startsWith("/sse")) {
  return MyMCP.serveSSE("/sse").fetch(...);
  //                     ↑ Base path is "/sse"
}

// Option B: Serving at root /
return MyMCP.serveSSE("/").fetch(...);
//                       ↑ Base path is "/"

Step 2: Test with curl

# If base path is /sse:
curl https://YOUR-WORKER.workers.dev/sse

# If base path is /:
curl https://YOUR-WORKER.workers.dev/

Expected: JSON response with server info Got 404? Your URL doesn't match the base path

Step 3: Update client config

Match the curl URL that worked:

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://YOUR-WORKER.workers.dev/sse"  // Must match curl URL!
    }
  }
}

Step 4: Restart Claude Desktop

Config only loads at startup:

  1. Quit Claude Desktop completely
  2. Reopen
  3. Check for tools

Common Variations

Server at /sse, client missing /sse:

// Server
MyMCP.serveSSE("/sse").fetch(...)

// Client (wrong)
"url": "https://worker.dev"  // ❌ Missing /sse

Fix:

"url": "https://worker.dev/sse"  // ✅ Include /sse

Server at /, client includes /sse:

// Server
MyMCP.serveSSE("/").fetch(...)

// Client (wrong)
"url": "https://worker.dev/sse"  // ❌ Server at root, not /sse

Fix:

"url": "https://worker.dev"  // ✅ No /sse

Problem 2: Localhost After Deployment

Symptoms

  • Connection timeout
  • Connection refused
  • Works in dev, fails in production

Root Cause

Client config still using localhost URL after deployment.

Debugging Steps

Step 1: Check client config

cat ~/.config/claude/claude_desktop_config.json

Look for:

{
  "mcpServers": {
    "my-mcp": {
      "url": "http://localhost:8788/sse"  // ❌ localhost!
    }
  }
}

Step 2: Get deployed URL

npx wrangler deploy

# Output shows:
# Deployed to: https://my-mcp.YOUR_ACCOUNT.workers.dev

Step 3: Update client config

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse"  // ✅ Deployed URL
    }
  }
}

Step 4: Restart Claude Desktop


Problem 3: Worker Not Deployed

Symptoms

  • curl returns connection refused/timeout
  • No response at all

Debugging Steps

Step 1: Check deployment status

npx wrangler whoami
# Shows: logged in as...

npx wrangler deployments list
# Shows: recent deployments (or none)

Step 2: Deploy

npx wrangler deploy

Step 3: Verify deployment

curl https://YOUR-WORKER.workers.dev/

# Should return SOMETHING (even 404 means it's running)

Problem 4: OAuth URL Mismatch

Symptoms

  • OAuth error: redirect_uri does not match
  • OAuth flow starts but fails at callback
  • Token exchange fails

Root Cause

OAuth URLs don't match deployed URL.

Debugging Steps

Step 1: Check ALL three OAuth URLs

cat ~/.config/claude/claude_desktop_config.json

Look for:

{
  "mcpServers": {
    "my-mcp": {
      "url": "https://worker.dev/sse",  // ← Check 1
      "auth": {
        "type": "oauth",
        "authorizationUrl": "https://worker.dev/authorize",  // ← Check 2
        "tokenUrl": "https://worker.dev/token"  // ← Check 3
      }
    }
  }
}

Step 2: Verify ALL URLs match

Must all use same:

  • Protocol: https:// (not mixed http/https)
  • Domain: Same Workers domain
  • No typos: authorize not auth, token not tokens

Step 3: Test each endpoint

curl https://worker.dev/sse  # Main endpoint
curl https://worker.dev/authorize  # OAuth authorize (should show HTML)
curl https://worker.dev/token  # Token endpoint

Problem 5: CORS Errors

Symptoms

  • Access to fetch at '...' blocked by CORS policy
  • Method Not Allowed for OPTIONS requests
  • Works in curl, fails in browser

Root Cause

Missing CORS headers or OPTIONS handler.

Debugging Steps

Step 1: Test with browser

Open browser console and try:

fetch('https://worker.dev/sse')

Step 2: Check OPTIONS handler

Your Worker should handle OPTIONS:

if (request.method === "OPTIONS") {
  return new Response(null, {
    status: 204,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
}

Step 3: Test OPTIONS request

curl -X OPTIONS https://worker.dev/sse -v

Expected: 204 No Content with CORS headers Got: 405 Method Not Allowed → Add OPTIONS handler


Problem 6: Environment Variables Missing

Symptoms

  • TypeError: env.API_KEY is undefined
  • Tools return empty data
  • Silent failures (no error, but wrong results)

Debugging Steps

Step 1: Check local development

# Check .dev.vars exists
cat .dev.vars

# Should have:
API_KEY=dev-key-123
DATABASE_URL=http://localhost:3000

Step 2: Check production config

# Check wrangler.jsonc
cat wrangler.jsonc

Public vars:

{
  "vars": {
    "ENVIRONMENT": "production",
    "LOG_LEVEL": "info"
  }
}

Secrets:

# List secrets
npx wrangler secret list

# Add missing secret
npx wrangler secret put API_KEY

Step 3: Add validation

In your init() method:

async init() {
  // Validate required env vars
  if (!this.env.API_KEY) {
    throw new Error("API_KEY not configured");
  }

  // Continue...
}

Problem 7: Durable Objects Not Working

Symptoms

  • TypeError: Cannot read properties of undefined (reading 'idFromName')
  • State not persisting
  • Durable Object class MyMCP has no migration defined

Debugging Steps

Step 1: Check binding

cat wrangler.jsonc

Must have:

{
  "durable_objects": {
    "bindings": [
      {
        "name": "MY_MCP",
        "class_name": "MyMCP",
        "script_name": "my-mcp-server"
      }
    ]
  }
}

Step 2: Check migration

{
  "migrations": [
    { "tag": "v1", "new_classes": ["MyMCP"] }
  ]
}

Step 3: Deploy with migration

npx wrangler deploy

First deployment requires migration!


Checking Worker Logs

Real-time logs

npx wrangler tail

Shows:

  • All console.log() output
  • Errors with stack traces
  • Request/response info

Filtering logs

# Only errors
npx wrangler tail --format=json | jq 'select(.level=="error")'

# Only specific message
npx wrangler tail | grep "API_KEY"

Common Error Messages

Error: "404 Not Found"

Cause: URL path mismatch (see Problem 1)

Fix:

  1. Check server base path
  2. Update client URL to match
  3. Restart Claude Desktop

Error: "Connection refused" / "ECONNREFUSED"

Cause: Worker not deployed or wrong URL

Fix:

  1. Deploy: npx wrangler deploy
  2. Update client config with deployed URL
  3. Restart Claude Desktop

Error: "OAuth error: redirect_uri does not match"

Cause: OAuth URLs don't match deployed domain

Fix:

  1. Update ALL three OAuth URLs in client config
  2. Use same domain and protocol for all
  3. Restart Claude Desktop

Error: "TypeError: env.BINDING is undefined"

Cause: Missing binding in wrangler.jsonc

Fix:

  1. Add binding to wrangler.jsonc
  2. Deploy: npx wrangler deploy
  3. Restart

Error: "Access to fetch blocked by CORS policy"

Cause: Missing CORS headers or OPTIONS handler

Fix:

  1. Add OPTIONS handler (see Problem 5)
  2. Deploy
  3. Test in browser

Error: "ZodError: Invalid input type"

Cause: Client sends wrong data type for parameter

Fix:

// Use Zod transform
param: z.string().transform(val => parseInt(val, 10))

Testing Checklist

Before declaring "it works":

  • Worker deployed: npx wrangler deploy succeeded
  • Worker running: curl https://worker.dev/ returns something
  • MCP endpoint: curl https://worker.dev/sse returns server info
  • Client config updated with deployed URL
  • Client config URL matches curl test
  • Claude Desktop restarted
  • Tools visible in Claude Desktop
  • Test tool call succeeds
  • Worker logs clean: npx wrangler tail shows no errors
  • (OAuth) All three URLs match
  • (DO) Bindings configured
  • (Secrets) Environment variables set

Advanced Debugging

Enable verbose logging

export class MyMCP extends McpAgent<Env> {
  async init() {
    console.log("MyMCP initializing...");
    console.log("Environment:", {
      hasAPIKey: !!this.env.API_KEY,
      hasDB: !!this.env.DB,
    });

    this.server.tool(
      "test",
      "Test tool",
      { msg: z.string() },
      async ({ msg }) => {
        console.log("Tool called with:", msg);
        return { content: [{ type: "text", text: `Echo: ${msg}` }] };
      }
    );
  }
}

View logs:

npx wrangler tail

Test MCP protocol directly

Use MCP Inspector for protocol-level debugging:

npx @modelcontextprotocol/inspector@latest
  1. Open http://localhost:5173
  2. Enter Worker URL
  3. Click "Connect"
  4. Try "List Tools"
  5. Inspect request/response

Benefits:

  • See exact JSON-RPC messages
  • Test individual tool calls
  • Verify protocol compliance

Check Cloudflare dashboard

  1. Visit https://dash.cloudflare.com/
  2. Go to Workers & Pages
  3. Find your Worker
  4. Check:
    • Deployment status
    • Recent logs
    • Analytics

Prevention

Add health check endpoint

if (pathname === "/" || pathname === "/health") {
  return new Response(JSON.stringify({
    name: "My MCP Server",
    version: "1.0.0",
    transports: { sse: "/sse", http: "/mcp" },
    status: "ok",
    timestamp: new Date().toISOString(),
  }));
}

Test: curl https://worker.dev/health


Add startup validation

async init() {
  // Validate environment
  if (!this.env.API_KEY) {
    throw new Error("API_KEY not configured");
  }

  // Log successful initialization
  console.log("MCP server initialized successfully");
}

Use descriptive 404 messages

return new Response(JSON.stringify({
  error: "Not Found",
  requestedPath: pathname,
  availablePaths: ["/sse", "/mcp", "/health"],
  hint: "Client URL must include base path",
  example: "https://worker.dev/sse"
}), { status: 404 });

Summary

Most common issues (in order):

  1. URL path mismatch (80% of problems)

    • Fix: Match client URL to server base path
  2. Localhost after deployment (10%)

    • Fix: Update config with deployed URL
  3. OAuth URL mismatch (5%)

    • Fix: Update ALL three OAuth URLs
  4. Missing environment variables (3%)

    • Fix: Add to .dev.vars or wrangler secrets
  5. Other (2%)

    • Check Worker logs: npx wrangler tail

Golden debugging workflow:

1. curl https://worker.dev/          # Worker running?
2. curl https://worker.dev/sse       # MCP endpoint works?
3. Check client config matches URL   # Config correct?
4. Restart Claude Desktop            # Reloaded config?
5. npx wrangler tail                 # Any errors?

Remember: 80% of MCP connection issues are URL path mismatches. Always start there!