"""The Novox Mesh SDK for Python — the skeleton (novox/hq ADR 0187). A tools bundle is a process the node's runtime launches and speaks MCP over stdio to: `initialize`, `tools/list` once, `tools/call` per call, newline-framed JSON-RPC on stdin and stdout. This module is the loop, so a bundle writes its tools and nothing else. A tool named `.` is the module's implementation of that seat's verb; any other name is the module's own. No transport here: the bus, the subjects and the memberships are the runtime's (ADR 0039, 0175). stdout is the protocol; log to stderr. """ import json import sys from dataclasses import dataclass, field from typing import Any, Callable, Dict, List PROTOCOL = "2025-03-26" @dataclass class Tool: name: str description: str run: Callable[[Dict[str, Any]], Any] # The JSON schema of the arguments, or the bare property map the catalogue's modules write. input: Dict[str, Any] = field(default_factory=dict) def as_schema(given: Dict[str, Any]) -> Dict[str, Any]: if not given: return {"type": "object", "properties": {}} if given.get("type") == "object" or "properties" in given: return given return {"type": "object", "properties": given} def serve(name: str, tools: List[Tool]) -> None: """Serve these tools to the runtime over stdio until stdin closes.""" by_name = {t.name: t for t in tools} out = sys.stdout def say(message: Dict[str, Any]) -> None: out.write(json.dumps(message) + "\n") out.flush() for line in sys.stdin: line = line.strip() if not line: continue try: request = json.loads(line) except ValueError: continue rid = request.get("id") method = request.get("method") params = request.get("params") or {} if method == "initialize": say({"jsonrpc": "2.0", "id": rid, "result": {"protocolVersion": PROTOCOL, "capabilities": {"tools": {}}, "serverInfo": {"name": name, "version": "1"}}}) elif method == "notifications/initialized": pass elif method == "ping": if rid is not None: say({"jsonrpc": "2.0", "id": rid, "result": {}}) elif method == "tools/list": say({"jsonrpc": "2.0", "id": rid, "result": {"tools": [ {"name": t.name, "description": t.description, "inputSchema": as_schema(t.input)} for t in tools]}}) elif method == "tools/call": tool = by_name.get(str(params.get("name", ""))) if tool is None: say({"jsonrpc": "2.0", "id": rid, "error": {"code": -32602, "message": f"{name} has no tool {params.get('name')}"}}) continue try: result = tool.run(params.get("arguments") or {}) say({"jsonrpc": "2.0", "id": rid, "result": {"content": [{"type": "text", "text": json.dumps(result)}]}}) except Exception as err: # the tool failed: a tool error, not a protocol one say({"jsonrpc": "2.0", "id": rid, "result": {"content": [{"type": "text", "text": str(err)}], "isError": True}}) elif rid is not None: say({"jsonrpc": "2.0", "id": rid, "error": {"code": -32601, "message": f"no {method}"}})