The MCP bundle tool would hang indefinitely when called by AI models, causing the entire MCP server to become unresponsive.
subprocess.run() in mcp_stdio.py:148-155 had no timeout parameter:
process = subprocess.run(
["node", str(script_path)],
cwd=str(mcp_server_root),
env=env,
capture_output=True,
text=True,
check=False,
# ❌ Missing timeout parameter!
)If the Node.js bundler script hung for any reason:
- File I/O deadlock
- Infinite loop in dependency resolution
- Unhandled promise rejection
- Node.js process stuck
...the Python subprocess would wait forever, blocking the MCP tool call indefinitely.
Added 10-second hard timeout with proper exception handling:
try:
process = subprocess.run(
["node", str(script_path)],
cwd=str(mcp_server_root),
env=env,
capture_output=True,
text=True,
check=False,
timeout=10.0, # ✓ Guaranteed exit after 10s
)
except subprocess.TimeoutExpired as e:
raise RuntimeError(
f"Bundle operation timed out after 10 seconds.\n"
f"project_dir: {env.get('PROJECT_DIR')}\n"
f"This usually indicates:\n"
f" 1. Infinite loop in bundler script\n"
f" 2. Hanging file I/O operation\n"
f" 3. Node.js process stuck\n"
f"Captured output before timeout:\n"
f"stdout: {e.stdout.decode('utf-8') if e.stdout else '<none>'}\n"
f"stderr: {e.stderr.decode('utf-8') if e.stderr else '<none>'}"
)Checked for potential infinite loop risks:
Lines 159-161 handle circular deps:
if (stack.has(normalizedEntry)) {
circularDeps.add(normalizedEntry);
return; // Exits recursion
}Lines 164-166:
if (visited.has(normalizedEntry)) {
return; // Prevents re-processing
}Line 102 uses proper regex exec pattern:
while ((match = requirePattern.exec(content)) !== null) {
requires.push(match[1]);
}Global regex resets properly with exec() in while loops.
All file I/O uses fs.promises with proper await, no blocking operations.
- MCP tool will exit within 10 seconds, even if bundler hangs
- AI models won't get stuck waiting for unresponsive tool calls
- Diagnostic info captured - timeout exception includes partial stdout/stderr
- Process killed - subprocess.TimeoutExpired terminates the child process
To test timeout behavior:
# Simulate hanging bundler by adding sleep in bundle-and-deploy.js:
# setTimeout(() => {}, 60000); // 60s sleepExpected result: RuntimeError raised after exactly 10 seconds with timeout message.