codex mcp-server退场:迁移App Server别只改命令
如果你的工具链里还有这一行:
codex mcp-server
现在最重要的事情不是把命令换掉,而是先确认它背后承载了什么协议责任。
OpenAI 在 8 月 24 日正式把 codex mcp-server 标记为 deprecated,并明确给出两个方向:
需要把 Codex 作为应用后端:
迁移到 Codex App Server
需要在 Claude Code 中调用 Codex:
使用 Codex plugin for Claude Code
这不是一个单纯的 CLI 重命名。
mcp-server 和 App Server 的设计目标不同。前者更像把 Codex 暴露成一个可被 MCP 客户端调用的能力;后者则把 Codex 的完整 Agent Harness 暴露出来,客户端需要理解长期会话、Turn、Item、审批、流式事件和状态恢复。
所以真正的迁移任务是:
Command Migration
→ Protocol Migration
→ State Migration
→ Failure Semantics Migration
如果只改启动命令,很容易得到一个“能连上,但跑不稳”的系统。
第一步先盘点你到底怎么用了mcp-server
我会先全仓库搜索:
rg "codex mcp-server" .
rg "mcpServers" .
rg "codex.*mcp" .
然后把调用方分成三类。
第一类:Claude Code 里把 Codex 当外部能力。这类不一定需要自己迁 App Server,OpenAI 现在明确建议使用 Codex plugin for Claude Code。
第二类:自己写了桌面端或 IDE,而且依赖会话、长任务、流式结果、审批、Artifact。这类应该迁 App Server。
第三类:只把 Codex 当同步函数,输入一次任务、等待最终结果。这里要重新判断是否真的需要 App Server;简单脚本可能继续保持简单接口更合适。
App Server迁移的核心不是HTTP,而是状态模型
我会把客户端状态最少拆成:
Thread
Turn
Item
Thread 代表持续工作上下文;Turn 是一次用户驱动的执行周期;Item 是 Turn 里的可观察工作单元。
例如:
Thread:
修复登录偶发401
Turn 1:
定位原因
Items:
- read AuthService.java
- read JwtFilter.java
- run tests
- summarize findings
Turn 2:
根据批准修改代码
Items:
- edit JwtFilter.java
- run auth tests
- create patch
如果客户端只有 request_id,这次迁移就是一个机会,把 Agent 的运行对象建完整。
先做initialize握手
任何长期运行的双向协议都不应该“连上就发业务消息”。
最少先交换:
Protocol Version
Client Capabilities
Server Capabilities
Feature Flags
例如:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"client": {
"name": "internal-ide",
"version": "2.4.0"
},
"capabilities": {
"approval": true,
"artifact": true,
"streaming": true
}
}
}
Server 返回:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2026-08",
"capabilities": {
"threadResume": true,
"toolApproval": true
}
}
}
以后 App Server 升级时,客户端可以明确知道当前能力是否兼容,而不是等某个事件字段突然变化以后才发现解析失败。
不要把通知事件当成普通Response
Agent Harness 天然会产生很多异步事件:
Item Started
Item Delta
Item Completed
Approval Requested
Turn Completed
Turn Failed
它们不是普通的 request → response 关系。
客户端最好有统一 Event Envelope:
public record AgentEvent(
long sequence,
String threadId,
String turnId,
String itemId,
String type,
JsonNode payload,
Instant occurredAt) {
}
这里我特别建议加 sequence。
为什么必须有sequence
长连接会遇到断线、重连、网络乱序和客户端卡顿。只靠时间戳不够,两个事件可能拥有相同时间。
更稳的是 Thread 内单调递增 Sequence:
901 ItemStarted
902 ItemDelta
903 ApprovalRequested
904 ApprovalGranted
905 ItemCompleted
重连时客户端告诉服务端:
last_sequence = 903
Server 只补:
904+
这比重新拉整个会话可靠得多。
做一个Catch-up接口
即使底层协议支持长连接,我仍然建议有事件补拉语义。
例如:
GET /threads/{threadId}/events?after=903
或者协议内:
{
"method": "thread/events",
"params": {
"threadId": "th_182",
"afterSequence": 903
}
}
断线恢复是生产 Harness 必须解决的问题。
审批必须成为一等事件
不要让 Server 输出一段“我准备运行 rm 命令,可以吗?”,然后客户端再从文本里解析。
真正协议应该是结构化事件:
{
"method": "approval/requested",
"params": {
"approvalId": "ap_91",
"threadId": "th_182",
"turnId": "turn_4",
"itemId": "item_17",
"action": {
"type": "shell",
"command": "git reset --hard HEAD~1"
},
"risk": "HIGH"
}
}
客户端再显式回:
{
"id": 71,
"method": "approval/resolve",
"params": {
"approvalId": "ap_91",
"decision": "DENY"
}
}
这样审批才能被审计、超时、撤销,并且绑定具体 Action。
审批要有TTL
如果 Agent 10:00 发起审批,用户第二天下午才点 Approve,仓库状态可能已经完全变化。
所以:
public record ApprovalRequest(
String approvalId,
String actionHash,
Instant createdAt,
Instant expiresAt) {
}
过期后必须重新计算 Action、重新审批,不能复用旧批准。
App Server客户端必须处理Backpressure
Agent 可能快速输出 Token Delta、Tool Event、Progress Event、Artifact Event。
如果 UI 处理速度跟不上,不能让内存里的事件队列无限增长。
例如:
BlockingQueue queue =
new ArrayBlockingQueue(2000);
达到上限时,不同事件要不同处理:
Token Delta:
可合并
Progress Heartbeat:
可丢弃旧值
Approval:
不可丢
Item Completed:
不可丢
Turn Failed:
不可丢
事件需要优先级
public enum EventPriority {
CRITICAL,
STATE,
PROGRESS,
STREAM
}
内存压力时,STREAM 可以合并,CRITICAL 必须持久化。
迁移时最容易漏掉Thread Resume
旧 MCP 调用很多时候是一次请求一次结束。
App Server 更适合长期 Thread,所以应用数据库不要只存最后一段聊天文本。
至少:
create table agent_thread (
thread_id varchar(128) primary key,
user_id varchar(128) not null,
workspace_id varchar(128) not null,
status varchar(32) not null,
last_sequence bigint not null,
created_at timestamptz not null,
updated_at timestamptz not null
);
客户端重启后:
重新加载Thread
→从last_sequence继续
Turn必须可以取消
长任务如果用户已经发现方向错了,不要等它跑完。
状态:
public enum TurnStatus {
CREATED,
RUNNING,
WAITING_APPROVAL,
CANCEL_REQUESTED,
CANCELED,
COMPLETED,
FAILED
}
这里 CANCEL_REQUESTED 很重要,因为 Tool 可能正在执行。
取消请求发出,不代表外部副作用已经撤销。
Tool正在执行时取消怎么办
如果 Tool 是 read_file,通常可以中断。
如果 Tool 是:
send_email
deploy
payment
取消不代表副作用能回滚。
Turn Cancel 结果必须告诉客户端:
{
"turnId": "turn_4",
"status": "CANCELED",
"sideEffects": [
{
"tool": "deploy",
"status": "CONFIRMED"
}
]
}
别把“Agent停止思考”写成“任务已撤销”。
做兼容层,不要一次切所有客户端
如果现在有 VS Code、内部 CLI、Web Console、Claude Code 四个调用方,可以先做 Legacy Adapter:
Old Client
↓
Compatibility Adapter
↓
App Server
先迁服务端,再逐个升级客户端。
兼容层必须有退役时间:
legacy:
enabled: true
deprecate_at: 2026-09-15
disable_at: 2026-10-01
监控:
legacy_request_total
降到 0 再删。
迁移要记录协议版本
每条 Run 都保存:
protocol_version
client_version
server_version
例如:
{
"protocol": "2026-08",
"client": "internal-ide/2.4.0",
"server": "codex-app-server/1.x"
}
事故时才能判断是不是特定版本组合才失败。
最小迁移测试集
我会至少跑:
1. 创建Thread
2. 开始Turn
3. Token Streaming
4. Tool Call
5. Approval Allow
6. Approval Deny
7. Approval Timeout
8. 网络断线
9. Sequence Catch-up
10. Turn Cancel
11. Tool执行中Cancel
12. App Server重启后Resume
13. 客户端旧版本连接
14. 不支持Capability协商
如果只有“hello world 能返回”,不算迁移完成。
迁移前后还要对比成本
协议迁移可能让客户端更容易保留长 Thread、更大 Context。
所以要比较:
tokens_per_turn
context_tokens
tool_calls
duration
不要只看功能是否通。
什么时候我不会迁App Server
如果场景只是:
CI里调用Codex做一次代码分析
输入固定、输出固定,没有多轮、审批、长会话和断线恢复,那 App Server 可能太重。
真正值得迁的是:
IDE
桌面端
协作应用
长期Agent
codex mcp-server 的 deprecated 提示只有一句话,但真正迁移时最容易低估的是:
旧接口连接的是“一个能力”
新App Server承载的是“一个运行时”
所以不要把这次升级做成 command rename。
更合理的是把它当成一次 Harness Protocol Migration:握手、状态、事件、序列、审批、取消、恢复和兼容都要一起补齐。
这些边界补齐以后,App Server 的价值才真正出现。
更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界:
https://www.zyentor.com/