refactor(supervisor): separate MCP dispatch from transport
This commit is contained in:
@@ -13,6 +13,7 @@ import tempfile
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
import typing
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -47,6 +48,7 @@ from bot_bottle.gateway.supervisor.server import (
|
||||
jsonrpc_error,
|
||||
jsonrpc_result,
|
||||
parse_jsonrpc,
|
||||
resolved_routes_payload,
|
||||
validate_proposed_file,
|
||||
)
|
||||
|
||||
@@ -701,9 +703,14 @@ class TestResolvedRoutesPayload(unittest.TestCase):
|
||||
" - host: api.anthropic.com\n"
|
||||
" - host: www.google.com\n"
|
||||
)
|
||||
payload = _handler(
|
||||
_FakeSuperviseResolver(bottle_id="b1", policy=policy)
|
||||
)._resolved_routes_payload()
|
||||
payload = resolved_routes_payload(
|
||||
typing.cast(
|
||||
supervise_server.PolicyResolver,
|
||||
_FakeSuperviseResolver(bottle_id="b1", policy=policy),
|
||||
),
|
||||
_SRC,
|
||||
_TOK,
|
||||
)
|
||||
assert payload is not None
|
||||
self.assertFalse(payload["isError"]) # type: ignore[index]
|
||||
data = json.loads(payload["content"][0]["text"]) # type: ignore[index]
|
||||
@@ -713,9 +720,14 @@ class TestResolvedRoutesPayload(unittest.TestCase):
|
||||
def test_orchestrator_error_fails_closed_to_empty(self) -> None:
|
||||
# resolve_client_context swallows resolver errors → deny-all (empty),
|
||||
# never another bottle's routes.
|
||||
payload = _handler(
|
||||
_FakeSuperviseResolver(raises=True)
|
||||
)._resolved_routes_payload()
|
||||
payload = resolved_routes_payload(
|
||||
typing.cast(
|
||||
supervise_server.PolicyResolver,
|
||||
_FakeSuperviseResolver(raises=True),
|
||||
),
|
||||
_SRC,
|
||||
_TOK,
|
||||
)
|
||||
assert payload is not None
|
||||
data = json.loads(payload["content"][0]["text"]) # type: ignore[index]
|
||||
self.assertEqual([], data["routes"])
|
||||
@@ -724,7 +736,13 @@ class TestResolvedRoutesPayload(unittest.TestCase):
|
||||
# A server without a resolver is a misconfig, not a mode: raise rather
|
||||
# than list anything.
|
||||
with self.assertRaises(_RpcInternalError):
|
||||
_handler(None)._resolved_routes_payload()
|
||||
_handler(None)._dispatch(
|
||||
parse_jsonrpc(
|
||||
b'{"jsonrpc":"2.0","id":1,"method":"tools/call",'
|
||||
b'"params":{"name":"list-egress-routes"}}',
|
||||
),
|
||||
ServerConfig(),
|
||||
)
|
||||
|
||||
|
||||
class TestNonBlockingSupervise(unittest.TestCase):
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Unit tests for framework-neutral supervisor MCP dispatch."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from dataclasses import dataclass
|
||||
|
||||
from bot_bottle.gateway.supervisor.mcp_dispatch import (
|
||||
Handlers,
|
||||
MethodNotFoundError,
|
||||
dispatch,
|
||||
)
|
||||
from bot_bottle.supervisor import types as _sv
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Request:
|
||||
method: str
|
||||
params: dict[str, object]
|
||||
|
||||
|
||||
class TestDispatch(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.calls: list[str] = []
|
||||
|
||||
def handler(name: str):
|
||||
def call(_params: dict[str, object]) -> str:
|
||||
self.calls.append(name)
|
||||
return name
|
||||
|
||||
return call
|
||||
|
||||
self.handlers = Handlers(
|
||||
initialize=handler("initialize"),
|
||||
tools_list=handler("tools_list"),
|
||||
list_routes=handler("list_routes"),
|
||||
check_proposal=handler("check_proposal"),
|
||||
propose=handler("propose"),
|
||||
)
|
||||
|
||||
def request(self, method: str, **params: object) -> _Request:
|
||||
return _Request(method=method, params=params)
|
||||
|
||||
def test_routes_protocol_methods(self) -> None:
|
||||
self.assertEqual(
|
||||
"initialize", dispatch(self.request("initialize"), self.handlers),
|
||||
)
|
||||
self.assertEqual(
|
||||
"tools_list", dispatch(self.request("tools/list"), self.handlers),
|
||||
)
|
||||
self.assertIsNone(
|
||||
dispatch(self.request("notifications/initialized"), self.handlers),
|
||||
)
|
||||
|
||||
def test_routes_each_tool_class(self) -> None:
|
||||
self.assertEqual(
|
||||
"list_routes",
|
||||
dispatch(
|
||||
self.request("tools/call", name=_sv.TOOL_LIST_EGRESS_ROUTES),
|
||||
self.handlers,
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
"check_proposal",
|
||||
dispatch(
|
||||
self.request("tools/call", name=_sv.TOOL_CHECK_PROPOSAL),
|
||||
self.handlers,
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
"propose",
|
||||
dispatch(self.request("tools/call", name=_sv.TOOL_EGRESS_ALLOW), self.handlers),
|
||||
)
|
||||
|
||||
def test_unknown_method_is_typed(self) -> None:
|
||||
with self.assertRaisesRegex(MethodNotFoundError, "unknown"):
|
||||
dispatch(self.request("unknown"), self.handlers)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user