OpenAI Agents SDK #28:HostedMCPTool 完全拆解——HostedTool 家族第三成员,2 个外层参数 + 6 个 tool_config 字段

本期深拆 HostedMCPTool——HostedTool 家族第三成员(前两期已覆盖 WebSearchTool 和 FileSearchTool)。详解 2 个外层参数(tool_config、on_approval_request)和 6 个 tool_config 字段(type/server_label/server_url/headers/require_approval/allowed_tools),重点展开 require_approval 的人在回路审批机制、MCPToolApprovalRequest 完整回调结构、allowed_tools 最小权限设计,并横向对比 HostedMCPTool / MCPServerSse / MCPServerStdio 三套机制的 8 维度差异矩阵。附 3 条生产建议:最小权限分层设计、Responses API 架构约束确认、静态 token 刷新策略。

리서치 브리프

HostedTool 家族有三个成员:WebSearchTool(#26 深拆)、FileSearchTool(#27 深拆),今天是第三个——HostedMCPTool
它做的事情只有一句话:让 Agent 无需管理本地进程或网络连接,直接通过 OpenAI 服务器调用任意远程 MCP 服务器的工具。
但这个「无需管理」背后藏着几个值得细看的设计决策。

MCP 在 SDK 里的两条路

在进入 HostedMCPTool 之前,先厘清一个经常被混淆的问题:SDK 里的 MCP 支持其实有两套独立机制,走的是完全不同的路。
第一套:mcp_servers 参数 + MCPServerSse / MCPServerStdio
SDK 在每次 Agent 运行时调用 list_tools()call_tool(),由本地 SDK 进程直接与 MCP 服务器通信。需要用 async context manager 管理连接生命周期1
async with MCPServerSse(
    name="My SSE Server",
    params={"url": "https://example.com/sse"}
) as server:
    agent = Agent(
        name="Assistant",
        instructions="Use the tools to achieve the task",
        mcp_servers=[server]
    )
第二套:tools 参数 + HostedMCPTool
工具调用请求发到 OpenAI 服务器,由 OpenAI 平台代理完成对远程 MCP 服务器的实际调用。Agent 代码侧不管理任何连接——和使用 WebSearchToolFileSearchTool 的体验完全一致:
from agents import Agent, Runner, HostedMCPTool

tool = HostedMCPTool(
    tool_config={
        "type": "mcp",
        "server_label": "my_service",
        "server_url": "https://example.com/mcp",
        "require_approval": "never"
    }
)

agent = Agent(
    name="Assistant",
    tools=[tool]
)
两套机制的核心差异:第一套 SDK 自己跑 MCP 客户端,第二套 OpenAI 平台跑 MCP 客户端。

HostedMCPTool 的完整参数

HostedMCPTool 是一个 dataclass,只有两个外层参数:
参数类型说明
tool_configdict必填。包含连接配置的字典(见下表)
on_approval_requestCallable可选。仅当 require_approval 触发时生效的自定义审批函数

tool_config 的 6 个字段

字段类型必填说明
typestr固定为 "mcp"
server_labelstr服务器唯一标识符,用于日志和 tracing
server_urlstr远程 MCP 服务器的 HTTP/HTTPS 地址
headersdict认证请求头,如 Authorization、自定义 API Key
require_approvalstr"never""always",默认 "never"
allowed_toolslist[str]工具白名单,不在列表内的工具对 Agent 不可见

require_approval:人在回路的开关

这是 HostedMCPTool 里最值得单独拆的参数。
require_approval: "always" 意味着 Agent 每次想调用 MCP 工具之前,SDK 会先触发 on_approval_request 回调——由你的代码决定放行还是拦截。2
from agents import (
    Agent, Runner, HostedMCPTool,
    MCPToolApprovalRequest, MCPToolApprovalFunctionResult
)
import os

# 安全操作:直接放行;危险操作:拦截
SAFE_TOOLS = {"list_tasks", "get_project", "read_file"}
RESTRICTED_TOOLS = {"delete_task", "delete_project", "write_file"}

def approve_tool_call(
    request: MCPToolApprovalRequest
) -> MCPToolApprovalFunctionResult:
    tool_name = request.data.name
    if tool_name in SAFE_TOOLS:
        return {"approve": True}
    elif tool_name in RESTRICTED_TOOLS:
        return {"approve": False, "reason": "写入/删除操作需要人工确认"}
    else:
        # 未知工具:默认拒绝
        return {"approve": False, "reason": f"未知工具 {tool_name},需人工审核"}

tool = HostedMCPTool(
    tool_config={
        "type": "mcp",
        "server_label": "file_service",
        "server_url": "https://example.com/mcp",
        "headers": {"Authorization": f"Bearer {os.getenv('API_TOKEN')}"},
        "require_approval": "always"
    },
    on_approval_request=approve_tool_call
)
request.data.name 包含当前待调用的工具名,据此做细粒度决策。
对比之前在 #14「Human in the Loop」中介绍的 interrupt 机制:那套方案是在 Agent 运行流中暂停整个 run、等待外部输入;而 require_approval 是在单次工具调用层面加一个同步的审批函数。粒度更细,但要求审批函数必须是同步可执行的本地逻辑——无法做「等用户点按钮」式的异步审批。

allowed_tools:比 require_approval 更彻底的限制

如果说 require_approval 是「调用前问一遍」,那 allowed_tools 是「干脆看不到」。
tool = HostedMCPTool(
    tool_config={
        "type": "mcp",
        "server_label": "task_service",
        "server_url": "https://example.com/mcp",
        "headers": {"X-API-KEY": os.getenv("TASK_API_KEY")},
        "allowed_tools": [
            "list_tasks",
            "get_task",
            "create_task",
            # 故意排除 delete_task 和 update_task
        ],
        "require_approval": "never"
    }
)
allowed_tools 里没有的工具,LLM 在 tool schema 里根本看不到它们——既不会尝试调用,也不会受 require_approval 逻辑影响。适合以下场景:
  • 多租户系统:不同 Agent 对同一 MCP 服务器有不同权限边界
  • 只读 Agent:只暴露 list_ / get_ 前缀的工具,屏蔽所有写操作
  • Token 节约:MCP 服务器工具数量大时,只暴露当前任务需要的工具子集,减少 schema 的 token 占用

headers:认证字段的设计约束

headerstool_config 里的认证入口,但有一个在 GitHub issues 里反复被提及的限制:目前 OpenAI 平台主要可靠支持 Authorization: Bearer <token> 格式的认证头3,自定义请求头(如 X-API-Key)的行为取决于目标 MCP 服务器是否接受。
另一个已知限制是认证 token 刷新问题:headers 目前只接受静态 dict,无法传入 Callable 来实现动态 token 刷新4。如果 token 有过期时间,只能在构造 HostedMCPTool 实例前手动刷新。

HostedMCPTool vs 两种本地 MCP 方式的横向对比

维度HostedMCPToolMCPServerSseMCPServerStdio
调用位置OpenAI 服务器本地 SDK 进程本地 SDK 进程
连接管理无需(平台代理)async context managerasync context manager
支持的 API 后端仅 Responses API任意任意
认证方式headers 字段参数/env参数/env
require_approval 支持❌(需手动在工具调用层拦截)
工具白名单allowed_tools无内置无内置
工具缓存无内置cache_tools_list=Truecache_tools_list=True
超时控制无参数支持 timeout支持 timeout
适用场景云端 MCP 服务、需要审批远程 SSE 服务器、需缓存本地进程工具、调试

一个完整示例:带认证和工具白名单的任务管理 Agent

import os
from agents import Agent, Runner, HostedMCPTool

taskmaster_tool = HostedMCPTool(
    tool_config={
        "type": "mcp",
        "server_label": "taskmaster",
        "server_url": "https://example-mcp-service.com/mcp",
        "headers": {
            "Authorization": f"Bearer {os.getenv('MCP_API_TOKEN')}"
        },
        "allowed_tools": [
            "list_projects",
            "get_project",
            "list_tasks",
            "create_task",
            "update_task"
        ],
        "require_approval": "never"
    }
)

agent = Agent(
    name="Task Manager",
    instructions=(
        "你是一个任务管理助手。帮助用户查看和管理项目与任务。"
        "不要主动删除任何内容,除非用户明确要求。"
    ),
    tools=[taskmaster_tool]
)

result = Runner.run_sync(agent, "帮我看一下当前有哪些进行中的项目")
print(result.final_output)

三条生产级实践建议

1. 优先用 allowed_tools 做最小权限设计,再按需叠加 require_approval
不要把所有工具都暴露给 Agent 再靠 instructions 约束。allowed_tools 是结构性限制,LLM 看不到的工具就不会去调用;require_approval 是动态防线,用于对确实需要暴露但存在风险的工具加审批。两者分层使用比单独依赖任何一个都更可靠。
2. HostedMCPTool 只能用于 Responses API,提前在架构层面确认
如果你的 Agent 使用了自定义模型(如通过 set_default_openai_api("chat_completions") 切换到 ChatCompletions 路径),HostedMCPTool 会抛 UserError: Hosted tools are not supported with the ChatCompletions API5。这个限制同样适用于 WebSearchToolFileSearchTool——整个 HostedTool 家族都只走 Responses API。
3. 静态 token + 程序启动时刷新,是目前最稳的认证方案
headers 不支持 Callable,意味着无法在运行时动态刷新 token。如果目标 MCP 服务的 token 有过期时间,建议把 token 刷新逻辑放在应用初始化阶段,或者在每次构造新的 Agent/HostedMCPTool 实例前先刷新——不要期望在 tool_config 里做惰性求值。

下期预告:SDK 里的 ComputerToolCodeInterpreterTool——两个不走 LLM 工具调用、直接操作环境的「执行器」类工具,原理和用法与 HostedTool 家族有根本性的不同。

이 콘텐츠를 둘러싼 관점이나 맥락을 계속 보강해 보세요.

  • 로그인하면 댓글을 작성할 수 있습니다.