@adhisang/minecraft-modding-mcp 4.2.0 → 5.0.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.
Files changed (120) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +6 -4
  3. package/dist/access-transformer-parser.d.ts +2 -1
  4. package/dist/access-transformer-parser.js +10 -1
  5. package/dist/build-suggested-call.js +31 -10
  6. package/dist/cache-registry.d.ts +8 -0
  7. package/dist/cache-registry.js +99 -6
  8. package/dist/config.d.ts +1 -0
  9. package/dist/config.js +4 -0
  10. package/dist/entry-tools/analyze-mod-service.d.ts +24 -24
  11. package/dist/entry-tools/analyze-symbol-service.d.ts +57 -22
  12. package/dist/entry-tools/analyze-symbol-service.js +124 -28
  13. package/dist/entry-tools/batch-class-members-service.d.ts +3 -1
  14. package/dist/entry-tools/batch-class-members-service.js +6 -6
  15. package/dist/entry-tools/batch-class-source-service.d.ts +3 -1
  16. package/dist/entry-tools/batch-class-source-service.js +5 -6
  17. package/dist/entry-tools/batch-mappings-service.d.ts +3 -1
  18. package/dist/entry-tools/batch-mappings-service.js +4 -5
  19. package/dist/entry-tools/batch-symbol-exists-service.d.ts +3 -1
  20. package/dist/entry-tools/batch-symbol-exists-service.js +4 -5
  21. package/dist/entry-tools/compare-minecraft-service.d.ts +10 -10
  22. package/dist/entry-tools/entry-tool-schema.d.ts +1 -1
  23. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +1 -3
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -2
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +0 -5
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +0 -1
  27. package/dist/entry-tools/inspect-minecraft/handlers/file.js +0 -1
  28. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +0 -1
  29. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +8 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/search.js +13 -2
  31. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +1 -1
  32. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +1 -1
  33. package/dist/entry-tools/inspect-minecraft/internal.d.ts +90 -89
  34. package/dist/entry-tools/inspect-minecraft/internal.js +30 -14
  35. package/dist/entry-tools/inspect-minecraft-service.d.ts +218 -218
  36. package/dist/entry-tools/inspect-minecraft-service.js +1 -1
  37. package/dist/entry-tools/manage-cache-service.d.ts +4 -4
  38. package/dist/entry-tools/response-contract.d.ts +1 -1
  39. package/dist/entry-tools/response-contract.js +1 -0
  40. package/dist/entry-tools/validate-project/cases/mixin.js +2 -1
  41. package/dist/entry-tools/validate-project/cases/project-summary.js +5 -3
  42. package/dist/entry-tools/validate-project-service.d.ts +42 -36
  43. package/dist/entry-tools/validate-project-service.js +3 -1
  44. package/dist/entry-tools/verify-mixin-target-service.d.ts +30 -0
  45. package/dist/entry-tools/verify-mixin-target-service.js +113 -22
  46. package/dist/error-mapping.d.ts +51 -0
  47. package/dist/error-mapping.js +157 -2
  48. package/dist/errors.js +4 -1
  49. package/dist/index.js +138 -71
  50. package/dist/java-process.d.ts +9 -0
  51. package/dist/java-process.js +11 -5
  52. package/dist/mapping/internal-types.d.ts +7 -0
  53. package/dist/mapping/lookup.d.ts +1 -1
  54. package/dist/mapping/lookup.js +7 -1
  55. package/dist/mapping/types.d.ts +6 -0
  56. package/dist/mapping-service.d.ts +26 -2
  57. package/dist/mapping-service.js +178 -36
  58. package/dist/mcp-helpers.d.ts +1 -0
  59. package/dist/mcp-helpers.js +25 -3
  60. package/dist/minecraft-explorer-service.js +24 -11
  61. package/dist/mixin/access-validators.js +33 -1
  62. package/dist/mixin/annotation-validators.js +53 -7
  63. package/dist/mixin/parsed-validator.js +2 -2
  64. package/dist/mixin-parser.js +48 -4
  65. package/dist/mod-decompile-service.js +2 -1
  66. package/dist/mod-remap-service.d.ts +20 -1
  67. package/dist/mod-remap-service.js +95 -40
  68. package/dist/nbt/java-nbt-codec.js +87 -2
  69. package/dist/observability.d.ts +6 -0
  70. package/dist/observability.js +10 -0
  71. package/dist/page-cursor.d.ts +17 -0
  72. package/dist/page-cursor.js +0 -0
  73. package/dist/registry-service.js +25 -1
  74. package/dist/resources.js +45 -9
  75. package/dist/response-utils.d.ts +29 -4
  76. package/dist/response-utils.js +95 -5
  77. package/dist/source/access-validate.js +12 -2
  78. package/dist/source/artifact-resolver.js +1 -1
  79. package/dist/source/cache-metrics.js +2 -2
  80. package/dist/source/class-source/members-builder.d.ts +44 -1
  81. package/dist/source/class-source/members-builder.js +51 -4
  82. package/dist/source/class-source/snippet-builder.d.ts +13 -0
  83. package/dist/source/class-source/snippet-builder.js +48 -4
  84. package/dist/source/class-source-helpers.js +67 -11
  85. package/dist/source/class-source.js +97 -20
  86. package/dist/source/indexer.d.ts +31 -6
  87. package/dist/source/indexer.js +95 -25
  88. package/dist/source/search.d.ts +1 -1
  89. package/dist/source/search.js +94 -22
  90. package/dist/source/validate-mixin.d.ts +2 -1
  91. package/dist/source/validate-mixin.js +45 -4
  92. package/dist/source/workspace-target.js +15 -9
  93. package/dist/source-jar-reader.d.ts +54 -1
  94. package/dist/source-jar-reader.js +121 -10
  95. package/dist/source-service.d.ts +28 -7
  96. package/dist/storage/files-repo.d.ts +12 -0
  97. package/dist/storage/files-repo.js +24 -0
  98. package/dist/storage/symbols-repo.d.ts +6 -0
  99. package/dist/storage/symbols-repo.js +6 -0
  100. package/dist/symbols/symbol-extractor.js +35 -6
  101. package/dist/text-truncate.d.ts +7 -0
  102. package/dist/text-truncate.js +21 -0
  103. package/dist/tiny-remapper-resolver.js +10 -0
  104. package/dist/tiny-remapper-service.js +3 -3
  105. package/dist/tool-contract-manifest.js +2 -2
  106. package/dist/tool-execution-gate.js +12 -1
  107. package/dist/tool-guidance.d.ts +4 -1
  108. package/dist/tool-guidance.js +37 -12
  109. package/dist/tool-schemas.d.ts +559 -494
  110. package/dist/tool-schemas.js +99 -63
  111. package/dist/types.d.ts +1 -0
  112. package/dist/version-diff-service.js +5 -0
  113. package/dist/version-service.d.ts +3 -0
  114. package/dist/version-service.js +18 -2
  115. package/dist/warning-details.d.ts +18 -0
  116. package/dist/warning-details.js +63 -0
  117. package/docs/README-ja.md +2 -2
  118. package/docs/examples.md +26 -2
  119. package/docs/tool-reference.md +23 -17
  120. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,46 @@ 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
+ ## [Unreleased]
9
+
10
+ ## [5.0.0] - 2026-06-04
11
+
12
+ ### Changed
13
+
14
+ - **Breaking:** `get-class-members` now returns at most **150** members per page by default (was 500). The existing `nextCursor` continuation already covers paging; callers that need more in one response can pass an explicit `maxMembers` (still capped at 5000). `maxClassResults` and `maxVersions` defaults are unchanged.
15
+ - **Breaking:** `inspect-minecraft` responses no longer echo the requested subject inside `summary.subject`. The raw requested subject still surfaces exactly once, in the always-on top-level `subject` block as `subject.requested` (paired with `subject.resolved`). Clients reading `summary.subject.requested` must switch to `subject.requested` (or correlate via `requestId`).
16
+ - **Breaking:** `meta.warningDetails[]` entries no longer duplicate the warning text. Each entry now carries `index: number` referencing its string in `meta.warnings[]` (1:1, order-preserving) instead of a `message` field. Clients reading `warningDetails[].message` must switch to `meta.warnings[detail.index]`.
17
+ - **Breaking:** `get-class-source` and `get-class-members` now omit the diagnostic fields `provenance`, `qualityFlags`, and `artifactContents` by default. Pass the new `includeProvenance: true` to include them. (`compact` keeps its existing meaning — strip empty fields, and for `get-class-members` also `context`.) Callers that previously read those fields without setting `compact` must now pass `includeProvenance: true`.
18
+ - **Breaking:** `get-class-members` member objects are slimmed on the wire. `accessFlags` is dropped (the modifiers are already encoded in `javaSignature`), `isSynthetic` is emitted only when `true`, and `ownerFqn` is hoisted to a single block-level `members.ownerFqn` when every member shares one owner (the common, non-`includeInherited` case); it stays per-member when `includeInherited` surfaces multiple owners. `name` and `javaSignature` are unchanged. Read the owner from `members.ownerFqn` (falling back to `member.ownerFqn`), and derive access modifiers from `javaSignature` instead of `accessFlags`. `diff-class-signatures` / `verify-mixin-target` member shapes are unchanged.
19
+ - **Breaking:** `get-class-members` omits `jvmDescriptor` on **field** members by default (the type is already in `javaSignature`). Method/constructor `jvmDescriptor` is always present for overload disambiguation. Pass `includeDescriptors: true` (legacy `get-class-members`) or `include: ["descriptors"]` (`inspect-minecraft` task=`class-members`) to also emit field descriptors.
20
+ - **Breaking:** `analyze-symbol` (and the `inspect-minecraft`-adjacent mapping flows `exists`/`map`/`exact-map`/`workspace`) now reuse the expert-tool candidate projection: when a lookup resolves to a single exact candidate that just duplicates `match`, the `candidates` block is omitted; for unresolved results with more than three candidates the tail is slimmed and `candidateDetailsTruncated: true` is surfaced. Read the resolved symbol from `match` rather than `candidates[0]`.
21
+ - **Breaking:** `validate-mixin` (and `validate-project` task=`mixin`/`project-summary`) now default `reportMode` to `summary-first` (was `full`) — for every caller, not just the MCP wire boundary. The default response hoists shared provenance/warnings/incomplete reasons and omits per-result `resolvedMembers`, `toolHealth`, `structuredWarnings`, `aggregatedWarnings`, and `confidenceBreakdown`; `summary`, `issues`, `issueSummary`, and top-level provenance/toolHealth are retained. Pass `reportMode="full"` or `explain=true` to restore per-result `resolvedMembers`/`toolHealth`/`resolutionTrace`. `explain=true` now also preserves `resolutionTrace` under `reportMode="compact"`.
22
+ - `verify-mixin-target`: `member.descriptor` now accepts empty / whitespace-only strings as "omitted" (normalized to `undefined`), matching every other optional descriptor field on the surface. Previously rejected with `ERR_INVALID_INPUT`. Non-breaking — no change for callers passing a real descriptor.
23
+ - **Breaking:** `get-class-source` and `get-class-members` now take the same `kind`-based `target` shape as `resolve-artifact`: `target: { kind: "version"|"jar"|"coordinate"|"workspace"|"dependency", ... }`, plus `target: { kind: "artifact", artifactId }` to reuse an already-resolved artifact. The old `target: { type: "artifact", artifactId }` form is removed — migrate to `{ kind: "artifact", artifactId }`. The redundant `{ type: "resolve", kind, value }` wrapper is still accepted (the `type` key is now ignored), but `{ kind, value }` is the canonical shape. Published `suggestedCall` targets for these tools now use the `kind`-based shape.
24
+ - **Breaking:** `analyze-symbol` `task="lifecycle"` gains explicit range controls `fromVersion`, `toVersion`, `maxVersions`, `includeTimeline`, and `includeSnapshots` (accepted only for `task="lifecycle"`; supplying them on another task is now rejected instead of silently ignored). The existing `version` field is kept as a back-compat alias for `toVersion` (range end; `toVersion` takes precedence). **Behavior change:** the lifecycle scan default grew from the previous hard-coded **5 versions** to the service default of **120** (clamped at 400) — pass `maxVersions: 5` to restore the old window. A lifecycle request still requires an end anchor (`version` or `toVersion`).
25
+ - `analyze-symbol` `subject.kind="symbol"` is now real selector-based auto-detect (owner+descriptor ⇒ method, owner only ⇒ field, otherwise ⇒ class) with the inferred kind surfaced as a warning and documented via the schema `describe()`. Previously `"symbol"` was silently coerced to `class` (exists/map/workspace), rejected (api-overview), or ignored (exact-map/lifecycle). `summary.subject.kind` now reports the resolved concrete kind instead of echoing `"symbol"`. Non-breaking — the enum keeps all four values and explicit kinds are unaffected.
26
+ - **Breaking:** `check-symbol-exists` now defaults `signatureMode` to `name-only` (was `exact`), matching `find-mapping` so the two sibling lookup tools behave identically. A `kind="method"` query with no descriptor now matches by owner+name (resolving uniquely or reporting ambiguous overloads) instead of failing with `ERR_INVALID_INPUT`; pass `signatureMode="exact"` to require a descriptor. `find-mapping` gains a `nameMode` parameter (default `auto`, mirroring `check-symbol-exists`): `auto` accepts a dotless class name for any `sourceMapping` (previously only `obfuscated`), `fqcn` requires a fully-qualified name. `analyze-symbol`'s own `signatureMode` default is intentionally left at `exact`.
27
+ - **Breaking:** the per-tool `compact` boolean is removed from all expert tools (`resolve-artifact`, `find-mapping`, `resolve-method-mapping-exact`, `resolve-workspace-symbol`, `check-symbol-exists`, `get-class-source`, `get-class-members`, `search-class-source`, `list-artifact-files`) and the four `batch-*` tools, replaced by the entry-tool **`detail`** (`summary` | `standard` | `full`) + **`include[]`** response contract — one response-shaping vocabulary across the whole server. Per-tool defaults keep every default response byte-identical: resolution/mapping tools + batch default `detail: "summary"` (= old `compact: true`); `get-class-source`/`get-class-members`/`search-class-source`/`list-artifact-files` default `detail: "standard"` (= old `compact: false`, post-Phase-4 diagnostic strip). **Migration:** `compact: true` → `detail: "summary"`, `compact: false` → `detail: "full"`. `include[]` opts specific field groups back in (`["provenance"]`, `["candidates"]`, `["samples"]`, `["artifact"]`, `["paths"]`, `["descriptors"]`). The Phase-4 `includeProvenance` / `includeDescriptors` flags are retained as aliases for `include: ["provenance"]` / `include: ["descriptors"]`. Expert and batch responses now also report `meta.detailApplied` / `meta.includeApplied`.
28
+ - `resolve-method-mapping-exact` is now documented as a strict shortcut for `find-mapping` (kind=method, signatureMode=exact): its description, schema, and the README/tool-reference entries explain that it additionally requires a **complete** descriptor projection (returns `mapping_unavailable` when class references cannot all be projected, where `find-mapping`'s exact mode resolves leniently). Documentation-only — no wire, behavior, or input-schema change; the tool is retained.
29
+ - The display caps that were previously clamped silently now emit a clamp warning so the documented contract is truthful: `list-versions` `limit` (capped at 200), `get-class-api-matrix` `maxRows` (capped at 5000), and `compare-versions` `maxClassResults` (capped at 5000) push `"<field> was clamped to <cap> from <value>."` when an over-range value is supplied (and `inspect-minecraft task="versions"` surfaces the `list-versions` warning). Non-breaking — out-of-range values still clamp and succeed; the caps are unchanged.
30
+ - Resource read errors (`mc://…` URIs) now carry the same recovery metadata as the equivalent tool call: `retryClass` and `issueOrigin` are always present, and `hints`, `suggestedCall`, `exampleCalls`, `fieldErrors`, and `context` are surfaced whenever the underlying `AppError` supplies them (previously the resource envelope returned only `code` + `detail`). The envelope keeps its existing nested `{ error, meta }` shape and the generic `problems/resource` type/title. Additive and non-breaking for clients reading by key.
31
+ - **Breaking:** error envelopes add a new `retryClass` value `"server"` for non-recoverable internal server faults. `ERR_INTERNAL` (sanitized programming bugs / unexpected throws, including every non-`AppError` thrown by a handler) and `ERR_DB_FAILURE` (SQLite integrity/migration failures) now report `retryClass: "server"` instead of `"transient"`, so agents stop retrying deterministic server bugs. Genuinely-transient codes are unchanged — `ERR_REPO_FETCH_FAILED`, `ERR_ARTIFACT_RESOLUTION_FAILED`, `ERR_STAGE_BUDGET_PRE_PARSE`, `ERR_LIMIT_EXCEEDED`, and `ERR_JAVA_PROCESS_FAILED` stay `"transient"`. Clients that exhaustively `switch` on `retryClass` must add a `"server"` case (treat it like `"permanent"` for retry posture: do not retry the identical call). `issueOrigin` is unchanged (`ERR_INTERNAL` stays `tool_issue`).
32
+
33
+ ### Fixed
34
+
35
+ - `remap-mod-jar` no longer serves a stale remapped JAR after a newer Fabric **yarn** build is published for the same Minecraft version. Yarn Tiny mappings are now cached per build coordinate (`mappings/yarn/${coordinate}.tiny`) and the resolved yarn coordinate is part of the remap cache key, so a new build produces a fresh remap instead of reusing the old jar. The newest build is re-checked at most once per 24h per MC version (a small on-disk metadata marker keeps the check off the cache-hit fast path; on a Fabric Maven outage the last-known-good build is reused rather than failing). Mojang mappings are immutable per release and unaffected. `remap-mod-jar` gains an optional `forceRemap: true` to bypass the cache and re-resolve on demand. The remap pipeline version bumps (`v2`→`v3`), a one-time invalidation of previously cached remapped jars; obsolete `mappings/${version}-yarn.tiny` files can be reclaimed via `manage-cache` on the `mapping` kind.
36
+
37
+ ### Performance
38
+
39
+ - `get-class-api-matrix` now computes per-row cross-namespace mappings only for the requested page (`maxRows`/`cursor` window) instead of the whole class on every request, so paginated/deep-page calls scale with the window rather than the full member set. `rows`, row order, `rowCount`, `nextCursor`, and `cursorIgnored` are byte-identical. **Behavior change:** on paginated calls, `warnings` (per-row ambiguity notes) and `ambiguousRowCount` now reflect only the returned page; an unpaginated call (no `maxRows`) still reports them for the whole class.
40
+
41
+ ## [4.2.1] - 2026-05-23
42
+
43
+ ### Fixed
44
+ - Mojang binary-remap cache writes recover from corrupt `<cacheDir>/remapped/<artifactId>.jar` directories instead of surfacing raw `ENOTEMPTY`, and `manage-cache` can list and delete those corrupt `binary-remap` entries by `selector.artifactId`.
45
+ - `isAppError(value)` now narrows to `AppError` via `instanceof AppError` instead of `Error & { code: string }`. Plain `Error` objects that happen to carry a `code` field no longer pass the guard, so unrelated errors can no longer leak through `AppError`-specific error handling.
46
+ - Java NBT `TAG_String` now encodes/decodes via MUTF-8 to match Mojang's `DataInputStream.readUTF` / `DataOutputStream.writeUTF` contract. Supplementary characters (emoji, supplementary CJK) and the NUL character (`U+0000`) now round-trip correctly. ASCII and common BMP byte sequences are byte-identical to the previous UTF-8 output, so existing NBT payloads are unaffected.
47
+
8
48
  ## [4.2.0] - 2026-05-17
9
49
 
10
50
  ### Added
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  It runs over stdio and works with Claude Desktop, Claude Code, VS Code, Codex CLI, Gemini CLI, and other MCP-capable clients.
17
17
 
18
- **37 tools** (6 entry + 31 expert) | **7 resources** | **4 namespace mappings** | **SQLite-backed cache**
18
+ **41 tools** (6 entry + 35 expert) | **9 resources** | **4 namespace mappings** | **SQLite-backed cache**
19
19
 
20
20
  ## Features
21
21
 
@@ -164,6 +164,7 @@ These notes cover high-frequency decisions during onboarding. For the full pitfa
164
164
  - `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.
165
165
  - `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.
166
166
  - If a workspace was built with `GRADLE_USER_HOME=/tmp/...` or another isolated Gradle home, pass that path as `gradleUserHome` so source, mapping, runtime, and project validation lookups use the same Loom cache instead of stale caches under the MCP process home.
167
+ - `manage-cache` reports corrupt Mojang binary-remap cache directories under `cacheKinds: ["binary-remap"]` with `status: "corrupt"`, and can delete them by `selector.artifactId` in preview/apply workflows.
167
168
 
168
169
  ### Inspect Minecraft source from a version
169
170
 
@@ -202,7 +203,8 @@ These notes cover high-frequency decisions during onboarding. For the full pitfa
202
203
  "version": "1.21.10",
203
204
  "sourceMapping": "mojang",
204
205
  "targetMapping": "intermediary",
205
- "signatureMode": "name-only"
206
+ "signatureMode": "name-only",
207
+ "detail": "standard"
206
208
  }
207
209
  }
208
210
  ```
@@ -305,13 +307,13 @@ Tools for converting symbol names between namespaces and checking symbol existen
305
307
  | Tool | Purpose |
306
308
  | --- | --- |
307
309
  | `find-mapping` | Look up mapping candidates for class, field, or method symbols |
308
- | `resolve-method-mapping-exact` | Resolve one method mapping with strict owner, name, and descriptor matching |
310
+ | `resolve-method-mapping-exact` | Strict shortcut for `find-mapping` (kind=method, signatureMode=exact); requires the full owner+name+descriptor triple |
309
311
  | `get-class-api-matrix` | Show one class API across `obfuscated`, `mojang`, `intermediary`, and `yarn` |
310
312
  | `resolve-workspace-symbol` | Resolve compile-visible symbol names from a Gradle workspace |
311
313
  | `check-symbol-exists` | Check whether a class, field, or method exists in a namespace |
312
314
  <!-- END GENERATED TOOL TABLE: mapping-symbols -->
313
315
 
314
- 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.
316
+ Expert and batch tools share the `detail` (`summary` | `standard` | `full`) + `include[]` response contract for shorter responses (replacing the old per-tool `compact` flag). See [docs/tool-reference.md](docs/tool-reference.md) for per-tool defaults and the full field list.
315
317
 
316
318
  ### NBT Utilities
317
319
 
@@ -1,8 +1,9 @@
1
1
  export type AccessTransformerAccessAction = "public" | "protected" | "package-private" | "private";
2
2
  export type AccessTransformerFinalAction = "add" | "remove";
3
+ export type AccessTransformerTargetKind = "class" | "field" | "method" | "wildcard-all" | "wildcard-method";
3
4
  export type AccessTransformerEntry = {
4
5
  line: number;
5
- targetKind: "class" | "field" | "method";
6
+ targetKind: AccessTransformerTargetKind;
6
7
  owner: string;
7
8
  target: string;
8
9
  name?: string;
@@ -19,10 +19,19 @@ function splitMemberToken(tokens) {
19
19
  if (tokens.length === 1) {
20
20
  const token = tokens[0] ?? "";
21
21
  const descriptorStart = token.indexOf("(");
22
+ // Wildcards: "*" targets all fields and methods; "*()" targets all methods.
23
+ // (Standard Forge/NeoForge AT syntax — previously parsed as a literal name.)
24
+ if (token === "*") {
25
+ return { targetKind: "wildcard-all", name: "*" };
26
+ }
22
27
  if (descriptorStart >= 0) {
28
+ const namePart = token.slice(0, descriptorStart);
29
+ if (namePart === "*") {
30
+ return { targetKind: "wildcard-method", name: "*", descriptor: token.slice(descriptorStart) };
31
+ }
23
32
  return {
24
33
  targetKind: "method",
25
- name: token.slice(0, descriptorStart),
34
+ name: namePart,
26
35
  descriptor: token.slice(descriptorStart)
27
36
  };
28
37
  }
@@ -6,16 +6,33 @@ function asParamsRecord(params) {
6
6
  }
7
7
  return params;
8
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 {};
9
+ // Placeholder sentinels are `<...>` tokens that a caller must substitute before
10
+ // the call is executable (e.g. "<version>", "<fully-qualified-class-name>").
11
+ // The two JVM pseudo-method-names `<init>` / `<clinit>` are real, valid values
12
+ // and are explicitly excluded so constructor lookups are not misclassified.
13
+ const PLACEHOLDER_SENTINEL_RE = /<(?!init>)(?!clinit>)[^<>]+>/;
14
+ function containsPlaceholderSentinel(value) {
15
+ if (typeof value === "string") {
16
+ return PLACEHOLDER_SENTINEL_RE.test(value);
17
+ }
18
+ if (Array.isArray(value)) {
19
+ return value.some(containsPlaceholderSentinel);
20
+ }
21
+ if (value && typeof value === "object") {
22
+ return Object.values(value).some(containsPlaceholderSentinel);
16
23
  }
24
+ return false;
25
+ }
26
+ export function buildSuggestedCall(spec) {
17
27
  const primaryParams = asParamsRecord(spec.params);
18
- if (primaryParams) {
28
+ // A `suggestedCall` must be directly executable. Reject any primary payload
29
+ // that still carries a placeholder sentinel (e.g. "<version>") even when it
30
+ // is schema-valid: those belong in `exampleCalls` as templates, not as a
31
+ // call the caller can replay verbatim.
32
+ if (primaryParams && !containsPlaceholderSentinel(primaryParams)) {
33
+ if (SUGGESTED_CALL_VALIDATE_OFF) {
34
+ return { suggestedCall: { tool: spec.tool, params: primaryParams } };
35
+ }
19
36
  // Unknown-tool fail-open: the registry is populated at index.ts startup;
20
37
  // service-level tests that do not boot index.ts run with an empty
21
38
  // registry and rely on this pass-through. Callers that synthesize a tool
@@ -31,14 +48,18 @@ export function buildSuggestedCall(spec) {
31
48
  }
32
49
  const droppedMarker = spec.params !== undefined ? { _suggestedCallPrimaryDropped: true } : {};
33
50
  if (spec.examples && spec.examples.length > 0) {
51
+ // Example calls are templates (placeholders allowed) and only their
52
+ // schema-shape is checked. Mirror the primary path's unknown-tool
53
+ // fail-open so service-level tests with an unpopulated registry still
54
+ // surface templates instead of silently dropping them.
55
+ const exampleSchemaUnavailable = SUGGESTED_CALL_VALIDATE_OFF || !getToolSchema(spec.tool);
34
56
  const validated = spec.examples
35
57
  .map((example) => {
36
58
  const exampleParams = asParamsRecord(example.params);
37
59
  if (!exampleParams) {
38
60
  return null;
39
61
  }
40
- const result = validateToolParams(spec.tool, exampleParams);
41
- if (!result.valid) {
62
+ if (!exampleSchemaUnavailable && !validateToolParams(spec.tool, exampleParams).valid) {
42
63
  return null;
43
64
  }
44
65
  return {
@@ -34,6 +34,14 @@ type CacheEntryPage = {
34
34
  entries: CacheEntry[];
35
35
  nextCursor?: string;
36
36
  };
37
+ /**
38
+ * Whether a cache entry path contains the selector version as a whole version
39
+ * token. Used as a fallback when an entry has no structured meta.version. The
40
+ * match is anchored so that a coarse selector like "1.2" does NOT match a "1.21"
41
+ * path (which would cause destructive prune/delete to hit the wrong caches),
42
+ * while still allowing major.minor sweeps where "1.21" matches "1.21.4".
43
+ */
44
+ export declare function pathContainsVersion(path: string, version: string): boolean;
37
45
  export type CacheRegistryConfig = {
38
46
  cacheDir: string;
39
47
  sqlitePath: string;
@@ -99,6 +99,20 @@ function inferVersion(...candidates) {
99
99
  }
100
100
  return undefined;
101
101
  }
102
+ /**
103
+ * Whether a cache entry path contains the selector version as a whole version
104
+ * token. Used as a fallback when an entry has no structured meta.version. The
105
+ * match is anchored so that a coarse selector like "1.2" does NOT match a "1.21"
106
+ * path (which would cause destructive prune/delete to hit the wrong caches),
107
+ * while still allowing major.minor sweeps where "1.21" matches "1.21.4".
108
+ */
109
+ export function pathContainsVersion(path, version) {
110
+ const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
111
+ // Preceding char must not be a digit or dot (avoid matching inside a longer
112
+ // version like 1.21 -> 21); following char must not be a digit (block
113
+ // 1.2 -> 1.21) but a dot is allowed so 1.21 sweeps 1.21.4.
114
+ return new RegExp(`(?<![\\d.])${escaped}(?!\\d)`).test(path);
115
+ }
102
116
  function inferMapping(...candidates) {
103
117
  for (const candidate of candidates) {
104
118
  const normalized = candidate?.toLowerCase();
@@ -329,7 +343,7 @@ function matchesSelector(entry, selector, runtimeInfo) {
329
343
  }
330
344
  if (selector.version) {
331
345
  const version = typeof maybeMeta.version === "string" ? maybeMeta.version : undefined;
332
- if (version !== selector.version && !entry.path.includes(selector.version)) {
346
+ if (version !== selector.version && !pathContainsVersion(entry.path, selector.version)) {
333
347
  return false;
334
348
  }
335
349
  }
@@ -442,6 +456,9 @@ function workspaceCacheEntries(workspaceCache) {
442
456
  }));
443
457
  }
444
458
  async function fileBackedEntries(config, cacheKind) {
459
+ if (cacheKind === "binary-remap") {
460
+ return binaryRemapEntries(config);
461
+ }
445
462
  const root = kindRoot(config, cacheKind);
446
463
  const files = await listFilesRecursive(root);
447
464
  const entries = [];
@@ -466,17 +483,91 @@ async function fileBackedEntries(config, cacheKind) {
466
483
  inUse: filePath.endsWith(".lock") ||
467
484
  filePath.endsWith(".wal") ||
468
485
  filePath.endsWith(".journal"),
469
- ...(cacheKind === "downloads" || cacheKind === "mod-remap" || cacheKind === "binary-remap"
486
+ ...(cacheKind === "downloads" || cacheKind === "mod-remap"
470
487
  ? { jarPath: filePath }
471
- : {}),
472
- ...(cacheKind === "binary-remap"
473
- ? { artifactId: normalizedEntryId.replace(/\.jar$/i, "") }
474
488
  : {})
475
489
  }
476
490
  });
477
491
  }
478
492
  return entries;
479
493
  }
494
+ /**
495
+ * Binary-remap cache entries are keyed by the final artifact id even when the
496
+ * on-disk entry is a corrupt final directory or a leftover temp path.
497
+ */
498
+ function parseBinaryRemapEntryName(name) {
499
+ const legacyTempMatch = /^(.+)\.jar\.tmp\..+$/.exec(name);
500
+ if (legacyTempMatch?.[1]) {
501
+ return { artifactId: legacyTempMatch[1], corrupt: true };
502
+ }
503
+ const tempMatch = /^(.+)\.tmp\.\d+\.\d+\.[A-Za-z0-9_-]+\.jar$/.exec(name);
504
+ if (tempMatch?.[1]) {
505
+ return { artifactId: tempMatch[1], corrupt: true };
506
+ }
507
+ const finalJarMatch = /^(.+)\.jar$/.exec(name);
508
+ if (finalJarMatch?.[1]) {
509
+ return { artifactId: finalJarMatch[1], corrupt: false };
510
+ }
511
+ return undefined;
512
+ }
513
+ async function binaryRemapEntries(config) {
514
+ const root = kindRoot(config, "binary-remap");
515
+ if (!existsSync(root)) {
516
+ return [];
517
+ }
518
+ const entries = [];
519
+ // Do not recurse here: a corrupt `<artifactId>.jar` directory must remain one
520
+ // selectable cache entry so `selector.artifactId` can remove it recursively.
521
+ const dirents = await readdir(root, { withFileTypes: true });
522
+ for (const entry of dirents.sort((left, right) => left.name.localeCompare(right.name))) {
523
+ if (!entry.isFile() && !entry.isDirectory()) {
524
+ continue;
525
+ }
526
+ const parsed = parseBinaryRemapEntryName(entry.name);
527
+ if (!parsed) {
528
+ continue;
529
+ }
530
+ const filePath = join(root, entry.name);
531
+ const fileStat = await stat(filePath);
532
+ const corrupt = parsed.corrupt || entry.isDirectory();
533
+ const sizeBytes = entry.isDirectory()
534
+ ? await directoryFileSizeBytes(filePath)
535
+ : fileStat.size;
536
+ entries.push({
537
+ cacheKind: "binary-remap",
538
+ entryId: entry.name,
539
+ path: filePath,
540
+ sizeBytes,
541
+ status: corrupt ? "corrupt" : "healthy",
542
+ meta: {
543
+ updatedAt: fileStat.mtime.toISOString(),
544
+ version: inferVersion(filePath, entry.name),
545
+ mapping: inferMapping(filePath, entry.name),
546
+ scope: inferScope(filePath, entry.name),
547
+ projectPath: inferProjectPath(filePath, config.pathRuntimeInfo),
548
+ partial: entry.isFile() && fileStat.size === 0,
549
+ corrupt,
550
+ inUse: false,
551
+ jarPath: filePath,
552
+ artifactId: parsed.artifactId
553
+ }
554
+ });
555
+ }
556
+ return entries;
557
+ }
558
+ async function directoryFileSizeBytes(root) {
559
+ const files = await listFilesRecursive(root);
560
+ let totalBytes = 0;
561
+ for (const filePath of files) {
562
+ try {
563
+ totalBytes += (await stat(filePath)).size;
564
+ }
565
+ catch {
566
+ // The entry may disappear during cleanup or a concurrent cache write.
567
+ }
568
+ }
569
+ return totalBytes;
570
+ }
480
571
  export function createCacheRegistry(config) {
481
572
  const workspaceCache = config.workspaceContextCache ?? getProcessWorkspaceContextCache();
482
573
  async function collectEntries(cacheKinds, selector) {
@@ -564,7 +655,9 @@ export function createCacheRegistry(config) {
564
655
  continue;
565
656
  }
566
657
  if (existsSync(entry.path)) {
567
- await rm(entry.path, { force: true });
658
+ // Only binary-remap inventory can return directories as entries;
659
+ // other file-backed kinds keep their existing file-only contract.
660
+ await rm(entry.path, { recursive: entry.cacheKind === "binary-remap", force: true });
568
661
  }
569
662
  }
570
663
  }
package/dist/config.d.ts CHANGED
@@ -12,6 +12,7 @@ declare const DEFAULTS: {
12
12
  readonly fetchTimeoutMs: 15000;
13
13
  readonly fetchRetries: 2;
14
14
  readonly searchScanPageSize: 250;
15
+ readonly searchScanMaxBytes: 67108864;
15
16
  readonly indexInsertChunkSize: 200;
16
17
  readonly maxMappingGraphCache: 16;
17
18
  readonly maxSignatureCache: 2000;
package/dist/config.js CHANGED
@@ -20,6 +20,7 @@ const DEFAULTS = {
20
20
  fetchTimeoutMs: 15000,
21
21
  fetchRetries: 2,
22
22
  searchScanPageSize: 250,
23
+ searchScanMaxBytes: 67_108_864,
23
24
  indexInsertChunkSize: 200,
24
25
  maxMappingGraphCache: 16,
25
26
  maxSignatureCache: 2_000,
@@ -41,6 +42,8 @@ const MAX_CACHE_BYTES_LOWER_BOUND = 1_024;
41
42
  const TIMEOUT_LOWER_BOUND_MS = 500;
42
43
  const SEARCH_SCAN_PAGE_SIZE_LOWER_BOUND = 1;
43
44
  const SEARCH_SCAN_PAGE_SIZE_UPPER_BOUND = 10_000;
45
+ const SEARCH_SCAN_MAX_BYTES_LOWER_BOUND = 1_024;
46
+ const SEARCH_SCAN_MAX_BYTES_UPPER_BOUND = 4_294_967_296;
44
47
  const INDEX_INSERT_CHUNK_SIZE_LOWER_BOUND = 1;
45
48
  const INDEX_INSERT_CHUNK_SIZE_UPPER_BOUND = 20_000;
46
49
  const CACHE_ENTRIES_LOWER_BOUND = 1;
@@ -163,6 +166,7 @@ export function loadConfig() {
163
166
  fetchTimeoutMs: parseNumber(process.env.MCP_FETCH_TIMEOUT_MS, DEFAULTS.fetchTimeoutMs, TIMEOUT_LOWER_BOUND_MS, Number.MAX_SAFE_INTEGER),
164
167
  fetchRetries: parseNumber(process.env.MCP_FETCH_RETRIES, DEFAULTS.fetchRetries, MAX_RETRIES_LOWER_BOUND, MAX_RETRIES_UPPER_BOUND),
165
168
  searchScanPageSize: parseNumber(process.env.MCP_SEARCH_SCAN_PAGE_SIZE, DEFAULTS.searchScanPageSize, SEARCH_SCAN_PAGE_SIZE_LOWER_BOUND, SEARCH_SCAN_PAGE_SIZE_UPPER_BOUND),
169
+ searchScanMaxBytes: parseNumber(process.env.MCP_SEARCH_SCAN_MAX_BYTES, DEFAULTS.searchScanMaxBytes, SEARCH_SCAN_MAX_BYTES_LOWER_BOUND, SEARCH_SCAN_MAX_BYTES_UPPER_BOUND),
166
170
  indexInsertChunkSize: parseNumber(process.env.MCP_INDEX_INSERT_CHUNK_SIZE, DEFAULTS.indexInsertChunkSize, INDEX_INSERT_CHUNK_SIZE_LOWER_BOUND, INDEX_INSERT_CHUNK_SIZE_UPPER_BOUND),
167
171
  maxMappingGraphCache: parseNumber(process.env.MCP_CACHE_GRAPH_MAX, DEFAULTS.maxMappingGraphCache, CACHE_ENTRIES_LOWER_BOUND, CACHE_ENTRIES_UPPER_BOUND),
168
172
  maxSignatureCache: parseNumber(process.env.MCP_CACHE_SIGNATURE_MAX, DEFAULTS.maxSignatureCache, CACHE_ENTRIES_LOWER_BOUND, CACHE_ENTRIES_UPPER_BOUND),
@@ -75,8 +75,7 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
75
75
  include: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
76
76
  }, "strip", z.ZodTypeAny, {
77
77
  limit: number;
78
- searchType: "class" | "field" | "method" | "all" | "content";
79
- task: "search" | "remap" | "summary" | "class-source" | "decompile";
78
+ task: "search" | "summary" | "class-source" | "remap" | "decompile";
80
79
  subject: {
81
80
  kind: "jar";
82
81
  jarPath: string;
@@ -85,18 +84,19 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
85
84
  jarPath: string;
86
85
  className: string;
87
86
  };
88
- includeFiles: boolean;
87
+ searchType: "class" | "field" | "method" | "all" | "content";
89
88
  executionMode: "preview" | "apply";
89
+ includeFiles: boolean;
90
90
  maxLines?: number | undefined;
91
91
  maxChars?: number | undefined;
92
+ detail?: "summary" | "standard" | "full" | undefined;
93
+ include?: string[] | undefined;
94
+ query?: string | undefined;
92
95
  targetMapping?: "mojang" | "yarn" | undefined;
93
96
  outputJar?: string | undefined;
94
- query?: string | undefined;
95
- detail?: "full" | "summary" | "standard" | undefined;
96
- include?: string[] | undefined;
97
97
  maxFiles?: number | undefined;
98
98
  }, {
99
- task: "search" | "remap" | "summary" | "class-source" | "decompile";
99
+ task: "search" | "summary" | "class-source" | "remap" | "decompile";
100
100
  subject: {
101
101
  kind: "jar";
102
102
  jarPath: string;
@@ -108,19 +108,18 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
108
108
  limit?: number | undefined;
109
109
  maxLines?: number | undefined;
110
110
  maxChars?: number | undefined;
111
- targetMapping?: "mojang" | "yarn" | undefined;
112
- outputJar?: string | undefined;
111
+ detail?: "summary" | "standard" | "full" | undefined;
112
+ include?: string[] | undefined;
113
113
  query?: string | undefined;
114
114
  searchType?: "class" | "field" | "method" | "all" | "content" | undefined;
115
- detail?: "full" | "summary" | "standard" | undefined;
116
- include?: string[] | undefined;
115
+ targetMapping?: "mojang" | "yarn" | undefined;
116
+ outputJar?: string | undefined;
117
+ executionMode?: "preview" | "apply" | undefined;
117
118
  includeFiles?: boolean | undefined;
118
119
  maxFiles?: number | undefined;
119
- executionMode?: "preview" | "apply" | undefined;
120
120
  }>, {
121
121
  limit: number;
122
- searchType: "class" | "field" | "method" | "all" | "content";
123
- task: "search" | "remap" | "summary" | "class-source" | "decompile";
122
+ task: "search" | "summary" | "class-source" | "remap" | "decompile";
124
123
  subject: {
125
124
  kind: "jar";
126
125
  jarPath: string;
@@ -129,18 +128,19 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
129
128
  jarPath: string;
130
129
  className: string;
131
130
  };
132
- includeFiles: boolean;
131
+ searchType: "class" | "field" | "method" | "all" | "content";
133
132
  executionMode: "preview" | "apply";
133
+ includeFiles: boolean;
134
134
  maxLines?: number | undefined;
135
135
  maxChars?: number | undefined;
136
+ detail?: "summary" | "standard" | "full" | undefined;
137
+ include?: string[] | undefined;
138
+ query?: string | undefined;
136
139
  targetMapping?: "mojang" | "yarn" | undefined;
137
140
  outputJar?: string | undefined;
138
- query?: string | undefined;
139
- detail?: "full" | "summary" | "standard" | undefined;
140
- include?: string[] | undefined;
141
141
  maxFiles?: number | undefined;
142
142
  }, {
143
- task: "search" | "remap" | "summary" | "class-source" | "decompile";
143
+ task: "search" | "summary" | "class-source" | "remap" | "decompile";
144
144
  subject: {
145
145
  kind: "jar";
146
146
  jarPath: string;
@@ -152,15 +152,15 @@ export declare const analyzeModSchema: z.ZodEffects<z.ZodObject<{
152
152
  limit?: number | undefined;
153
153
  maxLines?: number | undefined;
154
154
  maxChars?: number | undefined;
155
- targetMapping?: "mojang" | "yarn" | undefined;
156
- outputJar?: string | undefined;
155
+ detail?: "summary" | "standard" | "full" | undefined;
156
+ include?: string[] | undefined;
157
157
  query?: string | undefined;
158
158
  searchType?: "class" | "field" | "method" | "all" | "content" | undefined;
159
- detail?: "full" | "summary" | "standard" | undefined;
160
- include?: string[] | undefined;
159
+ targetMapping?: "mojang" | "yarn" | undefined;
160
+ outputJar?: string | undefined;
161
+ executionMode?: "preview" | "apply" | undefined;
161
162
  includeFiles?: boolean | undefined;
162
163
  maxFiles?: number | undefined;
163
- executionMode?: "preview" | "apply" | undefined;
164
164
  }>;
165
165
  export type AnalyzeModInput = z.infer<typeof analyzeModSchema>;
166
166
  type AnalyzeModDeps = {