transparent mode uses new, builtin technology

This commit is contained in:
lda
2026-04-30 05:09:30 +07:00 Verified
parent dedb1e2519
commit 9d67bd09fc
12 changed files with 278 additions and 6 deletions
+14
View File
@@ -6,6 +6,8 @@ from .broker_server import (
build_service_from_config,
create_broker_server,
load_broker_config,
run_broker_server,
run_transparent_proxy_server,
)
from .capabilities import (
CatalogNodeEntry,
@@ -33,6 +35,12 @@ from .models import (
from .mcp_sdk_adapter import McpSdkAdapter
from .service import WfMcpService
from .store import FileStore, Store
from .transparent_proxy import (
broker_config_to_fastmcp_config,
connection_to_fastmcp_server_config,
create_transparent_proxy_client,
create_transparent_proxy_server,
)
from .wrappers import wrap_discovered_tool
__all__ = [
@@ -58,12 +66,18 @@ __all__ = [
"ToolCallResult",
"WfMcpService",
"build_service_from_config",
"broker_config_to_fastmcp_config",
"connection_to_fastmcp_server_config",
"create_broker_server",
"create_transparent_proxy_client",
"create_transparent_proxy_server",
"discover_connection_capabilities",
"load_broker_config",
"make_event",
"parse_connection_id",
"qualify_node_name",
"run_broker_server",
"run_transparent_proxy_server",
"specs_from_discovered_tools",
"wrap_discovered_tool",
]
+17
View File
@@ -13,6 +13,7 @@ from .mcp_sdk_adapter import McpSdkAdapter
from .models import BrokerConfig, ConnectionConfig
from .service import WfMcpService
from .store import FileStore
from .transparent_proxy import create_transparent_proxy_server
def load_broker_config(path: str | Path) -> BrokerConfig:
@@ -221,5 +222,21 @@ def run_broker_server(config_path: str | Path, transport: str = "stdio") -> None
server.run(transport=normalize_transport(transport))
def run_transparent_proxy_server(
config_path: str | Path,
transport: str = "stdio",
*,
resources_as_tools: bool = False,
prompts_as_tools: bool = False,
) -> None:
config = load_broker_config(config_path)
server = create_transparent_proxy_server(
config,
resources_as_tools=resources_as_tools,
prompts_as_tools=prompts_as_tools,
)
server.run(transport=normalize_transport(transport), show_banner=False)
if __name__ == "__main__":
main()
+26 -1
View File
@@ -10,6 +10,7 @@ from .broker_server import (
build_service_from_config,
load_broker_config,
run_broker_server,
run_transparent_proxy_server,
)
@@ -30,6 +31,22 @@ def build_parser() -> argparse.ArgumentParser:
choices=["stdio", "sse", "streamable-http", "streamable_http"],
help="Transport to run the broker server with.",
)
serve.add_argument(
"--mode",
default="proxy",
choices=["broker", "proxy"],
help="Run admin/workflow broker mode or transparent proxy mode.",
)
serve.add_argument(
"--resources-as-tools",
action="store_true",
help="Expose proxied resources through list_resources/read_resource tools.",
)
serve.add_argument(
"--prompts-as-tools",
action="store_true",
help="Expose proxied prompts through list_prompts/get_prompt tools.",
)
subparsers.add_parser("connections", help="List configured connections.")
subparsers.add_parser("status", help="Show connection status and snapshot counts.")
@@ -92,7 +109,15 @@ def main(argv: list[str] | None = None) -> int:
args = parser.parse_args(argv)
if args.command == "serve":
run_broker_server(args.config, args.transport)
if args.mode == "proxy":
run_transparent_proxy_server(
args.config,
args.transport,
resources_as_tools=args.resources_as_tools,
prompts_as_tools=args.prompts_as_tools,
)
else:
run_broker_server(args.config, args.transport)
return 0
service = _service_from_config(args.config)
+103
View File
@@ -0,0 +1,103 @@
from __future__ import annotations
from typing import Any
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.client.transports.config import MCPConfigTransport
from fastmcp.client.transports.memory import FastMCPTransport
from fastmcp.mcp_config import MCPConfig
from fastmcp.server import create_proxy
from fastmcp.server.transforms import Namespace, PromptsAsTools, ResourcesAsTools
from .models import BrokerConfig, ConnectionConfig
def connection_to_fastmcp_server_config(
connection: ConnectionConfig,
) -> dict[str, Any]:
metadata = dict(connection.metadata)
transport = metadata.get("transport", "stdio")
if transport == "streamable_http":
metadata["transport"] = "http"
if transport == "stdio":
return {
"command": metadata["command"],
"args": list(metadata.get("args", [])),
"env": dict(metadata.get("env", {})),
"cwd": metadata.get("cwd"),
"transport": "stdio",
"description": metadata.get("description"),
}
if transport in {"http", "streamable-http", "sse"}:
return {
"url": metadata["url"],
"transport": transport,
"headers": dict(metadata.get("headers", {})),
"description": metadata.get("description"),
}
raise ValueError(f"unsupported MCP transport {transport!r}")
def broker_config_to_fastmcp_config(config: BrokerConfig) -> MCPConfig:
return MCPConfig.from_dict(
{
"mcpServers": {
connection.id: connection_to_fastmcp_server_config(connection)
for connection in config.connections
if connection.enabled
}
}
)
def create_transparent_proxy_server(
config: BrokerConfig,
*,
resources_as_tools: bool = False,
prompts_as_tools: bool = False,
) -> FastMCP[Any]:
root = FastMCP(
"wf-mcp-transparent-proxy",
instructions=(
"Transparent MCP proxy over configured upstream MCP connections. "
"Upstream tools, resources, and prompts are exposed as first-class "
"broker capabilities with connection-qualified names."
),
)
for connection in config.connections:
if not connection.enabled:
continue
server_config = broker_config_to_fastmcp_config(
BrokerConfig(store_root=config.store_root, connections=[connection])
)
transport = MCPConfigTransport(server_config, name_as_prefix=False)
client = Client(transport=transport, name=f"wf-mcp:{connection.id}")
proxy = create_proxy(client, name=f"Proxy-{connection.id}")
proxy.add_transform(Namespace(connection.id))
root.mount(proxy)
if resources_as_tools:
root.add_transform(ResourcesAsTools(root))
if prompts_as_tools:
root.add_transform(PromptsAsTools(root))
return root
def create_transparent_proxy_client(
config: BrokerConfig,
*,
resources_as_tools: bool = False,
prompts_as_tools: bool = False,
) -> Client[FastMCPTransport]:
return Client(
FastMCPTransport(
create_transparent_proxy_server(
config,
resources_as_tools=resources_as_tools,
prompts_as_tools=prompts_as_tools,
)
)
)