"""Runner wrapper for PAM_NODE MCP tools.""" from __future__ import annotations from typing import Any, Protocol from .models import ActionResult from .output_parser import parse_mcp_result class McpToolClient(Protocol): def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any: ... DEFAULT_NODE_MCP_TOOLS = { "get-online-ips": "pam_get_online_ips", "create-download-task": "pam_create_download_task", "poll-download-progress": "pam_poll_download_progress", "upgrade-ip": "pam_upgrade_ip", "poll-upgrade-progress": "pam_poll_upgrade_progress", "start-ip": "pam_start_ip", "stop-ip": "pam_stop_ip", "verify-ip": "pam_verify_ip", "download-log": "pam_download_log", "rollback-ip": "pam_rollback_ip", } class McpActionRunner: def __init__( self, client: McpToolClient | None = None, tool_names: dict[str, str] | None = None, ) -> None: self.client = client self.tool_names = tool_names or DEFAULT_NODE_MCP_TOOLS.copy() def run( self, action: str, *, params: dict[str, Any], ip: str | None = None, hash_code: str | None = None, stop_first: bool = False, **_: Any, ) -> ActionResult: if self.client is None: raise RuntimeError("MCP client is not configured") tool_name = self.tool_names.get(action) if not tool_name: raise ValueError(f"No MCP tool mapped for action: {action}") arguments = self._build_arguments( action, params=params, ip=ip, hash_code=hash_code, stop_first=stop_first, ) try: payload = self.client.call_tool(tool_name, arguments) except Exception as exc: # pragma: no cover - defensive wrapper return parse_mcp_result(action, {}, ok=False, tool_name=tool_name, error=str(exc)) return parse_mcp_result(action, payload, ok=True, tool_name=tool_name) def _build_arguments( self, action: str, *, params: dict[str, Any], ip: str | None, hash_code: str | None, stop_first: bool, ) -> dict[str, Any]: arguments = { "homeBaseUrl": params.get("HOME_BASE_URL"), "airportCode": params.get("AIRPORT_CODE"), "applicationName": params.get("APP_NAME"), "moduleName": params.get("MODULE_NAME"), "versionNumber": params.get("VERSION_NUMBER"), "actionType": params.get("ACTION_TYPE"), "timeOut": params.get("TIMEOUT"), "logName": params.get("LOG_NAME"), } if ip: arguments["targetIp"] = ip if hash_code: arguments["hashCode"] = hash_code if action == "rollback-ip": arguments["stopFirst"] = stop_first return {key: value for key, value in arguments.items() if value not in (None, "")}