make mcp "prodution ready"
All checks were successful
Build and Push Agent Docker Image / build (push) Successful in 2m11s

This commit is contained in:
2025-12-13 16:55:00 +01:00
parent b517c6939f
commit 0e24515303
5 changed files with 53 additions and 14 deletions

28
mcp/Dockerfile Normal file
View File

@@ -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"]

View File

@@ -10,4 +10,5 @@ dependencies = [
"dotenv>=0.9.9", "dotenv>=0.9.9",
"fastmcp>=2.13.3", "fastmcp>=2.13.3",
"psycopg[binary]>=3.3.2", "psycopg[binary]>=3.3.2",
"uvicorn>=0.38.0",
] ]

View File

@@ -106,5 +106,14 @@ def get_user_info() -> dict:
"roles": roles, "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__": if __name__ == "__main__":
mcp.run(transport='http', host='::1', port=9031) mcp.run(transport='http', host='::1', port=9031)

2
mcp/uv.lock generated
View File

@@ -82,6 +82,7 @@ dependencies = [
{ name = "dotenv" }, { name = "dotenv" },
{ name = "fastmcp" }, { name = "fastmcp" },
{ name = "psycopg", extra = ["binary"] }, { name = "psycopg", extra = ["binary"] },
{ name = "uvicorn" },
] ]
[package.metadata] [package.metadata]
@@ -91,6 +92,7 @@ requires-dist = [
{ name = "dotenv", specifier = ">=0.9.9" }, { name = "dotenv", specifier = ">=0.9.9" },
{ name = "fastmcp", specifier = ">=2.13.3" }, { name = "fastmcp", specifier = ">=2.13.3" },
{ name = "psycopg", extras = ["binary"], specifier = ">=3.3.2" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.3.2" },
{ name = "uvicorn", specifier = ">=0.38.0" },
] ]
[[package]] [[package]]

View File

@@ -17,36 +17,35 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) 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...") logger.info("Initializing Cavepedia agent...")
def check_mcp_available(url: str, timeout: float = 5.0) -> bool: 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: try:
# Just check if we can connect - don't need a full MCP handshake # Use the health endpoint instead of the MCP endpoint
response = httpx.get(url, timeout=timeout, follow_redirects=True) health_url = url.rsplit("/", 1)[0] + "/health"
# Any response (even 4xx/5xx) means server is reachable response = httpx.get(health_url, timeout=timeout, follow_redirects=True)
# 502 means upstream is down, so treat as unavailable if response.status_code == 200:
if response.status_code == 502: return True
logger.warning(f"MCP server returned 502 Bad Gateway") logger.warning(f"MCP health check returned {response.status_code}")
return False return False
return True
except Exception as e: except Exception as e:
logger.warning(f"MCP server not reachable: {e}") logger.warning(f"MCP server not reachable: {e}")
return False return False
# Try to configure MCP if server is available # Try to configure MCP if server is available
toolsets = [] toolsets = []
if check_mcp_available(MCP_URL): if check_mcp_available(CAVE_MCP_URL):
try: try:
from pydantic_ai.mcp import MCPServerStreamableHTTP from pydantic_ai.mcp import MCPServerStreamableHTTP
mcp_server = MCPServerStreamableHTTP( mcp_server = MCPServerStreamableHTTP(
url=MCP_URL, url=CAVE_MCP_URL,
timeout=30.0, timeout=30.0,
) )
toolsets.append(mcp_server) 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: except Exception as e:
logger.warning(f"Could not configure MCP server: {e}") logger.warning(f"Could not configure MCP server: {e}")
else: else:
@@ -54,7 +53,7 @@ else:
# Create the agent with Google Gemini model # Create the agent with Google Gemini model
agent = Agent( agent = Agent(
model=GoogleModel("gemini-2.5-pro"), model=GoogleModel("gemini-3-pro-preview"),
toolsets=toolsets if toolsets else None, 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. 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.