"""Drive /mcp through the app's ASGI callable, as a client's HTTP request would. Quart's test_client never reaches /mcp (it is dispatched before Quart's pipeline), so MCP tests call app.asgi_app directly. """ from __future__ import annotations import json async def asgi_request(app, method, path, headers=None, body=None): raw = [(k.lower().encode(), v.encode()) for k, v in (headers or {}).items()] if body is not None: raw.append((b"content-length", str(len(body)).encode())) scope = { "type": "http", "asgi": {"version": "3.0", "spec_version": "2.3"}, "http_version": "1.1", "method": method, "scheme": "http", "path": path, "raw_path": path.encode(), "query_string": b"", "root_path": "", "headers": raw, "client": ("testclient", 50000), "server": ("testserver", 80), } pending = [{"type": "http.request", "body": body or b"", "more_body": False}] sent = [] async def receive(): return pending.pop(0) if pending else {"type": "http.disconnect"} async def send(message): sent.append(message) await app.asgi_app(scope, receive, send) start = next(m for m in sent if m["type"] == "http.response.start") data = b"".join(m.get("body", b"") for m in sent if m["type"] == "http.response.body") return start["status"], dict(start.get("headers", [])), data def rpc_result(data: bytes) -> dict: """The JSON-RPC message from a JSON or a single-event SSE response body.""" text = data.decode() if text.lstrip().startswith("{"): return json.loads(text) lines = [ln[5:].strip() for ln in text.splitlines() if ln.startswith("data:")] return json.loads(lines[-1]) def rpc(method, params=None, id_=1) -> bytes: return json.dumps({"jsonrpc": "2.0", "id": id_, "method": method, "params": params or {}}).encode() HEADERS = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}