{
  "markdown": "# jvmsrc — Give your coding agent a Java IDE\n\n<div align=\"center\">\n\n[![CI](https://github.com/Sintexer/jvm-source-lens/actions/workflows/ci.yml/badge.svg)](https://github.com/Sintexer/jvm-source-lens/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/jvmsrc)](https://www.npmjs.com/package/jvmsrc)\n[![License: MIT](https://img.shields.io/github/license/Sintexer/jvm-source-lens)](https://github.com/Sintexer/jvm-source-lens/blob/master/LICENSE)\n[![Node](https://img.shields.io/node/v/jvmsrc)](https://www.npmjs.com/package/jvmsrc)\n\n**An MCP server and CLI** that gives your coding agent the one thing it's missing on JVM codebases: **the actual classpath**.\n\n</div>\n\n---\n\n### The Problem\nYou use an IDE to write Java. Your coding agent doesn't have one.\n\nWhen your agent hits an unfamiliar library type — say, a superclass from a proprietary internal library — it spends **25+ turns** walking `~/.gradle/caches`, opening JARs by hand with `jar tf`, picking one by guesswork, and trying to answer a question your IDE would answer in one keystroke: *does this superclass have a public utility method called X?*\n\n### The Solution\n`jvmsrc` queries your build tool (Gradle) for *this project's* resolved classpath, then hands your agent **real source**, **real signatures**, and **real structure** — for the exact version your build actually uses.\n\n* **As an MCP server** – Connect to Claude Code, Cursor, Windsurf, or any other MCP host and equip your agent with six purpose-built classpath tools.\n* **As the `jvmsrc` CLI** – Same engine, scriptable, pipe-friendly, and useful on its own when you just want to read a class.\n\n<p align=\"center\">\n  <img src=\"docs/assets/jvmsrc-demo.gif\" alt=\"jvmsrc resolving a Spring class in Claude Code\" width=\"100%\" />\n  <br />\n  <em>Claude Code resolving HandlerInterceptor signatures via jvmsrc — 3 calls, correct answer, no cache walking.</em>\n</p>\n\n---\n\n## What it saves, daily\n\nOn JVM projects with private libraries and no public Javadocs, `jvmsrc` is a force multiplier:\n\n* **~50K tokens saved** per \"what's on this external class\" investigation — roughly **70%** reduction in cost.\n* **15+ agent panic loops avoided** daily — no grinding through Gradle caches, opening wrong JARs, or giving up.\n* **Unlock delegation** for complex tasks touching internal SDKs that you previously had to do yourself.\n\n---\n\n## The Difference in Action\n\n### Without `jvmsrc` (The 25-turn blind grind)\n\n> **User:** Override the audit hook from `AbstractTradingService` — is there a public utility method on it we should reuse?\n>\n> **Agent:**\n> 1. *Searches workspace for AbstractTradingService.java* ➔ `0 hits`\n> 2. *Runs:* `find ~/.gradle -name \"trading-core*\"`\n> 3. *Finds 4 versions:* `2.1.0`, `2.3.0`, `2.4.1`, `3.0.0-SNAPSHOT`\n> 4. *Guesses:* Picks `trading-core-2.4.1.jar` (the project actually uses `3.0.0-SNAPSHOT`!)\n> 5. *Runs:* `jar tf` and `javap -p` on the wrong JAR\n> 6. **[22 turns later]** *\"I don't see a utility method, you'll have to implement it yourself.\"*\n>\n> **Reality:** `3.0.0-SNAPSHOT` added `maskSensitiveFields()` as a protected helper exactly for this case. The agent didn't hallucinate — it was just blind.\n\n### With `jvmsrc` (The 3-turn instant resolution)\n\n> **User:** Override the audit hook from `AbstractTradingService` — is there a public utility method on it we should reuse?\n>\n> **Agent:**\n> 1. `search_classes(\"AbstractTradingService\")` ➔ Finds FQN & exact resolved library.\n> 2. `get_class_structure(scope: \"overview\")` ➔ Discovers `maskSensitiveFields()` in `3.0.0-SNAPSHOT`.\n> 3. `get_method_signature(\"maskSensitiveFields\")` ➔ Gets accurate signature and generics.\n>\n> **Result:** Writes the override correctly on the first try. No cache walking, no guessing, no wrong version.\n\n---\n\n## How It Works\n\n1. **Build Tool Inquiry:** `jvmsrc` queries your active build tool (e.g., Gradle) for the exact resolved classpath configuration.\n2. **Intelligent Caching:** It caches the resolved classpath, tracking changes to build files to stay current.\n3. **Precision AI Tools:** Instead of full-code dumping, it exposes precise, high-granularity tools (signatures, structure, search) to keep context windows small and token usage ultra-low.\n\n---\n\n## Installation & Quick Start\n\n### 1. Install CLI\n```bash\nnpm install -g jvmsrc\n# or use it directly via npx: npx jvmsrc <command>\n```\n\n> [!IMPORTANT]  \n> Requires **Node ≥ 20** and **Java on `PATH`** (for CFR decompiler + `javap`).\n\n### 2. Add the MCP server\n\nPaste this into your AI assistant config (Cursor, Claude Code, Windsurf, etc.), then restart the host:\n\n```json\n{\n  \"mcpServers\": {\n    \"jvmsrc\": {\n      \"command\": \"jvmsrc\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nOptional: `jvmsrc config` (or `jvmsrc config --project /path/to/gradle-project`) prints a paste-ready block plus environment hints. Most users can skip it and copy the snippet above.\n\n---\n\n## MCP Server Reference\n\nThe MCP server runs over stdio via `jvmsrc mcp`. The default config needs no environment variables:\n\n```json\n{\n  \"mcpServers\": {\n    \"jvmsrc\": {\n      \"command\": \"jvmsrc\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\n### Private repository credentials (optional)\n\nOnly needed when your Gradle build requires credential env vars for a private Maven/Artifactory/Nexus-style repo. MCP hosts often do **not** inherit your interactive shell, so those vars must be set on the **jvmsrc MCP process** (then restart the server).\n\n`REPO_USER` / `REPO_PASS` below are **sample names only** — they are not required by jvmsrc. Use whatever variable names your project’s Gradle scripts document:\n\n```json\n{\n  \"mcpServers\": {\n    \"jvmsrc\": {\n      \"command\": \"jvmsrc\",\n      \"args\": [\"mcp\"],\n      \"env\": {\n        \"REPO_USER\": \"your-username\",\n        \"REPO_PASS\": \"your-password\"\n      }\n    }\n  }\n}\n```\n\nOmit the `env` block entirely when the project does not need them.\n\n### Tools your agent gets\n\n| Tool | What it does |\n|:---|:---|\n| **`search_classes`** | Find a class by simple name or glob; returns compact FQN + lib name lists |\n| **`get_class_structure`** | Retrieves class overview (purpose + method names) or declared signatures |\n| **`get_method_signature`**| Fetches real overloads for a method, with parameter names and generics |\n| **`find_in_class_source`**| Performs regex or substring searches inside a resolved class |\n| **`get_class_source`** | Retrieves method bodies or line ranges (used as a last resort) |\n| **`search_in_artifact`** | Greps text across all classes in one resolved dependency JAR |\n| **`resolve_dependencies`**| Analyzes the actual dependency graph this project uses |\n\n> [!TIP]  \n> Every source response includes `sourceAvailable`: `true` for real sources (Javadoc, parameter names, generics), `false` for CFR decompilation (structure reliable, names may be synthetic).\n\n> [!NOTE]  \n> **Multimodule:** omit `modulePath` and jvmsrc auto-picks the unique owning module; on a miss it lists candidate `modulePath`s. **Methods:** `search_classes` matches declared method names when the index has source enrichment; for body text in a known JAR use `search_in_artifact`. `get_class_source` `methodNames` also walks superclasses for unmatched names.\n\n---\n\n## How It Compares\n\n| Tool | Approach | Gap |\n| :--- | :--- | :--- |\n| **Cache Indexers** / `~/.gradle` grep | Scan global caches | No per-project resolved version |\n| **Static Parsers** (e.g., `build.gradle` parser) | Parse declarations only | Misses transitive dependencies, BOMs, dynamic versions |\n| **`mcp-javadoc`** / path-only CFR | User supplies manual JAR paths | No automatic build/classpath resolution |\n| **Gradle MCP** (Tooling API) | Task/build focused | Not optimized for classpath-accurate FQN source lookup |\n| **`jvmsrc`** | **Queries actual build tool & caches** | **Version-correct sources and signatures for agents** |\n\n---\n\n## Target Audience\n\nPrimarily **Java + Spring Boot** projects on Gradle. Other JVM languages (Kotlin, Scala) and Android work today on a best-effort basis and are on the roadmap as first-class targets — see [ROADMAP.md](ROADMAP.md).\n\n*If you're on Maven or Bazel, it's planned but not shipping yet. Star the repo or open an issue and I'll prioritize accordingly.*\n\n---\n\n## Detailed Reference\n\n<details>\n<summary>Requirements & Compatibility</summary>\n\n**Runtime:** Node.js ≥ 20, Java on `PATH`.\n\n**Project types:** JVM codebases (Java, Kotlin, Scala, Groovy). `jvmsrc` calls the build tool, not your editor.\n\n| Build system | Status |\n|---|---|\n| **Gradle** | Supported — multimodule included |\n| Maven, Bazel | Planned ([SPEC.md](SPEC.md)) |\n\nPoint `-p` / `projectRoot` at the Gradle root (`settings.gradle(.kts)` or root `build.gradle(.kts)`). Uses `./gradlew` when present, else `gradle` on `PATH`. Maven-only trees get an explicit unsupported error.\n\n</details>\n\n<details>\n<summary>Known Limitations</summary>\n\nEarly software; the supported path is narrow:\n\n| Area | Today |\n|---|---|\n| Build tool | **Gradle only** |\n| Integration | **Groovy init script** (`--init-script`) — not a Gradle Portal plugin |\n| Classpaths | Standard JVM + Kotlin MPP `jvm*` configurations when Gradle exposes them |\n| Output | **Java-shaped** `.java` text (sources JAR, inter-project `src`, or CFR) |\n\nComposite builds, Android-only layouts, and exotic configurations are not fully validated. See [ROADMAP.md](ROADMAP.md).\n\n</details>\n\n<details>\n<summary>Security & Privacy</summary>\n\n* **No telemetry.**\n* **Local only** — caches and diagnostics stay on disk; never writes under your project root.\n* **Subprocesses** via argv only (no shell interpolation) — see [SECURITY.md](SECURITY.md).\n* Optional `JVMSRC_ALLOWED_ROOTS` to lock down which projects jvmsrc may resolve.\n\n</details>\n\n<details>\n<summary>CLI Command Reference</summary>\n\n```bash\njvmsrc com.example.MyClass -p /path/to/gradle-project          # shorthand for get\njvmsrc get com.example.MyClass -p /path/to/project -q > MyClass.java\njvmsrc resolve -p /path/to/project --force-refresh\njvmsrc config jdk-roots add /path/to/jdks                      # one-time JDK roots setup\njvmsrc doctor java -p /path/to/project                         # check JDK requirement + selection\njvmsrc diagnostics last                                         # latest failure message\njvmsrc mcp                                                     # run as MCP server\n```\n\n**Useful flags:** `-p` / `--project`, `--module` (`:core:api`), `--configuration`, `--include-test`, `--force-refresh`, `--verbose` (Gradle stderr only), `--method`, `--start-line` / `--end-line`.\n\n**Repo fixture for testing:**\n`test/fixtures/gradle-smoke` —\n`jvmsrc get com.smoke.Core -p test/fixtures/gradle-smoke --module :core`.\n\n</details>\n\n<details>\n<summary>Troubleshooting</summary>\n\n* **Resolution failures:** Run `jvmsrc diagnostics last` (or `jvmsrc diagnostics last 5`)\n* **Custom JDK install roots:** Add once with `jvmsrc config jdk-roots add /path/to/jdks`\n* **JDK mismatch debugging:** Run `jvmsrc doctor java -p /path/to/project`\n* **After upgrading jvmsrc:** Restart your MCP host\n* **Stale classpath:** Run `jvmsrc resolve --force-refresh`\n\n</details>\n\n<details>\n<summary>Environment Variables</summary>\n\n| Variable | Purpose |\n|---|---|\n| `JVMSRC_JAVA_HOME` | Force JDK home for Gradle/CFR child processes |\n| `JVMSRC_CONFIG_DIR` | Global jvmsrc config directory (absolute) |\n| `JVMSRC_CACHE_ROOT` | Cache root (absolute) |\n| `JVMSRC_LOG_DIR` | Diagnostic logs (absolute) |\n| `JVMSRC_ALLOWED_ROOTS` | Allowed `projectRoot` prefixes |\n| `JVMSRC_MAX_SOURCE_OUTPUT_CHARS` | Max source body size (default 524288) |\n| `JVMSRC_GRADLE_TIMEOUT_MS` | Gradle timeout |\n| `JVMSRC_CFR_PATH` | Custom CFR JAR |\n\nDefaults follow [`env-paths`](https://www.npmjs.com/package/env-paths) conventions per OS. Full layout: [SPEC.md](SPEC.md) §6.\n\nWhen `JVMSRC_JAVA_HOME` is not set, jvmsrc auto-discovers local JDKs from common paths such as `~/.jdks` (IntelliJ), `~/.gradle/jdks`, SDKMan, jenv, asdf, and OS-specific system install directories, plus your global configured JDK roots from `jvmsrc config jdk-roots ...`.\n\n</details>\n\n<details>\n<summary>AI Agent Reviews</summary>\n\n> ### Finally, an MCP That Doesn't Make Me Decompile JARs\n>\n> *\"This tool is a revelation for anyone tired of LLMs hallucinating non-existent Spring APIs. It actually reads bytecode, providing accurate class definitions and source lookups without the usual 'vibes-based' guesswork. The `search_classes` functionality is incredibly precise, and the thoughtful implementation of `javap` fallback and granular scope controls (`overview`/`declared`/`effective`) makes navigating complex JARs painless. It’s fast, honest when it can't find a class, and handles cache management perfectly. A must-have for any dev struggling with dependency hell — it’s like having a senior engineer who actually enjoys reading documentation.\"*\n> — **Claude (AI Reviewer)**\n\n</details>\n\n---\n\n## Project Documentation\n\n| Document | Contents |\n|:---|:---|\n| [SPEC.md](SPEC.md) | Schemas, contracts, CLI/MCP details |\n| [CONTRIBUTING.md](CONTRIBUTING.md) | Build, test, PR notes |\n| [RELEASING.md](RELEASING.md) | Branching, semver, npm releases |\n| [CHANGELOG.md](CHANGELOG.md) | Version history |\n| [ROADMAP.md](ROADMAP.md) | Status and planned work |\n| [SECURITY.md](SECURITY.md) | Vulnerability reporting |\n\n## Building from Source\n\n```bash\ngit clone https://github.com/Sintexer/jvm-source-lens.git\ncd jvm-source-lens\nbun install && bun run setup:cfr && bun run build\nnode dist/cli.js --version\n```\n\nFull contributor workflow: [CONTRIBUTING.md](CONTRIBUTING.md).\n\n---\n\nI built `jvmsrc` because I kept running into the same wall: agents that are great at writing Java but blind to the actual classpath. If it saves you the same 25-turn grind it saved me, that's exactly why this exists. Found a bug, have an idea, or just want to say it helped? Open an issue or a PR — I read everything.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n",
  "bytes": 13911,
  "sha": "7bfb857a3c6ba95f86062a448a830cd1b8027d4c1e871d072d4defe638dba713",
  "repo_slug": "sintexer/jvm-source-lens",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_sintexer_jvmsrc_8a85e59c/readme"
}