MCP server
The MCP server (agent/mcp/, container image ai-mcp, port 8085) exposes the
ClickHouse analytics as callable tools over the Model Context Protocol. The
FastAPI agent is its only client — the agent's LLM asks for a tool
by name, the agent calls it here, and this server runs the SQL.
What it is
- Built on FastMCP (
mcp.server.fastmcp.FastMCP), pinned tomcp==1.29.0(the 2.0.0 release moved this import — don't bump without checking), Python 3.10+. - Runs over streamable-http (
transport="streamable-http"), not stdio — so it's a standalone, network-reachable server the separate agent container can call athttp://ai-mcp:8085/mcp. (A rawGET /mcpreturns406— it needs proper MCP headers; that 406 just means "reachable".) mcp_server.pyis a thin entrypoint: create the app, call each tool module'sregister(mcp)to attach its@mcp.tool()functions, thenmcp.run(...).
The 27 tools
Grouped by module under agent/mcp/tools/:
| Module | Tools (count) | Reads |
|---|---|---|
security_tools.py |
9 — suspicious_ip_activity, ip_request_history, top_suspicious_paths, suspicious_ip_range_activity, component_traffic_breakdown, pop_traffic_breakdown, pop_attack_summary, node_attack_summary, device_ip_correlation |
cproxy_events (+ elb_events via UNION ALL) |
qoe_tools.py |
5 — cmcd_qoe_score, qoe_score_trend, ip_range_qoe_scores, device_qoe_scores, pop_qoe_scores |
cproxy_events (CMCD columns) |
stream_tools.py |
6 — find_new_streams, count_active_streams, streams_with_recent_traffic, current_viewer_counts, get_stream_status, stream_lifecycle_events |
streamer_events; two hit the streamer REST API, not ClickHouse |
log_tools.py |
4 — find_http_errors, log_level_breakdown, top_source_functions, top_clients |
cproxy_events (status/clients) + streamer_events (level/source) |
query_tools.py |
3 — list_tables, describe_table, run_query |
any cdn_ai table (read-only) |
The full tool → table map (which table each tool actually queries) lives in the pilot reference so it stays in one place: CDN-AI reference — tools → table.
All 27 target the cdn_ai database (the retarget off the retired
cdn_access_logs table landed in 769d78b). run_query only accepts
SELECT/WITH; anything else is rejected before it reaches ClickHouse.
Shared plumbing (common.py)
- The ClickHouse client factory (
client()), readingCLICKHOUSE_*env — see settings. - The empty-result retry wrapper (8 × 0.3 s): retries a query that returns zero rows, a workaround for a past pod replication skew. It adds latency to genuinely-empty queries and is a candidate to trim.
SAFE_STREAM_IDand table-name validation to keep tool arguments from reaching SQL unchecked.
Adding a tool
- Add a
@mcp.tool()-decorated function inside a module'sregister(mcp). - Query
cdn_aiviacommon.client(); returnjson.dumps(...). - If the LLM should reach it, add its name to the right category in the agent's
TOOL_CATEGORIESso tool scoping surfaces it. - Prefer a purpose-built tool with the correct SQL baked in over expecting the small model to write SQL — that's the whole reason these exist.
Run / deploy
- Container:
agent/Dockerfile.mcp→ imageai-mcp; serves8085. - Staging: k8s Deployment
ai-mcp(services/ai/mcp), one replica. The agent finds it viaMCP_STREAMER_URL=http://ai-mcp:8085/mcp. - Two images, one repo:
ai-agentandai-mcpare built by the same CI from the sameagent/context with different Dockerfiles, and their tags are bumped together in the GitOps repo per push.