admin subserver survives reloading

This commit is contained in:
lda
2026-05-17 00:06:56 +07:00 Verified
parent dbf7c0b2a9
commit b5c896b77b
5 changed files with 51 additions and 34 deletions
+3 -2
View File
@@ -64,8 +64,9 @@ Unified mode currently reuses `ProxyRuntime` as its proxy mounting engine. The
the place where configured upstream MCP connections become mounted FastMCP
providers. `TransparentProxyRuntime` remains a compatibility alias.
`ProxyRuntime` now owns a small `ProxyMountRegistry`. Reload still clears the
visible mounted provider list and rebuilds it from current config, but unchanged
`ProxyRuntime` now owns a small `ProxyMountRegistry`. Proxy/admin tools are
registered once on the top-level local provider; reload only clears the visible
mounted upstream provider list and rebuilds it from current config. Unchanged
enabled connections reuse their cached proxy mount instead of recreating a new
client/proxy pair every time. Disabled or removed connections are no longer
mounted after reload, but their cached mounts are only *retired* internally for
+2 -2
View File
@@ -1,4 +1,4 @@
from .admin import create_proxy_admin_server
from .admin import register_proxy_admin_tools
from .runtime import (
ProxyRuntime,
TransparentProxyRuntime,
@@ -9,7 +9,7 @@ from .runtime import (
__all__ = [
"ProxyRuntime",
"TransparentProxyRuntime",
"create_proxy_admin_server",
"register_proxy_admin_tools",
"create_transparent_proxy_client",
"create_transparent_proxy_server",
]
+27 -23
View File
@@ -9,49 +9,49 @@ from ..notifications import FastMcpContextNotificationSink
from .reload_events import ProxyReloadResult, reload_change_events
def create_proxy_admin_server(
def register_proxy_admin_tools(
server: FastMCP[Any],
runtime: ProxyAdminRuntime,
) -> FastMCP[Any]:
"""Create the admin MCP server mounted under the broker namespace."""
admin = FastMCP(
"wf-mcp-admin",
instructions="Administrative tools for this wf-mcp proxy instance.",
)
) -> None:
"""Register proxy-runtime admin tools directly on the local provider."""
handlers = TransparentAdminHandlers(runtime)
@admin.tool(
@server.tool(
name="wf.admin.list_connections",
title="List Connections",
description="List configured MCP connections known to this proxy instance.",
)
async def list_connections() -> list[dict[str, Any]]:
return handlers.list_connections()
@admin.tool(
@server.tool(
name="wf.admin.get_connection_statuses",
title="Get Connection Statuses",
description="Show configured MCP connection status and basic catalog counts.",
)
async def get_connection_statuses() -> list[dict[str, Any]]:
return handlers.get_connection_statuses()
@admin.tool(
@server.tool(
name="wf.admin.get_config",
title="Get Config",
description="Return the current proxy configuration payload.",
)
async def get_config() -> dict[str, Any]:
return handlers.get_config()
@admin.tool(
@server.tool(
name="wf.admin.reload_config",
title="Reload Config",
description=(
"Reload the config file and remount enabled upstream MCP connections."
),
description="Reload the config file and remount enabled upstream MCP connections.",
)
async def reload_config(ctx: Context) -> dict[str, Any]:
result = handlers.reload_config()
await _send_reload_notifications(ctx, ProxyReloadResult.from_payload(result))
return result
@admin.tool(
@server.tool(
name="wf.admin.list_proxy_tools",
title="List Proxy Tools",
description=(
"List upstream tools projected through this proxy, with optional "
@@ -71,14 +71,16 @@ def create_proxy_admin_server(
cursor=cursor,
)
@admin.tool(
@server.tool(
name="wf.admin.get_proxy_tool",
title="Get Proxy Tool",
description="Return admin metadata and schema for one projected proxy tool.",
)
async def get_proxy_tool(proxy_name: str) -> dict[str, Any]:
return await handlers.get_proxy_tool(proxy_name)
@admin.tool(
@server.tool(
name="wf.admin.add_connection",
title="Add Connection",
description="Add a new MCP connection to the file-backed proxy config.",
)
@@ -97,7 +99,8 @@ def create_proxy_admin_server(
enabled=enabled,
)
@admin.tool(
@server.tool(
name="wf.admin.update_connection",
title="Update Connection",
description="Update server, account, metadata, or enabled state for a connection.",
)
@@ -116,29 +119,30 @@ def create_proxy_admin_server(
enabled=enabled,
)
@admin.tool(
@server.tool(
name="wf.admin.enable_connection",
title="Enable Connection",
description="Mark a configured MCP connection as enabled.",
)
async def enable_connection(connection_id: str) -> dict[str, Any]:
return handlers.enable_connection(connection_id)
@admin.tool(
@server.tool(
name="wf.admin.disable_connection",
title="Disable Connection",
description="Mark a configured MCP connection as disabled.",
)
async def disable_connection(connection_id: str) -> dict[str, Any]:
return handlers.disable_connection(connection_id)
@admin.tool(
@server.tool(
name="wf.admin.remove_connection",
title="Remove Connection",
description="Remove a configured MCP connection from the file-backed config.",
)
async def remove_connection(connection_id: str) -> dict[str, Any]:
return handlers.remove_connection(connection_id)
return admin
async def _send_reload_notifications(ctx: Context, result: ProxyReloadResult) -> None:
"""Notify the current client that reload may have changed visible capabilities."""
+4 -7
View File
@@ -12,9 +12,9 @@ from fastmcp.server.transforms.search import BM25SearchTransform
from ..control import BrokerConfigManager, ConfigMutationError
from ..events import EventBus
from ..models import BrokerConfig
from ..shared.names import ADMIN_NAMESPACE, LdaNamespace
from ..shared.names import ADMIN_NAMESPACE
from ..proxy_validation import validate_transparent_proxy_config
from .admin import create_proxy_admin_server
from .admin import register_proxy_admin_tools
from .mounts import ProxyMountRegistry, create_proxy_mount
from .tools import (
ProxyToolPayload,
@@ -72,6 +72,8 @@ class ProxyRuntime:
self.mounts: ProxyMountRegistry[FastMCP[Any]] = ProxyMountRegistry(
create_proxy_mount
)
if self.admin_tools:
register_proxy_admin_tools(self.server, self)
self.reload()
if resources_as_tools:
self.server.add_transform(ResourcesAsTools(self.server))
@@ -100,11 +102,6 @@ class ProxyRuntime:
validate_transparent_proxy_config(config)
self.server.providers[:] = [self.server.local_provider]
if self.admin_tools:
admin = create_proxy_admin_server(self)
admin.add_transform(LdaNamespace(ADMIN_NAMESPACE))
self.server.mount(admin)
mounts = self.mounts.active_mounts_for(config)
for mount in mounts:
self.server.mount(mount.proxy)
+15
View File
@@ -106,6 +106,21 @@ 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:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_local_admin_store",
connections=[],
)
runtime = ProxyRuntime(config)
assert len(runtime.server.providers) == 1
tools = asyncio.run(runtime.server.local_provider.list_tools())
names = {tool.name for tool in tools}
assert "wf.admin.list_connections" in names
assert "wf.admin.reload_config" in names
def test_transparent_proxy_rewrites_resource_links_returned_by_tools() -> None:
config = BrokerConfig(
store_root=local_temp_root() / "transparent_proxy_resource_link_store",