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",
"fastmcp>=2.13.3",
"psycopg[binary]>=3.3.2",
"uvicorn>=0.38.0",
]

View File

@@ -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)

2
mcp/uv.lock generated
View File

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

View File

@@ -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
# 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.