Native agent + MCP server that exposes a running HotSpot JVM to LLMs.
See plan/plan.md for the full design.
- M0 — minimal Java target (
target/Target.java) — done - M1 — JVMTI agent that mutates a static field from C++ — done
- M2 — JSON-RPC over unix socket, driven by
nc— done - M3 — MCP server fronting the agent, registered with Claude Code — done
- M4 — instance handles, find_instances, get/set_field, invoke, release, list_classes, describe_class — done
- M4.5 — constructor support in invoke, object handles in set_*_field — done
- M5-minimal — define_class, redefine_class — done (ASM helper jar deferred)
- Limitation pass — full modifier surface, char/byte/short types, array tools (array_length/get/set) — done
- M6-M8 — see plan
brew install openjdk@21 cmake
echo 'export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"' >> ~/.zshrc# Java target
( cd target && javac Target.java )
# Native payload
( cd payload && JAVA_HOME=/opt/homebrew/opt/openjdk@21 cmake -S . -B build && cmake --build build )Produces payload/build/libhotwire_payload.dylib (Linux: .so, Windows: .dll).
Start the target with the agent:
java -agentpath:$PWD/../payload/build/libhotwire_payload.dylib -cp $PWD TargetThe agent prints listening on /tmp/hotwire-<pid>.sock. Drive it from another
terminal with nc -U:
SOCK=/tmp/hotwire-$(pgrep -f 'Target$').sock
# smoke test
echo '{"id":1,"method":"ping"}' | nc -U "$SOCK" -w 1
# read counter
echo '{"id":2,"method":"get_static_field","params":{"class":"Target","field":"counter","type":"I"}}' | nc -U "$SOCK" -w 1
# overwrite counter
echo '{"id":3,"method":"set_static_field","params":{"class":"Target","field":"counter","type":"I","value":99999}}' | nc -U "$SOCK" -w 1
# tell the target to exit
echo '{"id":4,"method":"set_static_field","params":{"class":"Target","field":"running","type":"Z","value":false}}' | nc -U "$SOCK" -w 1Each nc -U ... -w 1 opens a fresh connection and times out after 1s of
silence. Multiple requests on a single connection are supported (one per line)
but nc makes that awkward; the dedicated CLI client comes in M3.
| method | params | result |
|---|---|---|
ping |
— | "pong" |
get_static_field |
{class, field, type} |
field value (matches type) |
set_static_field |
{class, field, type, value} |
"ok" |
type is a JNI signature: I int, J long, Z boolean, F float, D double.
class uses internal slash-form (java/lang/String, not java.lang.String).
target/ Minimal Java target used for M0/M1
payload/ C++ JVMTI agent (the meat)
mcp/ TypeScript MCP server (M3+)
demo-game/ JavaFX game used as the headline demo target (M6+)
plan/ Design doc