feat: implement mcp frontend

This commit is contained in:
hh
2026-05-20 09:59:47 +02:00
parent 99a30f256d
commit 7970d4be9b
7 changed files with 902 additions and 24 deletions
+27 -9
View File
@@ -16,8 +16,11 @@ 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
@@ -27,22 +30,37 @@ def build_stdio_proxy(spec: StdioMcp) -> FastMCP:
``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.
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
transport = StdioTransport(
command=command,
args=list(args),
env=spec.env,
cwd=str(spec.cwd) if spec.cwd is not None else None,
)
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``."""
transport = StreamableHttpTransport(url=spec.url, auth=spec.auth)
"""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))