From 8ee63517ec5b513ba3824684e8c5a0fbf8cbf03f Mon Sep 17 00:00:00 2001 From: robmsmt Date: Wed, 25 Mar 2026 15:37:22 +0100 Subject: [PATCH 1/2] add mcp service example --- backend/main.py | 2 ++ backend/routers/mcp.py | 34 +++++++++++++++++++++++++++++++++ backend/services/mcp_service.py | 32 +++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 backend/routers/mcp.py create mode 100644 backend/services/mcp_service.py diff --git a/backend/main.py b/backend/main.py index a5259eb..13eacee 100644 --- a/backend/main.py +++ b/backend/main.py @@ -18,6 +18,7 @@ metrics, rerank, tokenization, + mcp, ) settings = get_settings() @@ -69,6 +70,7 @@ async def backend_http_error_handler(request: Request, exc: BackendHTTPError): app.include_router(metrics.router) app.include_router(rerank.router) app.include_router(tokenization.router) +app.include_router(mcp.router) if __name__ == "__main__": import uvicorn diff --git a/backend/routers/mcp.py b/backend/routers/mcp.py new file mode 100644 index 0000000..18c24e0 --- /dev/null +++ b/backend/routers/mcp.py @@ -0,0 +1,34 @@ +import aiohttp +from fastapi import APIRouter, Request, Depends +from fastapi.responses import Response, JSONResponse +from backend.middleware.auth import require_auth +from backend.services.mcp_service import mcp_proxy, list_mcp_servers + +router = APIRouter() + + +@router.get("/v1/mcp") +async def list_mcp_servers(token: str = Depends(require_auth)): + return {"servers": list_mcp_servers()} + + +@router.post("/v1/mcp/{owner}/{repo}") +async def mcp_endpoint( + owner: str, + repo: str, + request: Request, + token: str = Depends(require_auth), +): + body = await request.body() + try: + data, status = await mcp_proxy(owner, repo, body) + return Response( + content=data, + status_code=status, + media_type="application/json", + ) + except aiohttp.ClientError: + return JSONResponse( + status_code=404, + content={"error": f"MCP server '{owner}/{repo}' not reachable"}, + ) diff --git a/backend/services/mcp_service.py b/backend/services/mcp_service.py new file mode 100644 index 0000000..d755602 --- /dev/null +++ b/backend/services/mcp_service.py @@ -0,0 +1,32 @@ +import aiohttp + +NAMESPACE = "rob-poc" +MCP_PORT = 8080 + +# Hardcoded MCP server registry: owner/repo -> K8s service DNS +MCP_SERVERS = { + "alan5543/calculator-mcp": f"http://tool-gym-alan5543-calculator-mcp-dev.{NAMESPACE}.svc.cluster.local:{MCP_PORT}", +} + + +async def mcp_proxy(owner: str, repo: str, body: bytes) -> tuple[bytes, int]: + """Forward a JSON-RPC request to the MCP server. Returns (body, status).""" + url = MCP_SERVERS.get(f"{owner}/{repo}") + if not url: + return b'{"error":"MCP server not found"}', 404 + async with aiohttp.ClientSession() as session: + async with session.post( + url, + data=body, + headers={"Content-Type": "application/json"}, + timeout=aiohttp.ClientTimeout(total=30), + ) as resp: + data = await resp.read() + return data, resp.status + + +def list_mcp_servers() -> list[dict]: + return [ + {"owner": k.split("/")[0], "repo": k.split("/")[1]} + for k in MCP_SERVERS + ] From 0e16da59058e27ab923fd567fb2e17267dd5d0a6 Mon Sep 17 00:00:00 2001 From: robmsmt Date: Wed, 25 Mar 2026 15:40:09 +0100 Subject: [PATCH 2/2] fix ruff --- backend/routers/mcp.py | 2 +- backend/services/mcp_service.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/routers/mcp.py b/backend/routers/mcp.py index 18c24e0..4298705 100644 --- a/backend/routers/mcp.py +++ b/backend/routers/mcp.py @@ -8,7 +8,7 @@ @router.get("/v1/mcp") -async def list_mcp_servers(token: str = Depends(require_auth)): +async def get_mcp_servers(token: str = Depends(require_auth)): return {"servers": list_mcp_servers()} diff --git a/backend/services/mcp_service.py b/backend/services/mcp_service.py index d755602..d01bfba 100644 --- a/backend/services/mcp_service.py +++ b/backend/services/mcp_service.py @@ -26,7 +26,4 @@ async def mcp_proxy(owner: str, repo: str, body: bytes) -> tuple[bytes, int]: def list_mcp_servers() -> list[dict]: - return [ - {"owner": k.split("/")[0], "repo": k.split("/")[1]} - for k in MCP_SERVERS - ] + return [{"owner": k.split("/")[0], "repo": k.split("/")[1]} for k in MCP_SERVERS]