feat: implement claude code backend

This commit is contained in:
hh
2026-05-19 23:11:07 +02:00
parent 757065f21c
commit 99a30f256d
8 changed files with 797 additions and 7 deletions
+48
View File
@@ -0,0 +1,48 @@
"""Proxy ``FastMCP`` servers for user-declared external MCPs (``stdio``/``http``).
Both flavours end up as a ``FastMCPProxy`` instance, built via
``fastmcp.server.create_proxy``. The proxy lazily opens the underlying
client transport when the first MCP request arrives, so we don't pay
for connections that nothing routes to. From the aggregator app's
point of view a proxy is indistinguishable from a regular ``FastMCP``
namespace — same ``http_app`` surface, same mount semantics.
"""
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
if TYPE_CHECKING:
from fastmcp import FastMCP
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.
"""
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,
)
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)
return create_proxy(Client(transport, name=spec.name))