jvm-doctor
Diagnose running JVMs through an MCP server by exposing JVM process/resource info and runtime inspection tools.
List visible JVM processes via the
jvm://processesresource (limited to 10).Read key JVM startup flags via
jvm://{pid}/flags(23 curated flags, GC-aware, with value origins).Read safe JVM properties via
jvm://{pid}/propertiesusing a whitelist to avoid sensitive data.Capture a thread dump with
thread_dump: returns a merged, compact view of thread stacks plus deadlock detection.Inspect heap generation usage, GC counts, and pause times with
gc_stat.Inspect top-N heap object usage with
heap_histogram(default top 20).Intended for LLM-driven diagnosis; requires JDK 21 and same-user/pid-namespace attach access.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@jvm-doctorRun thread dump and check for deadlocks on PID 1234"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
jvm-doctor
概述
这是一个诊断运行中 JVM 的 MCP Server。封装了资源和工具便于为LLM提供运行时JVM信息,以便LLM进行诊断
针对thread_dump做了合并归档 端到端实测触发( 200 worker → jstack → thread_dump):116,102 B → 7,156 B,最大组 [计数=200] worker-0 BLOCKED
│ │ 20 worker │ 200 worker │ ├──────────────────┼───────────────────┼──────────────────────┤ │ 原始 jstack │ 21,195 B / 352 行 │ 116,102 B / 1,837 行 │ ├──────────────────┼───────────────────┼──────────────────────┤ │ thread_dump 输出 │ 7,153 B / 193 行 │ 7,156 B / 193 行 │ ├──────────────────┼───────────────────┼──────────────────────┤ │ 压缩比 │ 0.337 │ 0.062(约 16:1) │ ├──────────────────┼───────────────────┼──────────────────────┤ │ Java 线程 │ 36 │ 216 │ ├──────────────────┼───────────────────┼──────────────────────┤ │ 归并组 │ 35 │ 35 │ ├──────────────────┼───────────────────┼──────────────────────┤ │ 最大组 │ x20 │ x200 │ ├──────────────────┼───────────────────┼──────────────────────┤ │ 死锁段 │ ✅ 完整 │ ✅ 完整 │ └──────────────────┴───────────────────┴──────────────────────┘
归并后的输出体积正比于「不同栈的种类数」,与线程总数无关。 线程越多、重复度越高,压缩比越好 —— 36 线程时 0.337,216 线程时 0.062。 生产环境 400+ 线程的线程池场景会更极端
Related MCP server: heap-seance
环境依赖
JDK 21 (必须是 JDK 不是 JRE),需正确配置JAVA_HOME
uv
attach 要同用户,同pid namespace ,不支持容器
资源和工具
Resources
jvm://processes
当前用户可见的 JVM 进程列表。限制仅返回10条数据
jvm://{pid}/flags
获取JVM启动参数
返回的是 517 个里挑出来的子集23个,不是全部
字段集按 GC 分支,所以先看 UseG1GC / UseParallelGC 那几行
G1 下的 MaxNewSize 是软上限,不是实际新生代大小
每个值后边标注来值的来源
{command line} 用户显式传的
{ergonomic} JVM 按机器规格 / 当前 GC 推导
{default} 编译期默认值,从未被计算
jvm://{pid}/properties
考虑到为了避免敏感数据泄露给LLM,采用了白名单机制,仅根据白名单返回配置
Tools
thread_dump
抓一次线程快照,返回归并后的视图与死锁检测结果
gc_stat(TBD)
堆各代使用率、GC 次数与累计停顿
heap_histogram(TBD)
堆内对象占用 Top-N
安装使用
客户端配置
Claude Code 集成
.mcp.json文件里面配置mcp
{ "mcpServers": { "jvm-doctor": { "command": "uvx", "args": [ "--from", "https://github.com/lxhuang0903/jvm-doctor.git", "jvm-doctor" ] } } }
其他(TBD)
验证
靶子应用
项目源代码中提供了测试靶子DemoApp,用来模拟实际应用几种常见的异常场景
curl -O https://raw.githubusercontent.com/lxhuang0903/jvm-doctor/main/demo-app/DemoApp.java
JDK21下直接执行 java DemoApp.java 8080
java DemoApp.java # 前台跑,会打印自己的 PID curl localhost:8080/deadlock # 制造死锁(jstack 会报 Found one Java-level deadlock) curl localhost:8080/exhaust # 制造线程堆积(N 个线程 BLOCKED 在同一个 monitor) curl localhost:8080/leak # 每次分配 20MB 不释放(堆直方图里 byte[] 会飙上去) curl localhost:8080/status # 看当前制造了多少病
针对LLM提问,如“查看pid为1234的线程栈信息”
调试
MCP Inspector
npx @modelcontextprotocol/inspector uvx --from https://github.com/lxhuang0903/jvm-doctor.git jvm-doctor
Java 侧实现
java/ 下是同一份需求的 Java 实现,走 Spring AI 的 MCP server starter。
目的不是把 Python 侧翻译一遍,而是对比两条技术路径。
cd java && mvn package -DskipTests
java -jar target/jvm-doctor-java-0.1.0.jar # stdio,等客户端连出口和 Python 侧对齐:resource jvm://processes / jvm://{pid}/flags /
jvm://{pid}/properties,tool thread_dump。
依赖 Spring Boot 4.1.1 + Spring AI 2.0.1。注意 @McpTool / @McpResource
这套注解在 spring-ai-mcp-annotations 里,1.0.0 没有,2.0.x 才有;查版本要读
maven-metadata.xml,Maven Central 的搜索接口给的 latestVersion 是陈的。
同一份需求,两条路径
Python 侧解析 jcmd / jstack 的文本输出,Java 侧走 MXBean 拿结构化对象。
解析层整个消失:
Python 侧 | Java 侧 |
解析 jstack 352 行文本 |
|
找 |
|
正则抠 |
|
区分 GC 原生线程(判有没有 | 不返回 —— |
解析 |
|
从 |
|
对比 |
|
|
|
但归并、截断、返回什么给模型,两边一样要自己写 —— 那部分不是语言问题, 是 agent 工程问题。框架帮你做掉的是协议和解析,做不掉「工具返回什么」。
两条路径的真实差异
HotSpotDiagnosticMXBean 不能枚举全部 flag。 getDiagnosticOptions() 只返回
可运行时修改的那批,查具体 flag 只能按名字取。所以「按 GC 分档的 flag 白名单」
在 Java 侧是必需品,不是优化 —— 同一个设计决策,Python 侧是为了精简输出,
Java 侧是 API 逼的。
哨兵值的形态不同。 同一个「无上限」,两条路径给的值不一样:
flag | jcmd(Python 侧) | JMX(Java 侧) |
|
|
|
|
|
|
JMX 把 unsigned 64 位截断成了有符号值。-2 比 2⁶⁴−2 更难识别 —— 后者一看就是
极值,前者像个合法的小负数,模型更容易误读成「暂停目标 -2 毫秒」。
参数名在 Java 里默认丢失。 @McpResource(uri = "jvm://{pid}/flags") 靠参数名
绑定 {pid},而 Java 编译默认不保留参数名,要靠 -parameters(Spring Boot parent
已经配了 <maven.compiler.parameters>true</maven.compiler.parameters>)。接进
公司已有的 parent pom、或用 Gradle / IDE 直接编译时会失效。更稳的写法是在
@McpArg(name = "pid") / @McpToolParam(name = "...") 里显式写名字。
Python 侧没有这个问题 —— 参数名在运行时永远可读。
交叉验证:两条路径等价
同一个靶子(先 curl /deadlock 和 /exhaust?workers=200),两个实现各跑一次
thread_dump:
Python(解析 jstack 文本) | Java(ThreadMXBean) | |
原始输入 | 120,197 B / 1,882 行 | 不适用(拿的是对象) |
报告 | 9,651 B / 229 行 | 7,710 B / 153 行 |
最大归并组 | x200 | x200 |
死锁检出 | deadlock-1-A / B | deadlock-1-A / B |
Java 线程 | 219 | 216 |
GC / VM 原生线程 | 18 | 0 |
最大归并组和死锁两边一致 —— 这就是「归并判据等价」的证据。 组数或最大组对不上, 说明有一边的签名算错了,而且能立刻定位是哪一边。
剩下的差异都能解释:219 vs 216 是两次调用之间有几个临时 worker 超时退出了;
原生线程 18 vs 0 是机制差异(ThreadMXBean 只返回 Java 线程),这也是 Java 侧报告
更短的主要原因。
Java 侧这个出口还省掉两件事:死锁的等待关系是 getLockName() + getLockOwnerName()
直接拼的,不用从 Found one Java-level deadlock 段落里解析 monitor 地址再和主体的栈
对应(Python 侧还得处理「死锁段的栈会重复出现」这个坑);栈深度截断是
dumpAllThreads(true, true, 10) 的第三个参数,JMX 自带。
但归并本身两边都要自己写 —— 包括那个「无栈线程必须用线程名兜底」的判据: Signal Dispatcher / Attach Listener / DestroyJavaVM 都没有栈,只用空签名会把它们 错误地合并成「N 个线程卡在同一处」(Python 侧实测合出过一组 x8)。MXBean 给对象, 不给结论。
已知限制:stdout 污染没有机制保护
stdio 传输下 stdout 就是协议通道,MCP SDK 直接拿 System.out 写 JSON-RPC 帧
(反编译 StdioServerTransportProvider 可以看到 getstatic System.out)。任何别的
东西写进 stdout —— 一行日志、一句 System.out.println 调试、某个库的启动横幅 ——
都会插进帧之间,客户端解析失败、连接直接断,而且报错在客户端那侧,从 server
这边完全看不出原因。
这边靠 application.properties 压住:
logging.pattern.console=
logging.file.name=./jvm-doctor-java.log但这是约定,不是机制。 任何人加一行 System.out.println,约定就破了。
试过在 main() 第一行把 System.out 换成指向 stderr 的流,结果是协议帧自己也跟着
跑进了 stderr —— SDK 在它初始化那一刻读全局的 System.out,谁先改谁赢。要真正
做到「协议独占 stdout」,只能自己声明 transport bean,把抢在前面存下来的真 stdout
交给它:
PrintStream real = System.out; // 先抢到真 stdout
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.err), true));
return new StdioServerTransportProvider(mapper, System.in, real);代价是绕过 Spring AI 的自动配置,所以这里选择不做,文档化。
Python 侧有同样的问题(SDK 也是直接拿 sys.stdout),只是 Python 默认不会自动往
stdout 打日志,不容易触发。这是 stdio 传输的固有缺陷,不是某个 SDK 的疏忽。
Available Tools
3 toolsgc_statCRead-only
堆各代使用率、GC 次数与累计停顿。
| Name | Required | Description | Default |
|---|---|---|---|
| pid | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
注释已声明readOnlyHint=true,描述没有添加任何超出注释的行为信息,如返回格式、是否需要权限或是否影响系统。描述仅重复了内容,没有提供附加的行为上下文,也未与注释矛盾。
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
描述非常简洁,只有一句话,没有冗余内容。对于单一参数的工具,简洁性可接受,但可能过于简短而缺乏必要细节,不过结构上无浪费。
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
虽然存在输出模式,但描述未提供任何关于pid的上下文,也没有说明调用前置条件或预期行为。对于这样一个简单工具,缺少对必需参数的解释导致不完整。
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
schema_description_coverage为0%,描述未解释参数pid的含义、格式或用途。仅有的参数完全未说明,代理无法知道如何正确填充pid,描述没有补偿schema的缺失。
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
描述明确列出了工具返回的内容:堆各代使用率、GC次数与累计停顿,指向了具体的资源(GC统计)。但没有显式动词如'获取',且与兄弟工具thread_dump和heap_histogram相比,它聚焦于GC统计,可区分,但缺乏明确的动作词。
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
描述没有说明何时使用该工具,也没有提及替代工具或排除条件。它仅陈述功能,没有指导代理在何种场景下选择它而不是thread_dump或heap_histogram。
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
heap_histogramBRead-only
堆内对象占用 Top-N。
| Name | Required | Description | Default |
|---|---|---|---|
| pid | Yes | ||
| top | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already communicates that the call does not mutate state, so the description does not need to restate that. It does add useful scoping information by specifying the top-N heap-object occupancy, which is a snapshot-style read; however, it does not disclose potential side effects such as performance cost or whether a full GC might be triggered to compute the histogram.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is one short sentence with no filler and front-loads the core concept of heap object occupancy with Top-N. It earns high marks for economy, though it is slightly under-specified as a definition because it omits usage context.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only tool with an output schema and clear parameter names, the minimal description plus annotations may let an agent call it successfully. However, the definition lacks any context about when to use it, why an agent would choose it, or how the histogram is produced, leaving a clear gap in operational context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description must carry parameter meaning. It explains 'top' through the Top-N phrase, but it does not explicitly describe 'pid' as the target JVM/process whose heap is inspected. For two simple parameters, the minimal semantics are adequate, but the description only partially compensates for the missing schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The Chinese description '堆内对象占用 Top-N' communicates that the tool returns the top-N heap objects by occupancy, which clearly distinguishes it from sibling tools gc_stat and thread_dump. The only reason it is not a 5 is that it uses a nominal phrase rather than an explicit verb such as 'get' or 'return', leaving the exact operation implicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no guidance about when to choose heap_histogram over gc_stat or thread_dump, and it does not mention typical use cases such as memory-leak or large-object analysis. There is no 'when/when-not' or alternative routing, so an agent must infer usage from the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
thread_dumpARead-only
抓一次线程快照,返回归并后的视图与死锁检测结果。
| Name | Required | Description | Default |
|---|---|---|---|
| pid | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes the safe read-only nature, and the description adds useful output context by mentioning the merged view and deadlock detection. It does not contradict the annotations, but it also does not disclose much beyond that—no mention of scope, cost, or how the snapshot behaves across repeated calls.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The entire description is a single, front-loaded sentence that names the action first and then states the result. Every word contributes meaning, with no filler or repetition of schema fields.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter, read-only tool with an output schema, the description is largely sufficient: it states the action and the key outputs. It falls slightly short by not clarifying pid semantics, but the low complexity and supporting annotations/schema make the gap minor.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description does not explain the pid parameter at all. An agent must rely on the property name and type to infer it is a process ID, but there is no guidance about what process is targeted or what format/value is expected.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb-resource pair ('抓一次线程快照') and states the concrete outputs: a merged view and deadlock detection result. This clearly distinguishes thread_dump from its siblings gc_stat and heap_histogram, which target different runtime areas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is only implied: the description says it captures a thread snapshot and detects deadlocks, so an agent can infer it is for thread/diagnostic analysis. However, it does not explicitly state when to prefer this tool over gc_stat or heap_histogram, nor does it give any exclusions or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
gc_stat - First observed
heap_histogram - First observed
thread_dump
TDQS
Scored across 3 tools
Each tool targets a distinct JVM diagnostic area: garbage collection stats, thread snapshots, and heap object histograms. There is no meaningful overlap between these three concerns.
All tool names follow a consistent lowercase snake_case pattern using a domain prefix followed by a report type (gc_stat, thread_dump, heap_histogram). The naming convention is uniform and predictable.
Three tools is a tight, well-scoped set for a focused JVM diagnostics server. Each tool serves a distinct diagnostic purpose without unnecessary bloat.
Core JVM health diagnostics are covered for GC, threads, and heap usage. Minor gaps exist such as classloading or CPU/OS-level metrics, but the primary snapshot needs are addressed.
Maintenance
Related MCP Connectors
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
MCP server exposing Kettle Logic insight articles & industry guidance as tools + resources.
Governed data discovery, exact queries, decisions, simulations, and runtime utilities over MCP.
A MCP server built for developers enabling Git based project management with project and personal…
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceProvides comprehensive monitoring and observability for MCP server ecosystems with real-time health checks, performance metrics, distributed tracing, anomaly detection, and automated performance reports using OpenTelemetry and Prometheus.1MIT
- AlicenseDqualityCmaintenanceMCP server that provides 8 tools for Java memory leak investigation: \- Class histograms, GC pressure snapshots, JFR recordings, heap dumps, MAT leak suspects analysis, async-profiler allocation profiles \- Structured confidence-based verdicts (none/low/medium/high) requiring independent signal corroboration \- Designed for use inside Claude Code with two slash commands84Apache 2.0
- AlicenseNot gradedqualityDmaintenanceThis MCP server provides read-only Linux system diagnostics tools for inspecting system information, processes, and log snapshots. It enables AI models to analyze Linux system health, troubleshoot issues, and review security through workflow prompts and HTTP transport with API key authentication.MIT
- AlicenseAqualityDmaintenanceMCP server for profiling Java applications via JDK utilities (jcmd, jfr, jps). Enables AI assistants to diagnose performance, analyze threads, and inspect JFR recordings without manual CLI usage.26113 npm10MIT