test_mcp_rpc.py 3.81 KB
"""
Test android_mcp.py via MCP JSON-RPC stdio protocol.
Simulates exactly what Hermes does when it spawns the MCP subprocess.

Usage:
    python test_mcp_rpc.py
"""

import json
import subprocess
import sys
import os

PYTHON = "/data/data/com.termux/files/home/.hermes/hermes-agent/venv/bin/python"
MCP_SCRIPT = "/data/data/com.termux/files/home/.hermes/event_bus/android_mcp.py"
EVENT_DB = "/data/data/com.termux/files/home/.hermes/event_bus/events.db"


def send_rpc(proc, method: str, params: dict = None, msg_id: int = 1) -> dict:
    msg = {"jsonrpc": "2.0", "id": msg_id, "method": method}
    if params:
        msg["params"] = params
    line = json.dumps(msg) + "\n"
    proc.stdin.write(line.encode())
    proc.stdin.flush()
    raw = proc.stdout.readline()
    return json.loads(raw)


def send_notify(proc, method: str, params: dict = None):
    """Send a notification (no id, no response expected)."""
    msg = {"jsonrpc": "2.0", "method": method}
    if params:
        msg["params"] = params
    proc.stdin.write((json.dumps(msg) + "\n").encode())
    proc.stdin.flush()


def main():
    env = os.environ.copy()
    env["EVENT_BUS_DB"] = EVENT_DB
    env["HERMES_QUIET"] = "1"

    print("Spawning android_mcp.py via stdio...")
    proc = subprocess.Popen(
        [PYTHON, MCP_SCRIPT],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=env,
    )

    try:
        # 1. MCP initialize handshake
        resp = send_rpc(proc, "initialize", {
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {"name": "test-client", "version": "0.1"},
        }, msg_id=1)
        print("\n[1] initialize →", json.dumps(resp.get("result", resp.get("error")), indent=2))

        send_notify(proc, "notifications/initialized")

        # 2. List tools
        resp = send_rpc(proc, "tools/list", {}, msg_id=2)
        tools = resp.get("result", {}).get("tools", [])
        print(f"\n[2] tools/list → {len(tools)} tools:")
        for t in tools:
            print(f"    • {t['name']}: {t.get('description','')[:60]}")

        # 3. Call get_stats
        resp = send_rpc(proc, "tools/call", {
            "name": "get_stats",
            "arguments": {}
        }, msg_id=3)
        content = resp.get("result", {}).get("content", [])
        stats = json.loads(content[0]["text"]) if content else {}
        print(f"\n[3] get_stats →")
        print(f"    total_events={stats.get('total_events')}, "
              f"last_type={stats.get('last_event_type')}, "
              f"last_app={stats.get('last_event_app')}")

        # 4. Call get_latest
        resp = send_rpc(proc, "tools/call", {
            "name": "get_latest",
            "arguments": {"limit": 5}
        }, msg_id=4)
        content = resp.get("result", {}).get("content", [])
        result = json.loads(content[0]["text"]) if content else {}
        print(f"\n[4] get_latest(5) → {result.get('count')} events:")
        for ev in result.get("events", []):
            print(f"    [{ev['id']}] {ev['type']} | {ev['app']} | {ev['data']}")

        # 5. Call get_events with since_id
        last_id = result["events"][-1]["id"] if result.get("events") else 0
        resp = send_rpc(proc, "tools/call", {
            "name": "get_events",
            "arguments": {"since_id": last_id, "limit": 10}
        }, msg_id=5)
        content = resp.get("result", {}).get("content", [])
        result2 = json.loads(content[0]["text"]) if content else {}
        print(f"\n[5] get_events(since_id={last_id}) → {result2.get('count')} new events")

        print("\n✓ MCP stdio protocol working correctly")

    finally:
        proc.terminate()
        stderr_out = proc.stderr.read().decode()
        if stderr_out.strip():
            print("\n[stderr]:", stderr_out[:500])


if __name__ == "__main__":
    main()