#!/usr/bin/env python3 """Small CLI for one-off SxCP MCP bridge calls. The repository smoke tests run with the system Python, so MCP dependencies are imported only after a network subcommand is selected. For live bridge calls, run this with the Python environment that has the `mcp` package installed. """ from __future__ import annotations import argparse import json import sys from typing import Any DEFAULT_BRIDGE_URL = "http://192.168.1.12:9188/mcp" def _json_loads(value: str) -> dict[str, Any]: try: parsed = json.loads(value) except json.JSONDecodeError as exc: raise argparse.ArgumentTypeError(str(exc)) from exc if not isinstance(parsed, dict): raise argparse.ArgumentTypeError("arguments JSON must decode to an object") return parsed def _json_default(value: Any) -> Any: if hasattr(value, "model_dump"): return value.model_dump(mode="json") if hasattr(value, "__dict__"): return value.__dict__ return str(value) async def _list_tools(bridge_url: str) -> int: from mcp.client.session import ClientSession from mcp.client.streamable_http import streamablehttp_client async with streamablehttp_client(bridge_url) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = (await session.list_tools()).tools for tool in tools: print(tool.name) return 0 async def _call_tool(bridge_url: str, tool_name: str, arguments: dict[str, Any]) -> int: from mcp.client.session import ClientSession from mcp.client.streamable_http import streamablehttp_client async with streamablehttp_client(bridge_url) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool(tool_name, arguments) print(json.dumps(result, ensure_ascii=True, indent=2, default=_json_default)) return 0 def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--bridge-url", default=DEFAULT_BRIDGE_URL, help=f"MCP bridge URL. Default: {DEFAULT_BRIDGE_URL}", ) subparsers = parser.add_subparsers(dest="command", required=True) subparsers.add_parser("list-tools", help="List available MCP tool names.") call_parser = subparsers.add_parser("call-tool", help="Call one MCP tool.") call_parser.add_argument("tool_name", help="Tool name to call.") call_parser.add_argument( "--arguments-json", type=_json_loads, default={}, help="JSON object with tool arguments.", ) return parser def main(argv: list[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) try: import anyio except ImportError as exc: raise SystemExit( "sxcp_mcp_client requires the MCP Python environment for network calls; " "try /media/p5/miniforge3/bin/python." ) from exc if args.command == "list-tools": return anyio.run(_list_tools, args.bridge_url) if args.command == "call-tool": return anyio.run(_call_tool, args.bridge_url, args.tool_name, args.arguments_json) parser.error(f"unknown command: {args.command}") return 2 if __name__ == "__main__": raise SystemExit(main())