@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.1

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.
Files changed (176) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +40 -23
  3. package/dist/build-suggested-call.d.ts +29 -0
  4. package/dist/build-suggested-call.js +58 -0
  5. package/dist/cache-registry.d.ts +3 -1
  6. package/dist/cache-registry.js +50 -6
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
  8. package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
  9. package/dist/entry-tools/batch-class-members-service.js +97 -0
  10. package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
  11. package/dist/entry-tools/batch-class-source-service.js +100 -0
  12. package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
  13. package/dist/entry-tools/batch-mappings-service.js +66 -0
  14. package/dist/entry-tools/batch-runner.d.ts +72 -0
  15. package/dist/entry-tools/batch-runner.js +90 -0
  16. package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
  17. package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
  18. package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
  19. package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
  20. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
  21. package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
  22. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
  23. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
  27. package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
  28. package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
  29. package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
  31. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
  32. package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
  33. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
  34. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
  35. package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
  36. package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
  37. package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
  38. package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
  39. package/dist/entry-tools/manage-cache-service.d.ts +16 -16
  40. package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
  41. package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
  42. package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
  43. package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
  44. package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
  45. package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
  46. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +102 -0
  47. package/dist/entry-tools/validate-project/cases/project-summary.js +415 -0
  48. package/dist/entry-tools/validate-project/internal.d.ts +142 -0
  49. package/dist/entry-tools/validate-project/internal.js +303 -0
  50. package/dist/entry-tools/validate-project-service.d.ts +67 -47
  51. package/dist/entry-tools/validate-project-service.js +13 -563
  52. package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
  53. package/dist/entry-tools/verify-mixin-target-service.js +323 -0
  54. package/dist/error-mapping.d.ts +40 -0
  55. package/dist/error-mapping.js +139 -0
  56. package/dist/errors.d.ts +6 -0
  57. package/dist/errors.js +6 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +147 -1354
  60. package/dist/mapping/internal-types.d.ts +54 -0
  61. package/dist/mapping/internal-types.js +14 -0
  62. package/dist/mapping/loaders/mojang.d.ts +2 -0
  63. package/dist/mapping/loaders/mojang.js +64 -0
  64. package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
  65. package/dist/mapping/loaders/tiny-loom.js +73 -0
  66. package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
  67. package/dist/mapping/loaders/tiny-maven.js +104 -0
  68. package/dist/mapping/loaders/types.d.ts +14 -0
  69. package/dist/mapping/loaders/types.js +2 -0
  70. package/dist/mapping/lookup.d.ts +52 -0
  71. package/dist/mapping/lookup.js +496 -0
  72. package/dist/mapping/parsers/normalize.d.ts +10 -0
  73. package/dist/mapping/parsers/normalize.js +52 -0
  74. package/dist/mapping/parsers/proguard.d.ts +20 -0
  75. package/dist/mapping/parsers/proguard.js +138 -0
  76. package/dist/mapping/parsers/symbol-records.d.ts +27 -0
  77. package/dist/mapping/parsers/symbol-records.js +216 -0
  78. package/dist/mapping/parsers/tiny.d.ts +9 -0
  79. package/dist/mapping/parsers/tiny.js +96 -0
  80. package/dist/mapping/types.d.ts +147 -0
  81. package/dist/mapping/types.js +2 -0
  82. package/dist/mapping-pipeline-service.js +3 -2
  83. package/dist/mapping-service.d.ts +8 -145
  84. package/dist/mapping-service.js +30 -1207
  85. package/dist/mixin/access-validators.d.ts +9 -0
  86. package/dist/mixin/access-validators.js +257 -0
  87. package/dist/mixin/annotation-validators.d.ts +5 -0
  88. package/dist/mixin/annotation-validators.js +162 -0
  89. package/dist/mixin/helpers.d.ts +28 -0
  90. package/dist/mixin/helpers.js +315 -0
  91. package/dist/mixin/parsed-validator.d.ts +8 -0
  92. package/dist/mixin/parsed-validator.js +337 -0
  93. package/dist/mixin/types.d.ts +208 -0
  94. package/dist/mixin/types.js +28 -0
  95. package/dist/mixin-validator.d.ts +9 -201
  96. package/dist/mixin-validator.js +8 -1020
  97. package/dist/source/access-validate.d.ts +4 -0
  98. package/dist/source/access-validate.js +254 -0
  99. package/dist/source/artifact-resolver.d.ts +111 -0
  100. package/dist/source/artifact-resolver.js +1271 -0
  101. package/dist/source/cache-metrics.d.ts +26 -0
  102. package/dist/source/cache-metrics.js +172 -0
  103. package/dist/source/class-source/members-builder.d.ts +34 -0
  104. package/dist/source/class-source/members-builder.js +46 -0
  105. package/dist/source/class-source/snippet-builder.d.ts +19 -0
  106. package/dist/source/class-source/snippet-builder.js +46 -0
  107. package/dist/source/class-source-helpers.d.ts +34 -0
  108. package/dist/source/class-source-helpers.js +140 -0
  109. package/dist/source/class-source.d.ts +42 -0
  110. package/dist/source/class-source.js +883 -0
  111. package/dist/source/descriptor-utils.d.ts +6 -0
  112. package/dist/source/descriptor-utils.js +37 -0
  113. package/dist/source/file-access.d.ts +4 -0
  114. package/dist/source/file-access.js +102 -0
  115. package/dist/source/indexer.d.ts +82 -0
  116. package/dist/source/indexer.js +522 -0
  117. package/dist/source/lifecycle/diff-utils.d.ts +9 -0
  118. package/dist/source/lifecycle/diff-utils.js +107 -0
  119. package/dist/source/lifecycle/diff.d.ts +2 -0
  120. package/dist/source/lifecycle/diff.js +265 -0
  121. package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
  122. package/dist/source/lifecycle/mapping-helpers.js +327 -0
  123. package/dist/source/lifecycle/runtime-check.d.ts +2 -0
  124. package/dist/source/lifecycle/runtime-check.js +142 -0
  125. package/dist/source/lifecycle/trace.d.ts +2 -0
  126. package/dist/source/lifecycle/trace.js +231 -0
  127. package/dist/source/lifecycle.d.ts +4 -0
  128. package/dist/source/lifecycle.js +5 -0
  129. package/dist/source/search.d.ts +51 -0
  130. package/dist/source/search.js +676 -0
  131. package/dist/source/shared-utils.d.ts +6 -0
  132. package/dist/source/shared-utils.js +55 -0
  133. package/dist/source/state.d.ts +26 -0
  134. package/dist/source/state.js +24 -0
  135. package/dist/source/symbol-resolver.d.ts +3 -0
  136. package/dist/source/symbol-resolver.js +212 -0
  137. package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
  138. package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
  139. package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
  140. package/dist/source/validate-mixin/pipeline/parse.js +10 -0
  141. package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
  142. package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
  143. package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
  144. package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
  145. package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
  146. package/dist/source/validate-mixin/pipeline-context.js +93 -0
  147. package/dist/source/validate-mixin.d.ts +22 -0
  148. package/dist/source/validate-mixin.js +799 -0
  149. package/dist/source/workspace-target.d.ts +18 -0
  150. package/dist/source/workspace-target.js +305 -0
  151. package/dist/source-resolver.d.ts +1 -0
  152. package/dist/source-resolver.js +1 -1
  153. package/dist/source-service.d.ts +164 -170
  154. package/dist/source-service.js +70 -6116
  155. package/dist/stage-emitter.d.ts +13 -0
  156. package/dist/stage-emitter.js +30 -0
  157. package/dist/stdio-supervisor.d.ts +61 -0
  158. package/dist/stdio-supervisor.js +326 -9
  159. package/dist/tool-contract-manifest.d.ts +1 -1
  160. package/dist/tool-contract-manifest.js +23 -6
  161. package/dist/tool-guidance.d.ts +82 -0
  162. package/dist/tool-guidance.js +734 -0
  163. package/dist/tool-schema-registry.d.ts +16 -0
  164. package/dist/tool-schema-registry.js +37 -0
  165. package/dist/tool-schemas.d.ts +3518 -0
  166. package/dist/tool-schemas.js +813 -0
  167. package/dist/types.d.ts +36 -0
  168. package/dist/version-service.js +7 -6
  169. package/dist/workspace-context-cache.d.ts +32 -0
  170. package/dist/workspace-context-cache.js +66 -0
  171. package/dist/workspace-mapping-service.d.ts +16 -0
  172. package/dist/workspace-mapping-service.js +173 -1
  173. package/docs/README-ja.md +416 -0
  174. package/docs/examples.md +483 -0
  175. package/docs/tool-reference.md +462 -0
  176. package/package.json +17 -4
package/CHANGELOG.md CHANGED
@@ -5,6 +5,67 @@ 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
+ ## [4.1.1] - 2026-05-10
9
+
10
+ ### Documentation
11
+ - `package.json` `description` and `keywords` now mirror the GitHub repository's `description` and topics. `description` summarizes the actual feature surface (source inspection, Mojang/Yarn/Intermediary mappings, version diffs, Fabric/Forge/NeoForge mod JAR analysis, Mixin/Access Widener/Access Transformer validation); `keywords` expands from three entries to the published 15-topic set.
12
+
13
+ ### Fixed
14
+ - Concurrent class source/member lookups in the same MCP server process now share one in-flight source indexing/decompile rebuild for the same binary fallback artifact.
15
+ - `validate-project task="project-summary"` no longer performs heavyweight source indexing for its `minecraft.artifact.resolved` task probe. The probe now uses lightweight artifact metadata while preserving the public `tasks` response shape.
16
+ - `validate-mixin` mapping-health checks no longer load the full Tiny mapping graph for `obfuscated` or `mojang` requests, preventing `validate-project task="project-summary"` worker restarts on large Mojang-mapped workspaces.
17
+ - `validate-project task="project-summary"` task report no longer marks executed validators as `skipped` when the lightweight `minecraft.artifact.resolved` probe fails. Mixin / access-widener / access-transformer entries now reflect their real outcome (`ok` with `counts`, or `error`).
18
+ - `validate-mixin` `toolHealth.tinyMappingsAvailable` now means "Tiny is sufficient for the request": `true` when Tiny is not required (`obfuscated` / `mojang`) or is loaded, `false` only when `intermediary` / `yarn` was requested and Tiny is unavailable. `confidenceScore` is no longer penalized when Tiny is intentionally skipped, including on graph-load failure.
19
+ - `validate-project task="project-summary"` lightweight artifact probe defaults omitted `scope` to `"vanilla"` to match `validate-mixin`'s resolve stage, skipping workspace-wide source-jar discovery unless the caller explicitly requests `scope="merged"` or `scope="loader"`.
20
+ - `validate-project task="project-summary"` stage notifications are now best-effort. Telemetry/transport failures no longer abort the summary, count as validation errors, hide nested `validate-mixin` work behind zero-count summaries, or surface as `ERR_ARTIFACT_PROBE_FAILED` in `tasks["minecraft.artifact.resolved"]`.
21
+
22
+ ## [4.1.0] - 2026-05-09
23
+
24
+ ### Documentation
25
+ - README onboarding and the Japanese README now clarify MCP client setup and packaged documentation links, and the npm package includes the Markdown docs those links reference.
26
+
27
+ ### Added
28
+ - `validate-project task="project-summary"` now returns an additive `tasks` per-probe status report alongside the existing `summary` / `project` / `workspace` blocks. The seven probes (`workspace.detected`, `gradle.readable`, `loom.cache.found`, `minecraft.artifact.resolved`, `mixins.validated`, `accessWideners.validated`, `accessTransformers.validated`) each carry `status: "ok" | "skipped" | "missing" | "error"` plus optional sub-fields (e.g. `evidence`, `propertiesPath`, `buildScripts`, `cachePath`, `artifactId`, `mapping`, `counts`). The headline `result.summary.status` is unchanged; the new field surfaces which probes succeeded even when the overall status is `blocked`. With `detail: "summary"` (or when `include` does not contain `"workspace"`), each entry is slimmed to `status` / `error?` / `warnings?`. Set `VALIDATE_PROJECT_TASKS_OFF=1` to omit the field entirely.
29
+ - `get-class-members` now returns an additive `status: "ok" | "members_unavailable" | "partial"` field. `"ok"` covers both populated classes (`counts.total > 0`) and genuinely empty classes whose binary signature parsed without error and produced no decompiled fallback. `"partial"` fires whenever `decompiledFallback` is populated (bytecode returned zero but indexed source supplied member names; `qualityFlags` includes `"members-from-decompiled-source"`). `"members_unavailable"` fires when binary signature extraction throws a non-`ERR_CLASS_NOT_FOUND` error and no decompiled fallback is available; the response then carries `unavailableReason: string` and a `suggestedCall: { tool: "get-class-source", params }` whose params validate against `get-class-source`'s input schema. `ERR_CLASS_NOT_FOUND` still propagates as a thrown error. The shape is purely additive — `members` / `counts` / `decompiledFallback` / `decompiledMemberCounts` / `qualityFlags` are unchanged for callers that ignore `status`. Set `MEMBERS_STATUS_LEGACY=1` to omit the new fields entirely (legacy shape).
30
+ - `verify-mixin-target` (new tool): single-call probe that answers "does this owner / member exist, and which `@Shadow` / `@Accessor` / `@Invoker` should the mixin use?" Inputs accept `target.kind="workspace" | "version" | "coordinate" | "dependency" | "jar"`, an optional `mixinMemberName` (caller-authored mixin field/method name), and a discriminated `member: { kind: "method" | "field", name, descriptor? }`. Output carries `exists`, `matches[]`, `candidates[]` (descriptor-mismatch and Levenshtein-near misses via the same `suggestSimilar` helper used by `validate-mixin`), and an `accessorAdvice` block with `suggestedAnnotation` (`"@Shadow"` / `"@Accessor"` / `"@Invoker"` / `"@Inject-only"` / `null`), a deterministic `exampleSnippet`, and `candidates` when the rule table cannot decide between `@Shadow` and `@Invoker`. `accessorAdvice` is emitted only when `matches.length === 1` (a single unique match): descriptor-mismatch / nearest-neighbor / candidate-only responses carry no advice, and ambiguous overload responses (descriptor omitted, multiple matches) also carry no advice — re-call with an explicit `descriptor` to disambiguate. `@Accessor` setter templates (when `mixinMemberName` matches `^set[A-Z]`) emit `void name(<type> value);` instead of the zero-arg getter shape. `"@Inject-only"` is a pseudo-tag — not a real annotation — that signals "target already visible, no `@Shadow` required". Owner-not-found surfaces `ERR_CLASS_NOT_FOUND` with `details.suggestedCall.tool === "find-class"`. Namespace mismatch (explicit `mapping` differs from resolved `mappingApplied`) surfaces `ERR_NAMESPACE_MISMATCH` with `details.requestedMapping` and `details.mappingApplied` rather than silently returning a misleading `exists:false`; auto-translation of owner/member names across namespaces is deferred. Set `VERIFY_MIXIN_TARGET_OFF=1` to remove the tool from `tools/list` entirely (direct calls then return the SDK "Tool not found" tool-result envelope — `{ content: [{ type: "text", text: "MCP error -32602: Tool verify-mixin-target not found" }], isError: true }` — so callers cannot rely on `error.code === "ERR_*"` while the toggle is set).
31
+ - `ProblemDetails.code` may now be `ERR_WORKER_RESTART`, `ERR_MIXIN_PARSE_FAILED`, `ERR_STAGE_BUDGET_PRE_PARSE`, `ERR_WORKSPACE_VERSION_UNRESOLVED`, `ERR_DEPENDENCY_VERSION_UNRESOLVED`, or `ERR_BATCH_ABORTED`. Restart envelopes are produced by the supervisor when a worker exits while handling `tools/call`; `ERR_STAGE_BUDGET_PRE_PARSE` is raised by `validate-mixin` when a pre-parse stage exhausts its soft-deadline; `ERR_WORKSPACE_VERSION_UNRESOLVED` / `ERR_DEPENDENCY_VERSION_UNRESOLVED` carry structured `suggestedCall` payloads for caller-side recovery; `ERR_BATCH_ABORTED` (HTTP 412) fills un-started slots in batch tools when `failFast: true` and an earlier entry already failed.
32
+ - `validate-mixin` now reports `result.targetOutcomes[]` (per-target `status: "ok" | "deferred-budget" | "tool-issue"` plus optional `slowTarget` / `elapsedMs` / `budgetMs`) and `summary.targetsDeferredBudget` / `summary.degradedReason: "stage-budget" | "stage-budget-pre-target"` so callers can distinguish completed work from work skipped by the soft-deadline.
33
+ - `meta.restart` (synthetic worker-restart responses only) carries `tool` / `durationMs` / `lastStage` / `lastStageElapsedMs` / `lastStageMeta` / `exit` / `retryRecommendation` for caller diagnostics. `meta.stageBudgetExhausted` / `meta.budgetMs` / `meta.elapsedMs` accompany `ERR_STAGE_BUDGET_PRE_PARSE` error envelopes so callers can detect budget-driven failures from the public envelope without parsing the message string.
34
+ - `validate-mixin` batch-mode results (`paths` / `config` / `project`) now preserve typed AppError metadata on per-entry errors: each `results[i]` carries optional `errorCode` and `errorDetails` (`failedStage`, `stageBudgetExhausted`, `budgetMs`, `elapsedMs`, …) so callers can distinguish budget-driven failures from generic per-file processing errors.
35
+ - `target.kind="workspace"` is now accepted by `resolve-artifact`, `get-class-source`, and `get-class-members`. The synthesizer reads `gradle.properties` and `build.gradle(.kts)` from `projectPath` to detect Minecraft version, compile mapping, and loader, then rewrites the call to the resolved `target.kind="version"` shape. Detected facts and a `cacheHit` flag surface on `provenance.workspaceResolution`.
36
+ - `target.kind="dependency"` (with optional `versionFromProject`) is now accepted by the same three tools. It probes four de-duplicated `gradle.properties` keys (`name_version`, `camelCaseVersion`, `group_name_version`, `groupNameVersion`) and falls back to `~/.gradle/caches/modules-2/files-2.1/<group>/<name>/`, picking the semver-newest non-snapshot. Dependency JARs disable the Mojang binary-remap path; mapping mismatches between the caller's `mapping` and the artifact namespace surface as warnings instead of being remapped.
37
+ - `provenance.workspaceResolution` and `provenance.dependencyResolution` are emitted whenever the new target kinds are used. The new target kinds and the `manage-cache` workspace integration are scoped to the three tools whose input shape carries a `target` field; `find-class`, `search-class-source`, `list-artifact-files`, and `find-mapping` keep their existing `artifactId` / `version`-based shapes unchanged in this release.
38
+ - `manage-cache` exposes a new `cacheKinds: "workspace"` for the in-memory `WorkspaceContextCache`. Listing returns one entry per cached `projectPath`; `delete` invalidates entries by `selector.projectPath`. `prune` / `rebuild` / `verify` are no-ops for this kind.
39
+ - `ProblemDetails.exampleCalls?: Array<{ tool: string; params: Record<string, unknown>; reason: string }>` — additive always-valid fallback emitted when the primary `suggestedCall` payload fails the schema-validation gate. Each entry already passed `getToolSchema(tool).safeParse(params)` at construction time and is safe for the agent to re-call as-is. Construction sites supply alternatives via the `examples: [{ params, reason }]` argument to `buildSuggestedCall(...)`.
40
+ - `tool-schema-registry` runtime registry (internal module exporting `registerToolSchema` / `getToolSchema` / `validateToolParams` / `listRegisteredTools`) populated at server startup from the same zod schemas already used by the `server.tool(...)` registrations. Powers the new `suggestedCall` validation gate; a registry-completeness test in `tests/tool-schema-registry.test.ts` asserts every tool name from `EXPECTED_TOOLS` has a registered schema.
41
+ - `batch-class-source`, `batch-class-members`, `batch-symbol-exists`, and `batch-mappings` (4 new tools) read source / list members / probe symbol existence / translate mappings for fixed shortlists (1..50 entries) in a single call. Each batch shares one resolved artifact (`summary.sharedArtifactId`) instead of paying N artifact-resolution round-trips, dispatches per entry through the existing bounded worker pool (`concurrency: 1..8`, default 4), and returns a per-entry `{ index, status: "ok" | "error", result?, error?, warnings, durationMs }` envelope plus an aggregate `summary: { total, ok, error }`. `failFast: true` halts dispatch on the first entry error — un-started entries are returned with `error.code === "ERR_BATCH_ABORTED"` while in-flight workers complete (no AbortSignal wiring). `compact: true` (default) applies the matching single-tool's compact projection per entry. Per-entry errors carry a `suggestedCall` proposing the matching single tool (`get-class-source` / `get-class-members` / `check-symbol-exists` / `find-mapping`) with the shared artifact threaded through; the suggestion is validated through the same schema gate as single-tool errors. `batch-symbol-exists` accepts only `target.kind="workspace" | "version"` because library/coordinate/jar artifacts carry library versions, not Minecraft versions, which would corrupt the mapping query (rejected at the schema gate with `ERR_INVALID_INPUT`). `batch-mappings` requires top-level `version` (single-version batch); per-entry `version` is rejected as an unrecognized key. `batch-class-source` rejects duplicate `entries[].outputFile` values at the schema gate with `ERR_INVALID_INPUT` (`fieldErrors[0].path === "entries.<i>.outputFile"`), since concurrent worker dispatch would otherwise race on the same file. The shared envelope, `failFast` semantics, per-entry retry mapping, and disabled-tool envelope shape are documented in the new `Batch lookup contract` section of `docs/tool-reference.md`. Set `BATCH_TOOLS_OFF=1` to remove all four tools from `tools/list` (rollback path); direct calls then return the SDK "Tool not found" tool-result envelope (`{ content: [{ type: "text", text: "MCP error -32602: Tool <name> not found" }], isError: true }`), so callers cannot rely on `error.code === "ERR_*"` while the toggle is set.
42
+
43
+ ### Changed
44
+ - `ERR_MAPPING_NOT_APPLIED` now returns a `suggestedCall` of `{ target: { kind: "workspace" }, projectPath, mapping: <detected> }` whenever `projectPath` is supplied, the workspace's compile mapping is non-obfuscated, the failed target was `target.kind="version"`, AND the workspace's detected `minecraftVersion` matches the failed `value`. Coordinate, jar, synthesized dependency failures, and version-mismatched workspaces all keep the legacy obfuscated retry so the suggested retry never silently redirects to a different artifact. Cache hits short-circuit; cache misses now run bounded detection of both compile mapping and Minecraft version (build.gradle / gradle.properties reads only) before deciding whether to suggest the workspace retry. Set `WORKSPACE_FALLBACK_LEGACY=1` to restore the previous obfuscated retry shape.
45
+ - `target.kind="workspace"` now honors a top-level `scope` field. The precedence is `target.scope` → top-level `scope` → loader-derived default (`merged` when a loader is detected, otherwise `vanilla`); previously the loader-derived default silently shadowed the caller's top-level `scope`.
46
+ - Dependency targets (`target.kind="dependency"`) no longer fail with `ERR_MAPPING_NOT_APPLIED` when the caller asks for `mojang` / `intermediary` / `yarn` against a binary-only artifact. The synthesizer keeps `forceBinaryRemapDisabled=true`, but the resolver now bypasses Mojang remap enforcement on dependency-origin failures, returns the JAR with `mappingApplied: "obfuscated"` and `qualityFlags: ["dependency-mapping-unverified"]`, and surfaces a warning that the requested mapping is not enforced. Callers MUST treat the artifact as namespace-unknown and validate symbol availability themselves; `mappingApplied` is no longer fabricated to the requested mapping value.
47
+ - `target.kind="dependency"` now rejects unsafe Maven version tokens before synthesizing the coordinate. `gradle.properties` values that contain path separators, `..`, NUL, control characters, or any character outside `[A-Za-z0-9._+-]` are filtered out (the next configured property key is tried instead, with the rejection recorded under `attempts[]` as `gradle.properties:<key>:rejected-unsafe-version`). Modules-2 directory entries that fail the same validation are excluded from candidates. Explicit `target.version` strings that fail validation raise `ERR_INVALID_INPUT` with `fieldErrors[0].path === "target.version"`.
48
+ - `target.kind="workspace"` no longer falls back to the latest stable Minecraft version when `gradle.properties` does not declare one. Both `strict: true` and `strict: false` now raise `ERR_WORKSPACE_VERSION_UNRESOLVED` when the workspace version cannot be detected, so callers cannot silently resolve and index the wrong Minecraft API surface. The `details.strict` flag records which strict value was supplied.
49
+ - `target.kind="dependency"` no longer picks the semver-newest entry when `~/.gradle/caches/modules-2/files-2.1/<group>/<name>/` contains multiple cached versions. The synthesizer now raises `ERR_DEPENDENCY_VERSION_UNRESOLVED` with `details.ambiguous=true` and `details.candidatesSeen[]` enumerating the cached versions, so a global cache populated by unrelated projects cannot resolve a version the workspace does not actually use. Single-entry caches and `gradle.properties` matches keep their previous behavior.
50
+ - `get-class-members` now passes the caller's raw `mapping` (preserving `undefined`) into the resolver, mirroring `get-class-source`. A workspace target with an omitted `mapping` now resolves through the workspace's detected compile mapping instead of the obfuscated normalization, eliminating the silent obfuscated-result regression.
51
+ - When the worker process exits while handling `tools/call`, the supervisor returns a structured `{ error, meta }` envelope (synthetic `CallToolResult` with `isError: true`) instead of the previous raw JSON-RPC `-32603` error. Non-`tools/call` requests (`initialize`, `tools/list`, etc.) still receive the legacy envelope.
52
+ - `validate-mixin` runs each stage against an independent soft-deadline. When the `target-lookup` stage exhausts its budget mid-loop, the response is `validationStatus: "partial"` with completed targets retained and remaining ones recorded as `deferred-budget`. Pre-parse stages that exhaust their budget surface as `ERR_STAGE_BUDGET_PRE_PARSE` errors.
53
+ - `validate-mixin` budget-driven partial results no longer trigger the `loom-first → maven-first` retry heuristic. The retry path targets mapping infrastructure failures, not soft-deadline cuts; without this guard a stage-budget partial would re-run the full validation under `maven-first` and defeat the cooperative-deadline contract.
54
+ - `stdio-supervisor` records one worker-exit timestamp per `tool` per actual exit, regardless of how many concurrent `tools/call` requests were in flight. Per-pending-request recording inflated `recentRestarts` to N for a single crash and produced false `retryRecommendation: "report-bug"` under concurrent load.
55
+ - `validate-mixin` `targetOutcomes[]` now emits the `tool-issue` status with a `reason` for completed-but-unreliable targets (mapping / signature / remap failures). Previously the per-target outcome was unconditionally `status: "ok"` even when the underlying target classified as `validation-incomplete`, hiding per-target failures from `includeIssues=false` callers. Whole member-remap exceptions (the entire batch failing) now also flag the target as `tool-issue` with `reason: "member-remap-failed-whole"`; previously only per-member remap failures triggered the classifier.
56
+ - `stdio-supervisor` `lastStageStartedAt` is now refreshed only when the worker emits a different stage name (or on the first emit). Per-target progress notifications during `target-lookup` no longer reset the stage timer, so synthetic worker-restart envelopes report `meta.restart.lastStageElapsedMs` measured from stage entry — not from the most recent per-target update — restoring the documented diagnostic contract.
57
+ - `validate-mixin` now enforces the `inputValidation` stage budget (default 5_000 ms): a slow `sourcePath` read, version normalization, or related input-stage I/O surfaces as `ERR_STAGE_BUDGET_PRE_PARSE` with `failedStage: "input-validation"` instead of hanging the call. Previously the budget was declared in `MixinStageBudgets` but never checked.
58
+ - Synthetic worker-restart envelopes no longer expose `error.suggestedCall` when redaction modified the captured tool arguments (truncated long strings, redacted secret-bearing keys, or trimmed arrays / objects). The redacted form is still emitted on the diagnostic-only `meta.restart.redactedToolArgs` field along with `meta.restart.redactedToolArgsModified` so debug surfaces can inspect the placeholder values without granting them retry semantics.
59
+ - Every `ProblemDetails.suggestedCall` payload an agent receives is now validated against the registered zod schema for the named tool BEFORE leaving the process. Invalid payloads (e.g. the bug shape where `target.value` held a JSON-stringified inner target, producing chained `ERR_INVALID_INPUT` on retry) are dropped from the published envelope; when the gate drops a primary suggestion, `error.hints` gains the literal sentence `"suggested call payload failed schema validation; using fallback examples"` so the caller can observe the elision. Construction sites can pass `examples: [{ params, reason }]` to the new `buildSuggestedCall(...)` helper to surface always-valid fallbacks via `error.exampleCalls[]`. `suggestedCall.params` is published byte-identical to the caller-supplied object — the gate never injects schema defaults into the published payload. Set `SUGGESTED_CALL_VALIDATE_OFF=1` at process start to bypass the gate entirely (raw payloads pass through; the fallback hint is not added) as an emergency rollback path.
60
+ - The `validate-mixin` issue construction (`mixin-validator.ts` × 6 sites) and the supervisor's worker-restart envelope synthesis (`stdio-supervisor.ts` × 1 site) now route their result-level `suggestedCall` payloads through the same `buildSuggestedCall(...)` schema gate as error-side suggestions. Previously these 7 assignment-style construction sites (`obj.suggestedCall = { tool, params }`) bypassed the gate's literal-only invariant test, so a malformed payload would have been published unchanged. The invariant test (`tests/suggested-call-invariant.test.ts`) now also rejects assignment-style construction in `src/`, with `[literal]` / `[assign]` markers in offender reports.
61
+ - `buildSyntheticCallToolResult` now skips the `suggestedCall` synthesis entirely when `ctx.toolName` is missing (the supervisor's `tool = ctx.toolName ?? "unknown"` fallback path). Previously the synthesized `tool: "unknown"` flowed through `buildSuggestedCall`'s fail-open unknown-tool branch and published a `suggestedCall: { tool: "unknown", params: {...} }` payload that was not re-callable by the agent — violating the gate's stated invariant. The diagnostic-only `meta.restart.redactedToolArgs` field still surfaces the args so debug surfaces can inspect them. The `buildSuggestedCall` helper's fail-open behavior for unregistered tools is preserved because many service-level tests exercise code without booting `src/index.ts` and expect the fail-open contract; the fix is targeted at the one production code path (the supervisor) that was actually leaking unre-callable payloads.
62
+ - `batch-class-source`'s schema-level duplicate-`outputFile` guard now normalizes paths via `path.resolve` (mirroring the writer's `getClassSource` semantics) before comparing entries. Previously the guard keyed by `entry.outputFile.trim()` only, so semantically-equivalent aliases (`out.java` vs `./out.java` vs `dir/../out.java`) bypassed the check and concurrent writes still raced on the same physical file. The error message now cites the canonical resolved path so the user sees what collided.
63
+ - `errorToBatchEntryProblem` for non-`AppError` failures now emits a fixed sanitized `detail: "Unexpected server error."` to clients (matching the single-tool `mapErrorToProblem` generic-error contract) and logs the raw message server-side (`log("error", "batch.entry.unhandled", { instance, reason })`) for diagnostics. Previously the public per-entry `error.detail` carried `Error.message` verbatim, which could expose internal filesystem paths, parser internals, or assertion text through the public envelope while the top-level batch call still returned a normal result envelope.
64
+ - `buildSyntheticCallToolResult` now also drops `suggestedCall` when `ctx.toolName` is present but unregistered (typo, disabled tool, or version-skewed name from an older server run). The supervisor consults `getToolSchema` itself before invoking the gate; `buildSuggestedCall` continues to fail open for unregistered names by design (service-level tests that do not boot `src/index.ts` rely on the fail-open contract). The diagnostic-only `meta.restart.redactedToolArgs` field still surfaces the args.
65
+ - Batch lookup `summary` now carries `sharedArtifactWarnings?: string[]` populated from the one-shot shared `resolveArtifact` call. Previously the resolution warnings (version approximation, mapping fallback, source coverage gaps, workspace/dependency resolution caveats) were captured into the batch service's local `SharedArtifact` object and discarded, while per-entry calls dispatched by `artifactId` never re-ran the resolution. The field is omitted when no warnings were produced. Applies to `batch-class-source`, `batch-class-members`, and `batch-symbol-exists`; `batch-mappings` does not run a shared artifact resolution and is unaffected.
66
+ - `statusForErrorCode` now maps `ERR_STAGE_BUDGET_PRE_PARSE` to HTTP-style status 408 (Request Timeout). Previously the code fell through to status 500, misclassifying a controlled `validate-mixin` budget exhaustion (caller-recoverable via `mixinConfigPath` shrink, budget raise, or `MIXIN_STAGE_BUDGETS_OFF=1`) as an internal server failure.
67
+ - `statusForErrorCode` now maps `ERR_WORKSPACE_VERSION_UNRESOLVED` and `ERR_DEPENDENCY_VERSION_UNRESOLVED` to HTTP-style status 422 (Unprocessable Entity). Previously they fell through to status 500, misclassifying a caller-recoverable input gap (caller can retry by passing `target.kind="version"` with an explicit Minecraft version, or by pointing `projectPath` at a workspace whose `gradle.properties` carries `minecraft_version`) as an internal server failure. `batch-symbol-exists` is the most likely surface for this gap because workspace targets without a detectable Minecraft version are the new path's primary failure mode.
68
+
8
69
  ## [4.0.0] - 2026-04-18
9
70
 
10
71
  ### Changed
package/README.md CHANGED
@@ -11,20 +11,21 @@
11
11
 
12
12
  ---
13
13
 
14
- `@adhisang/minecraft-modding-mcp` is an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that gives AI assistants structured access to Minecraft source code, mappings, mod JARs, registry data, and validation workflows. It works with Claude Desktop, Claude Code, VS Code, Codex CLI, Gemini CLI, and other MCP-capable clients.
14
+ `@adhisang/minecraft-modding-mcp` is an MCP server for AI-assisted Minecraft modding workflows, built on the [Model Context Protocol](https://modelcontextprotocol.io/). Use it when an agent needs to inspect Minecraft source, resolve mappings, compare versions, analyze mod JARs, validate Mixin, Access Widener, or Access Transformer files, or work with NBT and registry data from an MCP client.
15
15
 
16
- **36 tools** (6 entry + 30 expert) | **7 resources** | **4 namespace mappings** | **SQLite-backed cache**
16
+ It runs over stdio and works with Claude Desktop, Claude Code, VS Code, Codex CLI, Gemini CLI, and other MCP-capable clients.
17
+
18
+ **37 tools** (6 entry + 31 expert) | **7 resources** | **4 namespace mappings** | **SQLite-backed cache**
17
19
 
18
20
  ## Features
19
21
 
20
- - **Source Exploration**: browse and search decompiled Minecraft source code with line-level precision and cursor-paginated file listing
21
- - **Multi-Mapping Conversion**: translate class, field, and method names between `obfuscated`, `mojang`, `intermediary`, and `yarn`
22
- - **Version Comparison**: diff class signatures and registry entries between Minecraft versions
23
- - **Mod JAR Analysis**: extract metadata, dependencies, entrypoints, Mixin configs, and packaged Access Transformer paths from Fabric, Forge, and NeoForge mod JARs
24
- - **Mixin, Access Widener, and Access Transformer Validation**: validate source, `.accesswidener`, and Forge/NeoForge access transformer files against a target Minecraft version
25
- - **NBT Round-Trip**: decode NBT binary to typed JSON, apply RFC 6902 patches, and encode it back to NBT
26
- - **Registry Data and Runtime Metrics**: query generated registry snapshots and inspect cache and latency counters
27
- - **MCP Resources**: expose versions, class source, artifact metadata, and mappings through URI-based resources
22
+ - **Source exploration**: browse, list, and search decompiled Minecraft source with line-level context
23
+ - **Mapping-aware symbol work**: convert class, field, and method names between `obfuscated`, `mojang`, `intermediary`, and `yarn`
24
+ - **Version comparison**: compare class signatures, registry entries, and migration-oriented summaries across Minecraft versions
25
+ - **Mod JAR analysis**: read Fabric, Forge, and NeoForge metadata, entrypoints, Mixin configs, dependencies, source, and remap previews
26
+ - **Project validation**: validate Mixin source, `.accesswidener` files, and Forge/NeoForge Access Transformer files against the target version
27
+ - **NBT, registry, cache, and diagnostics**: patch NBT payloads, inspect generated registry data, and manage cache/runtime state
28
+ - **MCP resources**: expose versions, class source, artifact metadata, and mappings through URI-based resources
28
29
 
29
30
  ## Quick Start
30
31
 
@@ -41,11 +42,11 @@ Start the server locally:
41
42
  npx -y @adhisang/minecraft-modding-mcp
42
43
  ```
43
44
 
44
- If automatic JAR downloads are blocked in your environment, set `MCP_VINEFLOWER_JAR_PATH` and `MCP_TINY_REMAPPER_JAR_PATH` in the client configuration.
45
+ Use this same command in MCP client configs. If automatic JAR downloads are blocked in your environment, set `MCP_VINEFLOWER_JAR_PATH` and `MCP_TINY_REMAPPER_JAR_PATH` there.
45
46
 
46
47
  ### Client Setup
47
48
 
48
- CLI clients:
49
+ CLI clients can register the package command directly.
49
50
 
50
51
  Claude Code:
51
52
 
@@ -61,7 +62,7 @@ codex mcp add minecraft-modding -- npx -y @adhisang/minecraft-modding-mcp
61
62
 
62
63
  Run `claude mcp list` or `codex mcp list` after registration to verify the server is available.
63
64
 
64
- The stdio transport auto-detects both newline-delimited and `Content-Length` framing, so the same server command works across Codex and standard MCP clients.
65
+ The stdio transport auto-detects newline-delimited and `Content-Length` framing, so the same server command works across Codex and standard MCP clients.
65
66
 
66
67
  #### Claude Desktop
67
68
 
@@ -135,9 +136,9 @@ Pass environment variables to override defaults:
135
136
 
136
137
  ## Start Here
137
138
 
138
- These six top-level workflow tools cover the common workflows and return summary-first results, so they are the best default starting points for agents and MCP clients.
139
+ These six top-level workflow tools cover the common paths and return summary-first results. They are the best default starting points for agents and MCP clients.
139
140
 
140
- All six return `result.summary` first, and can include `summary.nextActions` when there is a clear follow-up step. Use the table for tool selection, then use the examples and reference docs for exact payloads.
141
+ All six return `result.summary` first and can include `summary.nextActions` when there is a clear follow-up step. Pick the tool from the table, then use the examples and reference docs for exact payloads.
141
142
 
142
143
  | Tool | Start here for |
143
144
  | --- | --- |
@@ -150,14 +151,16 @@ All six return `result.summary` first, and can include `summary.nextActions` whe
150
151
 
151
152
  ### Workflow Notes
152
153
 
153
- Keep only the high-frequency notes here. For the full pitfall list, exact contract details, migration notes, and environment variables, see [docs/tool-reference.md](docs/tool-reference.md).
154
+ These notes cover high-frequency decisions during onboarding. For the full pitfall list, exact contracts, migration notes, and environment variables, see [docs/tool-reference.md](docs/tool-reference.md).
154
155
 
155
156
  - `search-class-source` defaults to `queryMode="auto"` and keeps separator queries such as `foo.bar`, `foo_bar`, and `foo$bar` on the indexed path. Use `queryMode="literal"` for an explicit full substring scan.
156
157
  - If you do not already have an artifact, prefer `subject.kind="workspace"` for `inspect-minecraft` instead of guessing artifact details. When artifact context is the only missing input, a retryable `suggestedCall` preserves the requested task.
157
158
  - `trace-symbol-lifecycle` expects `Class.method` in `symbol`. Keep exact overload matching in the separate `descriptor` field.
158
159
  - For unobfuscated releases such as `26.1+`, `check-symbol-exists` and `analyze-symbol task="exists"` validate `mojang` lookups against runtime bytecode when no mapping graph exists, and return `mapping_unavailable` when the runtime JAR itself is unreachable.
159
160
  - `analyze-mod` and `validate-project` require structured `subject` objects and canonical `include` groups; stale string-subject or domain-include payloads return `ERR_INVALID_INPUT` with a retryable `suggestedCall`.
160
- - `validate-project task="project-summary"` propagates `preferProjectVersion=true` across discovered Mixin, Access Widener, and Access Transformer checks. When no version can be resolved from the request or `gradle.properties`, the summary blocks with version-agnostic recovery guidance.
161
+ - `validate-project task="project-summary"` propagates `preferProjectVersion=true` across discovered Mixin, Access Widener, and Access Transformer checks. If no version can be resolved from the request or `gradle.properties`, the summary returns recovery guidance instead of guessing.
162
+ - `validate-mixin` and `validate-project` keep `mapping-health` lightweight for `obfuscated` and `mojang` validation, avoiding full Tiny mapping graph loads unless `intermediary` or `yarn` namespaces are requested.
163
+ - `validate-project task="project-summary"` uses a lightweight artifact probe for `tasks["minecraft.artifact.resolved"]`; it does not decompile Minecraft or rebuild the source index just to report per-probe status. Set `VALIDATE_PROJECT_TASKS_OFF=1` to omit the additive `tasks` field.
161
164
 
162
165
  ### Inspect Minecraft source from a version
163
166
 
@@ -239,7 +242,7 @@ Workspace summaries still default to discovering mixins and access wideners. Add
239
242
  ## Documentation
240
243
 
241
244
  - [Detailed example requests](docs/examples.md) for copyable payloads and common workflows
242
- - [Tool and configuration reference](docs/tool-reference.md) for exact inputs, outputs, resource behavior, environment variables, and migration notes start with the [Which Tool for Which Question](docs/tool-reference.md#which-tool-for-which-question) decision table when you are not sure which tool to call
245
+ - [Tool and configuration reference](docs/tool-reference.md) for exact inputs, outputs, resource behavior, environment variables, and migration notes. Start with the [Which Tool for Which Question](docs/tool-reference.md#which-tool-for-which-question) decision table when you are not sure which tool to call.
243
246
  - [日本語 README](docs/README-ja.md) for a Japanese onboarding overview
244
247
 
245
248
  ## Tool Surface
@@ -305,9 +308,7 @@ Tools for converting symbol names between namespaces and checking symbol existen
305
308
  | `check-symbol-exists` | Check whether a class, field, or method exists in a namespace |
306
309
  <!-- END GENERATED TOOL TABLE: mapping-symbols -->
307
310
 
308
- `resolve-artifact`, `find-mapping`, `resolve-method-mapping-exact`, `resolve-workspace-symbol`, and `check-symbol-exists` default `compact` to `true`: empty fields are stripped, `resolve-artifact` omits diagnostic fields (`provenance`, `artifactContents`, etc.), and mapping tools omit the redundant `candidates` array on full-confidence exact matches and slim the tail to `{kind, symbol, owner, name, descriptor, confidence, matchKind}` (signalled by `candidateDetailsTruncated`) for unresolved results with more than three candidates. Pass `compact: false` for the full diagnostic shape.
309
-
310
- `get-class-source`, `get-class-members`, `search-class-source`, and `list-artifact-files` accept the same `compact` parameter as opt-in (default `false`). When enabled it strips `provenance`, `artifactContents`, `qualityFlags`, and (for `get-class-members`) `context`, while preserving primary payloads (`sourceText`, `members`/`counts`, `hits`, `items`). See [docs/tool-reference.md](docs/tool-reference.md) for the full per-tool field list.
311
+ Several lookup tools support `compact` result shaping for shorter responses. See [docs/tool-reference.md](docs/tool-reference.md) for defaults and the full per-tool field list.
311
312
 
312
313
  ### NBT Utilities
313
314
 
@@ -337,14 +338,15 @@ Tools for extracting metadata from mod JARs, decompiling mod source, searching m
337
338
 
338
339
  ### Validation
339
340
 
340
- Tools for validating Mixin source, Access Widener files, and Forge/NeoForge Access Transformer files against a target Minecraft version. `validate-access-widener` defaults to vanilla bytecode validation; pass `projectPath`, `scope`, and `preferProjectVersion` for runtime-aware mode, which returns runtime `provenance` plus per-entry `resolvedRuntimeAccess` evidence. `validate-access-transformer` infers `atNamespace` from Forge/NeoForge workspace context when `projectPath` is provided and uses loader runtime artifacts for `scope="loader"`.
341
+ Tools for validating Mixin source, Access Widener files, and Forge/NeoForge Access Transformer files against a target Minecraft version. Workspace options let validation use loader/runtime context when a project path is available.
341
342
 
342
343
  <!-- BEGIN GENERATED TOOL TABLE: validation -->
343
344
  | Tool | Purpose |
344
345
  | --- | --- |
345
- | `validate-mixin` | Validate Mixin source against a target Minecraft version |
346
+ | `validate-mixin` | Validate Mixin source against a target Minecraft version (returns `validationStatus: "partial"` with `targetOutcomes` when a stage budget defers work) |
346
347
  | `validate-access-widener` | Validate Access Widener content against a target Minecraft version, optionally using runtime-aware Loom artifacts |
347
348
  | `validate-access-transformer` | Validate Access Transformer content against a target Minecraft version, optionally using Forge/NeoForge runtime artifacts |
349
+ | `verify-mixin-target` | Single-call probe for owner / member existence with `@Shadow` / `@Accessor` / `@Invoker` advice |
348
350
  <!-- END GENERATED TOOL TABLE: validation -->
349
351
 
350
352
  ### Registry & Diagnostics
@@ -358,6 +360,21 @@ Tools for querying generated registry data and inspecting server runtime state.
358
360
  | `get-runtime-metrics` | Inspect runtime metrics and latency snapshots |
359
361
  <!-- END GENERATED TOOL TABLE: registry-diagnostics -->
360
362
 
363
+ ### Batch Lookup
364
+
365
+ Tools that share one resolved artifact or Minecraft version across a fixed shortlist. Results include one status per item plus an aggregate `summary`. See [Batch lookup contract](docs/tool-reference.md#batch-lookup-contract) for failure handling and retry mapping.
366
+
367
+ Within one MCP server process, batch class lookups that need the same binary fallback share one in-flight source indexing/decompile rebuild for that artifact.
368
+
369
+ <!-- BEGIN GENERATED TOOL TABLE: batch-lookup -->
370
+ | Tool | Purpose |
371
+ | --- | --- |
372
+ | `batch-class-source` | Read source for many classes against one shared resolved artifact (1..50 entries per call) |
373
+ | `batch-class-members` | List members for many classes against one shared resolved artifact (1..50 entries per call) |
374
+ | `batch-symbol-exists` | Probe symbol existence for many entries against one shared Minecraft-version artifact (workspace / version targets only) |
375
+ | `batch-mappings` | Translate many symbols across mapping namespaces with one shared Minecraft version (no shared artifact) |
376
+ <!-- END GENERATED TOOL TABLE: batch-lookup -->
377
+
361
378
  Detailed parameter constraints, migration notes, resource behavior, and the full environment-variable matrix live in [docs/tool-reference.md](docs/tool-reference.md).
362
379
 
363
380
  ## Development
@@ -0,0 +1,29 @@
1
+ export declare const SUGGESTED_CALL_VALIDATE_OFF: boolean;
2
+ export type SuggestedCallExample = {
3
+ params: unknown;
4
+ reason: string;
5
+ };
6
+ export type SuggestedCallSpec = {
7
+ tool: string;
8
+ params: unknown;
9
+ examples?: SuggestedCallExample[];
10
+ };
11
+ export type ValidatedSuggestedCall = {
12
+ tool: string;
13
+ params: Record<string, unknown>;
14
+ };
15
+ export type ValidatedExampleCall = {
16
+ tool: string;
17
+ params: Record<string, unknown>;
18
+ reason: string;
19
+ valid: true;
20
+ };
21
+ export type SuggestedCallOutput = {
22
+ suggestedCall?: ValidatedSuggestedCall;
23
+ exampleCalls?: ValidatedExampleCall[];
24
+ /** Set to `true` when the caller supplied `params` but the gate dropped it.
25
+ * Spread into AppError `details`; `mapErrorToProblem` reads the marker to
26
+ * append the fallback hint to `error.hints` and strips it before emission. */
27
+ _suggestedCallPrimaryDropped?: true;
28
+ };
29
+ export declare function buildSuggestedCall(spec: SuggestedCallSpec): SuggestedCallOutput;
@@ -0,0 +1,58 @@
1
+ import { getToolSchema, validateToolParams } from "./tool-schema-registry.js";
2
+ export const SUGGESTED_CALL_VALIDATE_OFF = process.env.SUGGESTED_CALL_VALIDATE_OFF === "1";
3
+ function asParamsRecord(params) {
4
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
5
+ return undefined;
6
+ }
7
+ return params;
8
+ }
9
+ export function buildSuggestedCall(spec) {
10
+ if (SUGGESTED_CALL_VALIDATE_OFF) {
11
+ const rawParams = asParamsRecord(spec.params);
12
+ if (rawParams) {
13
+ return { suggestedCall: { tool: spec.tool, params: rawParams } };
14
+ }
15
+ return {};
16
+ }
17
+ const primaryParams = asParamsRecord(spec.params);
18
+ if (primaryParams) {
19
+ // Unknown-tool fail-open: the registry is populated at index.ts startup;
20
+ // service-level tests that do not boot index.ts run with an empty
21
+ // registry and rely on this pass-through. Callers that synthesize a tool
22
+ // name from runtime data (e.g. `?? "unknown"`) MUST skip this helper, or
23
+ // a non-callable payload escapes via this branch.
24
+ if (!getToolSchema(spec.tool)) {
25
+ return { suggestedCall: { tool: spec.tool, params: primaryParams } };
26
+ }
27
+ const primary = validateToolParams(spec.tool, primaryParams);
28
+ if (primary.valid) {
29
+ return { suggestedCall: { tool: spec.tool, params: primaryParams } };
30
+ }
31
+ }
32
+ const droppedMarker = spec.params !== undefined ? { _suggestedCallPrimaryDropped: true } : {};
33
+ if (spec.examples && spec.examples.length > 0) {
34
+ const validated = spec.examples
35
+ .map((example) => {
36
+ const exampleParams = asParamsRecord(example.params);
37
+ if (!exampleParams) {
38
+ return null;
39
+ }
40
+ const result = validateToolParams(spec.tool, exampleParams);
41
+ if (!result.valid) {
42
+ return null;
43
+ }
44
+ return {
45
+ tool: spec.tool,
46
+ params: exampleParams,
47
+ reason: example.reason,
48
+ valid: true
49
+ };
50
+ })
51
+ .filter((entry) => entry !== null);
52
+ if (validated.length > 0) {
53
+ return { exampleCalls: validated, ...droppedMarker };
54
+ }
55
+ }
56
+ return droppedMarker;
57
+ }
58
+ //# sourceMappingURL=build-suggested-call.js.map
@@ -1,5 +1,6 @@
1
1
  import { type PathRuntimeInfo } from "./path-converter.js";
2
- export declare const PUBLIC_CACHE_KINDS: readonly ["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap", "binary-remap"];
2
+ import { type WorkspaceContextCache } from "./workspace-context-cache.js";
3
+ export declare const PUBLIC_CACHE_KINDS: readonly ["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap", "binary-remap", "workspace"];
3
4
  export type PublicCacheKind = (typeof PUBLIC_CACHE_KINDS)[number];
4
5
  export declare const CACHE_HEALTH_STATES: readonly ["healthy", "partial", "stale", "orphaned", "corrupt", "in_use"];
5
6
  export type CacheHealthState = (typeof CACHE_HEALTH_STATES)[number];
@@ -37,6 +38,7 @@ export type CacheRegistryConfig = {
37
38
  cacheDir: string;
38
39
  sqlitePath: string;
39
40
  pathRuntimeInfo?: PathRuntimeInfo;
41
+ workspaceContextCache?: WorkspaceContextCache;
40
42
  };
41
43
  export interface CacheRegistry {
42
44
  summarize(input: {
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
4
4
  import { createError, ERROR_CODES } from "./errors.js";
5
5
  import { normalizeOptionalPathForHost } from "./path-converter.js";
6
6
  import Database from "./storage/sqlite.js";
7
+ import { getProcessWorkspaceContextCache } from "./workspace-context-cache.js";
7
8
  export const PUBLIC_CACHE_KINDS = [
8
9
  "artifact-index",
9
10
  "downloads",
@@ -11,7 +12,8 @@ export const PUBLIC_CACHE_KINDS = [
11
12
  "registry",
12
13
  "decompiled-source",
13
14
  "mod-remap",
14
- "binary-remap"
15
+ "binary-remap",
16
+ "workspace"
15
17
  ];
16
18
  export const CACHE_HEALTH_STATES = [
17
19
  "healthy",
@@ -40,6 +42,8 @@ function kindRoot(config, cacheKind) {
40
42
  return join(config.cacheDir, "remapped-mods");
41
43
  case "binary-remap":
42
44
  return join(config.cacheDir, "remapped");
45
+ case "workspace":
46
+ return "<in-memory:workspace-context-cache>";
43
47
  }
44
48
  }
45
49
  async function listFilesRecursive(root) {
@@ -417,6 +421,26 @@ async function artifactIndexEntries(config) {
417
421
  db.close();
418
422
  }
419
423
  }
424
+ function workspaceCacheEntries(workspaceCache) {
425
+ const contexts = workspaceCache.list();
426
+ return contexts.map((ctx) => ({
427
+ cacheKind: "workspace",
428
+ entryId: ctx.projectPath,
429
+ path: ctx.projectPath,
430
+ sizeBytes: 0,
431
+ status: "healthy",
432
+ meta: {
433
+ projectPath: ctx.projectPath,
434
+ minecraftVersion: ctx.minecraftVersion,
435
+ compileMapping: ctx.compileMapping,
436
+ loader: ctx.loader,
437
+ detectedAt: new Date(ctx.detectedAt).toISOString(),
438
+ updatedAt: new Date(ctx.detectedAt).toISOString(),
439
+ partial: ctx.partial === true,
440
+ dependencyVersionCount: ctx.dependencyVersions.size
441
+ }
442
+ }));
443
+ }
420
444
  async function fileBackedEntries(config, cacheKind) {
421
445
  const root = kindRoot(config, cacheKind);
422
446
  const files = await listFilesRecursive(root);
@@ -454,18 +478,25 @@ async function fileBackedEntries(config, cacheKind) {
454
478
  return entries;
455
479
  }
456
480
  export function createCacheRegistry(config) {
481
+ const workspaceCache = config.workspaceContextCache ?? getProcessWorkspaceContextCache();
457
482
  async function collectEntries(cacheKinds, selector) {
458
483
  const selectedKinds = cacheKinds?.length ? cacheKinds : [...PUBLIC_CACHE_KINDS];
459
484
  const preparedSelector = prepareSelector(selector, config.pathRuntimeInfo);
460
485
  const now = Date.now();
461
- const entries = await Promise.all(selectedKinds.map((cacheKind) => cacheKind === "artifact-index"
462
- ? artifactIndexEntries(config)
463
- : fileBackedEntries(config, cacheKind)));
486
+ const entries = await Promise.all(selectedKinds.map((cacheKind) => {
487
+ if (cacheKind === "artifact-index") {
488
+ return artifactIndexEntries(config);
489
+ }
490
+ if (cacheKind === "workspace") {
491
+ return Promise.resolve(workspaceCacheEntries(workspaceCache));
492
+ }
493
+ return fileBackedEntries(config, cacheKind);
494
+ }));
464
495
  const enriched = entries
465
496
  .flat()
466
497
  .map((entry) => ({
467
498
  ...entry,
468
- status: deriveEntryStatus(entry, config, now)
499
+ status: entry.cacheKind === "workspace" ? entry.status : deriveEntryStatus(entry, config, now)
469
500
  }));
470
501
  return sortEntries(enriched.filter((entry) => matchesSelector(entry, preparedSelector, config.pathRuntimeInfo)));
471
502
  }
@@ -475,8 +506,17 @@ export function createCacheRegistry(config) {
475
506
  const entries = await collectEntries(selectedKinds, input.selector);
476
507
  const kinds = {};
477
508
  for (const cacheKind of selectedKinds) {
478
- const root = kindRoot(config, cacheKind);
479
509
  const rows = entries.filter((entry) => entry.cacheKind === cacheKind);
510
+ if (cacheKind === "workspace") {
511
+ kinds[cacheKind] = {
512
+ cacheKind,
513
+ entryCount: rows.length,
514
+ totalBytes: 0,
515
+ status: "healthy"
516
+ };
517
+ continue;
518
+ }
519
+ const root = kindRoot(config, cacheKind);
480
520
  kinds[cacheKind] = {
481
521
  cacheKind,
482
522
  entryCount: rows.length,
@@ -519,6 +559,10 @@ export function createCacheRegistry(config) {
519
559
  db?.prepare("DELETE FROM artifacts WHERE artifact_id = ?").run([entry.entryId]);
520
560
  continue;
521
561
  }
562
+ if (entry.cacheKind === "workspace") {
563
+ workspaceCache.invalidate(entry.entryId);
564
+ continue;
565
+ }
522
566
  if (existsSync(entry.path)) {
523
567
  await rm(entry.path, { force: true });
524
568
  }
@@ -72,11 +72,11 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
72
72
  };
73
73
  signatureMode: "exact" | "name-only";
74
74
  maxCandidates: number;
75
- projectPath?: string | undefined;
76
75
  version?: string | undefined;
77
- sourceMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
78
- targetMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
79
- classNameMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
76
+ projectPath?: string | undefined;
77
+ sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
78
+ targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
79
+ classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
80
80
  detail?: "full" | "summary" | "standard" | undefined;
81
81
  include?: string[] | undefined;
82
82
  includeKinds?: ("class" | "field" | "method")[] | undefined;
@@ -89,11 +89,11 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
89
89
  descriptor?: string | undefined;
90
90
  owner?: string | undefined;
91
91
  };
92
- projectPath?: string | undefined;
93
92
  version?: string | undefined;
94
- sourceMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
95
- targetMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
96
- classNameMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
93
+ projectPath?: string | undefined;
94
+ sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
95
+ targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
96
+ classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
97
97
  nameMode?: "auto" | "fqcn" | undefined;
98
98
  detail?: "full" | "summary" | "standard" | undefined;
99
99
  include?: string[] | undefined;
@@ -112,11 +112,11 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
112
112
  };
113
113
  signatureMode: "exact" | "name-only";
114
114
  maxCandidates: number;
115
- projectPath?: string | undefined;
116
115
  version?: string | undefined;
117
- sourceMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
118
- targetMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
119
- classNameMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
116
+ projectPath?: string | undefined;
117
+ sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
118
+ targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
119
+ classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
120
120
  detail?: "full" | "summary" | "standard" | undefined;
121
121
  include?: string[] | undefined;
122
122
  includeKinds?: ("class" | "field" | "method")[] | undefined;
@@ -129,11 +129,11 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
129
129
  descriptor?: string | undefined;
130
130
  owner?: string | undefined;
131
131
  };
132
- projectPath?: string | undefined;
133
132
  version?: string | undefined;
134
- sourceMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
135
- targetMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
136
- classNameMapping?: "intermediary" | "mojang" | "yarn" | "obfuscated" | undefined;
133
+ projectPath?: string | undefined;
134
+ sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
135
+ targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
136
+ classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
137
137
  nameMode?: "auto" | "fqcn" | undefined;
138
138
  detail?: "full" | "summary" | "standard" | undefined;
139
139
  include?: string[] | undefined;
@@ -0,0 +1,34 @@
1
+ import type { GetClassMembersInput, GetClassMembersOutput, ResolveArtifactInput, ResolveArtifactOutput } from "../source-service.js";
2
+ import type { ArtifactScope, MappingSourcePriority, ResolveArtifactTargetInput, SourceMapping } from "../types.js";
3
+ import { type BatchOutput } from "./batch-runner.js";
4
+ export type BatchClassMembersDeps = {
5
+ resolveArtifact: (input: ResolveArtifactInput) => Promise<ResolveArtifactOutput>;
6
+ getClassMembers: (input: GetClassMembersInput) => Promise<GetClassMembersOutput>;
7
+ };
8
+ export type BatchClassMembersEntry = {
9
+ className: string;
10
+ access?: "public" | "all";
11
+ includeSynthetic?: boolean;
12
+ includeInherited?: boolean;
13
+ memberPattern?: string;
14
+ maxMembers?: number;
15
+ };
16
+ export type BatchClassMembersInput = {
17
+ target: ResolveArtifactTargetInput;
18
+ mapping?: SourceMapping;
19
+ sourcePriority?: MappingSourcePriority;
20
+ allowDecompile?: boolean;
21
+ projectPath?: string;
22
+ scope?: ArtifactScope;
23
+ preferProjectVersion?: boolean;
24
+ strictVersion?: boolean;
25
+ concurrency?: number;
26
+ failFast?: boolean;
27
+ compact?: boolean;
28
+ entries: readonly BatchClassMembersEntry[];
29
+ };
30
+ export declare class BatchClassMembersService {
31
+ private readonly deps;
32
+ constructor(deps: BatchClassMembersDeps);
33
+ execute(input: BatchClassMembersInput): Promise<BatchOutput<Record<string, unknown>>>;
34
+ }