Fixed the initialization timeout issue with minimal changes: 1. Added stdout flush after server connection to combat Docker buffering 2. Fixed docker-entrypoint.sh to not output to stdout in stdio mode 3. Added process.stdin.resume() to keep server alive 4. Added IS_DOCKER environment variable for future use 5. Updated README to prioritize Docker with correct -i flag configuration The core issue was Docker's block buffering preventing immediate JSON-RPC responses. The -i flag maintains stdin connection, and explicit flushing ensures responses reach Claude Desktop immediately. Also fixed "Shutting down..." message that was breaking JSON-RPC protocol by redirecting it to stderr in stdio mode. Docker is now the recommended installation method as originally intended. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
6.4 KiB
Docker MCP Initialization Timeout Fix Plan
Problem Summary
The n8n-MCP Docker container fails to work with Claude Desktop due to MCP initialization timeout:
- Claude sends
initializerequest - Server receives it (logs show "Message from client: {"method":"initialize"...}")
- Server appears to connect successfully
- No response is sent back to Claude
- Claude times out after 60 seconds
- Container outputs "Shutting down..." which breaks JSON-RPC protocol
Root Cause Analysis
1. Stdout Buffering in Docker
Docker containers often buffer stdout, especially when not running with TTY (-t flag). This is the most likely culprit:
- Node.js/JavaScript may buffer stdout when not connected to a TTY
- Docker's stdout handling differs from direct execution
- The MCP response might be stuck in the buffer
Evidence:
- Common Docker issue (moby/moby#1385, docker/compose#1549)
- Python requires
-uflag for unbuffered output in Docker - Different base images have different buffering behavior
2. MCP SDK Server Connection Issue
The server might not be properly completing the connection handshake:
const transport = new StdioServerTransport();
await server.connect(transport); // This might not complete properly
3. Missing Initialize Handler
While the MCP SDK should handle initialize automatically, there might be an issue with:
- Handler registration order
- Server capabilities configuration
- Transport initialization timing
4. Process Lifecycle Management
The container might be:
- Exiting too early
- Not keeping the event loop alive
- Missing proper signal handling
Fixing Plan
Phase 1: Immediate Fixes (High Priority)
1.1 Force Stdout Flushing
File: src/mcp/server-update.ts
Add explicit stdout flushing after server connection:
async run(): Promise<void> {
await this.ensureInitialized();
const transport = new StdioServerTransport();
await this.server.connect(transport);
// Force flush stdout
if (process.stdout.isTTY === false) {
process.stdout.write('', () => {}); // Force flush
}
logger.info('n8n Documentation MCP Server running on stdio transport');
// Keep process alive
process.stdin.resume();
}
1.2 Add TTY Support to Docker
File: Dockerfile
Add environment variable to detect Docker:
ENV IS_DOCKER=true
ENV NODE_OPTIONS="--max-old-space-size=2048"
File: Update Docker command in README
{
"mcpServers": {
"n8n-mcp": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-t", // Add TTY allocation
"--init", // Proper signal handling
"-e", "MCP_MODE=stdio",
"-e", "LOG_LEVEL=error",
"-e", "DISABLE_CONSOLE_OUTPUT=true",
"ghcr.io/czlonkowski/n8n-mcp:latest"
]
}
}
}
Phase 2: Robust Fixes (Medium Priority)
2.1 Implement Explicit Initialize Handler
File: src/mcp/server-update.ts
Add explicit initialize handler to ensure response:
import {
InitializeRequestSchema,
InitializeResult
} from '@modelcontextprotocol/sdk/types.js';
private setupHandlers(): void {
// Add explicit initialize handler
this.server.setRequestHandler(InitializeRequestSchema, async (request) => {
logger.debug('Handling initialize request', request);
const result: InitializeResult = {
protocolVersion: "2024-11-05",
capabilities: {
tools: {}
},
serverInfo: {
name: "n8n-documentation-mcp",
version: "1.0.0"
}
};
// Force immediate flush
if (process.stdout.isTTY === false) {
process.stdout.write('', () => {});
}
return result;
});
// ... existing handlers
}
2.2 Add Docker-Specific Stdio Handling
File: Create src/utils/docker-stdio.ts
export class DockerStdioTransport extends StdioServerTransport {
constructor() {
super();
// Disable buffering for Docker
if (process.env.IS_DOCKER === 'true') {
process.stdout.setDefaultEncoding('utf8');
if (process.stdout._handle && process.stdout._handle.setBlocking) {
process.stdout._handle.setBlocking(true);
}
}
}
protected async writeMessage(message: string): Promise<void> {
await super.writeMessage(message);
// Force flush in Docker
if (process.env.IS_DOCKER === 'true') {
process.stdout.write('', () => {});
}
}
}
Phase 3: Alternative Approaches (Low Priority)
3.1 Use Wrapper Script
Create a Node.js wrapper that ensures proper buffering:
File: docker-entrypoint.js
#!/usr/bin/env node
// Disable all buffering
process.stdout._handle?.setBlocking?.(true);
process.stdin.setRawMode?.(false);
// Import and run the actual server
require('./dist/mcp/index.js');
3.2 Switch to HTTP Transport for Docker
Consider using HTTP transport instead of stdio for Docker deployments, as it doesn't have buffering issues.
Testing Plan
-
Local Testing:
# Test with Docker TTY docker run -it --rm ghcr.io/czlonkowski/n8n-mcp:latest # Test initialize response echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05"},"id":1}' | \ docker run -i --rm ghcr.io/czlonkowski/n8n-mcp:latest -
Claude Desktop Testing:
- Apply fixes incrementally
- Test with each configuration change
- Monitor Claude Desktop logs
-
Debug Output: Add temporary debug logging to stderr:
console.error('DEBUG: Received initialize'); console.error('DEBUG: Sending response');
Implementation Priority
- Immediate: Add
-tflag to Docker command (no code changes) - High: Force stdout flushing in server code
- Medium: Add explicit initialize handler
- Low: Create Docker-specific transport class
Success Criteria
- Claude Desktop connects without timeout
- No "Shutting down..." message in JSON stream
- Tools are accessible after connection
- Connection remains stable