From 0e245153030b5dd81b740949f8e30613511a43b4 Mon Sep 17 00:00:00 2001 From: Paul Walko Date: Sat, 13 Dec 2025 16:55:00 +0100 Subject: [PATCH] make mcp "prodution ready" --- mcp/Dockerfile | 28 ++++++++++++++++++++++++++++ mcp/pyproject.toml | 1 + mcp/server.py | 9 +++++++++ mcp/uv.lock | 2 ++ web/agent/src/agent.py | 27 +++++++++++++-------------- 5 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 mcp/Dockerfile diff --git a/mcp/Dockerfile b/mcp/Dockerfile new file mode 100644 index 0000000..1b0e78f --- /dev/null +++ b/mcp/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.13-slim + +# Install uv +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +WORKDIR /app + +ENV PYTHONPATH=/app + +# Copy dependency files +COPY pyproject.toml uv.lock ./ + +# Install dependencies +RUN uv sync --frozen --no-dev + +# Copy application code +COPY server.py ./ + +# Create non-root user +RUN useradd --create-home --shell /bin/bash mcp +USER mcp + +EXPOSE 8021 + +ENV PORT=8021 +ENV HOST="0.0.0.0" + +CMD ["uv", "run", "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8021"] diff --git a/mcp/pyproject.toml b/mcp/pyproject.toml index 1e487e2..515a529 100644 --- a/mcp/pyproject.toml +++ b/mcp/pyproject.toml @@ -10,4 +10,5 @@ dependencies = [ "dotenv>=0.9.9", "fastmcp>=2.13.3", "psycopg[binary]>=3.3.2", + "uvicorn>=0.38.0", ] diff --git a/mcp/server.py b/mcp/server.py index bf6c73d..faff71d 100644 --- a/mcp/server.py +++ b/mcp/server.py @@ -106,5 +106,14 @@ def get_user_info() -> dict: "roles": roles, } +from starlette.responses import JSONResponse +from starlette.routing import Route + +async def health(request): + return JSONResponse({"status": "ok"}) + +app = mcp.http_app() +app.routes.append(Route("/health", health)) + if __name__ == "__main__": mcp.run(transport='http', host='::1', port=9031) diff --git a/mcp/uv.lock b/mcp/uv.lock index 78e46e5..8e49ff4 100644 --- a/mcp/uv.lock +++ b/mcp/uv.lock @@ -82,6 +82,7 @@ dependencies = [ { name = "dotenv" }, { name = "fastmcp" }, { name = "psycopg", extra = ["binary"] }, + { name = "uvicorn" }, ] [package.metadata] @@ -91,6 +92,7 @@ requires-dist = [ { name = "dotenv", specifier = ">=0.9.9" }, { name = "fastmcp", specifier = ">=2.13.3" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.3.2" }, + { name = "uvicorn", specifier = ">=0.38.0" }, ] [[package]] diff --git a/web/agent/src/agent.py b/web/agent/src/agent.py index f7cbdbc..cd69b2a 100644 --- a/web/agent/src/agent.py +++ b/web/agent/src/agent.py @@ -17,36 +17,35 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -MCP_URL = "https://mcp.caving.dev/mcp" +CAVE_MCP_URL = os.getenv("CAVE_MCP_URL", "https://mcp.caving.dev/mcp") logger.info("Initializing Cavepedia agent...") def check_mcp_available(url: str, timeout: float = 5.0) -> bool: - """Check if MCP server is reachable.""" + """Check if MCP server is reachable via health endpoint.""" try: - # Just check if we can connect - don't need a full MCP handshake - response = httpx.get(url, timeout=timeout, follow_redirects=True) - # Any response (even 4xx/5xx) means server is reachable - # 502 means upstream is down, so treat as unavailable - if response.status_code == 502: - logger.warning(f"MCP server returned 502 Bad Gateway") - return False - return True + # Use the health endpoint instead of the MCP endpoint + health_url = url.rsplit("/", 1)[0] + "/health" + response = httpx.get(health_url, timeout=timeout, follow_redirects=True) + if response.status_code == 200: + return True + logger.warning(f"MCP health check returned {response.status_code}") + return False except Exception as e: logger.warning(f"MCP server not reachable: {e}") return False # Try to configure MCP if server is available toolsets = [] -if check_mcp_available(MCP_URL): +if check_mcp_available(CAVE_MCP_URL): try: from pydantic_ai.mcp import MCPServerStreamableHTTP mcp_server = MCPServerStreamableHTTP( - url=MCP_URL, + url=CAVE_MCP_URL, timeout=30.0, ) toolsets.append(mcp_server) - logger.info(f"MCP server configured: {MCP_URL}") + logger.info(f"MCP server configured: {CAVE_MCP_URL}") except Exception as e: logger.warning(f"Could not configure MCP server: {e}") else: @@ -54,7 +53,7 @@ else: # Create the agent with Google Gemini model agent = Agent( - model=GoogleModel("gemini-2.5-pro"), + model=GoogleModel("gemini-3-pro-preview"), toolsets=toolsets if toolsets else None, instructions="""You are a helpful caving assistant. Help users with all aspects of caving including cave exploration, safety, surveying techniques, cave locations, geology, equipment, history, conservation, and any other caving-related topics.