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 端点。这时候要用的是另外两个类:MCPServerStdioMCPServerSse
本期完整拆解这两个类的参数、工作原理、性能陷阱和缓存机制。

先理解 MCP 的两种「传输模式」

MCP 规范定义了两类服务,区别在底层通信方式:1
传输方式服务类运行位置通信机制
stdioMCPServerStdio作为当前进程的子进程运行标准输入/输出管道
HTTP over SSEMCPServerSse远程或本地 HTTP 服务HTTP + Server-Sent Events
可以把 stdio 想象成「起一个本地子进程来当工具」,把 SSE 想象成「连一个带订阅推送的 HTTP 服务」。两者都由 SDK 统一调度,Agent 不感知底层传输差异。

MCPServerStdio:让子进程成为工具

参数全览

MCPServerStdio 构造函数接受 4 个参数:
参数类型必填说明
paramsMCPServerStdioParams子进程启动配置,含 commandargsenvcwd 等字段
cache_tools_listbool❌(默认 False是否缓存 list_tools() 结果
namestr | None❌(默认 None给这个 MCP server 取一个可读名称,用于日志和 tracing
client_session_timeout_secondsfloat | None客户端会话超时时间(秒)

MCPServerStdioParams 字段

params 本身是一个 TypedDict,核心字段如下:
字段类型说明
commandstr(必填)可执行命令,如 "npx""python""node"
argslist[str](可选)命令行参数,如 ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
envdict(可选)额外注入的环境变量
cwdstr(可选)子进程的工作目录

典型用法:接入官方文件系统 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 高度对称:
参数类型必填说明
paramsMCPServerSseParamsSSE 连接配置,含 urlheaderstimeoutsse_read_timeout
cache_tools_listbool❌(默认 False是否缓存 list_tools() 结果
namestr | None❌(默认 None可读名称
client_session_timeout_secondsfloat | None客户端会话超时

MCPServerSseParams 字段

字段类型说明
urlstr(必填)SSE 端点 URL,如 "http://localhost:8080/sse"
headersdict(可选)额外请求头,可在此传 Authorization token
timeoutfloat(可选)初始连接超时(秒)
sse_read_timeoutfloat(可选)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() 的隐性延迟

每次 Runner.run() 被调用,SDK 都会对所有 mcp_servers 执行一次 list_tools(),把工具列表告知 LLM。这个行为是无条件的,哪怕你的工具一分钟内不会变。1
对 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 操作:
  1. 对 MCP server 的 list_tools() 调用(记录时机、server name、返回工具数量)
  2. LLM 触发的工具调用(call_tool()),包含工具名、入参和耗时
这意味着不需要任何额外代码,就能在 OpenAI Dashboard 或自定义 trace processor 里看到完整的 MCP 调用链路。

三类 MCP 机制横向对比

上一期拆解了 HostedMCPTool。结合今天的内容,三套机制完整对比如下:
维度MCPServerStdioMCPServerSseHostedMCPTool
运行位置客户端本地子进程任意(本地/远程 HTTP)OpenAI 云端代理
传输方式stdin/stdout IPCHTTP + SSEHTTPS(Responses API)
鉴权方式env 注入环境变量headers 传 tokentool_config.headers
缓存工具列表cache_tools_listcache_tools_list❌ 不支持
审批机制require_approval 人工审批
需要 Responses API✅ 强依赖
适用场景本地 CLI 工具/文件系统自建 HTTP MCP 服务托管远程 MCP 服务器
选型建议:本地调试、命令行工具或需要访问本地文件系统 → MCPServerStdio;有自建的 HTTP 服务或内网 MCP 服务 → MCPServerSse;需要调用 OpenAI 平台代理的公共 MCP 服务且接受强锁定 Responses API → HostedMCPTool

3 条生产建议

1. 始终给每个 server 取 name
name 字段不只是可读性问题。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() 手动刷新。

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

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