mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-13 07:39:15 +03:00
fix(webui): surface MCP runtime connection failures (#5331)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Callable, Mapping
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
@@ -10,13 +12,19 @@ from websockets.datastructures import Headers
|
||||
|
||||
from nanobot.config.loader import get_config_path
|
||||
from nanobot.webui.http_utils import http_json_response
|
||||
from nanobot.webui.mcp_presets_api import custom_mcp_action
|
||||
from nanobot.webui.settings_routes import WebUISettingsRouter
|
||||
from nanobot.webui.settings_services import WebUISettingsServices
|
||||
|
||||
|
||||
def _router(*, authorized: bool = True) -> WebUISettingsRouter:
|
||||
def _router(
|
||||
*,
|
||||
authorized: bool = True,
|
||||
config_path: Path | None = None,
|
||||
mcp_runtime_status: Callable[[], Mapping[str, str]] | None = None,
|
||||
) -> WebUISettingsRouter:
|
||||
return WebUISettingsRouter(
|
||||
settings=WebUISettingsServices.create(get_config_path()),
|
||||
settings=WebUISettingsServices.create(config_path or get_config_path()),
|
||||
bus=SimpleNamespace(),
|
||||
logger=SimpleNamespace(exception=lambda *_args: None),
|
||||
check_api_token=lambda _request: authorized,
|
||||
@@ -28,6 +36,7 @@ def _router(*, authorized: bool = True) -> WebUISettingsRouter:
|
||||
),
|
||||
runtime_surface="browser",
|
||||
runtime_capabilities={},
|
||||
mcp_runtime_status=mcp_runtime_status,
|
||||
mcp_oauth_redirect_uri=lambda _request: "https://gateway.example/auth/mcp/callback",
|
||||
)
|
||||
|
||||
@@ -40,6 +49,46 @@ def _mutation_request(path: str, payload: dict[str, object]) -> SimpleNamespace:
|
||||
return request
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_list_serializes_local_runtime_failure_snapshot(tmp_path) -> None:
|
||||
config_path = tmp_path / "config.json"
|
||||
custom_mcp_action(
|
||||
"custom",
|
||||
{
|
||||
"name": ["team-docs"],
|
||||
"transport": ["streamableHttp"],
|
||||
"url": ["https://mcp.example.com/mcp"],
|
||||
},
|
||||
config_path=config_path,
|
||||
)
|
||||
snapshot_calls = 0
|
||||
|
||||
def runtime_snapshot() -> Mapping[str, str]:
|
||||
nonlocal snapshot_calls
|
||||
snapshot_calls += 1
|
||||
return {"team-docs": "failed"}
|
||||
|
||||
router = _router(
|
||||
config_path=config_path,
|
||||
mcp_runtime_status=runtime_snapshot,
|
||||
)
|
||||
request = SimpleNamespace(
|
||||
path="/api/settings/mcp-presets",
|
||||
headers=Headers(),
|
||||
)
|
||||
|
||||
response = await router.dispatch(None, request, "/api/settings/mcp-presets")
|
||||
|
||||
assert response is not None
|
||||
assert response.status_code == 200
|
||||
payload = json.loads(response.body)
|
||||
row = next(item for item in payload["presets"] if item["name"] == "team-docs")
|
||||
assert row["status"] == "configured"
|
||||
assert row["runtime_status"] == "failed"
|
||||
assert b'"runtime_status": "failed"' in response.body
|
||||
assert snapshot_calls == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_oauth_start_uses_gateway_callback_and_requires_api_auth(monkeypatch) -> None:
|
||||
config = SimpleNamespace(
|
||||
|
||||
Reference in New Issue
Block a user