read_resource to also read capability sources
This commit is contained in:
@@ -74,10 +74,7 @@ def _add_step(builder: WorkflowBuilder, step_id: str, step: DraftStep):
|
|||||||
).entry
|
).entry
|
||||||
if isinstance(step, DraftChooseStep):
|
if isinstance(step, DraftChooseStep):
|
||||||
return builder.choose(
|
return builder.choose(
|
||||||
*[
|
*[(clause.if_, clause.then) for clause in step.choose.clauses],
|
||||||
(clause.if_, clause.then)
|
|
||||||
for clause in step.choose.clauses
|
|
||||||
],
|
|
||||||
id=step_id,
|
id=step_id,
|
||||||
default=step.choose.default,
|
default=step.choose.default,
|
||||||
).entry
|
).entry
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ def register_service_admin_tools(
|
|||||||
@server.tool(
|
@server.tool(
|
||||||
name=name("read_resource"),
|
name=name("read_resource"),
|
||||||
title="Read Resource",
|
title="Read Resource",
|
||||||
description="Read a broker-catalog resource by qualified name.",
|
description=(
|
||||||
|
"Read a local docs or broker-catalog resource by qualified name, "
|
||||||
|
"for example wf.docs.workflow_capabilities."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
async def read_resource(qualified_name: str) -> dict[str, Any]:
|
async def read_resource(qualified_name: str) -> dict[str, Any]:
|
||||||
return await handlers.read_broker_resource(qualified_name)
|
return await handlers.read_broker_resource(qualified_name)
|
||||||
@@ -109,7 +112,10 @@ def register_service_admin_tools(
|
|||||||
@server.tool(
|
@server.tool(
|
||||||
name=name("render_prompt"),
|
name=name("render_prompt"),
|
||||||
title="Render Prompt",
|
title="Render Prompt",
|
||||||
description="Render a broker-catalog prompt by qualified name.",
|
description=(
|
||||||
|
"Render a local docs or broker-catalog prompt by qualified name, "
|
||||||
|
"for example wf.docs.workflow_authoring_guide."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
async def render_prompt(
|
async def render_prompt(
|
||||||
qualified_name: str,
|
qualified_name: str,
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ from wf_core import NodeUse, Workflow, execute_workflow_async
|
|||||||
from wf_platform import (
|
from wf_platform import (
|
||||||
CapabilityBuckets,
|
CapabilityBuckets,
|
||||||
CapabilitySource,
|
CapabilitySource,
|
||||||
|
DocumentationPrompt,
|
||||||
|
DocumentationResource,
|
||||||
SourcePermissions,
|
SourcePermissions,
|
||||||
SourceVisibility,
|
SourceVisibility,
|
||||||
page_items,
|
page_items,
|
||||||
@@ -363,6 +365,25 @@ class WfMcpService:
|
|||||||
return entry
|
return entry
|
||||||
|
|
||||||
async def read_resource(self, qualified_name: str) -> dict[str, Any]:
|
async def read_resource(self, qualified_name: str) -> dict[str, Any]:
|
||||||
|
local_resource = self._local_documentation_resource(qualified_name)
|
||||||
|
if local_resource is not None:
|
||||||
|
self._record_event(
|
||||||
|
make_event(
|
||||||
|
"resource_read_completed",
|
||||||
|
capability_id=qualified_name,
|
||||||
|
payload={"uri": local_resource.uri, "source": "local"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"uri": local_resource.uri,
|
||||||
|
"mimeType": local_resource.mime_type,
|
||||||
|
"text": local_resource.text,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
resource = self.get_resource(qualified_name)
|
resource = self.get_resource(qualified_name)
|
||||||
connection = self.connections.get(resource.connection_id)
|
connection = self.connections.get(resource.connection_id)
|
||||||
adapter = require_adapter(connection, self.adapters)
|
adapter = require_adapter(connection, self.adapters)
|
||||||
@@ -484,6 +505,31 @@ class WfMcpService:
|
|||||||
*,
|
*,
|
||||||
arguments: dict[str, str] | None = None,
|
arguments: dict[str, str] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
local_prompt = self._local_documentation_prompt(qualified_name)
|
||||||
|
if local_prompt is not None:
|
||||||
|
self._record_event(
|
||||||
|
make_event(
|
||||||
|
"prompt_get_completed",
|
||||||
|
capability_id=qualified_name,
|
||||||
|
payload={
|
||||||
|
"argument_keys": sorted((arguments or {}).keys()),
|
||||||
|
"source": "local",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"description": local_prompt.description,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": {
|
||||||
|
"type": "text",
|
||||||
|
"text": local_prompt.text,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
prompt = self.get_prompt(qualified_name)
|
prompt = self.get_prompt(qualified_name)
|
||||||
connection = self.connections.get(prompt.connection_id)
|
connection = self.connections.get(prompt.connection_id)
|
||||||
adapter = require_adapter(connection, self.adapters)
|
adapter = require_adapter(connection, self.adapters)
|
||||||
@@ -512,6 +558,28 @@ class WfMcpService:
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _local_documentation_resource(
|
||||||
|
self,
|
||||||
|
qualified_name: str,
|
||||||
|
) -> DocumentationResource | None:
|
||||||
|
"""Return a local docs resource from capability sources by qualified name."""
|
||||||
|
for source in self.capability_sources.values():
|
||||||
|
resource = source.capabilities.resources.get(qualified_name)
|
||||||
|
if isinstance(resource, DocumentationResource):
|
||||||
|
return resource
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _local_documentation_prompt(
|
||||||
|
self,
|
||||||
|
qualified_name: str,
|
||||||
|
) -> DocumentationPrompt | None:
|
||||||
|
"""Return a local docs prompt from capability sources by qualified name."""
|
||||||
|
for source in self.capability_sources.values():
|
||||||
|
prompt = source.capabilities.prompts.get(qualified_name)
|
||||||
|
if isinstance(prompt, DocumentationPrompt):
|
||||||
|
return prompt
|
||||||
|
return None
|
||||||
|
|
||||||
async def refresh_connection_catalog(
|
async def refresh_connection_catalog(
|
||||||
self,
|
self,
|
||||||
connection_id: str,
|
connection_id: str,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal, dataclass_transform
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|||||||
@@ -450,6 +450,43 @@ def test_server_exposes_platform_documentation_prompts() -> None:
|
|||||||
asyncio.run(run_proxy())
|
asyncio.run(run_proxy())
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_tools_can_read_local_documentation_source() -> None:
|
||||||
|
config = BrokerConfig(
|
||||||
|
store_root=local_temp_root() / "unified_admin_docs_store",
|
||||||
|
connections=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def run_proxy() -> None:
|
||||||
|
client = create_server_client(
|
||||||
|
config,
|
||||||
|
resources_as_tools=True,
|
||||||
|
prompts_as_tools=True,
|
||||||
|
safe_tool_names=True,
|
||||||
|
)
|
||||||
|
async with client:
|
||||||
|
resource = await client.call_tool(
|
||||||
|
"wf_admin_read_resource",
|
||||||
|
{"qualified_name": "wf.docs.workflow_capabilities"},
|
||||||
|
)
|
||||||
|
prompt = await client.call_tool(
|
||||||
|
"wf_admin_render_prompt",
|
||||||
|
{"qualified_name": "wf.docs.workflow_authoring_guide"},
|
||||||
|
)
|
||||||
|
|
||||||
|
resource_payload = _structured(resource)
|
||||||
|
prompt_payload = _structured(prompt)
|
||||||
|
assert resource_payload["contents"][0]["uri"] == (
|
||||||
|
"wf://docs/workflow-capabilities"
|
||||||
|
)
|
||||||
|
assert "# Workflow Capabilities" in resource_payload["contents"][0]["text"]
|
||||||
|
assert (
|
||||||
|
"wf://docs/workflow-capabilities"
|
||||||
|
in (prompt_payload["messages"][0]["content"]["text"])
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.run(run_proxy())
|
||||||
|
|
||||||
|
|
||||||
def test_server_reload_syncs_service_connection_source_enabled_state() -> None:
|
def test_server_reload_syncs_service_connection_source_enabled_state() -> None:
|
||||||
tmp_path = local_temp_root() / "unified_reload_service_source_store"
|
tmp_path = local_temp_root() / "unified_reload_service_source_store"
|
||||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user