713 lines
13 KiB
Markdown
713 lines
13 KiB
Markdown
# 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`:
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```json
|
|
{
|
|
"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`:**
|
|
```typescript
|
|
// Server
|
|
MyMCP.serveSSE("/sse").fetch(...)
|
|
|
|
// Client (wrong)
|
|
"url": "https://worker.dev" // ❌ Missing /sse
|
|
```
|
|
|
|
**Fix:**
|
|
```json
|
|
"url": "https://worker.dev/sse" // ✅ Include /sse
|
|
```
|
|
|
|
---
|
|
|
|
**Server at `/`, client includes `/sse`:**
|
|
```typescript
|
|
// Server
|
|
MyMCP.serveSSE("/").fetch(...)
|
|
|
|
// Client (wrong)
|
|
"url": "https://worker.dev/sse" // ❌ Server at root, not /sse
|
|
```
|
|
|
|
**Fix:**
|
|
```json
|
|
"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
|
|
|
|
```bash
|
|
cat ~/.config/claude/claude_desktop_config.json
|
|
```
|
|
|
|
Look for:
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"my-mcp": {
|
|
"url": "http://localhost:8788/sse" // ❌ localhost!
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Step 2: Get deployed URL
|
|
|
|
```bash
|
|
npx wrangler deploy
|
|
|
|
# Output shows:
|
|
# Deployed to: https://my-mcp.YOUR_ACCOUNT.workers.dev
|
|
```
|
|
|
|
#### Step 3: Update client config
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
npx wrangler whoami
|
|
# Shows: logged in as...
|
|
|
|
npx wrangler deployments list
|
|
# Shows: recent deployments (or none)
|
|
```
|
|
|
|
#### Step 2: Deploy
|
|
|
|
```bash
|
|
npx wrangler deploy
|
|
```
|
|
|
|
#### Step 3: Verify deployment
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
cat ~/.config/claude/claude_desktop_config.json
|
|
```
|
|
|
|
Look for:
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
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:
|
|
```javascript
|
|
fetch('https://worker.dev/sse')
|
|
```
|
|
|
|
#### Step 2: Check OPTIONS handler
|
|
|
|
Your Worker should handle OPTIONS:
|
|
```typescript
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# Check .dev.vars exists
|
|
cat .dev.vars
|
|
|
|
# Should have:
|
|
API_KEY=dev-key-123
|
|
DATABASE_URL=http://localhost:3000
|
|
```
|
|
|
|
#### Step 2: Check production config
|
|
|
|
```bash
|
|
# Check wrangler.jsonc
|
|
cat wrangler.jsonc
|
|
```
|
|
|
|
**Public vars:**
|
|
```jsonc
|
|
{
|
|
"vars": {
|
|
"ENVIRONMENT": "production",
|
|
"LOG_LEVEL": "info"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Secrets:**
|
|
```bash
|
|
# List secrets
|
|
npx wrangler secret list
|
|
|
|
# Add missing secret
|
|
npx wrangler secret put API_KEY
|
|
```
|
|
|
|
#### Step 3: Add validation
|
|
|
|
In your `init()` method:
|
|
```typescript
|
|
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
|
|
|
|
```bash
|
|
cat wrangler.jsonc
|
|
```
|
|
|
|
**Must have:**
|
|
```jsonc
|
|
{
|
|
"durable_objects": {
|
|
"bindings": [
|
|
{
|
|
"name": "MY_MCP",
|
|
"class_name": "MyMCP",
|
|
"script_name": "my-mcp-server"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Step 2: Check migration
|
|
|
|
```jsonc
|
|
{
|
|
"migrations": [
|
|
{ "tag": "v1", "new_classes": ["MyMCP"] }
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Step 3: Deploy with migration
|
|
|
|
```bash
|
|
npx wrangler deploy
|
|
```
|
|
|
|
First deployment requires migration!
|
|
|
|
---
|
|
|
|
## Checking Worker Logs
|
|
|
|
### Real-time logs
|
|
|
|
```bash
|
|
npx wrangler tail
|
|
```
|
|
|
|
**Shows:**
|
|
- All console.log() output
|
|
- Errors with stack traces
|
|
- Request/response info
|
|
|
|
### Filtering logs
|
|
|
|
```bash
|
|
# 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:**
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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:**
|
|
```bash
|
|
npx wrangler tail
|
|
```
|
|
|
|
---
|
|
|
|
### Test MCP protocol directly
|
|
|
|
Use MCP Inspector for protocol-level debugging:
|
|
|
|
```bash
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:**
|
|
```bash
|
|
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!
|