83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
"""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
|