【Bug已解决】Codex App: new task creation times out when configured MCP server is unreachable 解决方案
【Bug已解决】Codex App: new task creation times out when configured MCP server is unreachable 解决方案
原始报错线索:Codex App: new task creation times out when configured MCP server is unreachable(当用户配置的某个 MCP 服务器连不上时,创建新任务会一直超时,整个创建流程被拖死)。
一、现象长什么样
用户配了一个 MCP 服务器(可能是公司内网、可能暂时宕机)。然后:
- 点「新建任务」,界面转圈,几秒/几十秒后报「创建超时」;
- 创建任务本身根本不需要这个 MCP 服务器——它只是「锦上添花」的扩展能力;
- 但只要 MCP 不可达,创建就必超时;
- 把 MCP 配置删掉,创建立刻恢复正常。 根因是**「可选依赖」被当成了「必需依赖」,且没设超时隔离,主流程被一个无关紧要的服务拖死**。
二、背景:必需依赖 vs 可选依赖
任何创建流程都依赖一些东西:
- 必需依赖:数据库、鉴权服务——它们挂了,创建确实该失败;
- 可选依赖:MCP 服务器、插件后端、推荐服务——它们挂了,创建仍应成功,只是少了增强能力。 把可选依赖当成必需,是「创建被拖死」的根源。此外,即使依赖必需,也要有超时隔离:不能让一个慢依赖无限阻塞主流程。
三、为什么创建超时:根因
3.1 可选依赖同步阻塞主流程
创建任务时,代码里mcp.fetch_tools()是同步调用,没设超时,MCP 不可达就一直等 TCP 超时(常达数十秒)。
3.2 没区分必需 / 可选
MCP 挂了,代码把它当致命错误,整个创建 abort,而非「跳过增强、继续创建」。
3.3 重试放大超时
对不可达的 MCP 还做多次重试,每次都等满超时,总耗时叠加。
3.4 无健康度短路
没在创建前先快速探活 MCP,直接进重逻辑,浪费时间。
四、最小可运行复现(可选依赖阻塞主流程)
下面演示「MCP 不可达时同步等待」如何拖死创建:
import time, socket def fetch_mcp_tools_blocking(host, port): # 错误:无超时连接,不可达时等系统 TCP 超时(默认可长达几十秒) s = socket.create_connection((host, port)) # 阻塞直到超时 return s def create_task_bad(task): tools = fetch_mcp_tools_blocking("192.168.99.99", 9999) # 不可达 return {"task": task, "tools": tools} if __name__ == "__main__": t0 = time.time() try: create_task_bad({"name": "x"}) except Exception as e: print(f"创建失败({time.time()-t0:.1f}s): {e}") # 数十秒后才失败不可达的 MCP 让创建卡几十秒才失败——核心功能被可选依赖拖垮。
五、解决方案一:所有外部依赖调用必须设超时
把无超时的阻塞调用改成「短超时 + 快速失败」:
import socket def fetch_mcp_tools(host, port, timeout=2.0): """带超时的依赖探测,不可达就快速失败。""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) try: s.connect((host, port)) return ["tool_a", "tool_b"] # 示意 except (socket.timeout, OSError): return None # 不可达 finally: s.close() if __name__ == "__main__": import time t0 = time.time() tools = fetch_mcp_tools("192.168.99.99", 9999, timeout=2.0) print(f"探测耗时 {time.time()-t0:.1f}s, 工具={tools}") # 2s 内返回 None2 秒超时,不可达立刻返回None,不再拖几十秒。
六、解决方案二:区分必需 / 可选,可选失败降级
创建主流程把 MCP 当可选:不可达就跳过增强,任务照常创建:
def create_task(task, mcp_endpoint=None): """创建任务:MCP 是可选的,不可达则降级创建。""" task_id = f"task-{int(time.time())}" enhancement = None if mcp_endpoint: tools = fetch_mcp_tools(*mcp_endpoint, timeout=2.0) if tools is None: # 可选依赖不可用 -> 降级,不阻塞创建 enhancement = "skipped:mcp_unreachable" else: enhancement = tools return {"task_id": task_id, "task": task, "enhancement": enhancement} if __name__ == "__main__": # MCP 不可达,但任务照样建出来 result = create_task({"name": "x"}, mcp_endpoint=("192.168.99.99", 9999)) print(result) # enhancement='skipped:mcp_unreachable',创建成功可选依赖失败只影响「增强部分」,核心创建不受影响(解决 3.2)。
七、解决方案三:健康度短路 + 不重试不可达
创建前先快速探活,不可达直接跳过;且对「连接级不可达」不做重试(重试只用于「瞬时限流」):
def maybe_enhance(mcp_endpoint): """只对『可达』的 MCP 做增强;不可达立即放弃,不重试。""" if mcp_endpoint is None: return None # 探活:连接级失败不重试(重试对『主机不可达』无意义,只会叠加超时) tools = fetch_mcp_tools(*mcp_endpoint, timeout=1.5) if tools is None: return "skipped" # 不重试,直接降级 return tools def create_task_v2(task, mcp_endpoint=None): enhanced = maybe_enhance(mcp_endpoint) return {"task_id": f"task-{int(time.time())}", "enhanced": enhanced} if __name__ == "__main__": print(create_task_v2({"name": "x"}, ("192.168.99.99", 9999)))连接不可达是「确定性失败」,重试只会叠加超时——直接降级最合理(解决 3.3)。
八、解决方案四:把依赖增强异步化
更彻底的做法:创建任务先返回(不含 MCP 增强),MCP 增强在后台补充:
import threading def create_task_async(task, mcp_endpoint=None): task_id = f"task-{int(time.time())}" # 主流程立即返回,MCP 增强后台做 def background_enhance(): if mcp_endpoint: tools = fetch_mcp_tools(*mcp_endpoint, timeout=2.0) # 拿到后写入任务元数据(示意) print(f"[{task_id}] 后台增强: {tools}") threading.Thread(target=background_enhance, daemon=True).start() return {"task_id": task_id, "status": "created"} if __name__ == "__main__": r = create_task_async({"name": "x"}, ("192.168.99.99", 9999)) print("任务已创建(不等 MCP):", r) # 立即返回创建永远不被 MCP 阻塞——MCP 只是「后续补充」,彻底解耦。
九、排查清单
「依赖不可达时创建超时」,按下面排查:
- 依赖是必需还是可选?MCP 类增强应可选(第六节);
- 外部调用有超时吗?无超时阻塞是元凶;
- 可选失败是否降级?还是 abort 整个创建(第六节);
- 是否对『不可达』做无意义重试?连接级失败应直接降级(第七节);
- 能否异步化增强?创建先返回、增强后台补(第八节);
- 健康度能否短路?创建前先探活(第七节);
- 超时设置合理吗?2 秒级而非数十秒(第五节);
- 日志是否记录『MCP 跳过原因』。
十、小结
「依赖的 MCP 不可达时新任务创建超时」的根因是可选依赖被当成必需依赖,且无超时隔离,主流程被一个无关服务阻塞至超时。通用修复:
- 必设超时:所有外部依赖调用短超时(2s 级),不可达快速失败;
- 区分必需/可选:可选依赖(MCP)不可达则降级创建,核心功能不受影响(第六节);
- 不重试不可达:连接级失败确定性,直接降级而非叠加超时(第七节);
- 异步解耦:创建先返回,增强后台补充,彻底不被阻塞。 一句话:创建资源的主流程,绝不能被『可选增强依赖』阻塞;外部调用一律短超时,可选失败一律降级,确定性不可达一律不重试。把核心路径和增强依赖解耦,应用才能在「配置了个暂时宕机的 MCP」时,依然秒级创建任务——这与第 129 篇连接韧性、第 130 篇后台压缩、第 117 篇 SSH 保活,共同体现「外部依赖必须超时隔离、可选降级」。
