Files
beaver-gateway/src/beaver_gateway/mcp/client_pool.py
T

63 lines
2.3 KiB
Python

"""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))