Common MCP Errors and How to Fix Them: Complete Troubleshooting Guide With Code Examples
Model Context Protocol errors fail silently or with cryptic messages that make debugging difficult. This guide documents every common MCP error, the root cause for each, the exact fix with working TypeScript code, and the testing approach that catches these errors before they reach production.
Marcus Webb
June 19, 2026
Introduction
MCP (Model Context Protocol) errors fall into four categories: transport errors that prevent the connection from establishing, schema errors where the tool definitions are malformed, runtime errors where tools execute but produce wrong results, and security errors where the server rejects requests it should accept or accepts requests it should reject. Each category has distinct error messages and distinct fixes.
The Problem: Why MCP Errors Are Hard to Debug
MCP servers communicate over stdio or HTTP with JSON-RPC 2.0 messages. When something goes wrong the error often appears in the client (Claude Desktop) as a generic 'tool call failed' message rather than the underlying error. The actual error is in the MCP server logs which developers frequently do not have configured for easy access. This guide shows where to find the real error and what each specific error means.
Causes and Fixes: Every Common MCP Error
Error 1: Server Not Found / Connection Failed
// Error in Claude Desktop logs:
// "Failed to connect to MCP server: Connection refused"
// or
// "MCP server process exited with code 1"
// Root cause 1: Wrong path in Claude Desktop config
// Fix: Check claude_desktop_config.json
// WRONG โ relative path
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["server.js"]
}
}
}
// CORRECT โ absolute path required
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/Users/yourname/projects/mcp-server/dist/server.js"]
}
}
}
// Root cause 2: TypeScript source not compiled
// The config points to .ts file instead of compiled .js
// Fix: compile first, point to dist/ directory
// package.json:
{
"scripts": {
"build": "tsc",
"start": "node dist/server.js"
}
}
// Root cause 3: Missing shebang for executable scripts
// If using npx to run the server:
// Add to top of your server.ts:
// #!/usr/bin/env nodeError 2: Tool Schema Validation Failed
// Error: "Invalid tool definition: schema validation failed"
// or: Tool appears in list but calling it returns error
// WRONG โ missing required fields in schema
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{ name: "my-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// WRONG schema โ properties not defined correctly
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "search_web",
description: "Search the web",
inputSchema: {
type: "object",
// MISSING: properties definition
// MISSING: required array
}
}]
}));
// CORRECT schema โ complete and valid
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "search_web",
description: "Search the web for current information",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query to execute"
},
num_results: {
type: "number",
description: "Number of results to return (1-10)",
default: 5
}
},
required: ["query"] // List all required properties
}
}
]
}));
// CORRECT tool call handler with proper error handling
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ErrorCode,
McpError
} from "@modelcontextprotocol/sdk/types.js";
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "search_web") {
// Validate args before using them
if (!args || typeof args.query !== "string") {
throw new McpError(
ErrorCode.InvalidParams,
"query parameter is required and must be a string"
);
}
try {
const results = await performSearch(args.query, args.num_results || 5);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2)
}
]
};
} catch (error) {
// Proper error propagation
throw new McpError(
ErrorCode.InternalError,
`Search failed: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
});Error 3: Tool Returns Empty or Truncated Results
// Error: Tool executes but returns nothing useful
// Root cause: content array format incorrect
// WRONG โ returning plain string
return {
result: "Here are the search results..."
// MCP expects 'content' array, not 'result'
};
// WRONG โ content items missing 'type' field
return {
content: [
{ text: "Here are the results" } // Missing type field
]
};
// CORRECT โ proper content array format
return {
content: [
{
type: "text", // Required: 'text' | 'image' | 'resource'
text: "Here are the search results:\n\n1. Result one\n2. Result two"
}
]
// isError field: omit for success, set to true for errors
};
// For image content:
return {
content: [
{
type: "image",
data: base64EncodedImageData, // Base64 encoded
mimeType: "image/png" // Required for images
}
]
};
// For resource content (files, URLs):
return {
content: [
{
type: "resource",
resource: {
uri: "file:///path/to/file.txt",
mimeType: "text/plain",
text: fileContents // For text resources
}
}
]
};Error 4: Environment Variables Not Available in MCP Server
// Error: API keys and environment variables undefined in MCP server
// Root cause: MCP servers launched by Claude Desktop do not inherit shell env
// WRONG โ relying on process.env from shell profile
// Your .env or shell config does not apply to MCP server process
// CORRECT โ pass env vars explicitly in Claude Desktop config
{
"mcpServers": {
"my-api-server": {
"command": "node",
"args": ["/path/to/dist/server.js"],
"env": {
"API_KEY": "your-actual-api-key-here",
"DATABASE_URL": "postgresql://localhost:5432/mydb",
"LOG_LEVEL": "info"
}
}
}
}
// For sensitive keys: use a secrets manager and fetch at startup
// server.ts โ fetch secrets before server starts
async function loadSecrets() {
// Option 1: Read from a local secrets file not in source control
const secrets = JSON.parse(
await fs.readFile(path.join(process.env.HOME!, '.mcp-secrets.json'), 'utf-8')
);
process.env.API_KEY = secrets.apiKey;
}
async function main() {
await loadSecrets();
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();Examples: Complete Working MCP Server
// Complete MCP server with all error handling patterns applied
// Copy this as a starting template to avoid common errors
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "example-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Tool definitions
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_weather",
description: "Get current weather for a city",
inputSchema: {
type: "object" as const,
properties: {
city: {
type: "string",
description: "City name to get weather for"
},
units: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature units",
default: "celsius"
}
},
required: ["city"]
}
}
]
}));
// Tool execution with complete error handling
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_weather") {
// Input validation
if (!args?.city || typeof args.city !== "string") {
throw new McpError(ErrorCode.InvalidParams, "city is required");
}
try {
const data = await fetchWeather(args.city, args.units as string || "celsius");
return {
content: [{ type: "text" as const, text: formatWeatherResponse(data) }]
};
} catch (err) {
// Return error in isError format rather than throwing
// This allows Claude to handle the error gracefully
return {
content: [{
type: "text" as const,
text: `Failed to fetch weather: ${err instanceof Error ? err.message : "Unknown error"}`
}],
isError: true
};
}
}
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// MCP servers should NOT console.log โ it corrupts the stdio protocol
// Use console.error for debug output (goes to stderr, not stdout)
console.error("Server started");
}
main().catch(console.error);Common Mistakes
- Mistake 1: Using console.log in an MCP server โ stdout is the communication channel with the MCP client. Any console.log corrupts the JSON-RPC stream and breaks the connection. Use console.error for all debug output.
- Mistake 2: Not handling async errors โ unhandled promise rejections crash the server process and appear as connection errors in the client.
- Mistake 3: Returning responses outside the content array format โ any property not in the MCP specification is ignored by the client.
- Mistake 4: Not restarting Claude Desktop after config changes โ Claude Desktop loads MCP config at startup. Config changes require a full restart.
- Mistake 5: Using process.exit() on errors โ the server should handle errors gracefully and keep running. process.exit causes Claude Desktop to show a disconnected state.
Best Practices
- Test MCP servers with the MCP inspector tool before connecting to Claude Desktop: npx @modelcontextprotocol/inspector node dist/server.js
- Use Zod for input validation in tool handlers โ catches malformed arguments before they reach business logic and produces clear error messages.
- Implement health monitoring with a ping tool that returns server status โ helps diagnose connectivity issues in production MCP deployments.
- Version the MCP server and log the version on startup to stderr โ simplifies debugging when multiple versions are deployed.
FAQ
- Q: Where are MCP error logs in Claude Desktop? A: macOS: ~/Library/Logs/Claude/mcp*.log. Windows: %APPDATA%/Claude/logs/. Check these files first when debugging.
- Q: Can an MCP server have multiple transports? A: No. Choose stdio for local servers or HTTP/SSE for remote servers. A single server instance uses one transport.
- Q: How do I test MCP tools without Claude Desktop? A: Use the MCP Inspector: npx @modelcontextprotocol/inspector [server command]. It provides a web UI for testing tool calls.
- Q: Why does my MCP server work locally but fail when others run it? A: Usually an absolute path issue in the config or a missing environment variable. Use environment-independent paths and explicit env config.
Conclusion
MCP errors are diagnosable once the log location is known and the error categories are understood. Connection errors trace to config or path issues. Schema errors trace to malformed tool definitions. Runtime errors trace to content format issues or unhandled exceptions. The most important practices are using console.error instead of console.log, validating all inputs before processing, and testing with the MCP Inspector before connecting to Claude Desktop.