Nothing is more frustrating than an MCP server that works perfectly in development but fails mysteriously in production. Whether you're facing connection timeouts, protocol errors, or silent failures, debugging MCP servers requires a systematic approach and the right tools.
This comprehensive guide covers the most common MCP server issues encountered in production environments and provides practical solutions to get your servers running smoothly.
Understanding MCP Error Categories
MCP server issues generally fall into five main categories, each requiring different debugging strategies:
1. Connection and Transport Issues
These are the most common problems, often manifesting as:
"Connection refused" errors
Timeouts during server initialization
Intermittent disconnections
Protocol handshake failures
2. Protocol Compliance Problems
JSON-RPC protocol violations that cause:
Message parsing errors
Invalid response formats
Missing required fields
Version compatibility issues
3. Tool Execution Failures
Runtime errors in your MCP tools:
Unhandled exceptions in tool functions
Invalid parameter validation
External API failures
Resource access problems
4. Performance and Resource Issues
Problems that affect server reliability:
Memory leaks and high CPU usage
Slow response times
Resource exhaustion
Deadlocks and race conditions
Security-related issues:
Token validation errors
Permission denied responses
Session management problems
OAuth flow failures
Essential Debugging Tools
MCP Inspector: Your Primary Debugging Tool
The MCP Inspector is an invaluable tool for testing and debugging MCP servers. Install and use it to test your server directly:
npm install -g @modelcontextprotocol/inspector
npx @modelcontextprotocol/inspector path/to/your/mcp-server
CLIENT_PORT=8080 SERVER_PORT=9000
The Inspector provides a web interface where you can:
View real-time protocol messages
Test individual tools and resources
Inspect server capabilities
Monitor connection status
Setting Up Comprehensive Logging
Proper logging is crucial for debugging MCP servers. Here's a production-ready logging setup:
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'mcp-server' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
const addCorrelationId = (req, res, next) => {
req.correlationId = require('uuid').v4();
logger.defaultMeta.correlationId = req.correlationId;
next();
};
module.exports = { logger, addCorrelationId };
Network Debugging Tools
Use these command-line tools to diagnose network-related issues:
netstat -tlnp | grep :3000
telnet localhost 3000
tcpdump -i any -n port 3000
curl -v
Debugging Connection Issues
Problem: "Connection Refused" Errors
This is typically the first error you'll encounter. Here's a systematic approach to diagnose it:
Step 1: Verify Server Process
ps aux | grep mcp-server
tail -f /var/log/mcp-server.log
ss -tlnp | grep
Step 2: Validate Configuration
console.log('Server Configuration:', {
port: process.env.PORT || 3000,
host: process.env.HOST || '0.0.0.0',
nodeEnv: process.env.NODE_ENV,
databaseUrl: process.env.DATABASE_URL ? 'configured' : 'missing'
});
const requiredEnvVars = ['DATABASE_URL', 'MCP_API_KEY'];
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
if (missingVars.length > 0) {
console.error('Missing required environment variables:', missingVars);
process.exit(1);
}
Step 3: Test with Minimal Server Create a minimal test server to isolate the problem:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }));
});
server.listen(3000, '0.0.0.0', () => {
console.log('Minimal test server running on port 3000');
});
server.on('error', (error) => {
console.error('Server error:', error);
});
Problem: Intermittent Connection Drops
Intermittent connection issues are harder to debug but often relate to resource exhaustion or network instability:
const EventEmitter = require('events');
class ConnectionMonitor extends EventEmitter {
constructor(server) {
super();
this.server = server;
this.connections = new Set();
this.setupMonitoring();
}
setupMonitoring() {
this.server.on('connection', (socket) => {
this.connections.add(socket);
console.log(`New connection: ${socket.remoteAddress}:${socket.remotePort}`);
console.log(`Total connections: ${this.connections.size}`);
socket.on('close', () => {
this.connections.delete(socket);
console.log(`Connection closed. Remaining: ${this.connections.size}`);
});
socket.on('error', (error) => {
console.error('Socket error:', error);
this.connections.delete(socket);
});
});
setInterval(() => {
console.log(`Health check: ${this.connections.size} active connections`);
this.checkResourceUsage();
}, 30000);
}
checkResourceUsage() {
const memUsage = process.memoryUsage();
console.log('Memory usage:', {
rss: `${Math.round(memUsage.rss / 1024 / 1024)} MB`,
heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`,
external: `${Math.round(memUsage.external / 1024 / 1024)} MB`
});
if (memUsage.heapUsed > 500 * 1024 * 1024) {
console.warn('High memory usage detected');
this.emit('highMemoryUsage', memUsage);
}
}
}
const monitor = new ConnectionMonitor(server);
monitor.on('highMemoryUsage', (usage) => {
console.error('Memory usage alert:', usage);
});
Debugging Protocol Issues
Problem: JSON-RPC Parsing Errors
MCP uses JSON-RPC 2.0, and protocol violations will cause immediate failures:
function validateJSONRPC(message) {
const errors = [];
if (!message.jsonrpc || message.jsonrpc !== '2.0') {
errors.push('Missing or invalid jsonrpc version');
}
if (!message.method && !message.result && !message.error) {
errors.push('Message must have method, result, or error');
}
if (message.method && typeof message.method !== 'string') {
errors.push('Method must be a string');
}
if (message.id !== undefined && typeof message.id !== 'string' && typeof message.id !== 'number') {
errors.push('ID must be string, number, or null');
}
return errors;
}
function validateIncomingMessage(req, res, next) {
try {
const message = JSON.parse(req.body);
const errors = validateJSONRPC(message);
if (errors.length > 0) {
console.error('Protocol validation failed:', errors);
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32600,
message: 'Invalid Request',
data: errors
},
id: message.id || null
});
return;
}
req.jsonrpc = message;
next();
} catch (error) {
console.error('JSON parsing error:', error);
res.status(400).json({
jsonrpc: '2.0',
error: {
code: -32700,
message: 'Parse error'
},
id: null
});
}
}
Problem: stdout/stderr Confusion
A critical rule for MCP servers: only JSON-RPC messages should go to stdout, everything else to stderr:
class StdoutManager {
constructor() {
this.originalConsoleLog = console.log;
this.originalConsoleError = console.error;
this.setupRedirection();
}
setupRedirection() {
console.log = (...args) => {
process.stderr.write(`[LOG] ${args.join(' ')}\n`);
};
console.error = (...args) => {
process.stderr.write(`[ERROR] ${args.join(' ')}\n`);
};
}
writeJSONRPC(message) {
process.stdout.write(JSON.stringify(message) + '\n');
}
restore() {
console.log = this.originalConsoleLog;
console.error = this.originalConsoleError;
}
}
const stdoutManager = new StdoutManager();
stdoutManager.writeJSONRPC({
jsonrpc: '2.0',
result: { status: 'success' },
id: 1
});
Debugging Tool Execution Issues
Problem: Tool Functions Throwing Unhandled Exceptions
Wrap all tool functions with comprehensive error handling:
function createSafeTool(toolName, toolFunction) {
return async function safeTool(params) {
const startTime = Date.now();
const correlationId = require('uuid').v4();
try {
console.error(`[${correlationId}] Starting tool: ${toolName}`, { params });
if (!params || typeof params !== 'object') {
throw new Error('Invalid parameters: expected object');
}
const result = await toolFunction(params);
console.error(`[${correlationId}] Tool completed: ${toolName} in ${Date.now() - startTime}ms`);
return result;
} catch (error) {
console.error