
OpenAI Agents SDK 每日技术拆解
2026/05/20 09:06:04@wei
OpenAI Agents SDK #29:MCPServerStdio vs MCPServerSse,本地 MCP 工具的两种接法
本期拆解 MCPServerStdio 和 MCPServerSse——OpenAI Agents SDK 接入本地 MCP 生态的两套机制。完整解析两个类的所有参数(MCPServerStdioParams/MCPServerSseParams 字段、cache_tools_list、name、client_session_timeout_seconds),重点讲清 list_tools() 隐性延迟陷阱与缓存策略(cache_tools_list + invalidate_tools_cache),并与上期 HostedMCPTool 做三维对比矩阵。附生产建议:命名 server、按场景设超时、默认开缓存。
リサーチノート
上一期(#28)拆解了 HostedMCPTool——让 OpenAI 云端平台代理调用远程 MCP 服务器。但现实项目里更常见的场景是:MCP 服务跑在你自己的机器上,或者你有一个自建的 SSE 端点。这时候要用的是另外两个类:
MCPServerStdio 和 MCPServerSse。本期完整拆解这两个类的参数、工作原理、性能陷阱和缓存机制。
先理解 MCP 的两种「传输模式」
MCP 规范定义了两类服务,区别在底层通信方式:1
| 传输方式 | 服务类 | 运行位置 | 通信机制 |
|---|---|---|---|
| stdio | MCPServerStdio | 作为当前进程的子进程运行 | 标准输入/输出管道 |
| HTTP over SSE | MCPServerSse | 远程或本地 HTTP 服务 | HTTP + Server-Sent Events |
可以把 stdio 想象成「起一个本地子进程来当工具」,把 SSE 想象成「连一个带订阅推送的 HTTP 服务」。两者都由 SDK 统一调度,Agent 不感知底层传输差异。
MCPServerStdio:让子进程成为工具
参数全览
MCPServerStdio 构造函数接受 4 个参数:| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
params | MCPServerStdioParams | ✅ | 子进程启动配置,含 command、args、env、cwd 等字段 |
cache_tools_list | bool | ❌(默认 False) | 是否缓存 list_tools() 结果 |
name | str | None | ❌(默认 None) | 给这个 MCP server 取一个可读名称,用于日志和 tracing |
client_session_timeout_seconds | float | None | ❌ | 客户端会话超时时间(秒) |
MCPServerStdioParams 字段
params 本身是一个 TypedDict,核心字段如下:| 字段 | 类型 | 说明 |
|---|---|---|
command | str(必填) | 可执行命令,如 "npx"、"python"、"node" |
args | list[str](可选) | 命令行参数,如 ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] |
env | dict(可选) | 额外注入的环境变量 |
cwd | str(可选) | 子进程的工作目录 |
典型用法:接入官方文件系统 MCP Server
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async def main():
async with MCPServerStdio(
params={
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp/samples"],
},
cache_tools_list=True, # 工具列表不变时开启缓存,避免每次 run 重新 list
name="filesystem-server", # 便于 tracing 里识别
) as server:
agent = Agent(
name="FileAgent",
instructions="帮用户读写 /tmp/samples 目录下的文件",
mcp_servers=[server],
)
result = await Runner.run(agent, "列出目录里有哪些文件")
print(result.final_output)
asyncio.run(main())async with 是关键——MCPServerStdio 实现了异步上下文管理器,进入时启动子进程,退出时自动清理。不要在 async with 块外面使用这个 server 对象。MCPServerSse:连接远程 SSE 服务
参数全览
MCPServerSse 的签名与 MCPServerStdio 高度对称:| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
params | MCPServerSseParams | ✅ | SSE 连接配置,含 url、headers、timeout、sse_read_timeout |
cache_tools_list | bool | ❌(默认 False) | 是否缓存 list_tools() 结果 |
name | str | None | ❌(默认 None) | 可读名称 |
client_session_timeout_seconds | float | None | ❌ | 客户端会话超时 |
MCPServerSseParams 字段
| 字段 | 类型 | 说明 |
|---|---|---|
url | str(必填) | SSE 端点 URL,如 "http://localhost:8080/sse" |
headers | dict(可选) | 额外请求头,可在此传 Authorization token |
timeout | float(可选) | 初始连接超时(秒) |
sse_read_timeout | float(可选) | SSE 读取超时(秒),长连接时建议显式设置 |
典型用法:连接自建 SSE 服务
from agents.mcp import MCPServerSse
async with MCPServerSse(
params={
"url": "http://localhost:8080/sse",
"headers": {"Authorization": "Bearer my-token"},
"timeout": 10.0,
"sse_read_timeout": 300.0, # 长连接 SSE 保持 5 分钟
},
cache_tools_list=True,
name="my-sse-server",
) as server:
agent = Agent(
name="DataAgent",
instructions="使用工具查询业务数据",
mcp_servers=[server],
)
...最容易踩的坑:list_tools() 的隐性延迟
对 stdio server 来说,
list_tools() 意味着进程间 IPC 通信。对 SSE server 来说,每次 list_tools() 都是一次完整的 HTTP 往返。如果你的 Agent 每分钟被调用几十次,这就是几十次不必要的网络开销。解法就是
cache_tools_list=True——SDK 会在首次 list_tools() 后把结果缓存在内存里,后续 run 直接复用。什么时候不能开缓存? 工具列表会动态变化的场景,比如:
- 用户授权后才能看到某些工具
- MCP server 支持热插拔插件
需要手动刷新缓存时,调用
server.invalidate_tools_cache():# 某个操作后工具列表发生变化,手动失效
server.invalidate_tools_cache()
# 下次 Runner.run 会重新执行 list_tools()多个 MCP Server 并联
mcp_servers 接收列表,可以同时挂多个 server,类型混搭没有限制:async with MCPServerStdio(
params={"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]},
cache_tools_list=True,
name="local-fs",
) as fs_server, MCPServerSse(
params={"url": "http://remote-tools:8080/sse"},
cache_tools_list=True,
name="remote-api",
) as api_server:
agent = Agent(
name="MultiToolAgent",
instructions="综合使用本地文件和远程 API 完成任务",
mcp_servers=[fs_server, api_server],
)
result = await Runner.run(agent, "从远程 API 拉数据并写入本地文件")LLM 会看到两个 server 暴露的全部工具,按需调用。SDK 会在 Tracing 里分别记录每个 server 的
list_tools 调用,便于排查。Tracing 自动追踪 MCP 操作
OpenAI Agents SDK 的 Tracing 子系统会自动捕获两类 MCP 操作:
- 对 MCP server 的
list_tools()调用(记录时机、server name、返回工具数量) - LLM 触发的工具调用(
call_tool()),包含工具名、入参和耗时
这意味着不需要任何额外代码,就能在 OpenAI Dashboard 或自定义 trace processor 里看到完整的 MCP 调用链路。
三类 MCP 机制横向对比
上一期拆解了 HostedMCPTool。结合今天的内容,三套机制完整对比如下:
| 维度 | MCPServerStdio | MCPServerSse | HostedMCPTool |
|---|---|---|---|
| 运行位置 | 客户端本地子进程 | 任意(本地/远程 HTTP) | OpenAI 云端代理 |
| 传输方式 | stdin/stdout IPC | HTTP + SSE | HTTPS(Responses API) |
| 鉴权方式 | env 注入环境变量 | headers 传 token | tool_config.headers |
| 缓存工具列表 | cache_tools_list | cache_tools_list | ❌ 不支持 |
| 审批机制 | ❌ | ❌ | require_approval 人工审批 |
| 需要 Responses API | ❌ | ❌ | ✅ 强依赖 |
| 适用场景 | 本地 CLI 工具/文件系统 | 自建 HTTP MCP 服务 | 托管远程 MCP 服务器 |
选型建议:本地调试、命令行工具或需要访问本地文件系统 →
MCPServerStdio;有自建的 HTTP 服务或内网 MCP 服务 → MCPServerSse;需要调用 OpenAI 平台代理的公共 MCP 服务且接受强锁定 Responses API → HostedMCPTool。3 条生产建议
1. 始终给每个 server 取
namename 字段不只是可读性问题。SDK 的 Tracing 会把它嵌入每条 MCP 操作记录,生产环境里多个 server 并联时不命名会让 trace 变成乱麻,第一时间定位不了是哪个 server 出了问题。2.
client_session_timeout_seconds 按场景设默认值在高延迟环境下容易触发超时报错,尤其是 SSE server 在云端时。建议首次部署就显式配置,而不是等报错了再加。stdio server 在启动较慢的 Node.js 进程时同样需要适当延长。
3.
cache_tools_list=True 是默认该开的大多数 MCP server 的工具列表在进程生命周期内不变。关掉缓存(默认行为)意味着每次
Runner.run 都多一次 IPC 或 HTTP 往返,高频调用时累积延迟很可观。非动态工具场景无条件开启,动态场景在工具变化时调 invalidate_tools_cache() 手动刷新。
このコンテンツについて、さらに観点や背景を補足しましょう。