thing
This commit is contained in:
@@ -246,3 +246,20 @@ names.
|
|||||||
|
|
||||||
The implementation should avoid having broker mode and transparent proxy mode
|
The implementation should avoid having broker mode and transparent proxy mode
|
||||||
define separate copies of the same admin/control capabilities.
|
define separate copies of the same admin/control capabilities.
|
||||||
|
|
||||||
|
## Current Inventory Surfaces
|
||||||
|
|
||||||
|
Two source listings now exist on purpose:
|
||||||
|
|
||||||
|
- `list_spec_sources()`
|
||||||
|
- compatibility/planner view
|
||||||
|
- only returns enabled planner-visible sources with node specs
|
||||||
|
- `list_sources()`
|
||||||
|
- full capability-source inventory
|
||||||
|
- returns every source plus visibility, permissions, counts, and the names
|
||||||
|
owned in each capability bucket
|
||||||
|
|
||||||
|
The broader `list_sources()` view is the one humans and LLM authoring clients
|
||||||
|
should use when deciding what exists. The narrower `list_spec_sources()` view is
|
||||||
|
still useful when the question is only "what can the planner currently place in
|
||||||
|
a graph?"
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ class BrokerAdminHandlers:
|
|||||||
def list_spec_sources(self) -> list[dict[str, Any]]:
|
def list_spec_sources(self) -> list[dict[str, Any]]:
|
||||||
return self.service.list_spec_sources()
|
return self.service.list_spec_sources()
|
||||||
|
|
||||||
|
def list_sources(self) -> list[dict[str, Any]]:
|
||||||
|
return self.service.list_sources()
|
||||||
|
|
||||||
async def read_broker_resource(self, qualified_name: str) -> dict[str, Any]:
|
async def read_broker_resource(self, qualified_name: str) -> dict[str, Any]:
|
||||||
return await self.service.read_resource(qualified_name)
|
return await self.service.read_resource(qualified_name)
|
||||||
|
|
||||||
|
|||||||
@@ -63,3 +63,15 @@ class CapabilitySource:
|
|||||||
"prompt_count": len(self.capabilities.prompts),
|
"prompt_count": len(self.capabilities.prompts),
|
||||||
"resource_count": len(self.capabilities.resources),
|
"resource_count": len(self.capabilities.resources),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_inventory(self) -> dict[str, Any]:
|
||||||
|
"""Return source metadata plus the capability names it owns."""
|
||||||
|
return {
|
||||||
|
**self.as_status(),
|
||||||
|
"capabilities": {
|
||||||
|
"tools": sorted(self.capabilities.tools),
|
||||||
|
"node_specs": sorted(self.capabilities.node_specs),
|
||||||
|
"prompts": sorted(self.capabilities.prompts),
|
||||||
|
"resources": sorted(self.capabilities.resources),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -250,6 +250,16 @@ class WfMcpService:
|
|||||||
and source.visibility.planner
|
and source.visibility.planner
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def list_sources(self) -> list[dict[str, Any]]:
|
||||||
|
"""Return every capability source with the names it currently owns."""
|
||||||
|
return [
|
||||||
|
source.as_inventory()
|
||||||
|
for source in sorted(
|
||||||
|
self.capability_sources.values(),
|
||||||
|
key=lambda source: source.id,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
def list_available_specs(self) -> list[CatalogNodeEntry]:
|
def list_available_specs(self) -> list[CatalogNodeEntry]:
|
||||||
"""Return planner-visible node catalog entries from every visible source."""
|
"""Return planner-visible node catalog entries from every visible source."""
|
||||||
return self.get_planner_catalog().entries()
|
return self.get_planner_catalog().entries()
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ def register_broker_tools(server: FastMCP, service: WfMcpService) -> None:
|
|||||||
async def list_spec_sources() -> list[dict[str, Any]]:
|
async def list_spec_sources() -> list[dict[str, Any]]:
|
||||||
return handlers.list_spec_sources()
|
return handlers.list_spec_sources()
|
||||||
|
|
||||||
|
@server.tool()
|
||||||
|
async def list_sources() -> list[dict[str, Any]]:
|
||||||
|
return handlers.list_sources()
|
||||||
|
|
||||||
@server.tool()
|
@server.tool()
|
||||||
async def read_broker_resource(qualified_name: str) -> dict[str, Any]:
|
async def read_broker_resource(qualified_name: str) -> dict[str, Any]:
|
||||||
return await handlers.read_broker_resource(qualified_name)
|
return await handlers.read_broker_resource(qualified_name)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
|
|||||||
assert "get_connection_statuses" in tool_names
|
assert "get_connection_statuses" in tool_names
|
||||||
assert "refresh_connection_catalog" in tool_names
|
assert "refresh_connection_catalog" in tool_names
|
||||||
assert "get_planner_catalog" in tool_names
|
assert "get_planner_catalog" in tool_names
|
||||||
|
assert "list_sources" in tool_names
|
||||||
assert "list_spec_sources" in tool_names
|
assert "list_spec_sources" in tool_names
|
||||||
assert "invoke_broker_method" in tool_names
|
assert "invoke_broker_method" in tool_names
|
||||||
assert "call_broker_tool" in tool_names
|
assert "call_broker_tool" in tool_names
|
||||||
@@ -101,6 +102,15 @@ def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
|
|||||||
assert "wf.mcp" in source_ids
|
assert "wf.mcp" in source_ids
|
||||||
assert "wf.std" in source_ids
|
assert "wf.std" in source_ids
|
||||||
|
|
||||||
|
_content, all_sources_payload_raw = asyncio.run(
|
||||||
|
server.call_tool("list_sources", {})
|
||||||
|
)
|
||||||
|
all_sources_payload = cast(dict[str, Any], cast(object, all_sources_payload_raw))
|
||||||
|
all_sources = all_sources_payload["result"]
|
||||||
|
all_source_ids = {source["id"] for source in all_sources}
|
||||||
|
assert "wf.admin" in all_source_ids
|
||||||
|
assert "demo.personal" in all_source_ids
|
||||||
|
|
||||||
|
|
||||||
def test_broker_admin_tools_are_backed_by_wf_admin_source() -> None:
|
def test_broker_admin_tools_are_backed_by_wf_admin_source() -> None:
|
||||||
service = WfMcpService(store=FileStore(local_temp_root() / "broker_admin_source"))
|
service = WfMcpService(store=FileStore(local_temp_root() / "broker_admin_source"))
|
||||||
|
|||||||
@@ -98,6 +98,24 @@ def test_service_installs_builtin_stdlib_specs_by_default() -> None:
|
|||||||
assert all(source["kind"] == "system" for source in sources)
|
assert all(source["kind"] == "system" for source in sources)
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_lists_all_capability_sources_with_owned_capability_names() -> None:
|
||||||
|
service = WfMcpService(store=FileStore(local_temp_root() / "source_inventory"))
|
||||||
|
|
||||||
|
sources = service.list_sources()
|
||||||
|
sources_by_id = {source["id"]: source for source in sources}
|
||||||
|
|
||||||
|
std_source = sources_by_id["wf.std"]
|
||||||
|
assert "wf.std.runtime_error" in std_source["capabilities"]["node_specs"]
|
||||||
|
assert std_source["capabilities"]["tools"] == []
|
||||||
|
|
||||||
|
mcp_source = sources_by_id["wf.mcp"]
|
||||||
|
assert mcp_source["capabilities"]["node_specs"] == ["wf.mcp.call_tool"]
|
||||||
|
|
||||||
|
admin_source = sources_by_id["wf.admin"]
|
||||||
|
assert admin_source["visibility"]["planner"] is False
|
||||||
|
assert "wf.admin.list_sources" in admin_source["capabilities"]["tools"]
|
||||||
|
|
||||||
|
|
||||||
def test_wf_std_source_contains_authoring_ops() -> None:
|
def test_wf_std_source_contains_authoring_ops() -> None:
|
||||||
service = WfMcpService(store=FileStore(local_temp_root() / "stdlib_source_store"))
|
service = WfMcpService(store=FileStore(local_temp_root() / "stdlib_source_store"))
|
||||||
specs = service.capability_sources["wf.std"].capabilities.node_specs
|
specs = service.capability_sources["wf.std"].capabilities.node_specs
|
||||||
|
|||||||
Reference in New Issue
Block a user