/** * @file Runs every MCP tool (`mcp/server.js`) against a REAL in-test dashboard * (throwaway DB and CLAUDE_HOME), so a tool whose path or query drifts from * the actual API fails here rather than in a user's session. The MCP server is * spawned asynchronously because the dashboard lives in this process. * @author Nguyễn Ngọc Trí Vĩ */ const { it, before, after } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const readline = require("node:readline"); const { spawn } = require("node:child_process"); const TMP = path.join(os.tmpdir(), `agenthub-mcp-live-${Date.now()}-${process.pid}`); process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db"); process.env.CLAUDE_HOME = path.join(TMP, "home"); process.env.DASHBOARD_DATA_DIR = path.join(TMP, "data"); process.env.DASHBOARD_LIVENESS_PROBE = "0"; const { createApp, startServer } = require("../index"); const { db } = require("../db"); let server; let child; let rpc; before(async () => { server = await startServer(createApp(), 0); const url = `http://127.0.0.1:${server.address().port}`; child = spawn(process.execPath, [path.resolve(__dirname, "..", "..", "mcp", "server.js")], { env: { ...process.env, AGENTHUB_URL: url }, stdio: ["pipe", "pipe", "inherit"], }); const waiting = new Map(); readline.createInterface({ input: child.stdout }).on("line", (line) => { const msg = JSON.parse(line); waiting.get(msg.id)?.(msg); }); let next = 1; rpc = (method, params) => { const id = next++; child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); return new Promise((resolve) => waiting.set(id, resolve)); }; await rpc("initialize", { protocolVersion: "2025-06-18" }); }); after(() => { child?.stdin.end(); server?.close(); db?.close(); fs.rmSync(TMP, { recursive: true, force: true }); }); it("every tool answers JSON from the real API", async () => { const { result } = await rpc("tools/list", {}); const args = { get_session_cost: { session_id: "none" }, get_lane_cost: { lane: 1 }, get_session: { session_id: "none" }, get_session_stats: { session_id: "none" }, }; for (const { name } of result.tools) { const res = await rpc("tools/call", { name, arguments: args[name] || {} }); const out = res.result.content[0].text; if (name === "get_session" || name === "get_session_stats") { assert.match(out, /HTTP 404: Session not found/, name); } else if (name === "get_budget_status" && res.result.isError) { // Budgets come from another phase; until then the route is missing. assert.match(out, /Not available on this dashboard/); } else { assert.equal(res.result.isError, undefined, `${name}: ${out.slice(0, 200)}`); JSON.parse(out); } } });