rename fns/test files
This commit is contained in:
+2
-2
@@ -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_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_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
|
## 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
|
- `tests/authoring`: builder, node decorator, ops, async runtime, subgraph, and
|
||||||
demo workflow comparisons.
|
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.
|
naming behavior.
|
||||||
- `tests/rewrite`: local rewrite/port experiments that should keep exercising
|
- `tests/rewrite`: local rewrite/port experiments that should keep exercising
|
||||||
real user ergonomics.
|
real user ergonomics.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ with [`wf_mcp_operator_manual.md`](wf_mcp_operator_manual.md).
|
|||||||
|
|
||||||
| Package | Responsibility |
|
| 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.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.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. |
|
| `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
|
## Dependency Rules
|
||||||
|
|
||||||
- `wf_mcp.sdk` should not import `wf_core` or `wf_authoring`.
|
- `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.workflow` is the only layer that converts MCP capabilities into node specs.
|
||||||
- `wf_mcp.broker` may coordinate `sdk`, `storage`, `control`, and `workflow`.
|
- `wf_mcp.broker` may coordinate `sdk`, `storage`, `control`, and `workflow`.
|
||||||
- `wf_mcp.control` should not know about live MCP clients or workflow execution.
|
- `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
|
## Hot Reload
|
||||||
|
|
||||||
Transparent proxy reload is intentionally isolated in
|
Proxy reload is intentionally isolated in
|
||||||
`wf_mcp.transparent_proxy.runtime`. FastMCP does not currently expose a complete
|
`wf_mcp.proxy.runtime`. FastMCP does not currently expose a complete
|
||||||
provider/proxy unmount lifecycle that we can rely on for safe per-connection
|
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,
|
teardown. Until that exists, reload should be treated as best-effort remounting,
|
||||||
not a fully safe session/subscription lifecycle.
|
not a fully safe session/subscription lifecycle.
|
||||||
@@ -114,7 +114,7 @@ gone.
|
|||||||
|
|
||||||
If this becomes multiple distributions, likely split points are:
|
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-broker`: `broker`, `storage`, `workflow`, `shared`
|
||||||
- `wf-mcp-sdk`: `sdk`, `capabilities`, `models`, `shared`
|
- `wf-mcp-sdk`: `sdk`, `capabilities`, `models`, `shared`
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from .broker import (
|
from .broker import (
|
||||||
load_broker_config,
|
|
||||||
WfMcpService,
|
WfMcpService,
|
||||||
|
load_broker_config,
|
||||||
)
|
)
|
||||||
from .capabilities import (
|
from .capabilities import (
|
||||||
DiscoveredPrompt,
|
DiscoveredPrompt,
|
||||||
@@ -13,13 +13,13 @@ from .models import (
|
|||||||
ConnectionConfig,
|
ConnectionConfig,
|
||||||
RawWorkflowPlan,
|
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 .sdk import McpSdkAdapter
|
||||||
from .storage import FileStore, Store
|
from .storage import FileStore, Store
|
||||||
from .proxy import (
|
|
||||||
create_transparent_proxy_client,
|
|
||||||
create_transparent_proxy_server,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AuthRecord",
|
"AuthRecord",
|
||||||
@@ -34,8 +34,8 @@ __all__ = [
|
|||||||
"RawWorkflowPlan",
|
"RawWorkflowPlan",
|
||||||
"Store",
|
"Store",
|
||||||
"WfMcpService",
|
"WfMcpService",
|
||||||
"create_transparent_proxy_client",
|
"create_proxy_client",
|
||||||
"create_transparent_proxy_server",
|
"create_proxy_server",
|
||||||
"load_broker_config",
|
"load_broker_config",
|
||||||
"validate_transparent_proxy_config",
|
"validate_proxy_config",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from .admin import register_proxy_admin_tools
|
from .admin import register_proxy_admin_tools
|
||||||
from .runtime import (
|
from .runtime import (
|
||||||
ProxyRuntime,
|
ProxyRuntime,
|
||||||
create_transparent_proxy_client,
|
create_proxy_client,
|
||||||
create_transparent_proxy_server,
|
create_proxy_server,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ProxyRuntime",
|
"ProxyRuntime",
|
||||||
"register_proxy_admin_tools",
|
"register_proxy_admin_tools",
|
||||||
"create_transparent_proxy_client",
|
"create_proxy_client",
|
||||||
"create_transparent_proxy_server",
|
"create_proxy_server",
|
||||||
]
|
]
|
||||||
|
|||||||
+13
-13
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
@@ -13,18 +13,18 @@ from fastmcp.server.transforms.search import BM25SearchTransform
|
|||||||
from ..control import BrokerConfigManager, ConfigMutationError
|
from ..control import BrokerConfigManager, ConfigMutationError
|
||||||
from ..events import EventBus
|
from ..events import EventBus
|
||||||
from ..models import BrokerConfig
|
from ..models import BrokerConfig
|
||||||
|
from ..proxy_validation import validate_proxy_config
|
||||||
from ..shared.names import ADMIN_NAMESPACE
|
from ..shared.names import ADMIN_NAMESPACE
|
||||||
from ..proxy_validation import validate_transparent_proxy_config
|
|
||||||
from .admin import register_proxy_admin_tools
|
from .admin import register_proxy_admin_tools
|
||||||
from .mounts import ProxyMountRegistry, create_proxy_mount
|
from .mounts import ProxyMountRegistry, create_proxy_mount
|
||||||
|
from .reload_events import ProxyReloadResult, reload_change_events
|
||||||
|
from .safe_names import SafeToolNames
|
||||||
from .tools import (
|
from .tools import (
|
||||||
ProxyToolPayload,
|
ProxyToolPayload,
|
||||||
collect_proxy_tools,
|
collect_proxy_tools,
|
||||||
filter_proxy_tools,
|
filter_proxy_tools,
|
||||||
proxy_tools_page,
|
proxy_tools_page,
|
||||||
)
|
)
|
||||||
from .reload_events import ProxyReloadResult, reload_change_events
|
|
||||||
from .safe_names import SafeToolNames
|
|
||||||
|
|
||||||
_SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
|
_SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
|
||||||
# Stable discovery/control spine.
|
# Stable discovery/control spine.
|
||||||
@@ -64,8 +64,8 @@ _SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
|
|||||||
class ProxyRuntime:
|
class ProxyRuntime:
|
||||||
"""Mount configured upstream MCP connections into one FastMCP server.
|
"""Mount configured upstream MCP connections into one FastMCP server.
|
||||||
|
|
||||||
The `transparent_proxy` package name is compatibility history. This runtime
|
This runtime is the shared proxy mounting engine used by the public server
|
||||||
is now the shared proxy mounting engine used by the public server surface.
|
surface. It owns mounted upstream providers, not workflow execution.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -84,9 +84,9 @@ class ProxyRuntime:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.manager = None if config_path is None else BrokerConfigManager(config_path)
|
self.manager = None if config_path is None else BrokerConfigManager(config_path)
|
||||||
self.server: FastMCP[Any] = FastMCP(
|
self.server: FastMCP[Any] = FastMCP(
|
||||||
"wf-mcp-transparent-proxy",
|
"wf-mcp-proxy",
|
||||||
instructions=(
|
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 "
|
"Upstream tools, resources, and prompts are exposed as first-class "
|
||||||
"broker capabilities with connection-qualified names."
|
"broker capabilities with connection-qualified names."
|
||||||
),
|
),
|
||||||
@@ -129,7 +129,7 @@ class ProxyRuntime:
|
|||||||
|
|
||||||
def reload(self) -> dict[str, Any]:
|
def reload(self) -> dict[str, Any]:
|
||||||
config = self.current_config()
|
config = self.current_config()
|
||||||
validate_transparent_proxy_config(config)
|
validate_proxy_config(config)
|
||||||
if self.on_reload is not None:
|
if self.on_reload is not None:
|
||||||
self.on_reload(config)
|
self.on_reload(config)
|
||||||
self.server.providers[:] = [self.server.local_provider]
|
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: BrokerConfig,
|
||||||
*,
|
*,
|
||||||
config_path: str | Path | None = None,
|
config_path: str | Path | None = None,
|
||||||
@@ -230,7 +230,7 @@ def create_transparent_proxy_server(
|
|||||||
admin_tools: bool = True,
|
admin_tools: bool = True,
|
||||||
event_bus: EventBus | None = None,
|
event_bus: EventBus | None = None,
|
||||||
) -> FastMCP[Any]:
|
) -> FastMCP[Any]:
|
||||||
validate_transparent_proxy_config(
|
validate_proxy_config(
|
||||||
config,
|
config,
|
||||||
resources_as_tools=resources_as_tools,
|
resources_as_tools=resources_as_tools,
|
||||||
prompts_as_tools=prompts_as_tools,
|
prompts_as_tools=prompts_as_tools,
|
||||||
@@ -247,7 +247,7 @@ def create_transparent_proxy_server(
|
|||||||
).server
|
).server
|
||||||
|
|
||||||
|
|
||||||
def create_transparent_proxy_client(
|
def create_proxy_client(
|
||||||
config: BrokerConfig,
|
config: BrokerConfig,
|
||||||
*,
|
*,
|
||||||
config_path: str | Path | None = None,
|
config_path: str | Path | None = None,
|
||||||
@@ -260,7 +260,7 @@ def create_transparent_proxy_client(
|
|||||||
) -> Client[FastMCPTransport]:
|
) -> Client[FastMCPTransport]:
|
||||||
return Client(
|
return Client(
|
||||||
FastMCPTransport(
|
FastMCPTransport(
|
||||||
create_transparent_proxy_server(
|
create_proxy_server(
|
||||||
config,
|
config,
|
||||||
config_path=config_path,
|
config_path=config_path,
|
||||||
resources_as_tools=resources_as_tools,
|
resources_as_tools=resources_as_tools,
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ from typing import Any
|
|||||||
from fastmcp.mcp_config import MCPConfig
|
from fastmcp.mcp_config import MCPConfig
|
||||||
|
|
||||||
from .models import BrokerConfig, ConnectionConfig
|
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(
|
def connection_to_fastmcp_server_config(
|
||||||
connection: ConnectionConfig,
|
connection: ConnectionConfig,
|
||||||
@@ -37,7 +36,7 @@ def connection_to_fastmcp_server_config(
|
|||||||
|
|
||||||
def broker_config_to_fastmcp_config(config: BrokerConfig) -> MCPConfig:
|
def broker_config_to_fastmcp_config(config: BrokerConfig) -> MCPConfig:
|
||||||
"""Convert broker config into FastMCP's multi-server config object."""
|
"""Convert broker config into FastMCP's multi-server config object."""
|
||||||
validate_transparent_proxy_config(config)
|
validate_proxy_config(config)
|
||||||
return MCPConfig.from_dict(
|
return MCPConfig.from_dict(
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ProxyConfigError(ValueError):
|
|||||||
"""Raised when a broker config cannot safely run as a transparent proxy."""
|
"""Raised when a broker config cannot safely run as a transparent proxy."""
|
||||||
|
|
||||||
|
|
||||||
def validate_transparent_proxy_config(
|
def validate_proxy_config(
|
||||||
config: BrokerConfig,
|
config: BrokerConfig,
|
||||||
*,
|
*,
|
||||||
resources_as_tools: bool = False,
|
resources_as_tools: bool = False,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Literal, dataclass_transform
|
from typing import Any, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ def test_root_facade_exports_core_entrypoints() -> None:
|
|||||||
FileStore,
|
FileStore,
|
||||||
McpSdkAdapter,
|
McpSdkAdapter,
|
||||||
WfMcpService,
|
WfMcpService,
|
||||||
create_transparent_proxy_client,
|
create_proxy_client,
|
||||||
load_broker_config,
|
load_broker_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ def test_root_facade_exports_core_entrypoints() -> None:
|
|||||||
assert FileStore.__name__ == "FileStore"
|
assert FileStore.__name__ == "FileStore"
|
||||||
assert McpSdkAdapter.__name__ == "McpSdkAdapter"
|
assert McpSdkAdapter.__name__ == "McpSdkAdapter"
|
||||||
assert WfMcpService.__name__ == "WfMcpService"
|
assert WfMcpService.__name__ == "WfMcpService"
|
||||||
assert callable(create_transparent_proxy_client)
|
assert callable(create_proxy_client)
|
||||||
assert callable(load_broker_config)
|
assert callable(load_broker_config)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from mcp.client.session import ClientSession
|
|||||||
from mcp.client.stdio import StdioServerParameters, stdio_client
|
from mcp.client.stdio import StdioServerParameters, stdio_client
|
||||||
|
|
||||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
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
|
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:
|
async def inspect_capabilities() -> mcp_types.ServerCapabilities:
|
||||||
client = create_transparent_proxy_client(config)
|
client = create_proxy_client(config)
|
||||||
async with client:
|
async with client:
|
||||||
initialize_result = client.initialize_result
|
initialize_result = client.initialize_result
|
||||||
assert initialize_result is not None
|
assert initialize_result is not None
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ from mcp.client.session import ClientSession
|
|||||||
from mcp.client.stdio import StdioServerParameters, stdio_client
|
from mcp.client.stdio import StdioServerParameters, stdio_client
|
||||||
|
|
||||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
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
|
from .test_support import fixture_server_path, local_temp_root
|
||||||
|
|
||||||
|
|
||||||
NotificationProbe = Callable[
|
NotificationProbe = Callable[
|
||||||
[Callable[[mcp_types.ServerNotification], None]],
|
[Callable[[mcp_types.ServerNotification], None]],
|
||||||
Awaitable[None],
|
Awaitable[None],
|
||||||
@@ -93,7 +92,7 @@ def test_transparent_proxy_does_not_relay_upstream_protocol_notifications_yet()
|
|||||||
if isinstance(message, mcp_types.ServerNotification):
|
if isinstance(message, mcp_types.ServerNotification):
|
||||||
record(message)
|
record(message)
|
||||||
|
|
||||||
client = create_transparent_proxy_client(config)
|
client = create_proxy_client(config)
|
||||||
client._session_kwargs["message_handler"] = message_handler
|
client._session_kwargs["message_handler"] = message_handler
|
||||||
async with client:
|
async with client:
|
||||||
await client.call_tool("fixture.personal.emit_notifications_tool")
|
await client.call_tool("fixture.personal.emit_notifications_tool")
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ from typing import Any
|
|||||||
import mcp.types as mcp_types
|
import mcp.types as mcp_types
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from wf_mcp.broker import load_broker_config
|
||||||
from wf_mcp.events import EventBus, InMemoryEventSink
|
from wf_mcp.events import EventBus, InMemoryEventSink
|
||||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
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_proxy_client
|
||||||
from wf_mcp.proxy import ProxyRuntime, create_transparent_proxy_client
|
|
||||||
from wf_mcp.proxy.reload_events import (
|
from wf_mcp.proxy.reload_events import (
|
||||||
ProxyReloadResult,
|
ProxyReloadResult,
|
||||||
reload_change_events,
|
reload_change_events,
|
||||||
)
|
)
|
||||||
from wf_mcp.proxy.tools import ProxyToolPayload, ProxyToolsPage
|
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
|
from .test_support import fixture_server_path, local_temp_root
|
||||||
|
|
||||||
@@ -28,9 +28,9 @@ def _structured(result: Any) -> dict[str, Any]:
|
|||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_proxy_lists_and_calls_upstream_tools() -> None:
|
def test_proxy_lists_and_calls_upstream_tools() -> None:
|
||||||
config = BrokerConfig(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_store",
|
store_root=local_temp_root() / "proxy_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -46,7 +46,7 @@ def test_transparent_proxy_lists_and_calls_upstream_tools() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
async def run_proxy() -> None:
|
||||||
client = create_transparent_proxy_client(config)
|
client = create_proxy_client(config)
|
||||||
async with client:
|
async with client:
|
||||||
tools = await client.list_tools()
|
tools = await client.list_tools()
|
||||||
names = [tool.name for tool in 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())
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_local_admin_store",
|
store_root=local_temp_root() / "proxy_local_admin_store",
|
||||||
connections=[],
|
connections=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,9 +121,9 @@ def test_transparent_proxy_registers_admin_tools_on_local_provider() -> None:
|
|||||||
assert "wf.admin.reload_config" in names
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_resource_link_store",
|
store_root=local_temp_root() / "proxy_resource_link_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -139,7 +139,7 @@ def test_transparent_proxy_rewrites_resource_links_returned_by_tools() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
async def run_proxy() -> None:
|
||||||
client = create_transparent_proxy_client(config)
|
client = create_proxy_client(config)
|
||||||
async with client:
|
async with client:
|
||||||
result = await client.call_tool("fixture.personal.resource_link_tool")
|
result = await client.call_tool("fixture.personal.resource_link_tool")
|
||||||
link = result.content[0]
|
link = result.content[0]
|
||||||
@@ -153,9 +153,9 @@ def test_transparent_proxy_rewrites_resource_links_returned_by_tools() -> None:
|
|||||||
asyncio.run(run_proxy())
|
asyncio.run(run_proxy())
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_proxy_rejects_invalid_connection_config() -> None:
|
def test_proxy_rejects_invalid_connection_config() -> None:
|
||||||
config = BrokerConfig(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_invalid_store",
|
store_root=local_temp_root() / "proxy_invalid_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -197,7 +197,7 @@ def test_transparent_proxy_rejects_invalid_connection_config() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ProxyConfigError) as exc_info:
|
with pytest.raises(ProxyConfigError) as exc_info:
|
||||||
validate_transparent_proxy_config(config)
|
validate_proxy_config(config)
|
||||||
|
|
||||||
message = str(exc_info.value)
|
message = str(exc_info.value)
|
||||||
assert "duplicate connection id 'fixture.personal'" in message
|
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
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_helper_store",
|
store_root=local_temp_root() / "proxy_helper_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -226,7 +226,7 @@ def test_transparent_proxy_can_expose_resources_and_prompts_as_tools() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
async def run_proxy() -> None:
|
||||||
client = create_transparent_proxy_client(
|
client = create_proxy_client(
|
||||||
config,
|
config,
|
||||||
resources_as_tools=True,
|
resources_as_tools=True,
|
||||||
prompts_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())
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_search_store",
|
store_root=local_temp_root() / "proxy_search_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -260,7 +260,7 @@ def test_transparent_proxy_can_collapse_upstream_tools_behind_search() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_proxy() -> 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:
|
async with client:
|
||||||
tools = await client.list_tools()
|
tools = await client.list_tools()
|
||||||
names = [tool.name for tool in 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())
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_admin_inventory_store",
|
store_root=local_temp_root() / "proxy_admin_inventory_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -297,7 +297,7 @@ def test_transparent_proxy_admin_inventory_ignores_search_visibility() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_proxy() -> 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:
|
async with client:
|
||||||
visible_names = [tool.name for tool in await client.list_tools()]
|
visible_names = [tool.name for tool in await client.list_tools()]
|
||||||
assert "fixture.personal.echo_tool" not in visible_names
|
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())
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_paged_tools_store",
|
store_root=local_temp_root() / "proxy_paged_tools_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
@@ -346,7 +346,7 @@ def test_transparent_proxy_proxy_tool_listing_supports_filters_and_cursor() -> N
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
async def run_proxy() -> None:
|
||||||
client = create_transparent_proxy_client(config)
|
client = create_proxy_client(config)
|
||||||
async with client:
|
async with client:
|
||||||
first_page_result = await client.call_tool(
|
first_page_result = await client.call_tool(
|
||||||
"wf.admin.list_proxy_tools",
|
"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())
|
asyncio.run(run_proxy())
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_proxy_admin_tools_mutate_config_file() -> None:
|
def test_proxy_admin_tools_mutate_config_file() -> None:
|
||||||
tmp_path = local_temp_root() / "transparent_proxy_admin_store"
|
tmp_path = local_temp_root() / "proxy_admin_store"
|
||||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
config_path = tmp_path / "wf_mcp.config.json"
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
@@ -407,7 +407,7 @@ def test_transparent_proxy_admin_tools_mutate_config_file() -> None:
|
|||||||
config = load_broker_config(config_path)
|
config = load_broker_config(config_path)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
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:
|
async with client:
|
||||||
add_result = await client.call_tool(
|
add_result = await client.call_tool(
|
||||||
"wf.admin.add_connection",
|
"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:
|
def test_proxy_admin_reload_remounts_connections() -> None:
|
||||||
tmp_path = local_temp_root() / "transparent_proxy_reload_store"
|
tmp_path = local_temp_root() / "proxy_reload_store"
|
||||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
config_path = tmp_path / "wf_mcp.config.json"
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
@@ -497,7 +497,7 @@ def test_transparent_proxy_admin_reload_remounts_connections() -> None:
|
|||||||
config = load_broker_config(config_path)
|
config = load_broker_config(config_path)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
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:
|
async with client:
|
||||||
initial_tools = await client.list_tools()
|
initial_tools = await client.list_tools()
|
||||||
initial_names = [tool.name for tool in initial_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())
|
asyncio.run(run_proxy())
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_proxy_admin_reload_sends_list_changed_notifications() -> None:
|
def test_proxy_admin_reload_sends_list_changed_notifications() -> None:
|
||||||
tmp_path = local_temp_root() / "transparent_proxy_reload_notification_store"
|
tmp_path = local_temp_root() / "proxy_reload_notification_store"
|
||||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
config_path = tmp_path / "wf_mcp.config.json"
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
@@ -564,7 +564,7 @@ def test_transparent_proxy_admin_reload_sends_list_changed_notifications() -> No
|
|||||||
notifications.append(message)
|
notifications.append(message)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
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
|
client._session_kwargs["message_handler"] = message_handler
|
||||||
async with client:
|
async with client:
|
||||||
await client.call_tool("wf.admin.reload_config")
|
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
|
assert "notifications/prompts/list_changed" in methods
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_proxy_config_mutation_does_not_notify_before_reload() -> None:
|
def test_proxy_config_mutation_does_not_notify_before_reload() -> None:
|
||||||
tmp_path = local_temp_root() / "transparent_proxy_staged_notification_store"
|
tmp_path = local_temp_root() / "proxy_staged_notification_store"
|
||||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||||
config_path = tmp_path / "wf_mcp.config.json"
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
@@ -598,7 +598,7 @@ def test_transparent_proxy_config_mutation_does_not_notify_before_reload() -> No
|
|||||||
notifications.append(message)
|
notifications.append(message)
|
||||||
|
|
||||||
async def run_proxy() -> None:
|
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
|
client._session_kwargs["message_handler"] = message_handler
|
||||||
async with client:
|
async with client:
|
||||||
add_result = await client.call_tool(
|
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
|
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()
|
sink = InMemoryEventSink()
|
||||||
event_bus = EventBus(sink)
|
event_bus = EventBus(sink)
|
||||||
config = BrokerConfig(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_event_store",
|
store_root=local_temp_root() / "proxy_event_store",
|
||||||
connections=[],
|
connections=[],
|
||||||
)
|
)
|
||||||
runtime = ProxyRuntime(config, event_bus=event_bus)
|
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"
|
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(
|
config = BrokerConfig(
|
||||||
store_root=local_temp_root() / "transparent_proxy_reuse_store",
|
store_root=local_temp_root() / "proxy_reuse_store",
|
||||||
connections=[
|
connections=[
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
id="fixture.personal",
|
id="fixture.personal",
|
||||||
Reference in New Issue
Block a user