docs for client and for fastmcp patches
This commit is contained in:
@@ -1,28 +1,35 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from .service import WfMcpService
|
from .service import WfMcpService
|
||||||
|
|
||||||
|
_WORKFLOW_AUTHORING_GUIDE = """\
|
||||||
|
Build workflows from current capabilities instead of assuming a stale catalog.
|
||||||
|
|
||||||
|
Use `get_planner_catalog` when you need the current workflow-capability view.
|
||||||
|
Use `list_sources` and `list_spec_sources` when you need to understand what is
|
||||||
|
available and which sources are planner-visible.
|
||||||
|
Use `call_broker_tool` to test an upstream MCP tool manually before wrapping it
|
||||||
|
into a workflow.
|
||||||
|
|
||||||
|
Prefer namespaced capabilities, inspect before you rely on them, and test the
|
||||||
|
smallest reusable piece before saving a larger workflow artifact.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def register_broker_prompts(server: FastMCP, service: WfMcpService) -> None:
|
def register_broker_prompts(server: FastMCP, service: WfMcpService) -> None:
|
||||||
"""Register broker prompt handlers on a FastMCP server."""
|
"""Register broker prompt handlers on a FastMCP server."""
|
||||||
|
|
||||||
@server.prompt(
|
@server.prompt(
|
||||||
name="plan_with_catalog",
|
name="workflow_authoring_guide",
|
||||||
description="Provide the broker catalog as planning context.",
|
description="Explain how to inspect capabilities and test tools before authoring.",
|
||||||
)
|
)
|
||||||
def plan_with_catalog() -> list[dict[str, str]]:
|
def workflow_authoring_guide() -> list[dict[str, str]]:
|
||||||
payload = json.dumps(service.get_catalog().as_payload(), indent=2)
|
_ = service
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": (
|
"content": _WORKFLOW_AUTHORING_GUIDE,
|
||||||
"Plan a workflow using this broker catalog. "
|
|
||||||
"Prefer existing namespaced capabilities.\n\n"
|
|
||||||
f"{payload}"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ from ..shared.names import connection_id_to_resource_path
|
|||||||
|
|
||||||
|
|
||||||
class ResourceLinkRewritingTool(Tool):
|
class ResourceLinkRewritingTool(Tool):
|
||||||
"""Delegate tool execution while rewriting returned resource-link URIs."""
|
"""Delegate execution while compensating for one FastMCP proxy gap.
|
||||||
|
|
||||||
|
FastMCP already rewrites resources listed through namespace transforms, but
|
||||||
|
proxied tools can also return typed `ResourceLink` content blocks. Current
|
||||||
|
FastMCP does not rewrite those result payloads for us, so this wrapper keeps
|
||||||
|
the public tool schema and changes only the returned resource-link URIs.
|
||||||
|
"""
|
||||||
|
|
||||||
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
||||||
|
|
||||||
@@ -50,7 +56,12 @@ class ResourceLinkRewritingTool(Tool):
|
|||||||
|
|
||||||
|
|
||||||
class ResourceLinkNamespace(Transform):
|
class ResourceLinkNamespace(Transform):
|
||||||
"""Rewrite resource links returned by tools into one namespace."""
|
"""Mirror namespace URI projection inside proxied tool results.
|
||||||
|
|
||||||
|
This is a local stand-in for the result-transform behavior a fully
|
||||||
|
transparent FastMCP proxy would ideally provide itself. If upstream gains a
|
||||||
|
general result rewrite hook, this class should become deletable.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, prefix: str) -> None:
|
def __init__(self, prefix: str) -> None:
|
||||||
self._prefix = connection_id_to_resource_path(prefix)
|
self._prefix = connection_id_to_resource_path(prefix)
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ def rewrite_call_tool_result_resource_links(
|
|||||||
result: mcp_types.CallToolResult,
|
result: mcp_types.CallToolResult,
|
||||||
rewrite_uri: ResourceUriRewriter,
|
rewrite_uri: ResourceUriRewriter,
|
||||||
) -> mcp_types.CallToolResult:
|
) -> mcp_types.CallToolResult:
|
||||||
"""Return a copy of a tool result with ResourceLink content URIs rewritten."""
|
"""Return a copy of a tool result with ResourceLink content URIs rewritten.
|
||||||
|
|
||||||
|
This pure helper expresses the same missing proxy behavior as
|
||||||
|
`ResourceLinkNamespace` without depending on FastMCP wrapper classes.
|
||||||
|
"""
|
||||||
return result.model_copy(
|
return result.model_copy(
|
||||||
update={
|
update={
|
||||||
"content": [
|
"content": [
|
||||||
|
|||||||
@@ -92,7 +92,19 @@ def namespace_resource_uri(connection_id: str, uri: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class ProxyNamespace(Transform):
|
class ProxyNamespace(Transform):
|
||||||
"""Project MCP proxy names with dots for callables and slashes for URIs."""
|
"""Project one upstream MCP server into wf-mcp's public namespace.
|
||||||
|
|
||||||
|
This intentionally replaces FastMCP's stock `Namespace` transform for
|
||||||
|
proxied capabilities. FastMCP's transform is almost the right thing, but it
|
||||||
|
uses underscore-prefixed callable names and does not match our desired
|
||||||
|
downstream shape:
|
||||||
|
|
||||||
|
- tools/prompts use `connection.id.local_name`
|
||||||
|
- resources/templates use URI paths like `scheme://connection/id/path`
|
||||||
|
|
||||||
|
Keep this class narrow. It should only remake the namespace projection
|
||||||
|
behavior we need from FastMCP, not become a general proxy framework.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, connection_id: str) -> None:
|
def __init__(self, connection_id: str) -> None:
|
||||||
self._connection_id = connection_id
|
self._connection_id = connection_id
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
|
|||||||
assert "catalog.all" in resource_names
|
assert "catalog.all" in resource_names
|
||||||
assert "events.all" in resource_names
|
assert "events.all" in resource_names
|
||||||
assert "status.all" in resource_names
|
assert "status.all" in resource_names
|
||||||
assert "plan_with_catalog" in prompt_names
|
assert "workflow_authoring_guide" in prompt_names
|
||||||
|
assert "plan_with_catalog" not in prompt_names
|
||||||
|
|
||||||
_content, planner_catalog_raw = asyncio.run(
|
_content, planner_catalog_raw = asyncio.run(
|
||||||
server.call_tool("get_planner_catalog", {})
|
server.call_tool("get_planner_catalog", {})
|
||||||
|
|||||||
Reference in New Issue
Block a user