"""Proxy ``FastMCP`` servers for user-declared external MCPs (``stdio``/``http``). Built via ``fastmcp.server.create_proxy``; the proxy lazily opens its transport on first request, so unused MCPs cost nothing until routed to. """ from __future__ import annotations from typing import TYPE_CHECKING from fastmcp import Client from fastmcp.client.transports import StdioTransport, StreamableHttpTransport from fastmcp.server import create_proxy from beaver_gateway.mcp.lenient import LenientStdioTransport if TYPE_CHECKING: from fastmcp import FastMCP from fastmcp.client.transports.base import ClientTransport from beaver_gateway.mcp.types import HttpMcp, StdioMcp def build_stdio_proxy(spec: StdioMcp) -> FastMCP: """Wrap a stdio subprocess MCP into a mountable ``FastMCPProxy``. ``spec.command`` is a non-empty tuple; the first element is the executable and the rest are CLI args. ``StdioTransport`` keeps the subprocess alive across calls. When ``spec.lenient`` is set we swap in :class:`LenientStdioTransport`, which silently drops non-JSON-RPC stdout lines instead of forwarding them as exceptions (lets us ingest MCPs that print ``Processing...`` chatter on stdout without leaking warnings to downstream UIs). """ if not spec.command: msg = f"stdio MCP {spec.name!r} has empty command" raise ValueError(msg) command, *args = spec.command cwd = str(spec.cwd) if spec.cwd is not None else None transport: ClientTransport if spec.lenient: transport = LenientStdioTransport( command=command, args=list(args), env=spec.env, cwd=cwd ) else: transport = StdioTransport( command=command, args=list(args), env=spec.env, cwd=cwd ) return create_proxy(Client(transport, name=spec.name)) def build_http_proxy(spec: HttpMcp) -> FastMCP: """Wrap a remote streamable-HTTP MCP into a mountable ``FastMCPProxy``. ``spec.headers`` are folded into the upstream client; pass per-call auth (e.g. ``X-Api-Key``) here rather than as ``auth`` when the upstream rejects Bearer headers. """ transport = StreamableHttpTransport( url=spec.url, auth=spec.auth, headers=spec.headers ) return create_proxy(Client(transport, name=spec.name))