fix: AI 控制台 MCP 不可达给出明确文案——不再显示 SDK 含糊报错

MCP 客户端(streamable_http)在工具服务不可达/返回非 MCP 响应时只抛
'Server returned an error response',用户无法判断原因。改为:
- 后端 web/agent_api.py:_execute 包裹 _load_tools/run_stream,识别连接类错误
  (Server returned an error response/ConnectError/refused 等)后抛明确文案:
  'MCP server(8033) 不可达:无法加载设备工具(<url>)。请确认 MCP server 已启动…'
- 前端 static/admin/agent.js 加 _friendlyAgentError() 兜底映射并去掉
  'RuntimeError:' 之类前缀,三处错误展示统一使用
- 文档同步:MCP.md(依赖提示+本机启动命令)、API.md(SSE error 文案说明)、
  DEPLOY.md(故障排查新增一行)
实测:停掉 MCP 复现 → 前端显示明确文案;启动 MCP 后 AI 控制台正常完成任务
This commit is contained in:
2026-09-10 10:28:57 +08:00
parent 4e67764589
commit 357fe98e22
5 changed files with 55 additions and 12 deletions
+1 -1
View File
@@ -1195,7 +1195,7 @@ AI 可用设备列表(在线状态 + 是否有任务运行,前端据此把 b
- `event: delta` `{text, kind: content|reasoning}` — 流式文本增量 - `event: delta` `{text, kind: content|reasoning}` — 流式文本增量
- `event: step` `{tool, args, image?}` — 工具调用完成(MCP 步骤,image 为缩略截图) - `event: step` `{tool, args, image?}` — 工具调用完成(MCP 步骤,image 为缩略截图)
- `event: done` `{answer}` — 完成 - `event: done` `{answer}` — 完成
- `event: error` `{message}` — 失败 - `event: error` `{message}` — 失败(若因 MCP Server 未启动/不可达,message 为明确文案「MCP server(8033) 不可达 …」,不再是 SDK 原始的 `Server returned an error response`)
- 空闲时每 15s 发一行 `: keepalive` 注释防超时;`done`/`error` 后关流 - 空闲时每 15s 发一行 `: keepalive` 注释防超时;`done`/`error` 后关流
### POST /api/agent/stop ### POST /api/agent/stop
+1
View File
@@ -327,6 +327,7 @@ python web_server.py
| `WinError 10013` | 端口被排除/权限不足 | 改端口或用管理员运行 | | `WinError 10013` | 端口被排除/权限不足 | 改端口或用管理员运行 |
| `WinError 10048` | 端口被占用 | 改端口或杀占用进程 | | `WinError 10048` | 端口被占用 | 改端口或杀占用进程 |
| 监控页设备列表为空 | 设备不在池中 / adb 连不上 | 「工具 → 设备池管理」确认已添加且在线,检查设备网络与 5555 端口 | | 监控页设备列表为空 | 设备不在池中 / adb 连不上 | 「工具 → 设备池管理」确认已添加且在线,检查设备网络与 5555 端口 |
| AI 控制台报「MCP server(8033) 不可达」 | MCP Server 未启动/不可达 | 启动 MCP Server:本机 `MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> MCP_AUDIT_FILE=data/mcp_audit.log python -m mcp_server.mcp_server`;220 容器由 `scripts/start.sh` 自动拉起(`MCP_ENABLED=0` 会关) |
### 6.2 设备连接失败 ### 6.2 设备连接失败
+5
View File
@@ -166,3 +166,8 @@ Web 端「AI 控制台」Tab 内建的 Agent(`mcp_agent/`,OpenAI 兼容协
- 多轮会话记忆(同会话上下文保留,新建会话清空) - 多轮会话记忆(同会话上下文保留,新建会话清空)
- **自进化经验记忆**:一轮成功操作会被提炼成「配方」存入 `agent_experience` 表,下次相似任务自动注入参考(命中/写入均有 🧠 提示卡) - **自进化经验记忆**:一轮成功操作会被提炼成「配方」存入 `agent_experience` 表,下次相似任务自动注入参考(命中/写入均有 🧠 提示卡)
- 模型与 API Key 在 AI 控制台右上角 ⚙ 配置,存平台 `app_meta` - 模型与 API Key 在 AI 控制台右上角 ⚙ 配置,存平台 `app_meta`
> **依赖提示**:AI 控制台依赖本 MCP Server(默认 `http://127.0.0.1:8033/mcp`)。若未启动,发起指令会直接失败——平台已把 SDK 的含糊报错(`Server returned an error response` 等)映射为明确文案:**「MCP server(8033) 不可达 …」**。
> 本机(非容器)需手动拉起:
> `MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<平台密码> MCP_AUDIT_FILE=data/mcp_audit.log python -m mcp_server.mcp_server`
> 220 生产容器由 `scripts/start.sh` 自动拉起(`MCP_ENABLED=0` 会关)。
+15 -4
View File
@@ -3,6 +3,17 @@ let _agentBusy = false;
let _agentStream = null; // EventSource let _agentStream = null; // EventSource
let _agentCfgLoaded = false; let _agentCfgLoaded = false;
// 后端异常文案兜底:MCP 客户端在工具服务不可达时只抛
// "Server returned an error response" 这类含糊字样,这里映射成明确指引。
function _friendlyAgentError(msg){
const raw = String(msg || '');
const s = raw.replace(/^[A-Za-z_]*Error:\s*/, ''); // 去掉 "RuntimeError: " 之类前缀
if(/不可达|连接中断/.test(s)) return s; // 后端已给出明确文案,直接用
if(/mcp_server|server returned an error response|connecterror|connection refused|all connection attempts failed|server disconnected|network is unreachable|connect timeout/i.test(s))
return 'MCP server(8033) 不可达:AI 无法连接设备工具服务,请确认 MCP server 已启动(本机可运行 python -m mcp_server.mcp_server)';
return raw;
}
// ================== 初始化 ================== // ================== 初始化 ==================
let _agentDefaultSerial = ''; let _agentDefaultSerial = '';
function initAgentChat(){ function initAgentChat(){
@@ -46,7 +57,7 @@ function restoreRunningFlow(){
const chat = document.getElementById('agent-chat'); const chat = document.getElementById('agent-chat');
if(chat && !chat.children.length){ if(chat && !chat.children.length){
const div = newAssistantMsg(); const div = newAssistantMsg();
div.querySelector('.agent-text').textContent = '⚠ ' + r.error; div.querySelector('.agent-text').textContent = '⚠ ' + _friendlyAgentError(r.error);
} }
} }
}); });
@@ -313,8 +324,8 @@ function sendAgentMsg(){
if(!r||!r.ok){ if(!r||!r.ok){
const msg = r ? (r.error||'启动失败') : '请求失败'; const msg = r ? (r.error||'启动失败') : '请求失败';
const div = newAssistantMsg(); const div = newAssistantMsg();
div.querySelector('.agent-text').textContent = '⚠ ' + msg; div.querySelector('.agent-text').textContent = '⚠ ' + _friendlyAgentError(msg);
showToast(msg,'error'); showToast(_friendlyAgentError(msg),'error');
return; return;
} }
setRunning(true); setRunning(true);
@@ -399,7 +410,7 @@ function listenStream(runId){
if(ev.data) msg = JSON.parse(ev.data).message || msg; if(ev.data) msg = JSON.parse(ev.data).message || msg;
}catch(e){} }catch(e){}
if(!msgEl) msgEl = newAssistantMsg(); if(!msgEl) msgEl = newAssistantMsg();
msgEl.querySelector('.agent-text').textContent = '⚠ ' + msg; msgEl.querySelector('.agent-text').textContent = '⚠ ' + _friendlyAgentError(msg);
endRun(); endRun();
}); });
+33 -7
View File
@@ -869,14 +869,40 @@ def _agent_thread(run_id, prompt, serial, cfg):
"args": f"命中 {exp_ctx.count(chr(10) + '- ')} 条同类历史经验,已注入参考", "args": f"命中 {exp_ctx.count(chr(10) + '- ')} 条同类历史经验,已注入参考",
"image": None})) "image": None}))
mcp_url = getattr(getattr(agent, "s", None), "mcp_url", "")
def _mcp_unreachable(err):
"""MCP 客户端在工具服务不可达/返回非 MCP 响应时只抛含糊字样,
识别出来以便给用户明确指引(默认端口 8033)。"""
m = str(err).lower()
return any(k in m for k in (
"server returned an error response", "connecterror",
"connection refused", "all connection attempts failed",
"connection error", "server disconnected",
"network is unreachable", "connect timeout"))
async def _execute(): async def _execute():
await agent._load_tools() try:
return await agent.run_stream(prompt, target, await agent._load_tools()
history=history, except Exception as e:
on_delta=on_delta, on_tool=on_tool, if _mcp_unreachable(e):
should_stop=lambda: bool( raise RuntimeError(
stop_evt and stop_evt.is_set()), f"MCP server(8033) 不可达:无法加载设备工具({mcp_url})。"
extra_context=exp_ctx) f"请确认 MCP server 已启动(本机可运行 "
f"python -m mcp_server.mcp_server,默认端口 8033)") from e
raise
try:
return await agent.run_stream(prompt, target,
history=history,
on_delta=on_delta, on_tool=on_tool,
should_stop=lambda: bool(
stop_evt and stop_evt.is_set()),
extra_context=exp_ctx)
except Exception as e:
if _mcp_unreachable(e):
raise RuntimeError(
f"MCP server(8033) 连接中断:AI 无法继续操作设备({mcp_url})") from e
raise
# 整体超时保护:卡死时结束,释放单实例 # 整体超时保护:卡死时结束,释放单实例
answer = asyncio.run(asyncio.wait_for(_execute(), timeout=900)) answer = asyncio.run(asyncio.wait_for(_execute(), timeout=900))