v0.1.4-p2
parent
44792a02ac
commit
89ed5f7107
156
backend/main.py
156
backend/main.py
|
|
@ -1139,17 +1139,6 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
|
|||
"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:
|
||||
text = str(message or "").strip()
|
||||
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"}
|
||||
|
||||
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_from_container(
|
||||
bot_id=bot_id,
|
||||
url=url,
|
||||
method="POST",
|
||||
method="GET",
|
||||
headers=probe_headers,
|
||||
body_json=probe_payload,
|
||||
body_json=None,
|
||||
timeout_seconds=timeout_s,
|
||||
)
|
||||
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"}
|
||||
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"}
|
||||
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)"
|
||||
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 (HTTP endpoint responded as expected)"
|
||||
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"}
|
||||
|
||||
|
|
@ -1265,9 +1253,8 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
|
|||
}
|
||||
|
||||
req_headers = dict(headers)
|
||||
req_headers.setdefault("Content-Type", "application/json")
|
||||
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]
|
||||
if resp.status_code in {401, 403}:
|
||||
return {
|
||||
|
|
@ -1301,12 +1288,12 @@ def _probe_mcp_server(cfg: Dict[str, Any], bot_id: Optional[str] = None) -> Dict
|
|||
"message": "MCP endpoint is reachable",
|
||||
"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 {
|
||||
"ok": True,
|
||||
"transport": transport_type,
|
||||
"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",
|
||||
}
|
||||
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]:
|
||||
return _normalize_env_params(raw)
|
||||
|
||||
|
|
@ -2441,17 +2310,6 @@ async def start_bot(bot_id: str, session: Session = Depends(get_session)):
|
|||
if not bot:
|
||||
raise HTTPException(status_code=404, detail="Bot not found")
|
||||
_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)
|
||||
env_params = _read_env_store(bot_id)
|
||||
success = docker_manager.start_bot(
|
||||
|
|
|
|||
Loading…
Reference in New Issue