Files
tactility_apps/Apps/McpScreen/tools/smoke_test.py
T
2026-06-24 23:48:21 -04:00

116 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Host smoke test for the Tactility MCP Screen Phase 1 API."""
import argparse
import json
import socket
import urllib.request
def discover(timeout: float = 2.0) -> str | None:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.settimeout(timeout)
try:
sock.sendto(b"DISCOVER_SCREEN", ("255.255.255.255", 5000))
data, address = sock.recvfrom(128)
if data == b"SCREEN_IP_80":
return address[0]
except TimeoutError:
return None
finally:
sock.close()
return None
def rpc(ip: str, request_id: int, method: str, params: dict | None = None) -> dict:
payload = {
"jsonrpc": "2.0",
"id": request_id,
"method": method,
}
if params is not None:
payload["params"] = params
request = urllib.request.Request(
f"http://{ip}/api/mcp",
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=5) as response:
return json.loads(response.read())
def require_result(response: dict, label: str) -> None:
if "error" in response:
raise RuntimeError(f"{label} failed: {response['error']}")
if "result" not in response:
raise RuntimeError(f"{label} returned no result: {response}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--ip", help="Device IP; omit to use UDP discovery")
parser.add_argument(
"--draw",
action="store_true",
help="Also clear the app canvas and draw visible test text",
)
args = parser.parse_args()
ip = args.ip or discover()
if not ip:
raise SystemExit("MCP Screen was not discovered. Pass --ip DEVICE_IP to test directly.")
print(f"Testing MCP Screen at {ip}")
tools = rpc(ip, 1, "tools/list")
require_result(tools, "tools/list")
names = [tool["name"] for tool in tools["result"]["tools"]]
expected = {"get_capabilities", "clear_screen", "draw_text"}
if not expected.issubset(names):
raise RuntimeError(f"Missing tools: {sorted(expected - set(names))}")
print(f"tools/list: {', '.join(names)}")
capabilities = rpc(
ip,
2,
"tools/call",
{"name": "get_capabilities", "arguments": {}},
)
require_result(capabilities, "get_capabilities")
print("get_capabilities:", capabilities["result"]["content"][0]["text"])
if args.draw:
cleared = rpc(
ip,
3,
"tools/call",
{"name": "clear_screen", "arguments": {"color": 1}},
)
require_result(cleared, "clear_screen")
drawn = rpc(
ip,
4,
"tools/call",
{
"name": "draw_text",
"arguments": {
"text": "Tactility MCP is alive",
"x": 20,
"y": 30,
"size": 2,
},
},
)
require_result(drawn, "draw_text")
print("Visible draw test sent.")
print("Phase 1 smoke test passed.")
if __name__ == "__main__":
main()