v0.1.4-p2

main
mula.liu 2026-03-15 17:04:48 +08:00
parent 44792a02ac
commit 89ed5f7107
1 changed files with 7 additions and 149 deletions

View File

@ -1139,17 +1139,6 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
"probe_from": "validation", "probe_from": "validation",
} }
probe_payload = {
"jsonrpc": "2.0",
"id": "dashboard-probe",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "dashboard-nanobot", "version": "0.1.4"},
},
}
def _with_body_preview(message: str, preview: Any) -> str: def _with_body_preview(message: str, preview: Any) -> str:
text = str(message or "").strip() text = str(message or "").strip()
body = " ".join(str(preview or "").strip().split()) body = " ".join(str(preview or "").strip().split())
@ -1187,14 +1176,13 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
return {"ok": True, "transport": transport_type, "status_code": status_code, "message": "MCP SSE endpoint is reachable", "content_type": content_type, "probe_from": "bot-container"} return {"ok": True, "transport": transport_type, "status_code": status_code, "message": "MCP SSE endpoint is reachable", "content_type": content_type, "probe_from": "bot-container"}
probe_headers = dict(headers) probe_headers = dict(headers)
probe_headers.setdefault("Content-Type", "application/json")
probe_headers.setdefault("Accept", "application/json, text/event-stream") probe_headers.setdefault("Accept", "application/json, text/event-stream")
probe = docker_manager.probe_http_from_container( probe = docker_manager.probe_http_from_container(
bot_id=bot_id, bot_id=bot_id,
url=url, url=url,
method="POST", method="GET",
headers=probe_headers, headers=probe_headers,
body_json=probe_payload, body_json=None,
timeout_seconds=timeout_s, timeout_seconds=timeout_s,
) )
status_code = probe.get("status_code") status_code = probe.get("status_code")
@ -1206,8 +1194,8 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": "MCP endpoint not found", "probe_from": "bot-container"} return {"ok": False, "transport": transport_type, "status_code": status_code, "message": "MCP endpoint not found", "probe_from": "bot-container"}
if isinstance(status_code, int) and status_code >= 500: if isinstance(status_code, int) and status_code >= 500:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview("MCP endpoint server error", body_preview), "probe_from": "bot-container"} return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview("MCP endpoint server error", body_preview), "probe_from": "bot-container"}
if probe.get("ok") and status_code in {200, 201, 202, 204, 400, 405, 415, 422}: if probe.get("ok") and status_code in {200, 201, 202, 204, 400, 401, 403, 405, 406, 415, 422}:
reachability_msg = "MCP endpoint is reachable" if status_code in {200, 201, 202, 204} else "MCP endpoint is reachable (request format not fully accepted by probe)" reachability_msg = "MCP endpoint is reachable" if status_code in {200, 201, 202, 204} else "MCP endpoint is reachable (HTTP endpoint responded as expected)"
return {"ok": True, "transport": transport_type, "status_code": status_code, "message": reachability_msg, "probe_from": "bot-container"} return {"ok": True, "transport": transport_type, "status_code": status_code, "message": reachability_msg, "probe_from": "bot-container"}
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview(message or "Unexpected response from MCP endpoint", body_preview), "probe_from": "bot-container"} return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview(message or "Unexpected response from MCP endpoint", body_preview), "probe_from": "bot-container"}
@ -1265,9 +1253,8 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
} }
req_headers = dict(headers) req_headers = dict(headers)
req_headers.setdefault("Content-Type", "application/json")
req_headers.setdefault("Accept", "application/json, text/event-stream") req_headers.setdefault("Accept", "application/json, text/event-stream")
resp = client.post(url, headers=req_headers, json=probe_payload) resp = client.get(url, headers=req_headers)
body_preview = resp.text[:512] body_preview = resp.text[:512]
if resp.status_code in {401, 403}: if resp.status_code in {401, 403}:
return { return {
@ -1301,12 +1288,12 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
"message": "MCP endpoint is reachable", "message": "MCP endpoint is reachable",
"probe_from": "backend-host", "probe_from": "backend-host",
} }
if resp.status_code in {400, 405, 415, 422}: if resp.status_code in {400, 401, 403, 405, 406, 415, 422}:
return { return {
"ok": True, "ok": True,
"transport": transport_type, "transport": transport_type,
"status_code": resp.status_code, "status_code": resp.status_code,
"message": "MCP endpoint is reachable (request format not fully accepted by probe)", "message": "MCP endpoint is reachable (HTTP endpoint responded as expected)",
"probe_from": "backend-host", "probe_from": "backend-host",
} }
return { return {
@ -1334,124 +1321,6 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
} }
def _probe_mcp_server_for_start(cfg: Dict[str, Any], image_tag: str) -> Dict[str, Any]:
transport_type = str(cfg.get("type") or "streamableHttp").strip()
if transport_type not in {"streamableHttp", "sse"}:
transport_type = "streamableHttp"
url = str(cfg.get("url") or "").strip()
headers_raw = cfg.get("headers")
headers: Dict[str, str] = {}
if isinstance(headers_raw, dict):
for k, v in headers_raw.items():
key = str(k or "").strip()
if key:
headers[key] = str(v or "").strip()
timeout_raw = cfg.get("toolTimeout", 10)
try:
timeout_s = max(1, min(int(timeout_raw), 30))
except Exception:
timeout_s = 10
if not url:
return {
"ok": False,
"transport": transport_type,
"status_code": None,
"message": "MCP url is required",
"probe_from": "validation",
}
probe_payload = {
"jsonrpc": "2.0",
"id": "dashboard-start-probe",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "dashboard-nanobot", "version": "0.1.4"},
},
}
def _with_body_preview(message: str, preview: Any) -> str:
text = str(message or "").strip()
body = " ".join(str(preview or "").strip().split())
if not body:
return text
body = body[:240]
return f"{text}: {body}" if text else body
if transport_type == "sse":
probe_headers = dict(headers)
probe_headers.setdefault("Accept", "text/event-stream")
probe = docker_manager.probe_http_via_temporary_container(
image_tag=image_tag,
url=url,
method="GET",
headers=probe_headers,
body_json=None,
timeout_seconds=timeout_s,
)
status_code = probe.get("status_code")
content_type = str(probe.get("content_type") or "")
message = str(probe.get("message") or "").strip()
body_preview = probe.get("body_preview")
if status_code in {401, 403}:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": "Auth failed for MCP SSE endpoint", "content_type": content_type, "probe_from": "temp-container"}
if status_code == 404:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": "MCP SSE endpoint not found", "content_type": content_type, "probe_from": "temp-container"}
if isinstance(status_code, int) and status_code >= 500:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview("MCP SSE endpoint server error", body_preview), "content_type": content_type, "probe_from": "temp-container"}
if not probe.get("ok"):
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview(message or "Failed to connect MCP SSE endpoint from temporary probe container", body_preview), "content_type": content_type, "probe_from": "temp-container"}
if "text/event-stream" not in content_type.lower():
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview("Endpoint reachable, but content-type is not text/event-stream", body_preview), "content_type": content_type, "probe_from": "temp-container"}
return {"ok": True, "transport": transport_type, "status_code": status_code, "message": "MCP SSE endpoint is reachable", "content_type": content_type, "probe_from": "temp-container"}
probe_headers = dict(headers)
probe_headers.setdefault("Content-Type", "application/json")
probe_headers.setdefault("Accept", "application/json, text/event-stream")
probe = docker_manager.probe_http_via_temporary_container(
image_tag=image_tag,
url=url,
method="POST",
headers=probe_headers,
body_json=probe_payload,
timeout_seconds=timeout_s,
)
status_code = probe.get("status_code")
message = str(probe.get("message") or "").strip()
body_preview = probe.get("body_preview")
if status_code in {401, 403}:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": "Auth failed for MCP endpoint", "probe_from": "temp-container"}
if status_code == 404:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": "MCP endpoint not found", "probe_from": "temp-container"}
if isinstance(status_code, int) and status_code >= 500:
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview("MCP endpoint server error", body_preview), "probe_from": "temp-container"}
if probe.get("ok") and status_code in {200, 201, 202, 204, 400, 405, 415, 422}:
reachability_msg = "MCP endpoint is reachable" if status_code in {200, 201, 202, 204} else "MCP endpoint is reachable (request format not fully accepted by probe)"
return {"ok": True, "transport": transport_type, "status_code": status_code, "message": reachability_msg, "probe_from": "temp-container"}
return {"ok": False, "transport": transport_type, "status_code": status_code, "message": _with_body_preview(message or "Unexpected response from MCP endpoint", body_preview), "probe_from": "temp-container"}
def _preflight_mcp_servers_for_start(bot_id: str, image_tag: str) -> List[str]:
config_data = _read_bot_config(bot_id)
if not isinstance(config_data, dict):
return []
tools_cfg = config_data.get("tools")
if not isinstance(tools_cfg, dict):
return []
mcp_servers = _normalize_mcp_servers(tools_cfg.get("mcpServers"))
failures: List[str] = []
for server_name, cfg in mcp_servers.items():
result = _probe_mcp_server_for_start(cfg, image_tag=image_tag)
if result.get("ok"):
continue
message = str(result.get("message") or "MCP precheck failed").strip()
probe_from = str(result.get("probe_from") or "temp-container").strip()
failures.append(f"{server_name}: {message} [{probe_from}]")
return failures
def _parse_env_params(raw: Any) -> Dict[str, str]: def _parse_env_params(raw: Any) -> Dict[str, str]:
return _normalize_env_params(raw) return _normalize_env_params(raw)
@ -2441,17 +2310,6 @@ async def start_bot(bot_id: str, session: Session = Depends(get_session)):
if not bot: if not bot:
raise HTTPException(status_code=404, detail="Bot not found") raise HTTPException(status_code=404, detail="Bot not found")
_sync_workspace_channels(session, bot_id) _sync_workspace_channels(session, bot_id)
mcp_preflight_failures = _preflight_mcp_servers_for_start(bot_id, image_tag=bot.image_tag)
if mcp_preflight_failures:
bot.docker_status = "STOPPED"
session.add(bot)
session.commit()
_invalidate_bot_detail_cache(bot_id)
raise HTTPException(
status_code=400,
detail="MCP precheck failed: " + " | ".join(mcp_preflight_failures[:5]),
)
runtime_snapshot = _read_bot_runtime_snapshot(bot) runtime_snapshot = _read_bot_runtime_snapshot(bot)
env_params = _read_env_store(bot_id) env_params = _read_env_store(bot_id)
success = docker_manager.start_bot( success = docker_manager.start_bot(