Skip to content

backtest MCP tool fails with "Connection closed"; direct subprocess path works #32

Description

@eltonaguiar

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

  1. Audit the backtest MCP tool handler in mcp_server.py for stdout/stderr capture that could deadlock the stdio transport.
  2. If the handler is synchronously executing backtest.runner.main() in the server process, switch it to subprocess invocation (mirrors what Cursor does successfully).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions