backtest MCP tool fails with Connection closed; subprocess path works
Package: vibe-trading-ai 0.1.4 (pip, Python 3.14.x on Windows 11)
Transport: stdio MCP via vibe-trading-mcp.exe registered in Claude Code user config
Affected tool: mcp__vibe-trading__backtest only. All other 16 tools work.
Symptoms
Every call to the backtest MCP tool returns:
MCP error -32000: Connection closed
Reproduced across:
- 2 separate Claude Code sessions
- 2 separate subagents spawned within a session
- 6+ tool-call attempts total
- 3 different
run_dir inputs (new run_dir, pre-existing Cursor run_dir in ~/.vibe-trading/backtests/etf_rsi_mean_reversion, fresh run_dir with identical content)
- Both relative and absolute paths
All six tools returned Connection closed with no partial output. No logs written to <run_dir>/logs/. The MCP server continues accepting other tool calls normally after the failure -- only the backtest tool handler crashes the connection.
Working reproduction (direct Python, bypassing MCP)
The engine itself is completely fine. Invoking backtest.runner.main() directly via subprocess against the exact same run_dir succeeds in ~10 seconds and returns full metrics:
from pathlib import Path
from backtest.runner import main
main(Path("/path/to/run_dir"))
# -> prints JSON metrics to stdout, writes artifacts/ and logs/
This strongly suggests the bug is in the MCP tool handler wrapper, not the backtest engine. Probably the handler is attempting to capture stdout/stderr in a way that deadlocks under stdio transport, or is returning a result payload that exceeds a transport buffer.
Minimal reproduction run_dir
run_dir/
config.json
code/
signal_engine.py
config.json:
{
"source": "yfinance",
"codes": ["SPY", "QQQ", "XLK"],
"start_date": "2020-01-01",
"end_date": "2026-04-13",
"initial_capital": 100000,
"commission": 0.001
}
code/signal_engine.py: any class SignalEngine with a generate(data_map) -> Dict[str, pd.Series] method (e.g. the RSI example from ~/.vibe-trading/backtests/etf_rsi_mean_reversion/code/signal_engine.py).
Call via MCP: backtest(run_dir="/absolute/path/to/run_dir") -> MCP error -32000: Connection closed.
Call via subprocess: python -c "from pathlib import Path; from backtest.runner import main; main(Path('/absolute/path/to/run_dir'))" -> works, returns metrics JSON in ~10s.
Impact
Users driving vibe-trading through Claude Code / Cursor / other MCP clients cannot use the most important tool in the suite. Cursor works around this by calling backtest.runner.main() through a local tool handler that spawns a subprocess. Claude Code users hit a dead end unless they know to drop to direct Python.
Suggested fix directions
- Audit the
backtest MCP tool handler in mcp_server.py for stdout/stderr capture that could deadlock the stdio transport.
- If the handler is synchronously executing
backtest.runner.main() in the server process, switch it to subprocess invocation (mirrors what Cursor does successfully).
- Add a sanity test to CI: spawn the MCP server via stdio, call
backtest with a minimal run_dir, assert the call returns.
Happy to provide additional logs or test on alternate Python versions if useful.
backtestMCP tool fails withConnection closed; subprocess path worksPackage:
vibe-trading-ai 0.1.4(pip, Python 3.14.x on Windows 11)Transport: stdio MCP via
vibe-trading-mcp.exeregistered in Claude Code user configAffected tool:
mcp__vibe-trading__backtestonly. All other 16 tools work.Symptoms
Every call to the
backtestMCP tool returns:Reproduced across:
run_dirinputs (new run_dir, pre-existing Cursor run_dir in~/.vibe-trading/backtests/etf_rsi_mean_reversion, fresh run_dir with identical content)All six tools returned
Connection closedwith no partial output. No logs written to<run_dir>/logs/. The MCP server continues accepting other tool calls normally after the failure -- only thebacktesttool handler crashes the connection.Working reproduction (direct Python, bypassing MCP)
The engine itself is completely fine. Invoking
backtest.runner.main()directly via subprocess against the exact samerun_dirsucceeds in ~10 seconds and returns full metrics:This strongly suggests the bug is in the MCP tool handler wrapper, not the backtest engine. Probably the handler is attempting to capture stdout/stderr in a way that deadlocks under stdio transport, or is returning a result payload that exceeds a transport buffer.
Minimal reproduction run_dir
config.json:{ "source": "yfinance", "codes": ["SPY", "QQQ", "XLK"], "start_date": "2020-01-01", "end_date": "2026-04-13", "initial_capital": 100000, "commission": 0.001 }code/signal_engine.py: anyclass SignalEnginewith agenerate(data_map) -> Dict[str, pd.Series]method (e.g. the RSI example from~/.vibe-trading/backtests/etf_rsi_mean_reversion/code/signal_engine.py).Call via MCP:
backtest(run_dir="/absolute/path/to/run_dir")->MCP error -32000: Connection closed.Call via subprocess:
python -c "from pathlib import Path; from backtest.runner import main; main(Path('/absolute/path/to/run_dir'))"-> works, returns metrics JSON in ~10s.Impact
Users driving vibe-trading through Claude Code / Cursor / other MCP clients cannot use the most important tool in the suite. Cursor works around this by calling
backtest.runner.main()through a local tool handler that spawns a subprocess. Claude Code users hit a dead end unless they know to drop to direct Python.Suggested fix directions
backtestMCP tool handler inmcp_server.pyfor stdout/stderr capture that could deadlock the stdio transport.backtest.runner.main()in the server process, switch it to subprocess invocation (mirrors what Cursor does successfully).backtestwith a minimal run_dir, assert the call returns.Happy to provide additional logs or test on alternate Python versions if useful.