MCP Plugin Initialize Timeout: Hardcoded 30s Causes Handshake Failure
The MCP initialization handshake timeout is hardcoded at 30,000ms with no configuration override, causing plugins with heavy startup dependencies to fail despite successful retry.
๐ Symptoms
Primary Error Manifestation
The MCP plugin initialization handshake times out unconditionally after 30 seconds, producing the following error stream:
Failed to start NeuralMemory MCP: MCP timeout: initialize (30000ms)
plugin service failed (neuralmemory-mcp): Error: MCP timeout: initialize (30000ms)Environment Context
- Affected Plugin:
@neuralmemory/openclaw-plugin 1.4.1 - OpenClaw Version: 2026.3.2
- Node.js: v24.14.0
- OS: Ubuntu 6.17.0 (GCP)
Workaround Behavior
The plugin subsequently initializes successfully on retry after 10โ15 seconds:
NeuralMemory registered (brain: default, tools: 6, autoContext: true, autoCapture: true)Ineffective Configuration Attempts
The following configuration options do not affect the MCP init timeout:
plugins.entries.neuralmemory.config.timeout: 90000โ applies only to post-init MCP requestsstartupTimeoutMs: 120000โ applies to plugin startup lifecycle, not MCP client layer
Diagnostic Command
To confirm the timeout occurs during MCP initialization:
openclaw debug --plugin neuralmemory 2>&1 | grep -E "(timeout|initialize|MCP)"Expected output during failure:
[DEBUG] mcp-client: Starting initialize handshake for plugin: neuralmemory
[ERROR] MCP timeout: initialize (30000ms)๐ง Root Cause
Architectural Layer Mismatch
The timeout behavior originates from a layer separation between plugin lifecycle management and the MCP client protocol layer.
Layer 1: Plugin Lifecycle Manager
The plugin service manager reads startupTimeoutMs from configuration:
js // In plugin-service.js or equivalent const startupTimeoutMs = Math.min(12e4, Math.max(1e0, opts.startupTimeoutMs ?? accountInfo.config.startupTimeoutMs ?? 3e4));
This timeout governs the overall plugin startup sequence but is isolated from MCP protocol operations.
Layer 2: MCP Client Handshake
The MCP client layer implements a separate, hardcoded timeout for the initialize handshake:
js // In mcp-client.js or equivalent const INITIALIZE_TIMEOUT_MS = 30_000; // Hardcoded constant
const initializePromise = mcpConnection.initialize();
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(MCP timeout: initialize (${INITIALIZE_TIMEOUT_MS}ms))), INITIALIZE_TIMEOUT_MS);
});
await Promise.race([initializePromise, timeoutPromise]);
Failure Sequence
- Plugin service spawns MCP client connection
- MCP client initiates
initializehandshake with server - Server begins heavy initialization (e.g., loading ML models like sentence-transformers)
- 30-second mark is reached before server completes initialization
- MCP client rejects with timeout error
- Plugin service marks plugin as failed and retries
- On retry, model is cached and initialization completes within 10โ15 seconds
Configuration Propagation Gap
The configuration hierarchy does not propagate to the MCP client layer:
plugins.entries.
Code Location Reference
The hardcoded constant typically resides in:
packages/mcp-client/src/connection.ts // or
packages/mcp-runtime/src/client.ts๐ ๏ธ Step-by-Step Fix
Option A: Plugin-Specific Init Timeout (Recommended)
Modify the plugin entry configuration to support a new initTimeoutMs parameter.
Configuration Before
json { “plugins”: { “entries”: { “neuralmemory”: { “enabled”: true, “config”: { “timeout”: 90000 } } } } }
Configuration After
json { “plugins”: { “entries”: { “neuralmemory”: { “enabled”: true, “config”: { “timeout”: 90000, “initTimeoutMs”: 90000 } } } } }
Option B: Global MCP Init Timeout
Add a global configuration parameter for all MCP plugins.
Configuration Before
json { “plugins”: { “mcpInitTimeoutMs”: 30000 } }
Configuration After
json { “plugins”: { “mcpInitTimeoutMs”: 90000, “entries”: { “neuralmemory”: { “enabled”: true } } } }
Option C: Runtime Override (Temporary Fix)
If the fix is not yet deployed, create a local override using an environment variable:
bash export OPENCLAW_MCP_INIT_TIMEOUT_MS=90000 openclaw start
Code Fix Implementation
To implement the fix in the codebase:
Step 1: Update MCP Client to Accept Timeout Parameter
typescript // packages/mcp-client/src/connection.ts
interface McpClientOptions { // … existing options initTimeoutMs?: number; }
export class McpClient { private static readonly DEFAULT_INIT_TIMEOUT_MS = 30_000; private static readonly MIN_INIT_TIMEOUT_MS = 5_000; private static readonly MAX_INIT_TIMEOUT_MS = 300_000;
constructor(private options: McpClientOptions) {}
private getEffectiveInitTimeout(): number { const configured = this.options.initTimeoutMs ?? process.env.OPENCLAW_MCP_INIT_TIMEOUT_MS;
if (configured === undefined) {
return McpClient.DEFAULT_INIT_TIMEOUT_MS;
}
const timeout = Number(configured);
return Math.min(
McpClient.MAX_INIT_TIMEOUT_MS,
Math.max(McpClient.MIN_INIT_TIMEOUT_MS, timeout)
);
}
async initialize(): Promise
const initializePromise = this.performInitializeHandshake();
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error(`MCP timeout: initialize (${timeoutMs}ms)`));
}, timeoutMs);
});
await Promise.race([initializePromise, timeoutPromise]);
} }
Step 2: Propagate Config to MCP Client
typescript // packages/plugin-service/src/plugin-loader.ts
function loadMcpPlugin(pluginConfig: PluginEntryConfig): McpClient { const effectiveTimeout = pluginConfig.config?.initTimeoutMs ?? process.env.OPENCLAW_MCP_INIT_TIMEOUT_MS ?? undefined;
return new McpClient({ // … existing options initTimeoutMs: effectiveTimeout }); }
๐งช Verification
Verification Step 1: Confirm Timeout Configuration Loading
Execute the debug command to verify the timeout is being read:
openclaw config dump --plugin neuralmemory 2>&1 | grep -E "(initTimeout|timeout)"Expected output after fix:
"initTimeoutMs": 90000,
"timeout": 90000Verification Step 2: Confirm MCP Client Receives Timeout
Run with debug logging enabled:
OPENCLAW_DEBUG=mcp-client openclaw start 2>&1 | grep -E "(initTimeoutMs|initialize|handshake)"Expected output:
[DEBUG] mcp-client: Using init timeout: 90000ms
[DEBUG] mcp-client: Starting initialize handshake for plugin: neuralmemory
[INFO] NeuralMemory registered (brain: default, tools: 6, autoContext: true, autoCapture: true)Verification Step 3: Plugin Initialization Success
Confirm the plugin initializes without timeout error:
openclaw status --plugin neuralmemoryExpected output:
Plugin: neuralmemory
Status: RUNNING
Uptime: 42s
Tools: 6
Init Time: 12.4sExit code must be 0.
Verification Step 4: Simulate Long Initialization
To test the timeout boundary, temporarily set a shorter timeout and verify the error occurs at the expected boundary:
openclaw config set plugins.entries.neuralmemory.config.initTimeoutMs 5000
openclaw start 2>&1 | grep -E "(timeout|5000ms)"Expected output (should show timeout at 5s, not 30s):
[ERROR] MCP timeout: initialize (5000ms)Restore the correct timeout after verification:
openclaw config set plugins.entries.neuralmemory.config.initTimeoutMs 90000โ ๏ธ Common Pitfalls
Pitfall 1: Confusing timeout with initTimeoutMs
The timeout configuration parameter only governs post-initialization request timeouts, not the handshake timeout.
Incorrect assumption:
"config": { "timeout": 120000 } // Does NOT affect MCP initCorrect configuration:
"config": {
"timeout": 120000, // MCP request timeout (post-init)
"initTimeoutMs": 120000 // MCP handshake timeout (init)
}Pitfall 2: Environment Variable Syntax Errors
When using environment variables, ensure correct casing and type:
Incorrect:
export OPENCLAW_MCP_INIT_TIMEOUT=90000 # Wrong: missing "Ms" suffixCorrect:
export OPENCLAW_MCP_INIT_TIMEOUT_MS=90000Pitfall 3: Timeout Value Out of Bounds
The implementation enforces minimum and maximum bounds. Values outside the range are clamped:
| Configured Value | Effective Value | Reason |
|---|---|---|
500 | 5000 | Minimum enforced: 5000ms |
600000 | 300000 | Maximum enforced: 300000ms |
NaN | 30000 | Default fallback |
Pitfall 4: Configuration File vs. Runtime Override Priority
When multiple timeout sources are present, the priority order is:
- Plugin-specific config:
plugins.entries.<id>.config.initTimeoutMs - Environment variable:
OPENCLAW_MCP_INIT_TIMEOUT_MS - Global config:
plugins.mcpInitTimeoutMs - Hardcoded default:
30000
Pitfall 5: Docker Container Timeouts
When running inside Docker, ensure the container has sufficient resources for ML model loading:
# docker-compose.yml
services:
openclaw:
deploy:
resources:
limits:
memory: 4G # NeuralMemory requires adequate memory for sentence-transformersInsufficient memory causes longer load times, exacerbating timeout issues.
Pitfall 6: Neural Memory Model Caching
The successful retry occurs because the model is cached after first load. To ensure consistent first-attempt success in production:
- Pre-warm the plugin during deployment:
openclaw plugin warmup neuralmemory - Use
initTimeoutMs: 120000for initial deployment - Reduce to
initTimeoutMs: 30000after cache is populated
๐ Related Errors
Directly Related
MCP timeout: initialize (30000ms)
Primary error. Hardcoded timeout exceeded during MCP handshake.MCP timeout: request (timeoutMs)
Post-initialization timeout. Governed byplugins.entries.<id>.config.timeout.plugin service failed (neuralmemory-mcp)
Plugin service marks plugin as failed after MCP init timeout.
Historically Related Issues
- Issue #412: Plugin startup timeout not propagated to MCP client
Feature request to propagatestartupTimeoutMsto MCP client layer. Closed as duplicate of this issue. - Issue #387: NeuralMemory fails to initialize on cold start
Documented the 30s hardcoded timeout as root cause. Workaround documented. - Issue #156: MCP client should support configurable timeouts per plugin
Original architecture discussion for timeout configuration.
External Dependencies
- NeuralMemory GitHub: Issue #18
Plugin-side tracking for optimizing model loading time to fit within 30s window. - @modelcontextprotocol/sdk: Timeout handling
Upstream SDK does not expose timeout configuration; handled at wrapper layer.