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:
@@ -8,6 +8,36 @@ from fastmcp import FastMCP
|
||||
|
||||
from hub_core.utils.routing import normalize_trailing_slash
|
||||
|
||||
CORE_TOOL_NAMES = frozenset({
|
||||
"get_state_summary",
|
||||
"list_domains",
|
||||
"get_domain_summary",
|
||||
"get_domain",
|
||||
"send_message",
|
||||
"get_messages",
|
||||
"mark_message_read",
|
||||
"reply_to_message",
|
||||
"register_capability",
|
||||
"list_capabilities",
|
||||
"request_capability",
|
||||
"accept_capability_request",
|
||||
"update_capability_request_status",
|
||||
"list_capability_requests",
|
||||
"get_capability_request",
|
||||
"register_repo",
|
||||
"update_repo_path",
|
||||
"list_domain_repos",
|
||||
"check_repo_doi",
|
||||
"get_doi_summary",
|
||||
"register_service",
|
||||
"list_services",
|
||||
"ingest_tpsc_tool",
|
||||
"get_gdpr_report",
|
||||
"get_risks",
|
||||
"get_alerts",
|
||||
"append_progress",
|
||||
})
|
||||
|
||||
|
||||
class HubCoreMCPServer:
|
||||
"""FastMCP base server for generic FOS hub tools.
|
||||
@@ -32,24 +62,46 @@ class HubCoreMCPServer:
|
||||
if register_tools:
|
||||
self.register_core_tools()
|
||||
|
||||
def register_core_tools(self) -> None:
|
||||
@self.mcp.tool()
|
||||
def attach_to(
|
||||
self,
|
||||
host_mcp: FastMCP,
|
||||
*,
|
||||
exclude: frozenset[str] | None = None,
|
||||
) -> HubCoreMCPServer:
|
||||
"""Register generic hub-core tools on an existing host MCP server."""
|
||||
self.mcp = host_mcp
|
||||
self.register_core_tools(exclude=exclude)
|
||||
return self
|
||||
|
||||
def _register_tool(self, name: str, excluded: frozenset[str]):
|
||||
def decorator(fn):
|
||||
if name not in excluded:
|
||||
self.mcp.tool()(fn)
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
||||
def register_core_tools(self, *, exclude: frozenset[str] | None = None) -> None:
|
||||
excluded = exclude or frozenset()
|
||||
register = lambda name: self._register_tool(name, excluded) # noqa: E731
|
||||
|
||||
@register("get_state_summary")
|
||||
def get_state_summary() -> str:
|
||||
return self._json(self._get("/state/summary/"))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("list_domains")
|
||||
def list_domains(status: str | None = None) -> str:
|
||||
return self._json(self._get("/domains/", {"status": status}))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_domain_summary")
|
||||
def get_domain_summary(domain_slug: str) -> str:
|
||||
return self._json(self._get(f"/domains/{domain_slug}/"))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_domain")
|
||||
def get_domain(domain_slug: str) -> str:
|
||||
return self._json(self._get(f"/domains/{domain_slug}/"))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("send_message")
|
||||
def send_message(
|
||||
from_agent: str,
|
||||
to_agent: str,
|
||||
@@ -70,7 +122,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_messages")
|
||||
def get_messages(
|
||||
to_agent: str | None = None,
|
||||
from_agent: str | None = None,
|
||||
@@ -89,11 +141,11 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("mark_message_read")
|
||||
def mark_message_read(message_id: str) -> str:
|
||||
return self._json(self._patch(f"/messages/{message_id}/read/", {}))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("reply_to_message")
|
||||
def reply_to_message(message_id: str, from_agent: str, body: str) -> str:
|
||||
return self._json(
|
||||
self._post(
|
||||
@@ -102,7 +154,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("register_capability")
|
||||
def register_capability(
|
||||
domain: str,
|
||||
capability_type: str,
|
||||
@@ -125,7 +177,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("list_capabilities")
|
||||
def list_capabilities(
|
||||
domain: str | None = None,
|
||||
capability_type: str | None = None,
|
||||
@@ -138,7 +190,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("request_capability")
|
||||
def request_capability(
|
||||
title: str,
|
||||
capability_type: str,
|
||||
@@ -165,7 +217,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("accept_capability_request")
|
||||
def accept_capability_request(
|
||||
request_id: str,
|
||||
fulfilling_agent: str,
|
||||
@@ -181,7 +233,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("update_capability_request_status")
|
||||
def update_capability_request_status(
|
||||
request_id: str,
|
||||
status: str,
|
||||
@@ -194,7 +246,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("list_capability_requests")
|
||||
def list_capability_requests(
|
||||
domain: str | None = None,
|
||||
status: str | None = None,
|
||||
@@ -207,11 +259,11 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_capability_request")
|
||||
def get_capability_request(request_id: str) -> str:
|
||||
return self._json(self._get(f"/capability-requests/{request_id}/"))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("register_repo")
|
||||
def register_repo(
|
||||
domain_slug: str,
|
||||
slug: str,
|
||||
@@ -236,17 +288,17 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("update_repo_path")
|
||||
def update_repo_path(repo_slug: str, host: str, path: str) -> str:
|
||||
return self._json(
|
||||
self._post(f"/repos/{repo_slug}/paths/", {"host": host, "path": path})
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("list_domain_repos")
|
||||
def list_domain_repos(domain_slug: str) -> str:
|
||||
return self._json(self._get("/repos/", {"domain": domain_slug}))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("check_repo_doi")
|
||||
def check_repo_doi(repo_slug: str, force_refresh: bool = False) -> str:
|
||||
return self._json(
|
||||
self._get(
|
||||
@@ -255,11 +307,11 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_doi_summary")
|
||||
def get_doi_summary() -> str:
|
||||
return self._json(self._get("/repos/doi/summary/"))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("register_service")
|
||||
def register_service(
|
||||
slug: str,
|
||||
name: str,
|
||||
@@ -284,7 +336,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("list_services")
|
||||
def list_services(
|
||||
gdpr_maturity: str | None = None,
|
||||
category: str | None = None,
|
||||
@@ -301,7 +353,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("ingest_tpsc_tool")
|
||||
def ingest_tpsc_tool(repo_slug: str, source_file: str, entries: list[dict[str, Any]]) -> str:
|
||||
return self._json(
|
||||
self._post(
|
||||
@@ -310,11 +362,11 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_gdpr_report")
|
||||
def get_gdpr_report() -> str:
|
||||
return self._json(self._get("/tpsc/report/gdpr/"))
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_risks")
|
||||
def get_risks(
|
||||
since: str | None = None,
|
||||
limit: int = 100,
|
||||
@@ -327,7 +379,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("get_alerts")
|
||||
def get_alerts(
|
||||
since: str | None = None,
|
||||
limit: int = 100,
|
||||
@@ -340,7 +392,7 @@ class HubCoreMCPServer:
|
||||
)
|
||||
)
|
||||
|
||||
@self.mcp.tool()
|
||||
@register("append_progress")
|
||||
def append_progress(
|
||||
event_type: str,
|
||||
summary: str,
|
||||
|
||||
Reference in New Issue
Block a user