"""Stdio MCP framing: Cursor expects NDJSON responses (pi-subagents style).""" from __future__ import annotations import json import subprocess import unittest from pathlib import Path REPO = Path(__file__).resolve().parents[2] MCP = REPO / "scripts" / "mcp" class McpNdjsonFramingTest(unittest.TestCase): def _handshake(self, script: Path) -> dict: """Send NDJSON initialize; expect a single NDJSON JSON-RPC response line.""" req = { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "unittest", "version": "0"}, }, } r = subprocess.run( ["node", str(script)], input=json.dumps(req) + "\n", capture_output=True, text=True, cwd=str(REPO), timeout=10, check=False, ) out = (r.stdout or "").strip() # Must not use Content-Length response framing for Cursor IDE. self.assertFalse( out.lower().startswith("content-length:"), f"{script.name} still uses Content-Length response: {out[:120]!r}", ) lines = [ln for ln in out.splitlines() if ln.strip()] self.assertTrue(lines, f"{script.name} produced no stdout: stderr={r.stderr!r}") msg = json.loads(lines[0]) self.assertEqual(msg.get("jsonrpc"), "2.0") self.assertEqual(msg.get("id"), 1) self.assertIn("result", msg) return msg def test_agentctl_initialize_ndjson(self): msg = self._handshake(MCP / "agentctl-mcp.mjs") self.assertEqual(msg["result"]["serverInfo"]["name"], "agentctl") def test_jev_initialize_ndjson(self): msg = self._handshake(MCP / "jev-mcp.mjs") self.assertEqual(msg["result"]["serverInfo"]["name"], "jev") def test_pi_subagents_initialize_ndjson(self): msg = self._handshake(MCP / "pi-subagents-mcp.mjs") self.assertEqual(msg["result"]["serverInfo"]["name"], "pi-subagents") def test_mcp_json_stdio_type_for_all_node_servers(self): cfg = json.loads((REPO / ".cursor" / "mcp.json").read_text(encoding="utf-8")) for name in ("agentctl", "jev", "pi-subagents"): entry = cfg["mcpServers"][name] self.assertEqual(entry.get("type"), "stdio", name) self.assertTrue(any(a.startswith("scripts/") for a in entry["args"]), name) if __name__ == "__main__": unittest.main()