#!/usr/bin/env python3 """MCP test harness: run backtest then exercise all granular analysis tools.""" import json import subprocess import sys import time import threading BINARY = "/Users/masdevid/.cargo/bin/mt5-quant" TIMEOUT = 1200 # 20 min for backtest def send(proc, msg): line = json.dumps(msg) + "\n" proc.stdin.write(line.encode()) proc.stdin.flush() def recv(proc, timeout=30): """Read one JSON-RPC response line.""" proc.stdout.readline() # skip blank / notification lines deadline = time.time() + timeout while time.time() < deadline: line = proc.stdout.readline().decode("utf-8", errors="replace").strip() if not line: continue try: return json.loads(line) except json.JSONDecodeError: pass # skip non-JSON lines (logs etc) return None def recv_with_id(proc, expected_id, timeout=1200): """Read lines until we get a response with the expected id.""" deadline = time.time() + timeout while time.time() < deadline: line = proc.stdout.readline().decode("utf-8", errors="replace").strip() if not line: continue try: msg = json.loads(line) if msg.get("id") == expected_id: return msg except json.JSONDecodeError: pass return None def tool_call(proc, call_id, name, arguments=None): send(proc, { "jsonrpc": "2.0", "id": call_id, "method": "tools/call", "params": {"name": name, "arguments": arguments or {}} }) def extract_text(resp): if not resp: return "NO RESPONSE" result = resp.get("result", {}) content = result.get("content", []) if content: text = content[0].get("text", "") try: parsed = json.loads(text) return json.dumps(parsed, indent=2)[:1500] except Exception: return text[:1500] if "error" in resp: return f"ERROR: {resp['error']}" return str(resp)[:500] def ok(resp): if not resp: return False result = resp.get("result", {}) content = result.get("content", []) is_error = result.get("isError", True) if is_error: return False if content: text = content[0].get("text", "") try: parsed = json.loads(text) return parsed.get("success", True) is not False except Exception: return True return not is_error # ── Start server ────────────────────────────────────────────────────────────── print("Starting mt5-quant MCP server...") proc = subprocess.Popen( [BINARY], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ) # ── Handshake ───────────────────────────────────────────────────────────────── send(proc, { "jsonrpc": "2.0", "id": 0, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test-harness", "version": "1.0"} } }) init_resp = recv_with_id(proc, 0, timeout=10) if not init_resp: print("FATAL: no initialize response") proc.terminate() sys.exit(1) print("Initialized OK") send(proc, {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}) # ── Run backtest ────────────────────────────────────────────────────────────── print("\n=== run_backtest (DPS21, XAUUSD, M5, OHLC, skip_compile) ===") tool_call(proc, 1, "run_backtest", { "expert": "DPS21", "symbol": "XAUUSDc", "timeframe": "M5", "model": 1, # OHLC – faster "skip_compile": True, "timeout": 900, "startup_delay_secs": 15, }) resp = recv_with_id(proc, 1, timeout=TIMEOUT) print(extract_text(resp)) backtest_ok = ok(resp) print(f"backtest_ok={backtest_ok}") if not backtest_ok: print("Backtest failed – aborting analysis tests") proc.terminate() sys.exit(1) # ── Full analysis ───────────────────────────────────────────────────────────── print("\n=== analyze_report (latest, all analytics) ===") tool_call(proc, 2, "analyze_report", {}) resp = recv_with_id(proc, 2, timeout=60) print(extract_text(resp)) print(f"analyze_report ok={ok(resp)}") # ── Granular analytics ──────────────────────────────────────────────────────── granular = [ ("analyze_monthly_pnl", {}), ("analyze_drawdown_events", {}), ("analyze_top_losses", {"limit": 5}), ("analyze_loss_sequences", {}), ("analyze_position_pairs", {}), ("analyze_direction_bias", {}), ("analyze_streaks", {}), ("analyze_concurrent_peak", {}), ("analyze_profit_distribution", {}), ("analyze_time_performance", {}), ("analyze_hold_time_distribution",{}), ("analyze_layer_performance", {}), ("analyze_volume_vs_profit", {}), ("analyze_costs", {}), ("analyze_efficiency", {}), ("list_deals", {"limit": 5}), ("search_deals_by_comment", {"query": "tp"}), ("search_deals_by_magic", {"magic": "0"}), ] results = {} for i, (tool, args) in enumerate(granular, start=10): print(f"\n=== {tool} ===") tool_call(proc, i, tool, args) resp = recv_with_id(proc, i, timeout=30) status = "OK" if ok(resp) else "FAIL" results[tool] = status print(f" {status}") if status == "FAIL": print(" " + extract_text(resp)[:400]) # ── Summary ─────────────────────────────────────────────────────────────────── print("\n\n========== SUMMARY ==========") passed = [t for t, s in results.items() if s == "OK"] failed = [t for t, s in results.items() if s != "OK"] print(f"PASSED ({len(passed)}): {', '.join(passed)}") if failed: print(f"FAILED ({len(failed)}): {', '.join(failed)}") else: print("All granular analytics PASSED") proc.terminate()