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:
- Quit Claude Desktop completely
- Reopen
- 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:
authorizenotauth,tokennottokens
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 Allowedfor 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:
- Check server base path
- Update client URL to match
- Restart Claude Desktop
Error: "Connection refused" / "ECONNREFUSED"
Cause: Worker not deployed or wrong URL
Fix:
- Deploy:
npx wrangler deploy - Update client config with deployed URL
- Restart Claude Desktop
Error: "OAuth error: redirect_uri does not match"
Cause: OAuth URLs don't match deployed domain
Fix:
- Update ALL three OAuth URLs in client config
- Use same domain and protocol for all
- Restart Claude Desktop
Error: "TypeError: env.BINDING is undefined"
Cause: Missing binding in wrangler.jsonc
Fix:
- Add binding to wrangler.jsonc
- Deploy:
npx wrangler deploy - Restart
Error: "Access to fetch blocked by CORS policy"
Cause: Missing CORS headers or OPTIONS handler
Fix:
- Add OPTIONS handler (see Problem 5)
- Deploy
- 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 deploysucceeded - Worker running:
curl https://worker.dev/returns something - MCP endpoint:
curl https://worker.dev/ssereturns 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 tailshows 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
- Open http://localhost:5173
- Enter Worker URL
- Click "Connect"
- Try "List Tools"
- Inspect request/response
Benefits:
- See exact JSON-RPC messages
- Test individual tool calls
- Verify protocol compliance
Check Cloudflare dashboard
- Visit https://dash.cloudflare.com/
- Go to Workers & Pages
- Find your Worker
- 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):
-
URL path mismatch (80% of problems)
- Fix: Match client URL to server base path
-
Localhost after deployment (10%)
- Fix: Update config with deployed URL
-
OAuth URL mismatch (5%)
- Fix: Update ALL three OAuth URLs
-
Missing environment variables (3%)
- Fix: Add to .dev.vars or wrangler secrets
-
Other (2%)
- Check Worker logs:
npx wrangler tail
- Check Worker logs:
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!