admin subserver survives reloading
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user