rename fns/test files

This commit is contained in:
lda
2026-05-19 23:53:15 +07:00 Verified
parent da0dea6b52
commit d5264e179a
12 changed files with 85 additions and 87 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ This repository has three main packages plus examples and tests.
| --- | --- | --- |
| `wf_core` | Deterministic workflow kernel: models, validation, runtime, run state, traces, interrupts, foreach, and path/state operations. | Runtime users, `wf_authoring`, workflow adapters. |
| `wf_authoring` | Ergonomic workflow construction: `@node`, `NodeSpec`, builder DSL, conditions, path helpers, reusable ops, subgraph nodes. | Humans, tests, future LLM workflow builders. |
| `wf_mcp` | MCP integration: SDK adapters, broker/proxy runtime, storage, config control, transparent proxy, and workflow wrappers for discovered tools. | MCP-facing CLI/server code and future UI/control surfaces. |
| `wf_mcp` | MCP integration: SDK adapters, broker/proxy runtime, storage, config control, and workflow wrappers for discovered tools. | MCP-facing CLI/server code and future UI/control surfaces. |
## Important Entry Points
@@ -46,7 +46,7 @@ This repository has three main packages plus examples and tests.
- `tests/authoring`: builder, node decorator, ops, async runtime, subgraph, and
demo workflow comparisons.
- `tests/wf_mcp`: MCP SDK adapter, broker, transparent proxy, storage, CLI, and
- `tests/wf_mcp`: MCP SDK adapter, broker, proxy, storage, CLI, and
naming behavior.
- `tests/rewrite`: local rewrite/port experiments that should keep exercising
real user ergonomics.
+5 -5
View File
@@ -11,7 +11,7 @@ with [`wf_mcp_operator_manual.md`](wf_mcp_operator_manual.md).
| Package | Responsibility |
| --- | --- |
| `wf_mcp.transparent_proxy` | Expose configured upstream MCP servers as a transparent MCP proxy. Owns proxy runtime, admin tools, and proxy tool listing helpers. |
| `wf_mcp.proxy` | Expose configured upstream MCP servers through mounted FastMCP proxy providers. Owns proxy runtime, admin tools, and proxy tool listing helpers. |
| `wf_mcp.broker` | Coordinate remembered connections, catalog snapshots, discovery, events, and workflow execution through broker services. |
| `wf_mcp.workflow` | Convert discovered MCP tools into `wf_authoring` / `wf_core` node specs. |
| `wf_mcp.sdk` | Speak to upstream MCP servers through the MCP Python SDK. Owns adapter protocols, SDK transport/session calls, and SDK object converters. |
@@ -27,7 +27,7 @@ relevant concern package directly.
## Dependency Rules
- `wf_mcp.sdk` should not import `wf_core` or `wf_authoring`.
- `wf_mcp.transparent_proxy` should not import `wf_mcp.workflow`.
- `wf_mcp.proxy` should not import `wf_mcp.workflow`.
- `wf_mcp.workflow` is the only layer that converts MCP capabilities into node specs.
- `wf_mcp.broker` may coordinate `sdk`, `storage`, `control`, and `workflow`.
- `wf_mcp.control` should not know about live MCP clients or workflow execution.
@@ -55,8 +55,8 @@ proxy, planner, and admin UI surfaces project different capability kinds.
## Hot Reload
Transparent proxy reload is intentionally isolated in
`wf_mcp.transparent_proxy.runtime`. FastMCP does not currently expose a complete
Proxy reload is intentionally isolated in
`wf_mcp.proxy.runtime`. FastMCP does not currently expose a complete
provider/proxy unmount lifecycle that we can rely on for safe per-connection
teardown. Until that exists, reload should be treated as best-effort remounting,
not a fully safe session/subscription lifecycle.
@@ -114,7 +114,7 @@ gone.
If this becomes multiple distributions, likely split points are:
- `wf-mcp-proxy`: `transparent_proxy`, `control`, `shared`
- `wf-mcp-proxy`: `proxy`, `control`, `shared`
- `wf-mcp-broker`: `broker`, `storage`, `workflow`, `shared`
- `wf-mcp-sdk`: `sdk`, `capabilities`, `models`, `shared`
+9 -9
View File
@@ -1,6 +1,6 @@
from .broker import (
load_broker_config,
WfMcpService,
load_broker_config,
)
from .capabilities import (
DiscoveredPrompt,
@@ -13,13 +13,13 @@ from .models import (
ConnectionConfig,
RawWorkflowPlan,
)
from .proxy_validation import ProxyConfigError, validate_transparent_proxy_config
from .proxy import (
create_proxy_client,
create_proxy_server,
)
from .proxy_validation import ProxyConfigError, validate_proxy_config
from .sdk import McpSdkAdapter
from .storage import FileStore, Store
from .proxy import (
create_transparent_proxy_client,
create_transparent_proxy_server,
)
__all__ = [
"AuthRecord",
@@ -34,8 +34,8 @@ __all__ = [
"RawWorkflowPlan",
"Store",
"WfMcpService",
"create_transparent_proxy_client",
"create_transparent_proxy_server",
"create_proxy_client",
"create_proxy_server",
"load_broker_config",
"validate_transparent_proxy_config",
"validate_proxy_config",
]
+4 -4
View File
@@ -1,13 +1,13 @@
from .admin import register_proxy_admin_tools
from .runtime import (
ProxyRuntime,
create_transparent_proxy_client,
create_transparent_proxy_server,
create_proxy_client,
create_proxy_server,
)
__all__ = [
"ProxyRuntime",
"register_proxy_admin_tools",
"create_transparent_proxy_client",
"create_transparent_proxy_server",
"create_proxy_client",
"create_proxy_server",
]
+13 -13
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from pathlib import Path
from collections.abc import Callable
from pathlib import Path
from typing import Any
from fastmcp import FastMCP
@@ -13,18 +13,18 @@ from fastmcp.server.transforms.search import BM25SearchTransform
from ..control import BrokerConfigManager, ConfigMutationError
from ..events import EventBus
from ..models import BrokerConfig
from ..proxy_validation import validate_proxy_config
from ..shared.names import ADMIN_NAMESPACE
from ..proxy_validation import validate_transparent_proxy_config
from .admin import register_proxy_admin_tools
from .mounts import ProxyMountRegistry, create_proxy_mount
from .reload_events import ProxyReloadResult, reload_change_events
from .safe_names import SafeToolNames
from .tools import (
ProxyToolPayload,
collect_proxy_tools,
filter_proxy_tools,
proxy_tools_page,
)
from .reload_events import ProxyReloadResult, reload_change_events
from .safe_names import SafeToolNames
_SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
# Stable discovery/control spine.
@@ -64,8 +64,8 @@ _SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
class ProxyRuntime:
"""Mount configured upstream MCP connections into one FastMCP server.
The `transparent_proxy` package name is compatibility history. This runtime
is now the shared proxy mounting engine used by the public server surface.
This runtime is the shared proxy mounting engine used by the public server
surface. It owns mounted upstream providers, not workflow execution.
"""
def __init__(
@@ -84,9 +84,9 @@ class ProxyRuntime:
self.config = config
self.manager = None if config_path is None else BrokerConfigManager(config_path)
self.server: FastMCP[Any] = FastMCP(
"wf-mcp-transparent-proxy",
"wf-mcp-proxy",
instructions=(
"Transparent MCP proxy over configured upstream MCP connections. "
"MCP proxy over configured upstream MCP connections. "
"Upstream tools, resources, and prompts are exposed as first-class "
"broker capabilities with connection-qualified names."
),
@@ -129,7 +129,7 @@ class ProxyRuntime:
def reload(self) -> dict[str, Any]:
config = self.current_config()
validate_transparent_proxy_config(config)
validate_proxy_config(config)
if self.on_reload is not None:
self.on_reload(config)
self.server.providers[:] = [self.server.local_provider]
@@ -219,7 +219,7 @@ class ProxyRuntime:
}
def create_transparent_proxy_server(
def create_proxy_server(
config: BrokerConfig,
*,
config_path: str | Path | None = None,
@@ -230,7 +230,7 @@ def create_transparent_proxy_server(
admin_tools: bool = True,
event_bus: EventBus | None = None,
) -> FastMCP[Any]:
validate_transparent_proxy_config(
validate_proxy_config(
config,
resources_as_tools=resources_as_tools,
prompts_as_tools=prompts_as_tools,
@@ -247,7 +247,7 @@ def create_transparent_proxy_server(
).server
def create_transparent_proxy_client(
def create_proxy_client(
config: BrokerConfig,
*,
config_path: str | Path | None = None,
@@ -260,7 +260,7 @@ def create_transparent_proxy_client(
) -> Client[FastMCPTransport]:
return Client(
FastMCPTransport(
create_transparent_proxy_server(
create_proxy_server(
config,
config_path=config_path,
resources_as_tools=resources_as_tools,
+2 -3
View File
@@ -5,8 +5,7 @@ from typing import Any
from fastmcp.mcp_config import MCPConfig
from .models import BrokerConfig, ConnectionConfig
from .proxy_validation import validate_transparent_proxy_config
from .proxy_validation import validate_proxy_config
def connection_to_fastmcp_server_config(
connection: ConnectionConfig,
@@ -37,7 +36,7 @@ def connection_to_fastmcp_server_config(
def broker_config_to_fastmcp_config(config: BrokerConfig) -> MCPConfig:
"""Convert broker config into FastMCP's multi-server config object."""
validate_transparent_proxy_config(config)
validate_proxy_config(config)
return MCPConfig.from_dict(
{
"mcpServers": {
+1 -1
View File
@@ -14,7 +14,7 @@ class ProxyConfigError(ValueError):
"""Raised when a broker config cannot safely run as a transparent proxy."""
def validate_transparent_proxy_config(
def validate_proxy_config(
config: BrokerConfig,
*,
resources_as_tools: bool = False,
+1 -1
View File
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import Any, Literal, dataclass_transform
from typing import Any, Literal
from pydantic import BaseModel
+2 -2
View File
@@ -10,7 +10,7 @@ def test_root_facade_exports_core_entrypoints() -> None:
FileStore,
McpSdkAdapter,
WfMcpService,
create_transparent_proxy_client,
create_proxy_client,
load_broker_config,
)
@@ -21,7 +21,7 @@ def test_root_facade_exports_core_entrypoints() -> None:
assert FileStore.__name__ == "FileStore"
assert McpSdkAdapter.__name__ == "McpSdkAdapter"
assert WfMcpService.__name__ == "WfMcpService"
assert callable(create_transparent_proxy_client)
assert callable(create_proxy_client)
assert callable(load_broker_config)
+2 -2
View File
@@ -9,7 +9,7 @@ from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
from wf_mcp.models import BrokerConfig, ConnectionConfig
from wf_mcp.proxy import create_transparent_proxy_client
from wf_mcp.proxy import create_proxy_client
from .test_support import fixture_server_path, local_temp_root
@@ -58,7 +58,7 @@ def test_unified_proxy_initialize_capabilities_reflect_local_surface() -> None:
)
async def inspect_capabilities() -> mcp_types.ServerCapabilities:
client = create_transparent_proxy_client(config)
client = create_proxy_client(config)
async with client:
initialize_result = client.initialize_result
assert initialize_result is not None
+2 -3
View File
@@ -10,11 +10,10 @@ from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
from wf_mcp.models import BrokerConfig, ConnectionConfig
from wf_mcp.proxy import create_transparent_proxy_client
from wf_mcp.proxy import create_proxy_client
from .test_support import fixture_server_path, local_temp_root
NotificationProbe = Callable[
[Callable[[mcp_types.ServerNotification], None]],
Awaitable[None],
@@ -93,7 +92,7 @@ def test_transparent_proxy_does_not_relay_upstream_protocol_notifications_yet()
if isinstance(message, mcp_types.ServerNotification):
record(message)
client = create_transparent_proxy_client(config)
client = create_proxy_client(config)
client._session_kwargs["message_handler"] = message_handler
async with client:
await client.call_tool("fixture.personal.emit_notifications_tool")
@@ -8,16 +8,16 @@ from typing import Any
import mcp.types as mcp_types
import pytest
from wf_mcp.broker import load_broker_config
from wf_mcp.events import EventBus, InMemoryEventSink
from wf_mcp.models import BrokerConfig, ConnectionConfig
from wf_mcp.proxy_validation import ProxyConfigError, validate_transparent_proxy_config
from wf_mcp.proxy import ProxyRuntime, create_transparent_proxy_client
from wf_mcp.proxy import ProxyRuntime, create_proxy_client
from wf_mcp.proxy.reload_events import (
ProxyReloadResult,
reload_change_events,
)
from wf_mcp.proxy.tools import ProxyToolPayload, ProxyToolsPage
from wf_mcp.broker import load_broker_config
from wf_mcp.proxy_validation import ProxyConfigError, validate_proxy_config
from .test_support import fixture_server_path, local_temp_root
@@ -28,9 +28,9 @@ def _structured(result: Any) -> dict[str, Any]:
return content
def test_transparent_proxy_lists_and_calls_upstream_tools() -> None:
def test_proxy_lists_and_calls_upstream_tools() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_store",
store_root=local_temp_root() / "proxy_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -46,7 +46,7 @@ def test_transparent_proxy_lists_and_calls_upstream_tools() -> None:
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config)
client = create_proxy_client(config)
async with client:
tools = await client.list_tools()
names = [tool.name for tool in tools]
@@ -106,9 +106,9 @@ def test_transparent_proxy_lists_and_calls_upstream_tools() -> None:
asyncio.run(run_proxy())
def test_transparent_proxy_registers_admin_tools_on_local_provider() -> None:
def test_proxy_registers_admin_tools_on_local_provider() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_local_admin_store",
store_root=local_temp_root() / "proxy_local_admin_store",
connections=[],
)
@@ -121,9 +121,9 @@ def test_transparent_proxy_registers_admin_tools_on_local_provider() -> None:
assert "wf.admin.reload_config" in names
def test_transparent_proxy_rewrites_resource_links_returned_by_tools() -> None:
def test_proxy_rewrites_resource_links_returned_by_tools() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_resource_link_store",
store_root=local_temp_root() / "proxy_resource_link_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -139,7 +139,7 @@ def test_transparent_proxy_rewrites_resource_links_returned_by_tools() -> None:
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config)
client = create_proxy_client(config)
async with client:
result = await client.call_tool("fixture.personal.resource_link_tool")
link = result.content[0]
@@ -153,9 +153,9 @@ def test_transparent_proxy_rewrites_resource_links_returned_by_tools() -> None:
asyncio.run(run_proxy())
def test_transparent_proxy_rejects_invalid_connection_config() -> None:
def test_proxy_rejects_invalid_connection_config() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_invalid_store",
store_root=local_temp_root() / "proxy_invalid_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -197,7 +197,7 @@ def test_transparent_proxy_rejects_invalid_connection_config() -> None:
)
with pytest.raises(ProxyConfigError) as exc_info:
validate_transparent_proxy_config(config)
validate_proxy_config(config)
message = str(exc_info.value)
assert "duplicate connection id 'fixture.personal'" in message
@@ -208,9 +208,9 @@ def test_transparent_proxy_rejects_invalid_connection_config() -> None:
assert "connection id 'wf.admin' is reserved by wf-mcp" in message
def test_transparent_proxy_can_expose_resources_and_prompts_as_tools() -> None:
def test_proxy_can_expose_resources_and_prompts_as_tools() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_helper_store",
store_root=local_temp_root() / "proxy_helper_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -226,7 +226,7 @@ def test_transparent_proxy_can_expose_resources_and_prompts_as_tools() -> None:
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(
client = create_proxy_client(
config,
resources_as_tools=True,
prompts_as_tools=True,
@@ -242,9 +242,9 @@ def test_transparent_proxy_can_expose_resources_and_prompts_as_tools() -> None:
asyncio.run(run_proxy())
def test_transparent_proxy_can_collapse_upstream_tools_behind_search() -> None:
def test_proxy_can_collapse_upstream_tools_behind_search() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_search_store",
store_root=local_temp_root() / "proxy_search_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -260,7 +260,7 @@ def test_transparent_proxy_can_collapse_upstream_tools_behind_search() -> None:
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, search_tools=True)
client = create_proxy_client(config, search_tools=True)
async with client:
tools = await client.list_tools()
names = [tool.name for tool in tools]
@@ -279,9 +279,9 @@ def test_transparent_proxy_can_collapse_upstream_tools_behind_search() -> None:
asyncio.run(run_proxy())
def test_transparent_proxy_admin_inventory_ignores_search_visibility() -> None:
def test_proxy_admin_inventory_ignores_search_visibility() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_admin_inventory_store",
store_root=local_temp_root() / "proxy_admin_inventory_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -297,7 +297,7 @@ def test_transparent_proxy_admin_inventory_ignores_search_visibility() -> None:
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, search_tools=True)
client = create_proxy_client(config, search_tools=True)
async with client:
visible_names = [tool.name for tool in await client.list_tools()]
assert "fixture.personal.echo_tool" not in visible_names
@@ -318,9 +318,9 @@ def test_transparent_proxy_admin_inventory_ignores_search_visibility() -> None:
asyncio.run(run_proxy())
def test_transparent_proxy_proxy_tool_listing_supports_filters_and_cursor() -> None:
def test_proxy_proxy_tool_listing_supports_filters_and_cursor() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_paged_tools_store",
store_root=local_temp_root() / "proxy_paged_tools_store",
connections=[
ConnectionConfig(
id="fixture.personal",
@@ -346,7 +346,7 @@ def test_transparent_proxy_proxy_tool_listing_supports_filters_and_cursor() -> N
)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config)
client = create_proxy_client(config)
async with client:
first_page_result = await client.call_tool(
"wf.admin.list_proxy_tools",
@@ -384,8 +384,8 @@ def test_transparent_proxy_proxy_tool_listing_supports_filters_and_cursor() -> N
asyncio.run(run_proxy())
def test_transparent_proxy_admin_tools_mutate_config_file() -> None:
tmp_path = local_temp_root() / "transparent_proxy_admin_store"
def test_proxy_admin_tools_mutate_config_file() -> None:
tmp_path = local_temp_root() / "proxy_admin_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
@@ -407,7 +407,7 @@ def test_transparent_proxy_admin_tools_mutate_config_file() -> None:
config = load_broker_config(config_path)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
client = create_proxy_client(config, config_path=config_path)
async with client:
add_result = await client.call_tool(
"wf.admin.add_connection",
@@ -481,8 +481,8 @@ def test_transparent_proxy_admin_tools_mutate_config_file() -> None:
]
def test_transparent_proxy_admin_reload_remounts_connections() -> None:
tmp_path = local_temp_root() / "transparent_proxy_reload_store"
def test_proxy_admin_reload_remounts_connections() -> None:
tmp_path = local_temp_root() / "proxy_reload_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
@@ -497,7 +497,7 @@ def test_transparent_proxy_admin_reload_remounts_connections() -> None:
config = load_broker_config(config_path)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
client = create_proxy_client(config, config_path=config_path)
async with client:
initial_tools = await client.list_tools()
initial_names = [tool.name for tool in initial_tools]
@@ -543,8 +543,8 @@ def test_transparent_proxy_admin_reload_remounts_connections() -> None:
asyncio.run(run_proxy())
def test_transparent_proxy_admin_reload_sends_list_changed_notifications() -> None:
tmp_path = local_temp_root() / "transparent_proxy_reload_notification_store"
def test_proxy_admin_reload_sends_list_changed_notifications() -> None:
tmp_path = local_temp_root() / "proxy_reload_notification_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
@@ -564,7 +564,7 @@ def test_transparent_proxy_admin_reload_sends_list_changed_notifications() -> No
notifications.append(message)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
client = create_proxy_client(config, config_path=config_path)
client._session_kwargs["message_handler"] = message_handler
async with client:
await client.call_tool("wf.admin.reload_config")
@@ -577,8 +577,8 @@ def test_transparent_proxy_admin_reload_sends_list_changed_notifications() -> No
assert "notifications/prompts/list_changed" in methods
def test_transparent_proxy_config_mutation_does_not_notify_before_reload() -> None:
tmp_path = local_temp_root() / "transparent_proxy_staged_notification_store"
def test_proxy_config_mutation_does_not_notify_before_reload() -> None:
tmp_path = local_temp_root() / "proxy_staged_notification_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
@@ -598,7 +598,7 @@ def test_transparent_proxy_config_mutation_does_not_notify_before_reload() -> No
notifications.append(message)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
client = create_proxy_client(config, config_path=config_path)
client._session_kwargs["message_handler"] = message_handler
async with client:
add_result = await client.call_tool(
@@ -627,11 +627,11 @@ def test_transparent_proxy_config_mutation_does_not_notify_before_reload() -> No
assert "notifications/prompts/list_changed" in methods
def test_transparent_proxy_runtime_reload_publishes_local_change_events() -> None:
def test_proxy_runtime_reload_publishes_local_change_events() -> None:
sink = InMemoryEventSink()
event_bus = EventBus(sink)
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_event_store",
store_root=local_temp_root() / "proxy_event_store",
connections=[],
)
runtime = ProxyRuntime(config, event_bus=event_bus)
@@ -649,9 +649,9 @@ def test_transparent_proxy_runtime_reload_publishes_local_change_events() -> Non
assert catalog_changed[0].payload["reason"] == "transparent_reload"
def test_transparent_proxy_runtime_reload_reuses_unchanged_mounts() -> None:
def test_proxy_runtime_reload_reuses_unchanged_mounts() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_reuse_store",
store_root=local_temp_root() / "proxy_reuse_store",
connections=[
ConnectionConfig(
id="fixture.personal",