@adhisang/minecraft-modding-mcp 1.1.0 → 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,11 +5,69 @@ 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
+
57
+ ## [1.1.1] - 2026-03-02
58
+
59
+ ### Fixed
60
+ - `search-class-source` now safely supports recursive `fileGlob` patterns such as `**` without regex construction failures.
61
+ - `get-class-source` now rejects package-incompatible fallback matches and preserves canonical inner-class (`Outer.Inner`) lookup support.
62
+
8
63
  ## [1.1.0] - 2026-03-01
9
64
 
10
65
  ### Changed
11
66
  - Migrate stdio transport from mcp-use to @modelcontextprotocol/sdk.
12
67
 
68
+ ### Fixed
69
+ - Restore Codex startup handshake compatibility by accepting both newline-delimited and `Content-Length` stdio framing.
70
+
13
71
  ### Documentation
14
72
  - Add Quick Start setup for Claude Code, OpenAI Codex CLI, and Gemini CLI.
15
73
 
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
 
@@ -55,6 +55,10 @@ codex mcp add minecraft-modding -- npx -y @adhisang/minecraft-modding-mcp
55
55
  codex mcp list
56
56
  ```
57
57
 
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
+
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
+
58
62
  #### Gemini CLI
59
63
 
60
64
  Add the following to `~/.gemini/settings.json`:
@@ -112,7 +116,7 @@ Generate LCOV output for Codecov upload:
112
116
  pnpm test:coverage:lcov
113
117
  ```
114
118
 
115
- 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).
116
120
 
117
121
  ### MCP Client Configuration
118
122
 
@@ -174,10 +178,11 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading/s
174
178
  | Tool | Purpose | Key Inputs | Key Outputs |
175
179
  | --- | --- | --- | --- |
176
180
  | `list-versions` | List available Minecraft versions from Mojang manifest + local cache | `includeSnapshots?`, `limit?` | `result.latest`, `result.releases[]`, `meta.warnings[]` |
177
- | `resolve-artifact` | Resolve source artifact from `version` / `jar` / `coordinate` | `targetKind`, `targetValue`, `mapping?`, `sourcePriority?`, `allowDecompile?` | `artifactId`, `origin`, `mappingApplied`, `qualityFlags[]`, `adjacentSourceCandidates?`, `warnings[]` |
178
- | `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 |
179
- | `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[]` |
180
- | `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` |
181
186
  | `get-artifact-file` | Read full source file with byte guard | `artifactId`, `filePath`, `maxBytes?` | `content`, `contentBytes`, `truncated`, `mappingApplied` |
182
187
  | `list-artifact-files` | List indexed source file paths with cursor pagination | `artifactId`, `prefix?`, `limit?`, `cursor?` | `items[]`, `nextCursor?`, `mappingApplied` |
183
188
  | `index-artifact` | Rebuild index metadata for an existing artifact | `artifactId`, `force?` | `reindexed`, `reason`, `counts`, `indexedAt`, `durationMs` |
@@ -198,11 +203,11 @@ Tools for converting symbol names between namespaces and checking symbol existen
198
203
 
199
204
  | Tool | Purpose | Key Inputs | Key Outputs |
200
205
  | --- | --- | --- | --- |
201
- | `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[]` |
202
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[]` |
203
- | `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[]` |
204
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[]` |
205
- | `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[]` |
206
211
 
207
212
  ### NBT Utilities
208
213
 
@@ -220,9 +225,9 @@ Tools for extracting metadata from mod JARs, decompiling mod source, searching m
220
225
 
221
226
  | Tool | Purpose | Key Inputs | Key Outputs |
222
227
  | --- | --- | --- | --- |
223
- | `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 |
224
229
  | `decompile-mod-jar` | Decompile mod JAR and optionally return one class source | `jarPath`, `className?` | `outputDir`, `fileCount`, `files?`, `source?`, `warnings[]` |
225
- | `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[]` |
226
231
  | `search-mod-source` | Search decompiled mod source by class/method/field/content | `jarPath`, `query`, `searchType?`, `limit?` | `hits[]`, `totalHits`, `truncated`, `warnings[]` |
227
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[]` |
228
233
 
@@ -232,7 +237,7 @@ Tools for validating Mixin source and Access Widener files against a target Mine
232
237
 
233
238
  | Tool | Purpose | Key Inputs | Key Outputs |
234
239
  | --- | --- | --- | --- |
235
- | `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?` |
236
241
  | `validate-access-widener` | Parse/validate Access Widener content against target version | `content`, `version`, `mapping?`, `sourcePriority?` | `valid`, `issues[]`, `warnings[]`, `summary` |
237
242
 
238
243
  ### Registry & Diagnostics
@@ -248,11 +253,40 @@ Tools for querying generated registry data and inspecting server runtime state.
248
253
 
249
254
  `get-class-source` requires either `artifactId` or `targetKind`+`targetValue`. Supplying both is rejected.
250
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.
251
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.
252
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.
275
+ `search-class-source` `fileGlob` supports `*`, `**`, and `?`; recursive patterns such as `net/minecraft/**/*.java` are supported.
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.
253
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`.
254
281
  Mod tool `jarPath` inputs are normalized to a canonical local `.jar` file path before existence checks, cache keying, and processing.
255
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.
256
290
  `remap-mod-jar` requires Java to be installed and only supports Fabric/Quilt mods.
257
291
 
258
292
  ## Resources
@@ -295,7 +329,8 @@ All tools return exactly one of:
295
329
  "targetKind": "version",
296
330
  "targetValue": "1.21.10",
297
331
  "mapping": "official",
298
- "allowDecompile": true
332
+ "allowDecompile": true,
333
+ "projectPath": "/path/to/mod/workspace"
299
334
  }
300
335
  }
301
336
  ```
@@ -416,7 +451,10 @@ Get a high-level summary of what changed between two releases, including class a
416
451
  "name": "a.b.C",
417
452
  "sourceMapping": "official",
418
453
  "targetMapping": "mojang",
419
- "sourcePriority": "loom-first"
454
+ "sourcePriority": "loom-first",
455
+ "disambiguation": {
456
+ "ownerHint": "net.minecraft"
457
+ }
420
458
  }
421
459
  }
422
460
  ```
@@ -497,6 +535,20 @@ Get a high-level summary of what changed between two releases, including class a
497
535
  }
498
536
  ```
499
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
+
500
552
  ### NBT Utilities
501
553
 
502
554
  #### Decode Java NBT base64 to typed JSON
@@ -634,6 +686,24 @@ Check a Mixin class source for correctness against a target Minecraft version:
634
686
  }
635
687
  ```
636
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
+
637
707
  #### Validate Access Widener
638
708
 
639
709
  Check an Access Widener file for valid entries against the target version:
@@ -719,7 +789,7 @@ Check server performance counters, cache sizes, and latency snapshots:
719
789
  `find-mapping` supports lookup across `official`, `mojang`, `intermediary`, and `yarn`.
720
790
 
721
791
  Symbol query inputs use `kind` + `name` + optional `owner`/`descriptor`:
722
- - 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`.
723
793
  - field: `kind=field`, `owner=a.b.C`, `name=fieldName`
724
794
  - method: `kind=method`, `owner=a.b.C`, `name=methodName`, `descriptor=(I)V`
725
795
 
@@ -733,6 +803,7 @@ Symbol query inputs use `kind` + `name` + optional `owner`/`descriptor`:
733
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.
734
804
 
735
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.
736
807
  Use `resolve-workspace-symbol` when you need compile-visible names from actual Gradle Loom mappings in a workspace.
737
808
 
738
809
  ## Environment Variables
@@ -741,7 +812,7 @@ Use `resolve-workspace-symbol` when you need compile-visible names from actual G
741
812
 
742
813
  | Variable | Default | Description |
743
814
  | --- | --- | --- |
744
- | `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 |
745
816
  | `MCP_SQLITE_PATH` | `<cacheDir>/source-cache.db` | SQLite database path |
746
817
  | `MCP_SOURCE_REPOS` | Maven Central + Fabric + Forge + NeoForge | Comma-separated Maven repository URLs |
747
818
  | `MCP_LOCAL_M2` | `~/.m2/repository` | Local Maven repository path |
@@ -782,7 +853,7 @@ Use `resolve-workspace-symbol` when you need compile-visible names from actual G
782
853
  | Component | Technology |
783
854
  | --- | --- |
784
855
  | Runtime | Node.js 22+ (native `node:sqlite`) |
785
- | Transport | stdio (MCP standard) |
856
+ | Transport | stdio (MCP standard, auto-detects newline + `Content-Length` framing) |
786
857
  | Storage | SQLite — artifact metadata, source index, mapping cache |
787
858
  | Decompilation | [Vineflower](https://github.com/Vineflower/vineflower) (auto-downloaded) |
788
859
  | Remapping | [tiny-remapper](https://github.com/FabricMC/tiny-remapper) (requires Java) |
package/dist/cli.js CHANGED
@@ -1,22 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { startServer } from "./index.js";
3
- function keepProcessAliveUntilStdinCloses() {
4
- return new Promise((resolve) => {
5
- const keepAlive = setInterval(() => { }, 1 << 30);
6
- const release = () => {
7
- clearInterval(keepAlive);
8
- resolve();
9
- };
10
- if (process.stdin.destroyed) {
11
- release();
12
- return;
13
- }
14
- process.stdin.once("end", release);
15
- process.stdin.once("close", release);
16
- });
17
- }
18
3
  startServer()
19
- .then(() => keepProcessAliveUntilStdinCloses())
4
+ .then(() => undefined)
20
5
  .catch((err) => {
21
6
  console.error("Fatal: server failed to start", err);
22
7
  process.exit(1);
@@ -0,0 +1,27 @@
1
+ import { type JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
2
+ type StdioReadable = NodeJS.ReadStream;
3
+ type StdioWritable = NodeJS.WriteStream;
4
+ export declare class CompatStdioServerTransport {
5
+ private readonly stdin;
6
+ private readonly stdout;
7
+ private started;
8
+ private closed;
9
+ private mode;
10
+ private buffer;
11
+ onclose?: () => void;
12
+ onerror?: (error: Error) => void;
13
+ onmessage?: (message: JSONRPCMessage) => void;
14
+ constructor(stdin?: StdioReadable, stdout?: StdioWritable);
15
+ start(): Promise<void>;
16
+ send(message: JSONRPCMessage): Promise<void>;
17
+ close(): Promise<void>;
18
+ private readonly handleData;
19
+ private readonly handleStreamError;
20
+ private readonly handleStreamClosed;
21
+ private emitCloseOnce;
22
+ private processReadBuffer;
23
+ private detectMode;
24
+ private readLineDelimitedMessage;
25
+ private readContentLengthMessage;
26
+ }
27
+ export {};
@@ -0,0 +1,217 @@
1
+ import process from "node:process";
2
+ import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
3
+ function findHeaderBoundary(buffer) {
4
+ const crlfBoundary = buffer.indexOf("\r\n\r\n");
5
+ if (crlfBoundary !== -1) {
6
+ return { index: crlfBoundary, delimiterBytes: 4 };
7
+ }
8
+ const lfBoundary = buffer.indexOf("\n\n");
9
+ if (lfBoundary !== -1) {
10
+ return { index: lfBoundary, delimiterBytes: 2 };
11
+ }
12
+ return undefined;
13
+ }
14
+ function parseJsonRpcMessage(json) {
15
+ return JSONRPCMessageSchema.parse(JSON.parse(json));
16
+ }
17
+ function asError(value) {
18
+ return value instanceof Error ? value : new Error(String(value));
19
+ }
20
+ export class CompatStdioServerTransport {
21
+ stdin;
22
+ stdout;
23
+ started = false;
24
+ closed = false;
25
+ mode = "unknown";
26
+ buffer = Buffer.alloc(0);
27
+ onclose;
28
+ onerror;
29
+ onmessage;
30
+ constructor(stdin = process.stdin, stdout = process.stdout) {
31
+ this.stdin = stdin;
32
+ this.stdout = stdout;
33
+ }
34
+ async start() {
35
+ if (this.started) {
36
+ throw new Error("CompatStdioServerTransport already started. connect() should be called only once.");
37
+ }
38
+ this.started = true;
39
+ this.stdin.on("data", this.handleData);
40
+ this.stdin.on("error", this.handleStreamError);
41
+ this.stdin.on("end", this.handleStreamClosed);
42
+ this.stdin.on("close", this.handleStreamClosed);
43
+ this.stdin.resume();
44
+ }
45
+ async send(message) {
46
+ const json = JSON.stringify(message);
47
+ const frame = this.mode === "content-length"
48
+ ? `Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`
49
+ : `${json}\n`;
50
+ await new Promise((resolve) => {
51
+ if (this.stdout.write(frame)) {
52
+ resolve();
53
+ return;
54
+ }
55
+ this.stdout.once("drain", resolve);
56
+ });
57
+ }
58
+ async close() {
59
+ this.stdin.off("data", this.handleData);
60
+ this.stdin.off("error", this.handleStreamError);
61
+ this.stdin.off("end", this.handleStreamClosed);
62
+ this.stdin.off("close", this.handleStreamClosed);
63
+ if (this.stdin.listenerCount("data") === 0) {
64
+ this.stdin.pause();
65
+ }
66
+ this.buffer = Buffer.alloc(0);
67
+ this.emitCloseOnce();
68
+ }
69
+ handleData = (chunk) => {
70
+ if (chunk.length === 0) {
71
+ return;
72
+ }
73
+ this.buffer = Buffer.concat([this.buffer, chunk]);
74
+ this.processReadBuffer();
75
+ };
76
+ handleStreamError = (error) => {
77
+ this.onerror?.(error);
78
+ };
79
+ handleStreamClosed = () => {
80
+ this.emitCloseOnce();
81
+ };
82
+ emitCloseOnce() {
83
+ if (this.closed) {
84
+ return;
85
+ }
86
+ this.closed = true;
87
+ this.onclose?.();
88
+ }
89
+ processReadBuffer() {
90
+ while (true) {
91
+ try {
92
+ if (this.mode === "unknown") {
93
+ const detected = this.detectMode();
94
+ if (!detected) {
95
+ return;
96
+ }
97
+ this.mode = detected;
98
+ continue;
99
+ }
100
+ const modeBefore = this.mode;
101
+ const message = this.mode === "content-length"
102
+ ? this.readContentLengthMessage()
103
+ : this.readLineDelimitedMessage();
104
+ if (!message) {
105
+ // readLineDelimitedMessage may switch mode to "content-length"
106
+ // mid-stream; retry with the new parser instead of stopping.
107
+ if (this.mode !== modeBefore) {
108
+ continue;
109
+ }
110
+ return;
111
+ }
112
+ this.onmessage?.(message);
113
+ }
114
+ catch (caughtError) {
115
+ this.onerror?.(asError(caughtError));
116
+ this.mode = "unknown";
117
+ }
118
+ }
119
+ }
120
+ detectMode() {
121
+ // Skip blank leading lines that some clients may emit.
122
+ while (this.buffer.length > 0) {
123
+ if (this.buffer[0] === 0x0a) {
124
+ this.buffer = this.buffer.subarray(1);
125
+ continue;
126
+ }
127
+ if (this.buffer.length >= 2 && this.buffer[0] === 0x0d && this.buffer[1] === 0x0a) {
128
+ this.buffer = this.buffer.subarray(2);
129
+ continue;
130
+ }
131
+ break;
132
+ }
133
+ if (this.buffer.length === 0) {
134
+ return undefined;
135
+ }
136
+ const prefix = this.buffer
137
+ .subarray(0, Math.min(this.buffer.length, 32))
138
+ .toString("utf8")
139
+ .toLowerCase();
140
+ if (prefix.startsWith("content-length")) {
141
+ return "content-length";
142
+ }
143
+ const firstNewline = this.buffer.indexOf(0x0a);
144
+ if (firstNewline === -1) {
145
+ return undefined;
146
+ }
147
+ const firstLine = this.buffer.subarray(0, firstNewline).toString("utf8").replace(/\r$/, "");
148
+ if (/^\s*content-length\s*:/i.test(firstLine)) {
149
+ return "content-length";
150
+ }
151
+ return "line";
152
+ }
153
+ readLineDelimitedMessage() {
154
+ while (true) {
155
+ const newlineIndex = this.buffer.indexOf(0x0a);
156
+ if (newlineIndex === -1) {
157
+ return undefined;
158
+ }
159
+ const line = this.buffer.subarray(0, newlineIndex).toString("utf8").replace(/\r$/, "");
160
+ this.buffer = this.buffer.subarray(newlineIndex + 1);
161
+ if (line.trim().length === 0) {
162
+ continue;
163
+ }
164
+ if (/^\s*content-length\s*:/i.test(line)) {
165
+ // Reconstruct the header with the correct line ending so that
166
+ // findHeaderBoundary can locate \r\n\r\n or \n\n reliably.
167
+ const sep = this.buffer.length > 0 && this.buffer[0] === 0x0d ? "\r\n" : "\n";
168
+ this.buffer = Buffer.concat([Buffer.from(`${line}${sep}`, "utf8"), this.buffer]);
169
+ this.mode = "content-length";
170
+ return undefined;
171
+ }
172
+ return parseJsonRpcMessage(line);
173
+ }
174
+ }
175
+ readContentLengthMessage() {
176
+ const headerBoundary = findHeaderBoundary(this.buffer);
177
+ if (!headerBoundary) {
178
+ return undefined;
179
+ }
180
+ const headersRaw = this.buffer.subarray(0, headerBoundary.index).toString("utf8");
181
+ const headerLines = headersRaw
182
+ .split(/\r?\n/)
183
+ .map((line) => line.trim())
184
+ .filter((line) => line.length > 0);
185
+ let contentLength;
186
+ for (const headerLine of headerLines) {
187
+ const separatorIndex = headerLine.indexOf(":");
188
+ if (separatorIndex === -1) {
189
+ this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
190
+ throw new Error(`Malformed header line: ${headerLine}`);
191
+ }
192
+ const headerName = headerLine.slice(0, separatorIndex).trim().toLowerCase();
193
+ const headerValue = headerLine.slice(separatorIndex + 1).trim();
194
+ if (headerName === "content-length") {
195
+ const parsed = Number.parseInt(headerValue, 10);
196
+ if (!Number.isFinite(parsed) || parsed < 0) {
197
+ this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
198
+ throw new Error(`Invalid Content-Length header value: ${headerValue}`);
199
+ }
200
+ contentLength = parsed;
201
+ }
202
+ }
203
+ if (contentLength === undefined) {
204
+ this.buffer = this.buffer.subarray(headerBoundary.index + headerBoundary.delimiterBytes);
205
+ throw new Error("Missing Content-Length header.");
206
+ }
207
+ const messageStart = headerBoundary.index + headerBoundary.delimiterBytes;
208
+ const frameEnd = messageStart + contentLength;
209
+ if (this.buffer.length < frameEnd) {
210
+ return undefined;
211
+ }
212
+ const body = this.buffer.subarray(messageStart, frameEnd).toString("utf8");
213
+ this.buffer = this.buffer.subarray(frameEnd);
214
+ return parseJsonRpcMessage(body);
215
+ }
216
+ }
217
+ //# sourceMappingURL=compat-stdio-transport.js.map
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;