Skip to content

Add an MCP server for AI assistants to query heap dumps (3/4) - #123

Open
miguelnieto wants to merge 3 commits into
vlsi:mainfrom
miguelnieto:03_mcp-server
Open

miguelnieto wants to merge 3 commits into
vlsi:mainfrom
miguelnieto:03_mcp-server

Conversation

@miguelnieto

@miguelnieto miguelnieto commented Oct 17, 2026 •

Copy link
Copy Markdown

Summary

Adds a headless Model Context Protocol server, so that AI assistants (Claude Code and other MCP clients) can investigate heap dumps. They query with SQL, use MAT's per-object actions, and run analyses that return data rather than prose.

./MemoryAnalyzer -nosplash -application MatCalcitePlugin.mcp
claude mcp add mat-sql -- /path/to/mat/MemoryAnalyzer -nosplash -application MatCalcitePlugin.mcp

Stacked on #122 (SQL editor), which is stacked on #121 (MAT 1.17 / Java 21 / Calcite 1.42). Until those are merged, this PR also shows their commits. #129 (4/4, security updates) builds on this one. Review only 5f72e7d, or see just this PR's changes.

How it works

  • The server speaks MCP on stdio, JSON-RPC one message per line, using the MAT install the plugin is in. It is a new Equinox application, MatCalcitePlugin.mcp, in plugin.xml.
  • No new dependencies. JSON uses json-smart, which is already bundled.
  • Dumps stay open between calls. The first open builds MAT's indexes; later opens reuse them.
  • Requests run one at a time on a worker thread, since snapshots aren't shared between threads. Cancellation and ping are handled at once on the reading thread, so they work while a long query runs.
  • Every request gets an answer, even when a tool throws, so a client never hangs.
  • The code is in com.github.vlsi.mat.calcite.mcp. That package is exported only to the test bundle (x-friends). McpServer handles JSON-RPC without stdio, so the tests drive it directly.

Tools

Area Tools
Dumps and schema open_heap_dump, list_heap_dumps, close_heap_dump, list_tables, describe_table
SQL sql_query (markdown, row limit), explain_sql, sql_reference, export_query (CSV, all rows)
Per object, as on MAT's context menu object_info, list_references (incoming / outgoing), retained_set, path_to_gc_roots, immediate_dominators
Any other MAT command mat_query, e.g. find_leaks, top_consumers, dominator_tree
Analyses heap_overview, histogram, leak_suspects, compare_dumps, collection_contents, duplicate_strings, wasteful_collections
Control cancel_query

Results show objects as class @ 0xaddress, and the object tools take those addresses, so an assistant can chain calls. For example, it can find the biggest maps with sql_query, then ask path_to_gc_roots why one is alive.

Some of the tools in more detail:

  • leak_suspects returns MAT's leak suspects as data rather than report text:
    • the suspect's class loader;
    • the accumulation point, with the path from a GC root to it;
    • for a group, its biggest members;
    • for a thread, the stack frames whose locals hold memory.
  • duplicate_strings counts string values by a 64-bit hash, not by keeping each text in memory.
  • wasteful_collections counts 4 bytes per unused slot when the dump uses compressed pointers.

Cancellation

  • MCP notifications/cancelled, the cancel_query tool, and timeout_seconds (default 300 s; opening a dump has no default limit).
  • No Thread.interrupt(). MAT reads its index files through a FileChannel, which an interrupt closes for good, and that breaks the dump until it is reopened. Instead, each call sets a flag (QueryCancellation, from SQL editor: schema browser, Open in SQL Editor, cancel, date functions (2/4) #122) that the heap scan, the analysis loops and MAT's progress listener check.
  • A call cancelled while it still waits in the queue never runs.
  • A cancelled open_heap_dump deletes the index files it created, matching only MAT's index file names.

Security

The assistant reads heap contents. A heap can hold text written by outsiders, such as request bodies, messages or log lines, possibly written to steer an assistant. The server's instructions tell the assistant to treat heap text as data, never as instructions. Beyond that, the server limits what it can do:

  • No general file writes. export_query writes only .csv files inside the export directory: MAT_MCP_EXPORT_DIR, or the dump's directory. It refuses other paths (../ and symlinks included), writes through a temporary file moved atomically, and adds a leading ' to cells and headers that a spreadsheet would run as formulas.
  • mat_query refuses MAT commands that read or write files, open other dumps or run reports, judged by each argument's type and its Argument.Advice. Examples: export_hprof, delta_histogram -baseline, create_report. compare_dumps compares dumps that are already open.
  • open_heap_dump accepts only regular files.
  • SQL reaches only the heap schema: Calcite runs without extra function libraries or DDL, and the plugin's functions only read the heap.
  • Runaway work stops.
    • Infinite recursive queries, huge cross joins and pathological histogram regexes (e.g. ^(.+)+\1!) stop at the timeout.
    • A query that exhausts memory returns an error, and the server keeps working.
    • Cells are capped at 200 characters and results at 5,000 rows.

Testing

  • ./mvnw verify with JDK 21: 101 tests pass, 1 skipped (@Ignore on main too). McpServerTests covers:

    • the protocol;
    • every tool;
    • cancellation and timeouts, including a cancel that arrives before its call starts, and request-id reuse;
    • the security refusals, and the export rules;
    • cleanup after a failed open.
  • Over stdio with Claude Code: investigations on the test dump and on a 1.5 GB dump with 24M objects. Timings on the large dump:

    Tool Time
    duplicate_strings (4M strings) 13 s
    wasteful_collections 24 s
    heap_overview, leak_suspects under 1 s

    Two servers can run on one MAT install at the same time.

Notes for reviewers

  • Possible MAT bug found along the way. Before indexing app.hprof, MAT deletes old index files matching app + ([A-Za-z0-9]{1,20}\.)?index. That pattern also matches app.2.index, the index of a sibling dump app.2.hprof. The cleanup in this PR uses only MAT's exact index names, so it doesn't make this worse.
  • Known limits:
    • duplicate_strings needs about 80 bytes per distinct value, which matters only for dumps with tens of millions of distinct strings;
    • a single regex match inside SQL (SIMILAR TO on a huge string) can't be interrupted;
    • export size is bounded by the timeout, not by a size limit.

🤖 Generated with Claude Code

@miguelnieto
miguelnieto force-pushed the 03_mcp-server branch 2 times, most recently from afc0ddc to bbbdd11 Compare October 17, 2026 01:21
@miguelnieto miguelnieto changed the title Add an MCP server for AI assistants to query heap dumps (3/3) Add an MCP server for AI assistants to query heap dumps (3/4) Nov 7, 2026
miguelnieto and others added 3 commits November 7, 2026 16:45
BREAKING: requires MAT 1.17.0+ and Java 21. org.eclipse.mat.api 1.17.0
declares Bundle-RequiredExecutionEnvironment: JavaSE-21, so the execution
environment cannot go lower. Users on older MAT must stay on 1.6.x.

Platform
- Target platform moved from Eclipse 2022-12 to Eclipse 4.40, matching the
  Eclipse release MAT 1.17.0 builds against.
- MAT bundles come from the MAT 1.17.0 update site rather than an Eclipse
  simultaneous release, so the MAT API version matches the MAT users run.
  Only the units this plugin needs are listed; the full
  org.eclipse.mat.feature.feature.group would drag in mat.chart and
  therefore BIRT and Rhino, which are unused.
- Java 21: BREE, executionEnvironment, compiler source/target, CI matrix,
  README requirements.

Calcite 1.36 -> 1.42.0
- Aligned Calcite/Guava versions between MatCalcitePlugin/pom.xml and
  MatCalciteTargetPlatform.target (Guava 33.7.2-jre in both, after
  Renovate updated the pom only). They had drifted apart (1.41.0 vs 1.36.0)
  because Renovate only updates poms, not Tycho target definitions.
- Avatica 1.29.0 instead of the 1.28.0 Calcite 1.42.0 asks for: Avatica
  before 1.29.0 has CVE-2026-70410 (unsafe reflection in plugin
  instantiation, CVSS 8.8). Declared before calcite-core so Maven compiles
  against the Jackson 2.22.2 it brings, which the target ships.
- Added httpclient5/httpcore5 (new Avatica 1.28 dependencies) and
  org.jooq:joou-java-6 to the target platform and to Import-Package. Without
  them: NoClassDefFoundError for org/apache/hc/core5/util/Timeout and
  org/joou/UInteger.
- Excluded jakarta.transaction:jakarta.transaction-api from commons-dbcp2:
  it has an unsatisfiable Import-Package (javax.enterprise.context) and
  blocked geronimo-jta_1.1_spec from supplying javax.transaction.

ORDER BY and joins on heap objects
- Calcite 1.37+ sorts Map-typed values with Functions.compareMaps(), which
  calls entrySet(); HeapReference is a Map only for this['field'] and threw
  from entrySet(), so every ORDER BY over a heap object failed. entrySet()
  now returns one entry that orders like compareTo.
- Calcite sorts merge-join inputs through entrySet() but merges them with
  compareTo, so both must give the same order, or matches are dropped (the
  class/class loader join counted 469 instead of 985). Both now order by
  address, which is also constant time: ordering by label rebuilt the label
  on every comparison (ORDER BY this on 4M objects: about 2 min vs 10 s).
  ORDER BY toString(this) gives label order.

SQL functions
- getHexAddress(x): the object address as 0x..., as MAT shows it.
- getDominator(x) returned an error for objects dominated by the virtual
  root; it now returns null.

Tests
- testStackTraces tolerates MAT's own stack frame formatting, which changed
  in MAT commit 0c72944e ("Bug 581644"): "(Native Method)" became
  "(Object.java(Native Method))".
- New: ORDER BY this in both directions, a merge join over a strict subset,
  the class/class loader join phrased three ways (985 each).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r, date functions

Editor
- Right-click menu (Run, Explain, Undo/Redo, Cut/Copy/Paste, Delete, Select
  All, Toggle Comment); keys are workbench commands in their own context, so
  F5, Ctrl+Enter and Ctrl+Space no longer go to MAT's OQL commands.
- Schema browser beside the editor: classes by package, instanceof, thread
  stacks and functions, with columns and sizes; double-click inserts the
  name, quoted where SQL needs it. The filter is debounced and uses cached
  sorted names.
- "SQL Reference" tab; theme colors (with dark theme defaults); completion
  pops up after '.' and '"'; object icons in results; navigation history
  restores and reruns a query.
- Error underline at the right place for a selection and for Explain, and
  cleared on the next run. Cancel in the progress view and closing the tab
  stop a running query (QueryCancellation, checked by the heap scan).
- Fixes: keys lost after a table result on GTK/Wayland (no active shell),
  editor scroll jump on a transient resize, Undo/Redo keys, leaked title
  image and colors, closed-tab cleanup, debug output on stdout.

Open in SQL Editor
- On MAT's object menus: a query for the selected objects (by address, one
  statement per class) or for a class row (all instances, or the row's
  objects for a partial histogram), added to the SQL tab of the objects' own
  dump.

SQL
- Date and time functions: toDateTime, toEpochMillis, millisToDateTime,
  secondsToDateTime, toDuration.
- instanceof."java.lang.ref.Reference" (full class name as one identifier);
  identifiers quoted by Calcite's reserved words, not only SQL-92's.
- CALCITE-1.42-QUERIES.md: what Calcite 1.37-1.42 adds to heap queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MemoryAnalyzer -nosplash -application MatCalcitePlugin.mcp serves the Model
Context Protocol on stdio (JSON-RPC with json-smart, already bundled; no new
dependencies). Dumps stay open between calls.

Tools
- Dumps and schema: open/list/close, list_tables, describe_table.
- SQL: sql_query (markdown, row limit), explain_sql, sql_reference,
  export_query (CSV, no row limit).
- Per object, as on MAT's context menu: object_info, list_references,
  retained_set, path_to_gc_roots, immediate_dominators; mat_query for other
  MAT commands.
- Analyses: heap_overview, histogram, leak_suspects (as data: class loader,
  accumulation point, path from a GC root, thread stack locals),
  compare_dumps, collection_contents, duplicate_strings,
  wasteful_collections.

Cancellation
- notifications/cancelled, cancel_query and timeout_seconds. A flag per call
  (QueryCancellation), never Thread.interrupt: MAT reads its indexes through
  a FileChannel, which an interrupt closes for good. A cancelled open
  deletes the index files it created.

Security
- The assistant reads heap contents, which can hold text written to steer
  it. mat_query refuses commands that read or write files, open other dumps
  or run reports; export_query writes .csv only inside the export directory
  (MAT_MCP_EXPORT_DIR or the dump's), atomically, with formula cells
  defused. Slow regular expressions in histogram filters stop on timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant