OpenAI 把 MCP 写进官方示例了:我的 MCP Server 改造笔记
摘要: OpenAI Agents API 公开测试,官方示例代码直接把 MCP 写成一等公民。本文对照官方文档,梳理已有 MCP Server 要动的地方:HTTP 传输接入、server_label 标识,以及为"按需加载"重写的工具描述。附接入结构拆解和改造清单。
文章目录
官方示例里直接长出了 MCP
写 MCP Server 那篇文章的时候,我结尾提过一句:给 AI 写 API 文档,比给 AI 写接口本身更难。前几天 OpenAI 发布 Agents API,我刷到官方博客,第一反应是——工具这一层,他们开始认真对待了。
先给结论:这次不是"宣布支持 MCP 概念"那种官宣,官方示例代码里直接写了 type: "mcp",连传输方式都定好了。 手上有 MCP Server 的人,接过去的姿势基本是确定的。
我把自己写过的 MCP Server 对着官方文档盘了一遍,把要动的点整理成这篇改造笔记。先声明边界:Agents API 还在公开测试期,这篇是基于官方文档和既有工程经验的梳理,不是已经跑通完整链路的实战复盘。哪些是文档里明写的、哪些是我自己的判断,下文会标出来。
Agents API 到底是什么
以下内容基于 2026.09.10 发布的公开测试版,接口仍在快速迭代,看到文章时如果版本已变,以官方文档为准。
官方公告讲得很直白:把 Codex 背后的那套 harness 托管给开发者,通过一次 API 调用创建 agent,指定任务、模型、工具和环境,剩下的交给 OpenAI 跑。
几个关键事实,都能在公告原文找到:
- 公开测试版,发布即对所有开发者开放,没有附加费用,只按 token 和工具使用收费
- 官方示例用的模型是 gpt-6-astra
- 环境有三种选法:OpenAI 托管沙箱、自己的基础设施、或者 Cloudflare、Modal、Vercel 这些沙箱伙伴
- 底层 harness 开源,仓库在 GitHub 上,逻辑能自己翻
这些不展开讲。下面说我的正题:MCP 是接进去的。想核实的朋友可以直接看官方公告原文 Introducing the Agents API,接口细节在 Agents API overview 里。
MCP 接入长什么样
整体链路画成图是这么个走向:
你的应用 OpenAI 托管侧 你的服务器
│ │ │
▼ ▼ ▼
sessions.create ───────► Agents API ───────► Codex Harness ──HTTP──► MCP Server
│ │
tool search 按需加载工具定义 处理请求并返回结果
长会话上下文自动压缩
│
(返回结果沿同一条链路回到你的应用)
为什么要看这张图:它把"你只在登录侧动代码,中间全是托管"的关系摆清楚了。接 MCP 这件事,本质上就是在图的最右端多挂一台你的服务,中间这段不是你能改的。
官方公告里给的 MCP 配置是这么个形状:
{
"agent": {
"tools": [
{
"type": "mcp",
"server_label": "order_service",
"transport": {
"type": "http",
"server_url": "https://api.example.com/mcp"
}
}
]
}
}
为什么要看这段:它可能是未来一段时间里"怎么给 agent 挂工具"的标准写法,直接对应你的 MCP Server 要被谁、以什么方式找到。字段结构照官方公告来的,我把 server_label 和 server_url 换成了订单场景方便演示——官方原文的 label 是 openai_docs、地址指向他们自己的文档站。本地能核对的只有字段结构,四个键名和公告一致:
// 字段结构核对(对照官方公告原文):
type: "mcp"
server_label: "order_service" → 给这个工具源起的名字
transport.type: "http" → 走 HTTP 传输
transport.server_url: "https://…" → 你的 Server 地址
这一段里有三个信息点,接的时候一个都不能少:
type: "mcp":声明这是个 MCP 工具源server_label:给这个 server 起的名字,agent 日志里靠它区分是哪个源transport.http.server_url:你的 Server 得通过 HTTP 暴露出来,地址填这里
注意 transport 用的是 http,不是本地 stdio 那套。你的 Server 要能被一个跑在云上的 agent 从公网访问到。 这个前提看着不起眼,实际是本地开发最容易栽的地方,后面边界说明里再展开。
JS SDK 的调用长这样(也是官方示例,beta. 前缀如实保留;我为了突出 MCP 部分,省掉了 vault_ids、environment 这些非核心字段,input 换成了订单场景):
const session = await client.beta.agents.sessions.create({
agent: {
model: "gpt-6-astra",
tools: [
{
type: "mcp",
server_label: "order_service",
transport: {
type: "http",
server_url: "https://api.example.com/mcp"
}
}
]
},
input: "把上个月的订单统计出来"
});
为什么要看这段:确认 API 层面对接成本很低——一个 sessions.create,工具挂进 agent 就完事。麻烦的不是这行调用,是和它配套的整条链路。调用主体的结构也和上面对照过:
// 调用结构核验(对照官方公告示例):
client.beta.agents.sessions.create(...)
├─ agent.model: "gpt-6-astra" → 官方示例模型
├─ agent.tools[0].type: "mcp" → 工具源类型
└─ input: "把上个月的订单统计出来" → 任务描述(我换成了订单场景)
// 调用成功返回 session 后,agent 会带着配置的 MCP 工具开始跑任务
我的 MCP Server 要动哪几处
对照完官方示例,我盘出三个要动的点。
第一个:传输层收敛到 HTTP。 我之前写 MCP Server 用的本地 stdio,文章里还专门讨论过几种 transport 的取舍。Agents API 这侧场景是"云上 agent 调你的服务",stdio 直接没了意义,必须把 Server 包成 HTTP 服务。本地开发期可以先跑隧道转发顶一段,真上生产就得有固定公网入口。
第二个:给 Server 起个能看的 label。 server_label 是 agent 配置里给这台 server 起的名字,光看示例可能觉得它只是个摆设,实际作用在出错排查时才体现出来——agent 任务一多(官方还支持多子 agent 并行),日志里要靠它分清工具调用来自哪个源。我的建议是别让一个名字通吃所有环境,label 里带上环境标识,排查事故能省很多事。
第三个,也是最花功夫的:工具描述要按"按需加载"的规矩重写。 这个单独展开说。
三个改造点先汇总成一张表,方便对照:
| 改造点 | 做什么 | 为什么 |
|---|---|---|
| 传输层 | stdio 收敛到 HTTP | agent 在云上跑,你的 Server 得能被公网访问 |
| server_label | 给 Server 起个能看的名字 | 任务并行时,日志里分得清工具调用来自谁 |
| 工具描述 | 按"按需加载"的规矩重写 | 描述从"写清楚"升级为"会被检索选中" |
工具描述:从"写给人看"变成"写给检索器看"
Agents API 自带一个 tool search 机制,官方文档的原话是:按需加载相关的工具定义,省 token、保持缓存命中(原文档在这里)。我理解下来就是——agent 不会把你的所有工具一次性搬进上下文,而是需要的时候再去选一批进来。
这条规则对我这种写过 MCP Server 的人来说,是实打实的变化。以前写工具描述,目标是"让 AI 理解这工具干嘛、参数别传错"——这个我在写 MCP Server 那篇时做过三版对比,验证过描述从粗到细对传参的影响。现在多了一件事:描述得让"检索"认得出来,在你需要它的时候选得中它。
下面是我本地测试台里的观察,不是官方算法说明:
// 改前:名字和描述都太泛
{
"name": "query_orders",
"description": "处理订单数据",
"inputSchema": { "orderId": "string" }
}
// 改后:描述的落点具体到"什么场景会用到它"
{
"name": "query_order_shipping_status",
"description": "按订单号查询订单发货状态,返回预计送达时间",
"inputSchema": {
"orderId": { "type": "string", "description": "订单号,必填" }
}
}
为什么要看这段:这是我在 Agent 需要"查某个订单到没到"时,同一套底层方法换两种壳的对比。改前这版是我早期连 Agent 时真实用的描述,后来在本地测试台反复跑同一批查询任务,执行结果差异一眼能看出:
同一个查询任务在本地测试台反复跑:
改前 query_orders → 没有一次产生工具调用,直接作答
改后 query_order_shipping_status → 每次都先调工具拿数据,再基于数据作答
同样一个功能,描述写成"处理订单数据",在按需加载的场景下容易被别的带"数据"字样的工具挤掉;写成"按订单号查询发货状态",被挑中的概率明显更高。
我这里不敢说内部是关键词匹配还是语义匹配,官方没公开机制。但"更具体的描述更不容易被跳过"这个观测,我在自己的 MCP 测试台里复现过很多轮,下面把这个差异放进表里看:
| 维度 | 普通 API 集成 | Agents API + MCP |
|---|---|---|
| 工具怎么进上下文 | 自己的编排代码显式调 | tool search 按需加载,不是全量搬入 |
| 描述的作用 | 保证参数传对 | 参数传对 + 还可能决定"能不能被选中" |
| Server 地址怎么给 | 代码里写死 | server_url + 需要公网可达 |
| 多工具并行 | 自己写编排 | programmatic tool calling 帮你并行 |
这张表基本就是我改造笔记的核心差异:同样的工具,评价尺度从"用起来对不对"加了半个维度——“在需要的时候找不找得到”。
接入后怎么验证自己改对了
改完别急着上生产,按下面几步在自己环境里验一遍,每一步都能看到明确结果。
第一步:确认 MCP Server 本身活着。 用 MCP 官方提供的 Inspector(一条 npx 命令就能拉起来)打开你的 Server,能看到工具列表正常返回,说明服务端没问题。这一步跟 Agents API 无关,纯验你 Server 这一端:
# Inspector 打开后看到的工具列表(示意)
query_order_shipping_status 按订单号查询订单发货状态
query_orders 处理订单数据(改前,准备下线)
第二步:核对 agent 配置里的字段。 把官方示例的结构和你的配置逐键对比:type / server_label / transport.type / transport.server_url 四个键名不能错;server_url 要能外网访问,本地先 curl 一下确认能通。
第三步:跑一个描述对照实验。 挑一个最常用的工具,先用旧描述让 agent 跑一个"非调它不可"的任务,记下有没有调用;再把描述改具体,同一个任务再跑一遍。这个实验我在本地测试台做过,差异是明确的,你自己环境里也能复现:
同一个任务"查订单到哪了",两次运行对比:
旧描述 query_orders → 没有工具调用,直接作答
新描述 query_order_shipping_status → 先调工具,再基于数据作答
第四步:看 agent 日志里的 server_label。 公开测试期里 agent 跑完任务后,去日志里确认工具调用的来源标识是不是你配的 label——顺便就验证了 label 起得够不够清楚。
哪些场景其实不用上 MCP
MCP 接入看着规整,别什么都往里塞。就一个函数能解决的事(算个折扣、查个字典),直接在 agent 配置里定义成普通函数更轻。MCP 这套的价值在"复用已有的工具服务",尤其是你已经有一批 HTTP 服务想给多个 agent 共用的时候。
再交代几个硬前提,都是实操里绕不开的:
- 你的 Server 必须公网可达(或自托管环境能访问),这一点官方托管沙箱的链路里绕不掉
- 传输走 HTTP 后,旧代码如果是纯 stdio,要包的层不止一处,鉴权也得提前想好
- 如果你的 Server 用的是 SSE 这类更老的 MCP 传输形态,别想当然觉得直接能用,先对着 MCP 官方文档确认 HTTP transport 的要求,该包的兼容层一个都不能少
- 公开测试期,接口和配置随时可能变,生产接入前盯着更新日志再动手
真要对 MCP 协议本身深入,规范在 modelcontextprotocol.io 有完整文档,这篇不展开。
一份落地自查清单
接之前对着这四条过一遍,能少踩不少坑:
- MCP Server 已经包成 HTTP 服务,外网地址可达(本地开发先用隧道顶一段)
- server_label 带上了环境标识,出错时日志里能分清是哪台
- 每个工具的 description 写的是"什么场景会用到它",不是"它是什么"
- 公开测试期的更新日志放进关注列表,接口变了不慌
回头看
OpenAI 把 MCP 写进官方示例,等于给这一届 agent 定了工具层的接口基调。对跑过 MCP Server 的人来说,接入本身不麻烦,真正要花心思的是把"给 AI 看 API 文档"升级成"在按需加载的世界里让自己的工具被选中"。前者是讲清楚,后者是会被找到——两件事,难度不一样。
如果现在就要动手,我的建议是先挑一个最常用的工具,把 description 从"它是什么"改成"什么场景会用到它",跑一轮同样的任务对比调用情况。这个验证最直观,比把全套工具重写一遍稳妥得多。
更多推荐


所有评论(0)