MCP server that exposes JFR profiling data from Kubernetes JVM pods to an LLM agent.
- Exposes 9 MCP tools so an agent can discover pods, trigger/monitor recordings, and retrieve JFR analysis from any pod in the cluster.
- Delegates analysis to the Jafra Analyzer backend via a clean REST API — the MCP server has zero knowledge of JFR internals.
- Live tools (6):
list_profiled_pods,get_pod_jvm_status,get_jvm_statistics,get_recording_report,get_jfr_summary(all five wired to Jafra Analyzer —list_profiled_podsnow callsGET /api/v1/recordings). - Stub tools (3):
start_recording,stop_recording,get_recording,get_flame_graph— defined and advertised today; will be wired to the jafra-controller once its recording API is finalised.
LLM Agent
│ MCP (stdio / SSE / Streamable HTTP)
▼
JAFRA-profiler-mcp-server ← this repo
│ REST
▼
Jafra Analyzer (GET /api/v1/recordings)
│ (GET /api/v1/namespaces/{ns}/pods/{pod}/containers/{c}/report)
│ (GET /api/v1/namespaces/{ns}/pods/{pod}/containers/{c}/summary)
▼
Jafra Agent (DaemonSet — streams JFR chunks from every node)
│
└─ async-profiler sidecar injected by jafra-controller (MutatingWebhook)
- MCP Tool Contract Specification — Formal spec for all 9 tools with schemas, error codes, and examples
- MCP Tool Quick Reference — Quick lookup guide with workflow and polling strategy
- Quick Start Guide — Local dev and Kind/Kubernetes deployment
- Integration Reference — Image, environment variables, transport modes
| # | Tool | Status | What it does |
|---|---|---|---|
| 1 | list_profiled_pods |
✅ Live | Lists all pods with JFR recordings — calls GET /api/v1/recordings |
| 2 | get_pod_jvm_status |
✅ Live | Profiler readiness + latest recording info |
| 3 | get_jvm_statistics |
✅ Live | Live heap / thread / GC snapshot (no recording needed) |
| 4 | get_recording_report |
✅ Live | JMC rule-based findings for a pod/time window (calls /report) |
| 5 | get_jfr_summary |
✅ Live | Compact LLM-ready JFR event summary (calls /summary) |
| 6 | start_recording |
🔧 Stub | Trigger a JFR recording — wired to jafra-controller when recording API is ready |
| 7 | stop_recording |
🔧 Stub | Stop an in-progress recording early |
| 8 | get_recording |
🔧 Stub | Poll recording status: QUEUED → RECORDING → ANALYSING → READY |
| 9 | get_flame_graph |
🔧 Stub | Flame graph as SVG or JSON — wired to jafra-controller when ready |
1. list_profiled_pods() → find pods with JFR recordings
2. get_jfr_summary(ns, pod, container, last="5m") → broad performance overview
• scope — recording window, file metadata
• environment — JVM version, GC config, heap/container limits
• signals — CPU, GC pauses, heap, locks, allocations, threads, errors
3. get_recording_report(ns, pod, container, last="5m") → JMC rule findings
• findings["VMOperations"] score=100 → long VM operations
• findings["GC Pressure"] score=84 → GC is a problem
• findings["PID-1 Rule"] score=75 → Java running as PID 1
4. Agent correlates findings → Root Cause Analysis
GET /api/v1/recordings
→ RecordingsListResponse (all workloads with JFR recordings — used by list_profiled_pods)
GET /api/v1/namespaces/{ns}/pods/{pod}/containers/{container}/report?last=5m
→ JafraSummaryReport (JMC rule-based findings keyed by rule ID)
GET /api/v1/namespaces/{ns}/pods/{pod}/containers/{container}/summary?last=5m
→ JafraSummaryDocument (raw JFR event statistics, transformed to McpSummary)
404 error handling: If
podis not found in the analyzer,get_recording_reportandget_jfr_summaryreturnisError: truewith an actionable message listing all available workloads and a tip to calllist_profiled_podsfirst.
All request/response types are defined in ProfilingModels.java.
# src/main/resources/application.properties
# Jafra Analyzer base URL (or set JAFRA_ANALYZER_URL env var)
quarkus.rest-client.jafra-analyzer.url=${JAFRA_ANALYZER_URL:http://localhost:8080}
# Default Kubernetes namespace when callers omit it (or set PROFILER_DEFAULT_NAMESPACE)
profiler.controller.default-namespace=${PROFILER_DEFAULT_NAMESPACE:default}./mvnw clean package -DskipTests# linux/amd64 (for Kubernetes / OCP deployments)
podman build --platform linux/amd64 -t quay.io/khansaad/async-profiler-mcp-server:0.1.0 .
podman push quay.io/khansaad/async-profiler-mcp-server:0.1.0./mvnw testJAFRA-mcp-server/
├── Dockerfile
├── smoke_test.py ← end-to-end MCP handshake smoke test
├── manifests/
│ ├── async-profiler-mcp-server-kind.yaml ← Kind deployment (jafra-system namespace)
│ ├── async-profiler-mcp-server-openshift.yaml
│ └── async-profiler-mcp-server-minikube.yaml
└── src/
├── main/java/io/causa/profiler/
│ ├── dto/
│ │ └── ProfilingModels.java ← all DTOs: ProfilingModels, JafraSummaryReport,
│ │ JafraSummaryDocument, McpSummary, RecordingMetadata…
│ ├── mcp/
│ │ └── ProfilingTools.java ← all 9 @Tool methods
│ └── service/
│ ├── ProfilerService.java ← interface
│ └── ProfilerServiceClient.java ← Jafra Analyzer REST client +
│ summary_to_mcp.py logic (Java port)
└── test/java/io/causa/profiler/mcp/
└── ProfilingToolsTest.java ← 19 unit tests (all 9 tools, mocked service)