new mcp tools
This commit is contained in:
@@ -247,6 +247,20 @@ That direct-call surface is different from:
|
|||||||
|
|
||||||
It exists so authors can test the workflow-facing contract before composing it.
|
It exists so authors can test the workflow-facing contract before composing it.
|
||||||
|
|
||||||
|
The workflow-facing MCP surface now has dedicated discovery tools for the
|
||||||
|
authoring loop:
|
||||||
|
|
||||||
|
- `wf.workflow.list_capabilities`
|
||||||
|
- lists enabled planner-visible workflow-ready node specs
|
||||||
|
- `wf.workflow.inspect_capability`
|
||||||
|
- returns one full workflow capability contract with schemas and outcomes
|
||||||
|
- `wf.workflow.call_capability`
|
||||||
|
- executes one such capability once for direct testing
|
||||||
|
|
||||||
|
These are authoring-plane tools. They do not replace the privileged
|
||||||
|
`wf.admin.list_sources` source inventory, and older planner-catalog projections
|
||||||
|
may remain while callers migrate to the workflow-facing surface.
|
||||||
|
|
||||||
## Relationship To Capability Sources
|
## Relationship To Capability Sources
|
||||||
|
|
||||||
Sources own capability kinds:
|
Sources own capability kinds:
|
||||||
|
|||||||
@@ -44,6 +44,29 @@ class WorkflowSurfaceHandlers:
|
|||||||
]
|
]
|
||||||
return {"nodes": entries}
|
return {"nodes": entries}
|
||||||
|
|
||||||
|
async def list_capabilities(self) -> dict[str, Any]:
|
||||||
|
"""Return planner-visible workflow-ready node spec contracts."""
|
||||||
|
capabilities = [
|
||||||
|
detail.model_dump(mode="json")
|
||||||
|
for source in sorted(
|
||||||
|
self.service.capability_sources.values(),
|
||||||
|
key=lambda source: source.id,
|
||||||
|
)
|
||||||
|
if source.enabled and source.visibility.planner
|
||||||
|
for detail in source.as_inventory().capabilities.node_spec_details
|
||||||
|
]
|
||||||
|
return {"capabilities": capabilities}
|
||||||
|
|
||||||
|
async def inspect_capability(self, *, qualified_name: str) -> dict[str, Any]:
|
||||||
|
"""Return one planner-visible workflow capability contract."""
|
||||||
|
for source in self.service.capability_sources.values():
|
||||||
|
if not source.enabled or not source.visibility.planner:
|
||||||
|
continue
|
||||||
|
for detail in source.as_inventory().capabilities.node_spec_details:
|
||||||
|
if detail.name == qualified_name:
|
||||||
|
return detail.model_dump(mode="json")
|
||||||
|
raise KeyError(f"unknown workflow capability {qualified_name!r}")
|
||||||
|
|
||||||
async def call_capability(
|
async def call_capability(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -24,6 +24,22 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
|||||||
async def list_artifacts() -> dict[str, Any]:
|
async def list_artifacts() -> dict[str, Any]:
|
||||||
return await handlers.list_artifacts()
|
return await handlers.list_artifacts()
|
||||||
|
|
||||||
|
@server.tool(
|
||||||
|
name="wf.workflow.list_capabilities",
|
||||||
|
title="List Workflow Capabilities",
|
||||||
|
description="List planner-visible workflow-ready node capabilities.",
|
||||||
|
)
|
||||||
|
async def list_capabilities() -> dict[str, Any]:
|
||||||
|
return await handlers.list_capabilities()
|
||||||
|
|
||||||
|
@server.tool(
|
||||||
|
name="wf.workflow.inspect_capability",
|
||||||
|
title="Inspect Workflow Capability",
|
||||||
|
description="Return one planner-visible workflow capability contract.",
|
||||||
|
)
|
||||||
|
async def inspect_capability(qualified_name: str) -> dict[str, Any]:
|
||||||
|
return await handlers.inspect_capability(qualified_name=qualified_name)
|
||||||
|
|
||||||
@server.tool(
|
@server.tool(
|
||||||
name="wf.workflow.call_capability",
|
name="wf.workflow.call_capability",
|
||||||
title="Call Workflow Capability",
|
title="Call Workflow Capability",
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
|
|||||||
assert "wf.admin.call_tool" in names
|
assert "wf.admin.call_tool" in names
|
||||||
assert "wf.admin.get_events" in names
|
assert "wf.admin.get_events" in names
|
||||||
assert "wf.workflow.list_artifacts" in names
|
assert "wf.workflow.list_artifacts" in names
|
||||||
|
assert "wf.workflow.list_capabilities" in names
|
||||||
|
assert "wf.workflow.inspect_capability" in names
|
||||||
assert "wf.workflow.call_capability" in names
|
assert "wf.workflow.call_capability" in names
|
||||||
assert "wf.workflow.run_deployment" in names
|
assert "wf.workflow.run_deployment" in names
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,31 @@ def test_workflow_surface_lists_artifact_catalog_entries() -> None:
|
|||||||
assert "plan" not in nodes[0]
|
assert "plan" not in nodes[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_surface_lists_planner_visible_capabilities() -> None:
|
||||||
|
handlers = _handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_caps"))
|
||||||
|
|
||||||
|
payload = asyncio.run(handlers.list_capabilities())
|
||||||
|
names = [capability["name"] for capability in payload["capabilities"]]
|
||||||
|
|
||||||
|
assert "wf.std.runtime_error" in names
|
||||||
|
assert "wf.mcp.call_tool" in names
|
||||||
|
assert "wf.admin.list_sources" not in names
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_surface_inspects_one_capability() -> None:
|
||||||
|
handlers = _handlers(
|
||||||
|
FileWorkflowArtifactStore(local_temp_root() / "surface_inspect_cap")
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = asyncio.run(
|
||||||
|
handlers.inspect_capability(qualified_name="wf.std.runtime_error")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert payload["name"] == "wf.std.runtime_error"
|
||||||
|
assert payload["outcomes"] == ["ok"]
|
||||||
|
assert "input_schema" in payload
|
||||||
|
|
||||||
|
|
||||||
def test_workflow_surface_validates_deployment_dependencies() -> None:
|
def test_workflow_surface_validates_deployment_dependencies() -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_validate")
|
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_validate")
|
||||||
artifact_store.save_artifact(_artifact())
|
artifact_store.save_artifact(_artifact())
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"id": "context7.default",
|
"id": "context7.default",
|
||||||
"server": "context7",
|
"server": "context7",
|
||||||
"account": "default",
|
"account": "default",
|
||||||
"enabled": false,
|
"enabled": true,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"transport": "stdio",
|
"transport": "stdio",
|
||||||
"command": "pnpx",
|
"command": "pnpx",
|
||||||
|
|||||||
Reference in New Issue
Block a user