generated from coulomb/repo-seed
feat(capabilities): add write router factory and MCP composition (HUB-WP-0002)
Add create_capability_request_write_router with host workflow callbacks, CapabilityRequestReroute schema, HubCoreMCPServer.attach_to() with CORE_TOOL_NAMES exclude filtering, tests, and mark HUB-WP-0002 finished.
This commit is contained in:
@@ -14,6 +14,7 @@ from hub_core.routers import (
|
||||
create_capabilities_router,
|
||||
create_capability_catalog_router,
|
||||
create_capability_request_read_router,
|
||||
create_capability_request_write_router,
|
||||
create_domains_router,
|
||||
create_messages_router,
|
||||
create_policy_router,
|
||||
@@ -297,6 +298,59 @@ def test_capability_catalog_router_accepts_host_model_injection() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_capability_request_write_router_registers_write_paths() -> None:
|
||||
async def get_session():
|
||||
raise AssertionError("router construction should not resolve sessions")
|
||||
|
||||
router = create_capability_request_write_router(get_session)
|
||||
method_paths = {
|
||||
(method, route.path)
|
||||
for route in router.routes
|
||||
for method in getattr(route, "methods", set())
|
||||
}
|
||||
|
||||
assert method_paths == {
|
||||
("POST", "/capability-requests/"),
|
||||
("POST", "/capability-requests/{request_id}/accept"),
|
||||
("PATCH", "/capability-requests/{request_id}/status"),
|
||||
("PATCH", "/capability-requests/{request_id}"),
|
||||
("POST", "/capability-requests/{request_id}/dispute"),
|
||||
("POST", "/capability-requests/{request_id}/reroute"),
|
||||
}
|
||||
|
||||
|
||||
def test_capability_request_write_router_invokes_callbacks() -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
async def get_session():
|
||||
raise AssertionError("router construction should not resolve sessions")
|
||||
|
||||
async def route_request(session, body):
|
||||
calls.append("route")
|
||||
return None, None, "routed"
|
||||
|
||||
def build_request(body, requesting_domain, fulfilling_domain_id, catalog_entry_id, routing_note):
|
||||
calls.append("build")
|
||||
return object()
|
||||
|
||||
async def on_request_persisted(session, req, body):
|
||||
calls.append("persisted")
|
||||
|
||||
def check_transition(current, target):
|
||||
calls.append(f"transition:{current}->{target}")
|
||||
|
||||
router = create_capability_request_write_router(
|
||||
get_session,
|
||||
route_request=route_request,
|
||||
build_request=build_request,
|
||||
on_request_persisted=on_request_persisted,
|
||||
check_transition=check_transition,
|
||||
)
|
||||
|
||||
assert router.routes
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_capability_request_read_router_accepts_host_model_injection() -> None:
|
||||
async def get_session():
|
||||
raise AssertionError("router construction should not resolve sessions")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import asyncio
|
||||
|
||||
from hub_core.mcp import HubCoreMCPServer
|
||||
from fastmcp import FastMCP
|
||||
|
||||
from hub_core.mcp import CORE_TOOL_NAMES, HubCoreMCPServer
|
||||
|
||||
|
||||
def test_mcp_base_server_constructs_without_registering_tools() -> None:
|
||||
@@ -29,3 +31,21 @@ def test_mcp_base_server_registers_orientation_doi_and_fos10_tools() -> None:
|
||||
"get_risks",
|
||||
"get_alerts",
|
||||
} <= names
|
||||
assert names == CORE_TOOL_NAMES
|
||||
|
||||
|
||||
def test_attach_to_host_mcp_respects_exclude() -> None:
|
||||
host = FastMCP(name="host-hub")
|
||||
HubCoreMCPServer(
|
||||
name="host-hub",
|
||||
api_base="http://127.0.0.1:9999",
|
||||
register_tools=False,
|
||||
).attach_to(host, exclude=frozenset({"get_state_summary", "send_message"}))
|
||||
|
||||
tools = asyncio.run(host.list_tools())
|
||||
names = {tool.name for tool in tools}
|
||||
|
||||
assert "get_state_summary" not in names
|
||||
assert "send_message" not in names
|
||||
assert "get_domain_summary" in names
|
||||
assert len(names) == len(CORE_TOOL_NAMES) - 2
|
||||
|
||||
Reference in New Issue
Block a user