rename
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user