dark a11904b7c5 docs/build: 补齐中文注释、流程图和 Linux 解压即用打包脚本
- 为 pam_deploy_graph 生产代码补充中文模块、类、函数/方法文档字符串
- 将原有英文说明和主要英文异常提示改为中文
- 新增当前整体逻辑结构流程图文档,覆盖模块结构、执行链路、action 路由、人工确认和 checkpoint 续跑
- 新增 Linux 自带运行环境打包脚本,使用 PyInstaller 生成解压即用目录和 tar.gz
- 新增 Linux 打包说明,包含构建命令、运行方式、依赖说明和包大小评估
- 同步 README,补充流程图、打包方式、产物路径和大小预估
- 更新相关测试断言以匹配中文错误提示
2026-06-01 11:21:42 +08:00

101 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""PAM_NODE MCP 工具的 action runner 封装。"""
from __future__ import annotations
from typing import Any, Protocol
from .models import ActionResult
from .output_parser import parse_mcp_result
class McpToolClient(Protocol):
"""MCP 工具客户端需要实现的最小同步接口。"""
def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
"""调用指定 MCP tool并返回工具原始输出。"""
...
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:
"""把 Agent action 转换为 MCP tool 调用。"""
def __init__(
self,
client: McpToolClient | None = None,
tool_names: dict[str, str] | None = None,
) -> None:
"""保存 MCP client 和 action 到 tool name 的映射。"""
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:
"""执行一个 PAM_NODE action并归一化为 ActionResult。"""
if self.client is None:
raise RuntimeError("尚未配置 MCP client")
tool_name = self.tool_names.get(action)
if not tool_name:
raise ValueError(f"action 未映射 MCP tool: {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 - 防御性异常包装
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]:
"""把 Agent 参数转换为 MCP tool 所需的入参。"""
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, "")}