This commit is contained in:
lda
2026-05-13 20:59:33 +07:00 Verified
parent 92f5ca1755
commit 11afed99ab
6 changed files with 71 additions and 9 deletions
+2 -2
View File
@@ -59,10 +59,10 @@ 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.
Unified mode currently reuses this runtime as its proxy mounting engine. The Unified mode currently reuses `ProxyRuntime` as its proxy mounting engine. The
`transparent_proxy` package name is therefore partly legacy: the code is still `transparent_proxy` package name is therefore partly legacy: the code is still
the place where configured upstream MCP connections become mounted FastMCP the place where configured upstream MCP connections become mounted FastMCP
providers. providers. `TransparentProxyRuntime` remains a compatibility alias.
After a successful reload, the runtime publishes local `tools_changed`, After a successful reload, the runtime publishes local `tools_changed`,
`resources_changed`, `prompts_changed`, and `catalog_changed` events when an `resources_changed`, `prompts_changed`, and `catalog_changed` events when an
+2 -2
View File
@@ -10,7 +10,7 @@ from fastmcp.client.transports.memory import FastMCPTransport
from ..broker.config import build_service_from_config from ..broker.config import build_service_from_config
from ..broker.transport import normalize_transport from ..broker.transport import normalize_transport
from ..models import BrokerConfig from ..models import BrokerConfig
from ..transparent_proxy.runtime import TransparentProxyRuntime from ..transparent_proxy.runtime import ProxyRuntime
from ..workflow_surface import WorkflowSurfaceHandlers from ..workflow_surface import WorkflowSurfaceHandlers
@@ -25,7 +25,7 @@ def create_unified_proxy_server(
) -> FastMCP[Any]: ) -> FastMCP[Any]:
"""Create one MCP server with upstream proxy, admin, and workflow tools.""" """Create one MCP server with upstream proxy, admin, and workflow tools."""
service = build_service_from_config(config) service = build_service_from_config(config)
runtime = TransparentProxyRuntime( runtime = ProxyRuntime(
config, config,
config_path=config_path, config_path=config_path,
resources_as_tools=resources_as_tools, resources_as_tools=resources_as_tools,
+2
View File
@@ -1,11 +1,13 @@
from .admin import create_proxy_admin_server from .admin import create_proxy_admin_server
from .runtime import ( from .runtime import (
ProxyRuntime,
TransparentProxyRuntime, TransparentProxyRuntime,
create_transparent_proxy_client, create_transparent_proxy_client,
create_transparent_proxy_server, create_transparent_proxy_server,
) )
__all__ = [ __all__ = [
"ProxyRuntime",
"TransparentProxyRuntime", "TransparentProxyRuntime",
"create_proxy_admin_server", "create_proxy_admin_server",
"create_transparent_proxy_client", "create_transparent_proxy_client",
+11 -3
View File
@@ -40,7 +40,13 @@ _ADMIN_TOOL_NAMES = [
] ]
class TransparentProxyRuntime: 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 unified mode too.
"""
def __init__( def __init__(
self, self,
config: BrokerConfig, config: BrokerConfig,
@@ -176,6 +182,9 @@ class TransparentProxyRuntime:
raise KeyError(proxy_name) raise KeyError(proxy_name)
TransparentProxyRuntime = ProxyRuntime
def create_transparent_proxy_server( def create_transparent_proxy_server(
config: BrokerConfig, config: BrokerConfig,
*, *,
@@ -191,7 +200,7 @@ def create_transparent_proxy_server(
resources_as_tools=resources_as_tools, resources_as_tools=resources_as_tools,
prompts_as_tools=prompts_as_tools, prompts_as_tools=prompts_as_tools,
) )
return TransparentProxyRuntime( return ProxyRuntime(
config, config,
config_path=config_path, config_path=config_path,
resources_as_tools=resources_as_tools, resources_as_tools=resources_as_tools,
@@ -225,4 +234,3 @@ def create_transparent_proxy_client(
) )
) )
) )
+2
View File
@@ -29,8 +29,10 @@ def test_concern_package_imports_resolve() -> None:
from wf_mcp.broker import WfMcpService, load_broker_config from wf_mcp.broker import WfMcpService, load_broker_config
from wf_mcp.sdk import McpSdkAdapter from wf_mcp.sdk import McpSdkAdapter
from wf_mcp.storage import FileStore from wf_mcp.storage import FileStore
from wf_mcp.transparent_proxy import ProxyRuntime, TransparentProxyRuntime
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(load_broker_config) assert callable(load_broker_config)
assert TransparentProxyRuntime is ProxyRuntime
+52 -2
View File
@@ -11,7 +11,7 @@ import pytest
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_validation import ProxyConfigError, validate_transparent_proxy_config
from wf_mcp.transparent_proxy import TransparentProxyRuntime, create_transparent_proxy_client from wf_mcp.transparent_proxy import ProxyRuntime, create_transparent_proxy_client
from wf_mcp.broker import load_broker_config from wf_mcp.broker import load_broker_config
from .test_support import fixture_server_path, local_temp_root from .test_support import fixture_server_path, local_temp_root
@@ -484,6 +484,56 @@ 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:
tmp_path = local_temp_root() / "transparent_proxy_staged_notification_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": ".wf_mcp_store",
"connections": [],
}
),
encoding="utf-8",
)
config = load_broker_config(config_path)
notifications: list[mcp_types.ServerNotification] = []
async def message_handler(message: object) -> None:
if isinstance(message, mcp_types.ServerNotification):
notifications.append(message)
async def run_proxy() -> None:
client = create_transparent_proxy_client(config, config_path=config_path)
client._session_kwargs["message_handler"] = message_handler
async with client:
add_result = await client.call_tool(
"wf.admin.add_connection",
{
"connection_id": "fixture.personal",
"server": "fixture",
"account": "personal",
"metadata": {
"transport": "stdio",
"command": sys.executable,
"args": [fixture_server_path()],
},
},
)
assert _structured(add_result)["requires_reload"] is True
assert notifications == []
await client.call_tool("wf.admin.reload_config")
asyncio.run(run_proxy())
methods = [notification.root.method for notification in notifications]
assert "notifications/tools/list_changed" in methods
assert "notifications/resources/list_changed" in methods
assert "notifications/prompts/list_changed" in methods
def test_transparent_proxy_runtime_reload_publishes_local_change_events() -> None: def test_transparent_proxy_runtime_reload_publishes_local_change_events() -> None:
sink = InMemoryEventSink() sink = InMemoryEventSink()
event_bus = EventBus(sink) event_bus = EventBus(sink)
@@ -491,7 +541,7 @@ def test_transparent_proxy_runtime_reload_publishes_local_change_events() -> Non
store_root=local_temp_root() / "transparent_proxy_event_store", store_root=local_temp_root() / "transparent_proxy_event_store",
connections=[], connections=[],
) )
runtime = TransparentProxyRuntime(config, event_bus=event_bus) runtime = ProxyRuntime(config, event_bus=event_bus)
initial_event_count = len(sink.list_events()) initial_event_count = len(sink.list_events())
result = runtime.reload() result = runtime.reload()