@adhisang/minecraft-modding-mcp 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,55 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.0] - 2026-03-05
9
+
10
+ ### Added
11
+ - New `find-class` tool to resolve simple or fully-qualified class names to concrete source paths inside an indexed artifact.
12
+ - `resolve-artifact` / `get-class-source`: `scope` (`vanilla`/`merged`/`loader`), `preferProjectVersion`, `strictVersion`, `projectPath` parameters for workspace-driven resolution.
13
+ - `get-class-source`: `mode` (`metadata`/`snippet`/`full`), `maxChars`, `outputFile` parameters for token-efficient source retrieval.
14
+ - `search-class-source`: `queryMode` (`auto`/`token`/`literal`) controls FTS5 vs substring scan behavior.
15
+ - `get-mod-class-source`: `maxLines`, `maxChars`, `outputFile` truncation parameters matching `get-class-source` behavior.
16
+ - `find-mapping`: `disambiguation` hints (`ownerHint`, `descriptorHint`) to narrow ambiguous candidates; `ambiguityReasons` in `status=ambiguous` responses.
17
+ - `get-class-api-matrix`: `ambiguousRowCount` when ambiguity fallback is applied.
18
+ - `check-symbol-exists`: `nameMode` (`fqcn`/`auto`) and `signatureMode` (`exact`/`name-only`) parameters.
19
+ - `analyze-mod-jar`: `jarKind` (`binary`/`source`/`mixed`) in response.
20
+ - `resolve-artifact`: `sampleEntries` when a source JAR is resolved.
21
+ - `validate-mixin`: major enhancements — `sourcePath`, `sourcePaths`, `mixinConfigPath` (with `sourceRoot`/`sourceRoots[]` for batch auto-discovery), `explain` mode with `explanation`/`suggestedCall` on issues, `resolvedMembers` tracking, `category`/`issueOrigin` classification, `structuredWarnings` with severity, `warningCategoryFilter`, `treatInfoAsWarning`, `reportMode`, `preferProjectMapping`, `provenance` with `resolutionNotes`, `toolHealth`, `confidenceScore`, batch `processingErrors`/`totalValidationErrors`/`totalValidationWarnings`.
22
+ - Error details: structured `suggestedCall` (`{ tool, params }`) for agent-driven recovery on `get-class-source` and `resolve-artifact` errors.
23
+
24
+ ### Fixed
25
+ - `validate-mixin`: JVM descriptor false positives — method references with descriptors (e.g. `playerTouch(L...;)V`) are now stripped before comparison.
26
+ - `validate-mixin`: array-form `method = {"m1", "m2"}` parsing, `@Accessor(value = "name")` syntax, `@Accessor`/`@Invoker` parse failure escalation to `issues[]`.
27
+ - `validate-mixin`: `@Invoker` validation now checks methods only (not fields), preventing false negatives.
28
+ - `validate-mixin`: mapping fallback failures reported as `target-mapping-failed` warning (not `target-not-found` error).
29
+ - `validate-mixin`: multi-line annotation handling between `@Shadow`/`@Accessor` and declarations; inline annotations stripped from declaration lines.
30
+ - `validate-mixin`: `@Mixin(targets = "...")`/`targets = {"a", "b"}` string forms supported; simple `@Mixin` class names resolved via imports.
31
+ - `validate-mixin`: Fabric Loom split source sets and multi-module `sourceRoots` auto-discovery across common/fabric/neoforge/forge/quilt main+client paths.
32
+ - `validate-mixin`: parser supports `$` in names, FQN/generic/array/wildcard member signatures, `default`/`synchronized` modifiers, `interface` declarations.
33
+ - `validate-mixin`: `sourcePath` normalized with host/WSL path conversion; bytecode signature members remapped to requested mapping before validation.
34
+ - `validate-mixin`: `hideUncertain`/`minSeverity` filtering now recomputes `summary.parseWarnings` from filtered issues.
35
+ - Vineflower flags now passed before positional `<input-jar> <output-dir>` arguments, fixing false `ERR_DECOMPILER_FAILED` errors.
36
+ - Vineflower decompilation retries with fallback flag profiles on failure.
37
+ - `get-artifact-file`: UTF-8 boundary-safe byte truncation prevents malformed replacement characters.
38
+ - Version approximation detection avoids prefix false positives (e.g. `1.21.1` vs `1.21.10`).
39
+ - Classifier source-jar resolution correctly picks `<artifact>-<version>-<classifier>-sources.jar`.
40
+ - Default cache dir to `~/.cache/minecraft-modding-mcp` when `MCP_CACHE_DIR` unset.
41
+ - Cursor validation: invalid non-empty cursors now throw `ERR_INVALID_INPUT` instead of silently returning first page.
42
+ - `check-symbol-exists`: `ERR_INVALID_INPUT` for invalid symbol combinations before evaluating mapping availability.
43
+ - `resolve-artifact` mapping failures for version targets include actionable diagnostics (`searchedPaths`, `candidateArtifacts`, `recommendedCommand`).
44
+ - `search-mod-source` detects source-only jars and searches `.java` entries directly without decompilation.
45
+ - `CLASS_NOT_FOUND` / `MAPPING_NOT_APPLIED` errors include improved scope/context fields and recovery guidance.
46
+ - Mojang proguard mapping: JVM descriptor parsing fixes.
47
+
48
+ ### Changed
49
+ - Lazy `SourceService` initialization — deferred until first tool/resource access, reducing cold-start latency during MCP handshake.
50
+ - Eagerly init `SourceService` during MCP handshake idle time for faster first-request response.
51
+ - Codecov workflow temporarily disabled.
52
+
53
+ ### Performance
54
+ - Avoid duplicate UTF-8 decode during truncation.
55
+ - Eager `SourceService` init during handshake idle time.
56
+
8
57
  ## [1.1.1] - 2026-03-02
9
58
 
10
59
  ### Fixed
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  It lets you explore decompiled Minecraft source, convert symbol names across four naming namespaces (`official`, `mojang`, `intermediary`, `yarn`), analyze and decompile Fabric/Forge/NeoForge mod JARs, validate Mixin and Access Widener files, read and patch NBT data, and query generated registry snapshots — all through a structured tool and resource interface designed for Claude Desktop, VS Code, and other MCP-capable clients.
15
15
 
16
- **28 tools** | **7 resources** | **4 namespace mappings** | **SQLite-backed cache**
16
+ **29 tools** | **7 resources** | **4 namespace mappings** | **SQLite-backed cache**
17
17
 
18
18
  ## Features
19
19
 
@@ -57,6 +57,8 @@ codex mcp list
57
57
 
58
58
  The stdio transport auto-detects both newline-delimited and `Content-Length` framing, so Codex and newline-based MCP clients can use the same server command.
59
59
 
60
+ The server now lazily initializes heavyweight source/index services on first MCP request, reducing initial process startup latency for clients that only perform handshake/tool discovery.
61
+
60
62
  #### Gemini CLI
61
63
 
62
64
  Add the following to `~/.gemini/settings.json`:
@@ -114,7 +116,7 @@ Generate LCOV output for Codecov upload:
114
116
  pnpm test:coverage:lcov
115
117
  ```
116
118
 
117
- GitHub Actions upload workflow: `.github/workflows/codecov.yml` (triggered on `v*` tags and manual dispatch).
119
+ GitHub Actions upload workflow: `.github/workflows/codecov.yml` (temporarily disabled; when enabled, it runs on `v*` tags and manual dispatch).
118
120
 
119
121
  ### MCP Client Configuration
120
122
 
@@ -176,10 +178,11 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading/s
176
178
  | Tool | Purpose | Key Inputs | Key Outputs |
177
179
  | --- | --- | --- | --- |
178
180
  | `list-versions` | List available Minecraft versions from Mojang manifest + local cache | `includeSnapshots?`, `limit?` | `result.latest`, `result.releases[]`, `meta.warnings[]` |
179
- | `resolve-artifact` | Resolve source artifact from `version` / `jar` / `coordinate` | `targetKind`, `targetValue`, `mapping?`, `sourcePriority?`, `allowDecompile?` | `artifactId`, `origin`, `mappingApplied`, `qualityFlags[]`, `adjacentSourceCandidates?`, `warnings[]` |
180
- | `get-class-source` | Get class source by `artifactId` or resolve target on demand, with line filtering | `className`, `artifactId?`, `targetKind?`, `targetValue?`, `startLine?`, `endLine?`, `maxLines?` | `sourceText`, `returnedRange`, `truncated`, `artifactId`, mapping/provenance metadata |
181
- | `get-class-members` | Get class fields/methods/constructors from bytecode | `className`, `artifactId?`, `targetKind?`, `targetValue?`, `mapping?`, `access?`, `includeInherited?`, `maxMembers?` | `members.{constructors,fields,methods}`, `counts`, `truncated`, `context`, `warnings[]` |
182
- | `search-class-source` | Search indexed class source for symbols/text/path | `artifactId`, `query`, `intent?`, `match?`, `packagePrefix?`, `fileGlob?`, `symbolKind?`, `snippetLines?`, `includeDefinition?`, `includeOneHop?`, `limit?`, `cursor?` | `hits[]`, `relations?`, `nextCursor?`, `totalApprox`, `mappingApplied` |
181
+ | `resolve-artifact` | Resolve source artifact from `version` / `jar` / `coordinate` | `targetKind`, `targetValue`, `mapping?`, `sourcePriority?`, `allowDecompile?`, `projectPath?`, `scope?`, `preferProjectVersion?`, `strictVersion?` | `artifactId`, `origin`, `mappingApplied`, `qualityFlags[]`, `adjacentSourceCandidates?`, `sampleEntries?`, `warnings[]` |
182
+ | `find-class` | Resolve simple or fully-qualified class names inside an artifact | `className`, `artifactId`, `limit?` | `matches[]`, `total`, `warnings[]` |
183
+ | `get-class-source` | Get class source by `artifactId` or resolve target on demand (`mode=metadata` by default) | `className`, `mode?`, `artifactId?`, `targetKind?`, `targetValue?`, `projectPath?`, `scope?`, `preferProjectVersion?`, `strictVersion?`, `startLine?`, `endLine?`, `maxLines?`, `maxChars?`, `outputFile?` | `mode`, `sourceText`, `returnedRange`, `truncated`, `charsTruncated?`, `outputFile?`, `artifactId`, mapping/provenance metadata |
184
+ | `get-class-members` | Get class fields/methods/constructors from bytecode | `className`, `artifactId?`, `targetKind?`, `targetValue?`, `mapping?`, `access?`, `includeInherited?`, `maxMembers?`, `strictVersion?` | `members.{constructors,fields,methods}`, `counts`, `truncated`, `context`, `warnings[]` |
185
+ | `search-class-source` | Search indexed class source for symbols/text/path | `artifactId`, `query`, `intent?`, `match?`, `packagePrefix?`, `fileGlob?`, `symbolKind?`, `snippetLines?`, `includeDefinition?`, `includeOneHop?`, `queryMode?`, `limit?`, `cursor?` | `hits[]`, `relations?`, `nextCursor?`, `totalApprox`, `mappingApplied` |
183
186
  | `get-artifact-file` | Read full source file with byte guard | `artifactId`, `filePath`, `maxBytes?` | `content`, `contentBytes`, `truncated`, `mappingApplied` |
184
187
  | `list-artifact-files` | List indexed source file paths with cursor pagination | `artifactId`, `prefix?`, `limit?`, `cursor?` | `items[]`, `nextCursor?`, `mappingApplied` |
185
188
  | `index-artifact` | Rebuild index metadata for an existing artifact | `artifactId`, `force?` | `reindexed`, `reason`, `counts`, `indexedAt`, `durationMs` |
@@ -200,11 +203,11 @@ Tools for converting symbol names between namespaces and checking symbol existen
200
203
 
201
204
  | Tool | Purpose | Key Inputs | Key Outputs |
202
205
  | --- | --- | --- | --- |
203
- | `find-mapping` | Find mapping candidates for class/field/method symbols between namespaces | `version`, `kind`, `name`, `owner?`, `descriptor?`, `sourceMapping`, `targetMapping`, `sourcePriority?` | `querySymbol`, `mappingContext`, `resolved`, `status`, `resolvedSymbol?`, `candidates[]`, `provenance?`, `meta.warnings[]` |
206
+ | `find-mapping` | Find mapping candidates for class/field/method symbols between namespaces | `version`, `kind`, `name`, `owner?`, `descriptor?`, `sourceMapping`, `targetMapping`, `sourcePriority?`, `disambiguation?` | `querySymbol`, `mappingContext`, `resolved`, `status`, `resolvedSymbol?`, `candidates[]`, `ambiguityReasons?`, `provenance?`, `meta.warnings[]` |
204
207
  | `resolve-method-mapping-exact` | Resolve one method mapping with strict owner+name+descriptor matching | `version`, `kind` (`method`), `name`, `owner`, `descriptor`, `sourceMapping`, `targetMapping`, `sourcePriority?` | `querySymbol`, `mappingContext`, `resolved`, `status`, `resolvedSymbol?`, `candidates[]`, `provenance?`, `meta.warnings[]` |
205
- | `get-class-api-matrix` | Show one class API as a mapping matrix (`official/mojang/intermediary/yarn`) | `version`, `className`, `classNameMapping`, `includeKinds?`, `sourcePriority?` | `classIdentity`, `rows[]`, `meta.warnings[]` |
208
+ | `get-class-api-matrix` | Show one class API as a mapping matrix (`official/mojang/intermediary/yarn`) | `version`, `className`, `classNameMapping`, `includeKinds?`, `sourcePriority?` | `classIdentity`, `rows[]`, `ambiguousRowCount?`, `meta.warnings[]` |
206
209
  | `resolve-workspace-symbol` | Resolve compile-visible symbol names for a Gradle workspace (`build.gradle/.kts`) | `projectPath`, `version`, `kind`, `name`, `owner?`, `descriptor?`, `sourceMapping`, `sourcePriority?` | `querySymbol`, `mappingContext`, `resolved`, `status`, `resolvedSymbol?`, `candidates[]`, `workspaceDetection`, `meta.warnings[]` |
207
- | `check-symbol-exists` | Strict symbol presence check for class/field/method | `version`, `kind`, `name`, `owner?`, `descriptor?`, `sourceMapping`, `sourcePriority?` | `querySymbol`, `mappingContext`, `resolved`, `status`, `resolvedSymbol?`, `candidates[]`, `meta.warnings[]` |
210
+ | `check-symbol-exists` | Strict symbol presence check for class/field/method | `version`, `kind`, `name`, `owner?`, `descriptor?`, `sourceMapping`, `sourcePriority?`, `nameMode?`, `signatureMode?` | `querySymbol`, `mappingContext`, `resolved`, `status`, `resolvedSymbol?`, `candidates[]`, `meta.warnings[]` |
208
211
 
209
212
  ### NBT Utilities
210
213
 
@@ -222,9 +225,9 @@ Tools for extracting metadata from mod JARs, decompiling mod source, searching m
222
225
 
223
226
  | Tool | Purpose | Key Inputs | Key Outputs |
224
227
  | --- | --- | --- | --- |
225
- | `analyze-mod-jar` | Extract mod metadata/dependencies/entrypoints from mod JAR | `jarPath`, `includeClasses?` | `modId`, `loader`, `dependencies`, `entrypoints`, `mixinConfigs`, class stats |
228
+ | `analyze-mod-jar` | Extract mod metadata/dependencies/entrypoints from mod JAR | `jarPath`, `includeClasses?` | `modId`, `loader`, `jarKind`, `dependencies`, `entrypoints`, `mixinConfigs`, class stats |
226
229
  | `decompile-mod-jar` | Decompile mod JAR and optionally return one class source | `jarPath`, `className?` | `outputDir`, `fileCount`, `files?`, `source?`, `warnings[]` |
227
- | `get-mod-class-source` | Read one class source from decompiled mod cache | `jarPath`, `className` | `className`, `content`, `totalLines`, `warnings[]` |
230
+ | `get-mod-class-source` | Read one class source from decompiled mod cache | `jarPath`, `className`, `maxLines?`, `maxChars?`, `outputFile?` | `className`, `content`, `totalLines`, `truncated?`, `charsTruncated?`, `outputFilePath?`, `warnings[]` |
228
231
  | `search-mod-source` | Search decompiled mod source by class/method/field/content | `jarPath`, `query`, `searchType?`, `limit?` | `hits[]`, `totalHits`, `truncated`, `warnings[]` |
229
232
  | `remap-mod-jar` | Remap a Fabric mod JAR from intermediary to yarn/mojang names | `inputJar`, `targetMapping`, `mcVersion?`, `outputJar?` | `outputJar`, `mcVersion`, `fromMapping`, `targetMapping`, `resolvedTargetNamespace`, `warnings[]` |
230
233
 
@@ -234,7 +237,7 @@ Tools for validating Mixin source and Access Widener files against a target Mine
234
237
 
235
238
  | Tool | Purpose | Key Inputs | Key Outputs |
236
239
  | --- | --- | --- | --- |
237
- | `validate-mixin` | Parse/validate Mixin source against target Minecraft version | `source`, `version`, `mapping?`, `sourcePriority?` | `valid`, `issues[]`, `warnings[]`, `summary` |
240
+ | `validate-mixin` | Parse/validate Mixin source against target Minecraft version | `source?`, `sourcePath?`, `sourcePaths?`, `mixinConfigPath?`, `sourceRoot?`, `sourceRoots?`, `version`, `mapping?`, `sourcePriority?`, `projectPath?`, `scope?`, `preferProjectVersion?`, `minSeverity?`, `hideUncertain?`, `warningMode?`, `preferProjectMapping?`, `reportMode?`, `warningCategoryFilter?`, `treatInfoAsWarning?`, `explain?` | single: `valid`, `issues[]`, `warnings[]`, `structuredWarnings?`, `aggregatedWarnings?`, `summary` (incl. `parseWarnings`), `unfilteredSummary?`, `provenance?`, `resolvedMembers?`, `toolHealth?`, `confidenceScore?`; batch: `results[]`, `summary`, `toolHealth?` |
238
241
  | `validate-access-widener` | Parse/validate Access Widener content against target version | `content`, `version`, `mapping?`, `sourcePriority?` | `valid`, `issues[]`, `warnings[]`, `summary` |
239
242
 
240
243
  ### Registry & Diagnostics
@@ -250,13 +253,40 @@ Tools for querying generated registry data and inspecting server runtime state.
250
253
 
251
254
  `get-class-source` requires either `artifactId` or `targetKind`+`targetValue`. Supplying both is rejected.
252
255
  `get-class-members` requires either `artifactId` or `targetKind`+`targetValue`, and needs a binary jar (`binaryJarPath`) to read `.class` entries.
256
+ `validate-mixin` requires exactly one of `source`, `sourcePath`, `sourcePaths`, or `mixinConfigPath`. `sourcePath`/`sourcePaths[]` are normalized for host/WSL path formats before file reads. `mixinConfigPath` reads a mixin config JSON and auto-discovers source files for batch validation (`sourceRoots[]` or `sourceRoot` override lookup roots; otherwise common roots like `src/main/java`, `src/client/java`, `common/src/{main,client}/java`, `fabric/src/{main,client}/java`, `neoforge/src/{main,client}/java`, `forge/src/{main,client}/java`, and `quilt/src/{main,client}/java` are auto-detected from configured mixin classes).
257
+ `validate-mixin` single-file responses include `provenance.resolutionNotes?` when mapping fallback occurs.
258
+ `validate-mixin` validates `@Invoker` targets against methods only and `@Accessor` targets against fields only.
259
+ `validate-mixin` parser supports both `.class` literal targets and `targets = "..."` / `targets = {"a", "b"}` string forms.
260
+ `validate-mixin` parser handles multi-line annotations between `@Shadow`/`@Accessor` and declarations, and strips inline annotations from declaration lines.
261
+ `validate-mixin` distinguishes `target-mapping-failed` (warning, uncertain) from `target-not-found` (error) when class mapping fails.
262
+ `validate-mixin` issues and `structuredWarnings` include `category` (`mapping`, `configuration`, `validation`, `resolution`, or `parse`) to distinguish setup/tooling/parser limits from real validation errors.
263
+ `validate-mixin` supports post-filtering with `minSeverity`, `hideUncertain`, and `warningCategoryFilter`; `treatInfoAsWarning=false` suppresses info-level entries in `structuredWarnings`.
264
+ `validate-mixin` single-file responses include `resolvedMembers?` tracking each member's resolution status (`resolved` or `not-found`).
265
+ `validate-mixin` with `explain=true` enriches each issue with `explanation` and `suggestedCall` (tool + params) for agent-driven recovery.
266
+ `validate-mixin` batch `summary` uses `processingErrors` (exception count), `totalValidationErrors`, and `totalValidationWarnings` instead of the ambiguous `errors` field.
267
+ `resolve-artifact` with `targetKind=version` uses Loom cache discovery from `projectPath` only when `mapping=mojang`; mapping failures include `searchedPaths`, `candidateArtifacts`, and `recommendedCommand` in error details.
268
+ `resolve-artifact` supports `scope` (`vanilla`/`merged`/`loader`) and optional `preferProjectVersion=true` to override `targetValue` from `gradle.properties` (`minecraft_version`, `mc_version`, `minecraftVersion`) when `targetKind=version`.
269
+ `resolve-artifact` includes `sampleEntries` only when a source JAR is resolved; decompile-only paths leave it unset.
270
+ `find-class` returns type symbols (`class`/`interface`/`enum`/`record`) only; fully-qualified lookups are filtered by exact FQCN/file path to avoid false negatives when many classes share the same simple name.
253
271
  `search-class-source` uses `limit: 20` by default; `snippetLines` defaults to `8` and is clamped to `1..80`; `includeDefinition` and `includeOneHop` default to `false`.
272
+ `search-class-source` `queryMode` controls text search strategy: `auto` (default) uses indexed token search with literal fallback for separator queries, `token` keeps indexed token behavior only, and `literal` uses substring scan only.
254
273
  `search-class-source` with `match=regex` enforces `query.length <= 200` and a strict result cap of `100`.
274
+ `get-artifact-file` byte truncation now preserves UTF-8 character boundaries, preventing replacement-character (`�`) corruption when `maxBytes` cuts through multibyte text.
255
275
  `search-class-source` `fileGlob` supports `*`, `**`, and `?`; recursive patterns such as `net/minecraft/**/*.java` are supported.
256
276
  `get-class-source` fallback matching enforces package compatibility and returns `ERR_CLASS_NOT_FOUND` when only name-colliding classes from other packages exist.
277
+ `get-class-source` mode defaults to `metadata` (symbol outline only); `mode=snippet` auto-sets `maxLines=200` when no line range/max is provided; `mode=full` returns the entire source. `outputFile` writes the selected text and returns the file path in `outputFile`.
278
+ Decompile fallback for `resolve-artifact`/`get-class-source` now invokes Vineflower with flags before positional `<input-jar> <output-dir>` arguments to avoid false `ERR_DECOMPILER_FAILED` outcomes on valid jars.
257
279
  `resolve-artifact` with `targetKind=jar` only auto-adopts the exact sibling `"<jar-basename>-sources.jar"`. Other adjacent `*-sources.jar` files are returned as `adjacentSourceCandidates` info only and are never auto-selected.
280
+ For `targetKind=coordinate` with a classifier (`group:artifact:version:classifier`), local Maven source lookup checks `<artifact>-<version>-<classifier>-sources.jar` first and then `<artifact>-<version>-sources.jar`.
258
281
  Mod tool `jarPath` inputs are normalized to a canonical local `.jar` file path before existence checks, cache keying, and processing.
259
282
  `search-mod-source` enforces `query.length <= 200` and `limit <= 200`.
283
+ `search-mod-source` detects source-only jars and searches `.java` entries directly without decompilation.
284
+ `get-mod-class-source` supports `maxLines`, `maxChars`, and `outputFile` with truncation behavior aligned to `get-class-source`; when `outputFile` is set, the written file reflects applied truncation.
285
+ `find-mapping` returns `ambiguityReasons` when `status=ambiguous` to explain why candidates could not be uniquely resolved.
286
+ `get-class-api-matrix` returns `ambiguousRowCount` when one or more rows required ambiguity fallback.
287
+ `check-symbol-exists` defaults to strict FQCN class inputs; set `nameMode=auto` to allow short class names (ambiguous matches return `status=ambiguous`).
288
+ `check-symbol-exists` supports `signatureMode=name-only` to match methods by owner+name without requiring a descriptor. Single match returns `resolved`; multiple overloads return `ambiguous` with all candidates.
289
+ `check-symbol-exists` always validates input shape first and returns `ERR_INVALID_INPUT` for invalid symbol combinations, even when mapping data is unavailable.
260
290
  `remap-mod-jar` requires Java to be installed and only supports Fabric/Quilt mods.
261
291
 
262
292
  ## Resources
@@ -299,7 +329,8 @@ All tools return exactly one of:
299
329
  "targetKind": "version",
300
330
  "targetValue": "1.21.10",
301
331
  "mapping": "official",
302
- "allowDecompile": true
332
+ "allowDecompile": true,
333
+ "projectPath": "/path/to/mod/workspace"
303
334
  }
304
335
  }
305
336
  ```
@@ -420,7 +451,10 @@ Get a high-level summary of what changed between two releases, including class a
420
451
  "name": "a.b.C",
421
452
  "sourceMapping": "official",
422
453
  "targetMapping": "mojang",
423
- "sourcePriority": "loom-first"
454
+ "sourcePriority": "loom-first",
455
+ "disambiguation": {
456
+ "ownerHint": "net.minecraft"
457
+ }
424
458
  }
425
459
  }
426
460
  ```
@@ -501,6 +535,20 @@ Get a high-level summary of what changed between two releases, including class a
501
535
  }
502
536
  ```
503
537
 
538
+ #### Check class existence by short name (`nameMode=auto`)
539
+ ```json
540
+ {
541
+ "tool": "check-symbol-exists",
542
+ "arguments": {
543
+ "version": "1.21.10",
544
+ "kind": "class",
545
+ "name": "Blocks",
546
+ "nameMode": "auto",
547
+ "sourceMapping": "mojang"
548
+ }
549
+ }
550
+ ```
551
+
504
552
  ### NBT Utilities
505
553
 
506
554
  #### Decode Java NBT base64 to typed JSON
@@ -638,6 +686,24 @@ Check a Mixin class source for correctness against a target Minecraft version:
638
686
  }
639
687
  ```
640
688
 
689
+ #### Validate multiple Mixin files (batch)
690
+
691
+ Run the same validation settings against multiple Mixin source files:
692
+
693
+ ```json
694
+ {
695
+ "tool": "validate-mixin",
696
+ "arguments": {
697
+ "sourcePaths": [
698
+ "/path/to/PlayerMixin.java",
699
+ "/path/to/WorldMixin.java"
700
+ ],
701
+ "version": "1.21.10",
702
+ "mapping": "yarn"
703
+ }
704
+ }
705
+ ```
706
+
641
707
  #### Validate Access Widener
642
708
 
643
709
  Check an Access Widener file for valid entries against the target version:
@@ -723,7 +789,7 @@ Check server performance counters, cache sizes, and latency snapshots:
723
789
  `find-mapping` supports lookup across `official`, `mojang`, `intermediary`, and `yarn`.
724
790
 
725
791
  Symbol query inputs use `kind` + `name` + optional `owner`/`descriptor`:
726
- - class: `kind=class`, `name=a.b.C` (FQCN only)
792
+ - class: `kind=class`, `name=a.b.C` (default FQCN). For existence checks only, `nameMode=auto` allows short names like `C`.
727
793
  - field: `kind=field`, `owner=a.b.C`, `name=fieldName`
728
794
  - method: `kind=method`, `owner=a.b.C`, `name=methodName`, `descriptor=(I)V`
729
795
 
@@ -737,6 +803,7 @@ Symbol query inputs use `kind` + `name` + optional `owner`/`descriptor`:
737
803
  Method descriptor precision is best on Tiny-backed paths (`intermediary`/`yarn`). For `official <-> mojang`, Mojang `client_mappings` do not carry JVM descriptors, so descriptor queries may fallback to name matching and emit a warning.
738
804
 
739
805
  Use `resolve-method-mapping-exact` when candidate ranking is not enough and the workflow needs strict `owner+name+descriptor` certainty.
806
+ Use `find-mapping` `disambiguation.ownerHint` / `disambiguation.descriptorHint` to narrow ambiguous candidate sets.
740
807
  Use `resolve-workspace-symbol` when you need compile-visible names from actual Gradle Loom mappings in a workspace.
741
808
 
742
809
  ## Environment Variables
@@ -745,7 +812,7 @@ Use `resolve-workspace-symbol` when you need compile-visible names from actual G
745
812
 
746
813
  | Variable | Default | Description |
747
814
  | --- | --- | --- |
748
- | `MCP_CACHE_DIR` | `.cache/minecraft-modding-mcp` | Cache root for downloads and SQLite |
815
+ | `MCP_CACHE_DIR` | `~/.cache/minecraft-modding-mcp` | Cache root for downloads and SQLite |
749
816
  | `MCP_SQLITE_PATH` | `<cacheDir>/source-cache.db` | SQLite database path |
750
817
  | `MCP_SOURCE_REPOS` | Maven Central + Fabric + Forge + NeoForge | Comma-separated Maven repository URLs |
751
818
  | `MCP_LOCAL_M2` | `~/.m2/repository` | Local Maven repository path |
package/dist/config.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Config } from "./types.js";
2
2
  declare const DEFAULTS: {
3
- readonly cacheDir: ".cache/minecraft-modding-mcp";
3
+ readonly cacheDir: "~/.cache/minecraft-modding-mcp";
4
4
  readonly sourceRepos: readonly ["https://repo1.maven.org/maven2", "https://maven.fabricmc.net", "https://maven.minecraftforge.net", "https://maven.neoforged.net/releases"];
5
5
  readonly localM2Path: "~/.m2/repository";
6
6
  readonly indexedSearchEnabled: true;
package/dist/config.js CHANGED
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { isAbsolute, resolve } from "node:path";
4
4
  import { normalizePathForHost } from "./path-converter.js";
5
5
  const DEFAULTS = {
6
- cacheDir: ".cache/minecraft-modding-mcp",
6
+ cacheDir: "~/.cache/minecraft-modding-mcp",
7
7
  sourceRepos: [
8
8
  "https://repo1.maven.org/maven2",
9
9
  "https://maven.fabricmc.net",
@@ -4,6 +4,7 @@ export interface DecompileResult {
4
4
  filePath: string;
5
5
  content: string;
6
6
  }>;
7
+ decompileProfile?: string;
7
8
  }
8
9
  interface DecompileBinaryOptions {
9
10
  vineflowerJarPath?: string;
@@ -1,11 +1,16 @@
1
1
  import { access, constants } from "node:fs/promises";
2
- import { mkdirSync, readdirSync, statSync } from "node:fs";
2
+ import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
3
3
  import { createHash } from "node:crypto";
4
4
  import { basename, join, relative, sep } from "node:path";
5
5
  import { createError, ERROR_CODES, isAppError } from "../errors.js";
6
6
  import { assertJavaAvailable, runJavaProcess } from "../java-process.js";
7
7
  import { log } from "../logger.js";
8
8
  const DEFAULT_TIMEOUT_MS = 120_000;
9
+ const VINEFLOWER_FLAG_PROFILES = [
10
+ { label: "default", flags: ["-din=1", "-rbr=1", "-dgs=1"] },
11
+ { label: "relaxed", flags: ["-din=1", "-rbr=0", "-dgs=0"] },
12
+ { label: "safe", flags: ["-din=0", "-rbr=0", "-dgs=0"] },
13
+ ];
9
14
  function emitDecompileLog(event, details) {
10
15
  log(event === "decompile.error" ? "error" : "info", event, details);
11
16
  }
@@ -83,10 +88,14 @@ function decompileOutputDir(cacheDir, binaryJarPath, signature) {
83
88
  const digest = createHash("sha256").update(binaryJarPath).update(signature).digest("hex");
84
89
  return join(cacheDir, "decompiled", digest);
85
90
  }
86
- async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeoutMs) {
91
+ function clearOutputDir(dir) {
92
+ rmSync(dir, { recursive: true, force: true });
93
+ mkdirSync(dir, { recursive: true });
94
+ }
95
+ async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeoutMs, flags = VINEFLOWER_FLAG_PROFILES[0].flags) {
87
96
  const result = await runJavaProcess({
88
97
  jarPath: vineflowerJarPath,
89
- args: [binaryJarPath, outputDir, "-din=1", "-rbr=1", "-dgs=1"],
98
+ args: [...flags, binaryJarPath, outputDir],
90
99
  timeoutMs,
91
100
  normalizePathArgs: true
92
101
  });
@@ -97,8 +106,8 @@ async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeou
97
106
  details: {
98
107
  binaryJarPath,
99
108
  outputDir,
100
- code: result.exitCode,
101
- stdout: result.stdoutTail,
109
+ exitCode: result.exitCode,
110
+ stdoutTail: result.stdoutTail,
102
111
  stderrTail: result.stderrTail
103
112
  }
104
113
  });
@@ -145,31 +154,71 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
145
154
  };
146
155
  }
147
156
  }
148
- await runVineflower(options.vineflowerJarPath, normalizedBinaryJarPath, outputDir, timeoutMs);
149
- const javaFileNames = await collectJavaFiles(outputDir);
150
- if (javaFileNames.length === 0) {
151
- throw createError({
152
- code: ERROR_CODES.DECOMPILER_FAILED,
153
- message: "No Java files were produced by decompilation.",
154
- details: { binaryJarPath: normalizedBinaryJarPath, outputDir }
155
- });
157
+ const profilesAttempted = [];
158
+ let lastDecompileError;
159
+ for (const profile of VINEFLOWER_FLAG_PROFILES) {
160
+ profilesAttempted.push(profile.label);
161
+ try {
162
+ if (profilesAttempted.length > 1) {
163
+ clearOutputDir(outputDir);
164
+ emitDecompileLog("decompile.retry", {
165
+ binaryJarPath: normalizedBinaryJarPath,
166
+ profile: profile.label,
167
+ attempt: profilesAttempted.length
168
+ });
169
+ }
170
+ await runVineflower(options.vineflowerJarPath, normalizedBinaryJarPath, outputDir, timeoutMs, profile.flags);
171
+ const javaFileNames = await collectJavaFiles(outputDir);
172
+ if (javaFileNames.length === 0) {
173
+ throw createError({
174
+ code: ERROR_CODES.DECOMPILER_FAILED,
175
+ message: "No Java files were produced by decompilation.",
176
+ details: {
177
+ binaryJarPath: normalizedBinaryJarPath,
178
+ outputDir,
179
+ producedJavaCount: 0,
180
+ profile: profile.label
181
+ }
182
+ });
183
+ }
184
+ const javaFiles = await Promise.all(javaFileNames.map(async (candidate) => {
185
+ const abs = join(outputDir, candidate);
186
+ return {
187
+ filePath: normalizeOutputPath(outputDir, abs),
188
+ content: await readFileTreeText(abs)
189
+ };
190
+ }));
191
+ emitDecompileLog("decompile.done", {
192
+ durationMs: Date.now() - startedAt,
193
+ artifactIdCandidate: options.artifactIdCandidate,
194
+ javaFileCount: javaFiles.length,
195
+ profile: profile.label
196
+ });
197
+ return {
198
+ outputDir,
199
+ javaFiles,
200
+ decompileProfile: profile.label
201
+ };
202
+ }
203
+ catch (retryError) {
204
+ if (isAppError(retryError) && retryError.code !== ERROR_CODES.DECOMPILER_FAILED) {
205
+ throw retryError;
206
+ }
207
+ lastDecompileError = retryError;
208
+ }
156
209
  }
157
- const javaFiles = await Promise.all(javaFileNames.map(async (candidate) => {
158
- const abs = join(outputDir, candidate);
159
- return {
160
- filePath: normalizeOutputPath(outputDir, abs),
161
- content: await readFileTreeText(abs)
162
- };
163
- }));
164
- emitDecompileLog("decompile.done", {
165
- durationMs: Date.now() - startedAt,
166
- artifactIdCandidate: options.artifactIdCandidate,
167
- javaFileCount: javaFiles.length
168
- });
169
- return {
170
- outputDir,
171
- javaFiles
172
- };
210
+ throw isAppError(lastDecompileError)
211
+ ? createError({
212
+ code: ERROR_CODES.DECOMPILER_FAILED,
213
+ message: `Decompilation failed after trying all flag profiles.`,
214
+ details: {
215
+ binaryJarPath: normalizedBinaryJarPath,
216
+ outputDir,
217
+ profilesAttempted,
218
+ stderrTail: extractStderrTail(lastDecompileError)
219
+ }
220
+ })
221
+ : lastDecompileError;
173
222
  }
174
223
  catch (error) {
175
224
  emitDecompileLog("decompile.error", {