After helping thousands of developers debug MCP servers, here are the most common issues and their fixes.
| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Server not detected by Host | Wrong path in config | Use absolute paths in claude_desktop_config.json |
| 2 | "Cannot find module" error | CommonJS/ESM mismatch | Add "type": "module" to package.json |
| 3 | Tools don't appear in client | Server didn't declare tools capability | Ensure tools are registered before server.connect() |
| 4 | Garbled response / parse error | console.log() corrupting stdout | Replace ALL console.log with console.error |
| 5 | Tool called with wrong arguments | Poor Zod descriptions | Add detailed .describe() to every parameter |
| 6 | Connection drops randomly | Server process crashes on error | Wrap all tool handlers in try/catch, return isError: true |
| 7 | "Transport closed" error | Server exited prematurely | Check for missing dependencies or startup errors in stderr |
| 8 | SSE connection timeout | Missing CORS or wrong endpoint | Verify CORS headers and the correct SSE endpoint URL |
| 9 | Environment variables undefined | Not passed through config | Add "env" object to the server config in Host settings |
| 10 | Resource returns empty | Async resolution not awaited | Ensure resource handler is async and awaits all I/O |
// Always log to stderr, never stdout!
function debugLog(message: string, data?: any) {
if (process.env.DEBUG === "true") {
console.error(`[DEBUG] ${new Date().toISOString()} ${message}`,
data ? JSON.stringify(data, null, 2) : "");
}
}
// Usage in tool handlers:
server.tool("my_tool", "...", { ... }, async (args) => {
debugLog("Tool called with args:", args);
try {
const result = await doWork(args);
debugLog("Tool result:", result);
return { content: [{ type: "text", text: result }] };
} catch (e: any) {
debugLog("Tool error:", { message: e.message, stack: e.stack });
return { isError: true, content: [{ type: "text", text: e.message }] };
}
});