Files
2026-08-21 16:00:13 +08:00

83 lines
2.6 KiB
Python
Raw Permalink 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.
"""MCP 工具转 ToolSpec(executor 绑定 http_client 调 MCP server tools/call)。
MCP 工具命名 ``mcp_{server_name}_{tool_name}``(下划线分隔,冒号不含 OpenAI 命名规范),``risk_level`` 缺省 L2(外部工具),
``permission_policy`` 由 risk 经 ``default_policy`` 推导。executor 闭包捕获
http_client + server endpoint + auth_token。
协议:标准 MCP Streamable HTTP(JSON-RPC 2.0 over POST /message)
"""
from app.integrations.mcp.client import call_tool
from app.modules.safety.governor import default_policy
from app.modules.tool_runtime.definition import ToolSpec
def make_mcp_tool_specs(
server,
tools: list[dict],
http_client,
meta: dict | None = None,
transport=None,
) -> list[ToolSpec]:
"""把 MCP server 工具清单(list[dict])转为 ToolSpec 列表。
Args:
meta: 可选业务上下文
transport: 可选传输层实例(stdio 用,传给 executor 替代 HTTP call_tool)
"""
specs: list[ToolSpec] = []
for tool in tools:
name = tool.get("name")
if not name:
continue
risk_level = tool.get("risk_level", "L2")
input_schema = tool.get("inputSchema") or tool.get("input_schema")
specs.append(
ToolSpec(
name=f"mcp_{server.name}_{name}",
description=tool.get("description", ""),
risk_level=risk_level,
permission_policy=default_policy(risk_level),
executor=_make_mcp_executor(
http_client,
server.endpoint_url,
name,
server.auth_token,
meta=meta,
transport=transport,
),
input_schema=input_schema,
)
)
return specs
def _make_mcp_executor(
http_client,
endpoint_url,
tool_name,
auth_token,
meta: dict | None = None,
transport=None,
):
async def executor(input, context): # noqa: ARG001
call_meta = dict(meta or {})
if context:
user_role = context.get("user_role") or "free"
call_meta.setdefault("user_role", user_role)
call_meta.setdefault("caller", "hunjing")
if context.get("trace_id"):
call_meta["trace_id"] = context["trace_id"]
if transport is not None:
return await transport.call_tool(tool_name, input)
return await call_tool(
http_client,
endpoint_url,
tool_name,
input,
auth_token,
meta=call_meta or None,
)
return executor