@adhisang/minecraft-modding-mcp 7.0.0-rc.0 → 7.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -49
- package/README.md +2 -0
- package/dist/cache-registry.d.ts +10 -0
- package/dist/cache-registry.js +44 -3
- package/dist/entry-tools/batch-class-members-service.js +8 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +21 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +4 -0
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/validate-project/cases/mixin.js +26 -6
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -1
- package/dist/error-mapping.d.ts +118 -0
- package/dist/error-mapping.js +177 -7
- package/dist/index.js +3 -1
- package/dist/json-rpc-framing.d.ts +53 -1
- package/dist/json-rpc-framing.js +190 -25
- package/dist/mapping/loaders/tiny-loom-selection.js +10 -3
- package/dist/mapping/parsers/tiny.js +4 -0
- package/dist/mapping-service.js +24 -3
- package/dist/maven-resolver.d.ts +61 -0
- package/dist/maven-resolver.js +95 -4
- package/dist/maven-token.d.ts +65 -0
- package/dist/maven-token.js +120 -0
- package/dist/mcp-helpers.js +14 -3
- package/dist/minecraft-explorer-service.d.ts +13 -1
- package/dist/minecraft-explorer-service.js +200 -7
- package/dist/repo-downloader.d.ts +260 -0
- package/dist/repo-downloader.js +1083 -14
- package/dist/source/artifact-resolver.d.ts +97 -3
- package/dist/source/artifact-resolver.js +228 -24
- package/dist/source/class-source.d.ts +15 -0
- package/dist/source/class-source.js +163 -18
- package/dist/source/indexer.js +14 -0
- package/dist/source/nested-jars.d.ts +16 -1
- package/dist/source/nested-jars.js +66 -3
- package/dist/source/workspace-target.js +39 -20
- package/dist/source-jar-reader.d.ts +30 -2
- package/dist/source-jar-reader.js +39 -8
- package/dist/source-resolver.d.ts +48 -0
- package/dist/source-resolver.js +735 -81
- package/dist/source-service.d.ts +10 -1
- package/dist/stdio-supervisor.d.ts +75 -1
- package/dist/stdio-supervisor.js +240 -14
- package/dist/storage/artifacts-repo.d.ts +2 -0
- package/dist/storage/artifacts-repo.js +22 -0
- package/dist/storage/db.js +32 -10
- package/dist/tool-guidance.js +17 -7
- package/dist/tool-schemas.d.ts +10 -2
- package/dist/tool-schemas.js +4 -0
- package/dist/v1-parity-schemas.js +16 -0
- package/dist/version-diff-service.js +25 -4
- package/dist/version-service.js +23 -8
- package/dist/warning-details.js +3 -1
- package/dist/workspace-mapping-service.d.ts +8 -1
- package/dist/workspace-mapping-service.js +28 -19
- package/docs/tool-reference.md +31 -7
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,65 +7,147 @@ and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [7.0.0-rc.2] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Artifact downloads are now capped at 512 MiB per transfer, configurable with `MCP_MAX_DOWNLOAD_BYTES`. The ceiling is checked against the declared `Content-Length` before the body is read and again against the bytes actually received, so a chunked or mis-declared response is caught too. A breach fails with `ERR_LIMIT_EXCEEDED`, is not retried, and its partial file is removed on a best-effort basis; a cached copy is never served in its place. When every repository trips the cap, the final `ERR_REPO_FETCH_FAILED` says so: `hints` tells you to raise `MCP_MAX_DOWNLOAD_BYTES`, and `context.repoFailureCode` is `"ERR_LIMIT_EXCEEDED"`. Previously only a 15-second timeout bounded a transfer, so a fast link serving a very large body could fill the disk. The cap is per transfer and is unrelated to total cache size.
|
|
15
|
+
- Nested-jar extraction is now capped at 64 MiB per inner jar, configurable with `MCP_MAX_NESTED_JAR_ENTRY_BYTES`. The ceiling is checked against the entry's declared uncompressed size before extraction and against the bytes actually streamed. A breach fails that one entry with `ERR_LIMIT_EXCEEDED`; the shell jar's other inner jars still resolve. Previously inner jars were read whole into memory with no size check, so a highly compressible entry could expand far beyond the archive carrying it.
|
|
16
|
+
- The `nestedJars` error field is now bounded in bytes as well as entry count, and says when it has been shortened. Entries longer than 256 bytes are dropped individually, the list stops at 8 KiB in total, and `nestedJarsTruncated: true` accompanies a shortened list wherever one is emitted. Previously a truncated inventory was indistinguishable from a complete one.
|
|
17
|
+
- `resolve-artifact` now reports the `binary-jar-no-classes` quality flag for a binary jar that opens cleanly but contains no `.class` entry. Such jars are legitimate — `net.fabricmc:yarn` and `intermediary` `v2` carry only `mappings/mappings.tiny`, and a resource-only Fabric mod carries only `fabric.mod.json` and assets — so the flag describes the artifact and never refuses it. It is set for `coordinate`, `jar` and `version` targets alike. Known limit: a class-free jar that is not a Jar-in-Jar shell still fails during indexing with `ERR_DECOMPILER_FAILED` before any response exists, so the flag does not reach a caller in exactly that case.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Wire contract change.** `context.jarHash` is renamed to `context.jarSignature` on `get-class-members` and `batch-class-members`. The value never was a hash of the jar's bytes: it is a signature over the jar's resolved path, modification time and size, useful for spotting that a jar changed between two responses but not as a content identity or cache key. The value and its derivation are unchanged. Callers reading `context.jarHash` must read `context.jarSignature` instead.
|
|
22
|
+
- **Wire contract change.** An artifact named only by an opaque handle no longer draws the blame for carrying no binary jar. `get-class-members`, `batch-class-members` and `inspect-minecraft`'s `class-members` task now apply one rule: a caller-named jar target (`target: { kind: "jar", ... }`) is the only shape that reports `issueOrigin: "code_issue"`; every other shape, an `artifactId` or `resolved-id` handle from an earlier resolve included, reports `"tool_issue"`. Previously `get-class-members` reported `"code_issue"` for `target: { kind: "artifact", artifactId }`, even though a handle tells its holder nothing about whether the artifact has a binary jar and `resolve-artifact` does not accept one. `retryClass` is unchanged and still `"input"` here. The error now points at `manage-cache action="inspect"` with `selector: { artifactId }` and `include: ["cacheEntries"]`, which reports the artifact's stored jar as `meta.binaryJarPath` inside `cacheEntries`. Clients that branch on `issueOrigin` should read it per response rather than assume a fixed value per error code.
|
|
23
|
+
- **A dependency's `artifactId` is now derived from the jar's bytes when the jar is served from `~/.m2` or the Gradle dependency cache**, not from its modification time and size. This applies to the routes that reach a jar through a Maven coordinate, `target.kind="dependency"` and `target.kind="coordinate"`; a jar named directly by path (`target.kind="jar"`) is unchanged and still takes its identity from that path with the file's modification time and size. 7.0.0-rc.1's content-derived `artifactId` covered only the remote-download path, so on the local path a `touch`, a cache eviction and re-fetch of identical bytes, or a filesystem restore still minted a new `artifactId`. Each already-cached local dependency gets a new `artifactId` and re-indexes once on its first resolve after upgrading, including a fresh decompile for binary-only dependencies.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- `search-class-source`, `get-artifact-file`, `list-artifact-files` and `index-artifact` now accept a top-level `projectPath` parameter, matching `find-class`, and advertise it in their `tools/list` `inputSchema`. Previously a `target.kind="workspace"` or unversioned `target.kind="dependency"` call to these four tools was rejected with an error asking for `projectPath`, but their schemas had no such field, so the error's own advice could not be followed.
|
|
28
|
+
- A dependency resolve no longer warns that its requested mapping "is not enforced" when a source-backed jar actually supplied it. The warning used to fire whenever a non-obfuscated mapping was requested for a dependency, contradicting the same response's `mappingApplied` and `qualityFlags` when the mapping had genuinely succeeded. It now fires only when the binary remap was actually skipped, that is, when `qualityFlags` includes `dependency-mapping-unverified`.
|
|
29
|
+
- A dependency's own coordinate version is no longer read as a signal about Minecraft's obfuscation state. A version that parses as a modern Minecraft version — any `26.x` or later, as `org.jetbrains:annotations:26.0.2` does — made a `mapping: "mojang"` request on a binary-only dependency return `mappingApplied: "mojang"` with no quality flag and no warning, claiming a remap that was never performed. Such a dependency now reports `mappingApplied: "obfuscated"` with the `dependency-mapping-unverified` flag and warning, on `resolve-artifact`, `get-class-source` and `get-class-members` alike. The obfuscation check now applies only to targets that genuinely name a Minecraft version — a `target.kind="version"`, or a `coordinate` target naming the Minecraft runtime itself such as `net.minecraft:client:26.1`; a `target.kind="dependency"` request is never treated as one — so vanilla resolution is unaffected.
|
|
30
|
+
- `get-class-members` and `batch-class-members` now report `context.minecraftVersion: "unknown"` for a dependency artifact instead of a plausible-but-wrong value. The version used to be guessed from the jar's path, which for a jar served out of the Gradle cache yielded the cache-layout constant `2.1` and under `~/.m2` the dependency's own release number, and a fallback then substituted the dependency's coordinate version. The workspace's Minecraft version is not substituted either: `"unknown"` reports what the tool genuinely knows. Vanilla artifacts are unaffected.
|
|
31
|
+
- `get-class-members`, `batch-class-members`, `find-class` and `get-class-source` now recognize a dependency artifact however it was reached. Previously only a `target.kind="dependency"` request marked the artifact as a dependency; the same jar reached by its Maven coordinate or reused by its `artifactId` was treated as vanilla, so `context.minecraftVersion` again reported the Gradle cache-layout constant `2.1` or the dependency's own release number, and a class-not-found miss drew a false hint to retry with `mapping="mojang"`. A dependency named by a Maven coordinate is now detected on every route, and `net.minecraft` alone no longer marks an artifact as Minecraft: the artifact name is read too, so `net.minecraft:launchwrapper:1.12` is a dependency, as is a group that merely ends in `net.minecraft` or an artifact whose name merely contains `minecraft-client`. A jar named by bare path (`target.kind="jar"`) under the Gradle dependency cache or the local Maven repository, wherever `MCP_LOCAL_M2` puts it, reports `"unknown"` unless the part of the path below the store's root names the Minecraft runtime; directories above the store are never consulted. Minecraft's own jars are unaffected. Remaining limit: a third-party jar whose own file name starts with `minecraft-`, such as `minecraft-client-helper`, still reports its own release number when named by a bare jar path; reach it with a `dependency` or `coordinate` target instead.
|
|
32
|
+
- `batch-class-members` and `inspect-minecraft`'s `class-members` task no longer blame the caller when the artifact they themselves resolved from the request's target carries no binary jar. Both collapse the request to a single artifact before reading members, and the failure was reported as `issueOrigin: "code_issue"` — once per entry on a batch — pointing at an input that could not have chosen otherwise. It is now `"tool_issue"`, matching `get-class-members`. A caller who named a jar themselves (`target: { kind: "jar", ... }`) still gets `"code_issue"`. This closes the gap 7.0.0-rc.1 recorded as outstanding for `batch-class-members`.
|
|
33
|
+
- A binary jar that `resolve-artifact` reports is now also persisted when the resolve was served from the warm cache. Previously that path skipped the write, so a binary companion that appeared in `~/.m2` or the Gradle cache after the artifact was first recorded, or the mojang-remapped jar reconciled for a `binary-remap:obf->mojang` artifact, reached the `resolve-artifact` response but not the stored artifact, and `get-class-members` on the returned `artifactId` then failed with `ERR_CONTEXT_UNRESOLVED` saying the artifact has no binary jar. Affected artifacts repair themselves on their next resolve; no re-index or cache clear is needed. A resolve that reaches no jar never clears a previously stored one.
|
|
34
|
+
- Cached artifact bytes that a concurrent resolve or cache prune deleted are now re-downloaded instead of failing the call with a raw `ENOENT`. Both a cache hit and a `304` revalidation treat vanished bytes as a cache miss and transfer again — unconditionally in the 304 case, carrying no validator from any source, caller-supplied `requestHeaders` included. A 304 revalidation no longer overwrites a fresher freshness record that a concurrent resolve left beside replaced `-SNAPSHOT` bytes: that record's digest, `etag` and `lastModified` are reported as they stand and no extra transfer is made. A cached entry that is present but unreadable — a permission error, a directory where a jar should be — is now reported instead of being treated as a miss, which had put that URL on the network on every call while hiding the actionable error. When that failed every repository, the final `ERR_REPO_FETCH_FAILED` names the offending cache path and its errno (`EACCES`, `EISDIR`, `EPERM`, `EIO`) in `hints` and says to remove or repair it.
|
|
35
|
+
- Evicting a poisoned download no longer destroys a concurrent resolve's good jar, and an eviction that cannot delete no longer leaves an entry the cache re-adopts forever. A response that is not a readable archive is dropped from the download cache, but the deletion named only the path, so when another resolve of the same URL had meanwhile finished a real jar into the same slot, that jar was deleted on the strength of a check run against different bytes. The eviction now compares the digest of the body it is rejecting with the identity record beside the file and leaves the file alone when they differ; this narrows the window rather than closing it, since another process can still replace the file between the comparison and the deletion. An entry no record can identify is still deleted, so a cache poisoned by an earlier version still heals itself. Separately, when the deletion itself fails — a read-only cache directory, say — the file is now truncated to zero bytes, which the cache treats exactly like a missing one, so the next transfer replaces it; previously whatever survived was served back and rejected again on every later resolve.
|
|
36
|
+
- `Retry-After` is now honoured in the RFC 9110 HTTP-date form. That form used to parse as `NaN` and fall through to a roughly 200-millisecond backoff, hammering a server that had just asked for a long pause. The numeric form is now read strictly, so `Retry-After: 12abc` is malformed rather than 12 seconds. Both forms stay capped at 30 seconds, a date already in the past behaves like `Retry-After: 0`, and only the space and horizontal tab RFC 9110 permits are stripped: a value padded with a newline, a form feed or a non-breaking space stays malformed instead of being repaired into a pause. The delta is measured against the local clock rather than the response's `Date` header.
|
|
37
|
+
- A caller-supplied Maven coordinate can no longer reach a jar outside the configured local repository. Every segment of `group:artifact:version[:classifier]` becomes a path component, and none was validated: an `artifactId` or `version` carrying `../` walked out of the repository root, and a `groupId` of `.`, `..` or `.a` landed at the filesystem root. Each segment is now trimmed and checked against one rule — 1 to 200 characters from `[A-Za-z0-9._+-]`, no leading `.`, no `..` — with `version` and `classifier` additionally admitting a space, because `net.fabricmc:yarn:1.14 Pre-Release 1+build.10:v2` is a real coordinate. A rejected coordinate fails with `ERR_COORDINATE_PARSE_FAILED` naming the offending segment in `details.component`, including when a mandatory segment is blank; `g : a : 1.0` now resolves instead of failing on its padding. The `dependency` target route applies the same rule to `target.group` and `target.name` before it probes the Gradle cache (its old blocklist admitted `group="D:"` with `name="."`, which listed a directory outside the cache root into `candidatesSeen`), trims `target.version`, and names `target.group` or `target.name` in its field error instead of just `target`. Scope: the reachable impact was reading a file that is both named `<artifact>-<version>[-classifier][-sources].jar` and openable as a zip; no write location was ever reachable, since every cache and artifact write derives its filename from a sha256.
|
|
38
|
+
- One corrupt `-sources.jar` in `~/.m2` no longer aborts artifact resolution. A truncated or non-zip sources jar among the resolution candidates used to surface as a generic `ERR_ARTIFACT_RESOLUTION_FAILED`, with the Gradle cache, the remote sources repositories, the local-binary decompile branch and the remote binary fallbacks never tried. An unopenable candidate is now treated as "no sources here" and resolution moves on, as the binary side already did. A jar the caller named directly still reports the open failure rather than laundering it into "no sources".
|
|
39
|
+
- A sources jar found in `~/.m2` or the Gradle module cache now picks its binary companion by opening the candidates, and looks in both stores. Previously each store took the first companion that merely existed, so a truncated or half-copied jar in one cache shadowed a good copy in the other, and the unusable path was what got recorded on the artifact, so every later `get-class-members` on that `artifactId` went to the broken file. Candidates are now tried in preference order, the sources jar's own store first, and the first one that opens wins. A companion that opens but holds no `.class` entry carries the `binary-jar-no-classes` quality flag here too.
|
|
40
|
+
- `validate-access-widener` and `validate-access-transformer` no longer answer `ERR_CONTEXT_UNRESOLVED` because of a directory name above the Gradle cache. A runtime jar's loader was read from its whole path, so a Fabric workspace checked out under a folder called `forge`, `moddev-notes` or `srg-test` was taken for a Forge or NeoForge runtime and refused. The loader is now read only from the part of the path a build tool wrote — everything below the deepest dot-directory (`.gradle`, `.m2`), Gradle's `caches` root or a project `build` directory, or the file name alone when the path enters none of them. A NeoForge-patched jar inside a Loom cache is still reported as NeoForge.
|
|
41
|
+
- A stdio peer that sends a `Content-Length` header and then stops sending the body no longer silences the connection. 7.0.0-rc.0 closed this only for a declared length above the frame limit; a smaller declared length followed by no body still parked the reader forever, with every later request stuck behind it. After 30 seconds with no further bytes the session now ends with a diagnostic naming the declared and received byte counts. The budget measures silence, not total transfer time: every arriving byte re-arms it, so a slow or very large body is never cut off.
|
|
42
|
+
- A header block whose lines contradict each other — a second `Content-Length` line even when the first value was unusable, or a malformed line alongside a good one — is now rejected outright and ends the session. Previously the reader stopped at the first bad line and consumed only the header block, so the body bytes an earlier declaration had claimed were re-read as separate messages and the peer effectively chose where the frame ended. A block that never declares a usable length is still reported as an ordinary parse error and recovered from.
|
|
43
|
+
- Peers that frame `Content-Length` headers with bare LF line endings are no longer mis-read when the JSON body contains a `\r\n\r\n` pair, which is legal JSON whitespace. A CRLF terminator anywhere in the buffer used to win over an earlier LF one, cutting the header block inside the body and losing that message and the next. The first terminator now wins, whichever style it is.
|
|
44
|
+
- A fault while handling an already-decoded frame no longer changes how later replies are framed. Such a throw used to reset the detected framing mode, so every later response was silently downgraded to line framing while the faulting request went unanswered. The fault is now reported through the ordinary parse-error channel with the framing state left as it was, and it can no longer tear the session down.
|
|
45
|
+
- A running `validate-project` no longer loses its timeout when a client reuses its JSON-RPC id. The deadline was matched by id alone, so a queued duplicate with the same id was timed out in its place while the real `validate-project` kept running with no deadline and still holding the dispatch barrier; whenever the worker never answered, everything queued behind it waited forever. A deadline now belongs to the exact request it was armed for, and a request that does not hold the barrier cannot release it.
|
|
46
|
+
- An internal failure while handing a request to the MCP worker no longer leaves that request half-registered. The supervisor answers such a fault with a JSON-RPC `-32603` "failed to admit the request" error, but the entry it had already installed stayed live: for `validate-project` the abandoned deadline later fired a second reply for the same id, and the dispatch barrier stayed raised until it did. The `-32603` is now the only reply for that id — the entry, its deadline and any barrier it held are released before the error is written — and queued work resumes immediately. An `initialize` that faults at this point keeps its own handshake recovery and is not rolled back here.
|
|
47
|
+
- A `tools/call` whose `arguments` are nested thousands of levels deep — a few KB on the wire — is now answered instead of vanishing. The argument redaction that runs at admission had no depth bound, so such a frame overflowed the stack and the request got no reply on an id the client then waited on forever. The walk now stops at 32 levels and reports anything deeper as `"<max-depth>"` in the diagnostic copies (`meta.timeout.redactedToolArgs`, `meta.restart.redactedToolArgs`, marked by `redactedToolArgsModified`); no real tool schema nests near that deep. Any request the supervisor cannot admit, for any reason, is now answered with JSON-RPC `-32603` rather than dropped.
|
|
48
|
+
- A worker or JVM that writes one very long stderr line with no newline no longer makes the server's memory grow until that worker is next restarted. The pending line is capped at 64 KiB: the part already held is passed through as one truncated line with a `supervisor.worker_stderr_line_truncated` warning, the rest of that line is discarded, and normal line handling resumes at the next newline, so worker startup is still detected.
|
|
49
|
+
- `compare-versions` no longer reports every class as both added and removed when only one of the two versions has usable Mojang mappings. Each side used to be lifted independently, so a pair where one side stayed obfuscated compared two different namespaces and returned `unchanged: 0` beside two full class lists. The diff now falls back to the jars' own class names on both sides, the `obfuscated` namespace the result already reported, and the warning announcing that fallback lists `version` alongside `packageFilter` in its `affectedFields`. One case the fallback cannot repair: when the two versions straddle Minecraft's move to unobfuscated names and the obfuscated side's mappings cannot be loaded, the jars' own names are still two different schemes, so `warnings` now says outright that the added and removed lists are not meaningful and `unchanged` will be near zero.
|
|
50
|
+
- A mapping file that leaves a namespace column empty on a class row no longer files that class's fields and methods under the previous class. The previous name was left standing for that namespace, so every member row that followed was indexed under the wrong owner and a lookup could be answered with a member the owner does not declare. Such a row now clears the namespace instead.
|
|
51
|
+
- `MCP_LOOM_TINY_MAX_INDEX_ENTRIES` now accepts only plain ASCII digits. A value such as `1e9`, `2_000_000`, `10M` or `1.9` used to be read from its leading digits as 1, 2, 10 or 1, small enough to truncate every real Loom mapping load; such a value now falls back to the heap-derived budget. A plain integer such as `250000` is still honoured, and may still be deliberately small.
|
|
52
|
+
- `find-mapping` and `resolve-method-mapping-exact` now return the mapping loader's own warnings in `warnings` whenever a mapping was loaded, `mapping_unavailable` included, as `get-class-api-matrix` and `check-symbol-exists` already did. The notice that a Loom mapping index stopped at its entry budget — the one signal that the answer may be incomplete — was being dropped on exactly the two tools most likely to hit it, and a `mapping_unavailable` answer replaced those warnings with its own single "no mapping path is available" sentence; both now appear together. On a version with no Mojang mappings these two tools now also surface the pre-existing `does not expose client mappings URL` notice.
|
|
53
|
+
- `resolve-method-mapping-exact` now says when a `not_found` was caused by an `owner` with no package. An unqualified owner cannot be projected into the target namespace and can never match, but the response explained the miss only as an inherited or relocated member. The message now names the owner and asks for the fully-qualified one.
|
|
54
|
+
- A version-manifest or version-detail fetch that fails at the transport layer — DNS, a refused connection, a TLS handshake — now fails with `ERR_REPO_FETCH_FAILED` instead of `ERR_INTERNAL`, so a network fault no longer reads as a server bug. The underlying message travels in `details.cause`; the timeout mapping is unchanged.
|
|
55
|
+
- `MCP_SQLITE_CACHE_KB` and `MCP_SQLITE_MMAP_SIZE` now also tune the artifact index that `manage-cache` opens. It used to open the same database on the built-in defaults — 8000 KiB page cache, 256 MiB memory map — whatever the two variables said.
|
|
56
|
+
- A failure while rebuilding a corrupted artifact index now fails with `ERR_DB_FAILURE` and closes the half-opened database handle, instead of escaping as a raw filesystem error from a read-only directory or a full disk. `details.reason` is `"rebuild_failed"` and the underlying message is carried in the error message.
|
|
57
|
+
|
|
58
|
+
### Documentation
|
|
59
|
+
|
|
60
|
+
- `docs/tool-reference.md` no longer tells a caller to work around a parameter that now exists. Its "Essential Conventions" entry named `find-class` as the only flat-`artifactId` tool accepting a top-level `projectPath` and directed callers of the other four to resolve the artifact first, contradicting the "Common Pitfalls" entry in the same document. Both now state that all five tools accept a top-level `projectPath` for a target that cannot supply workspace context from its own fields; resolve-first stays documented in "Common Pitfalls" as the optional alternative it still is.
|
|
61
|
+
- `docs/tool-reference.md` now documents the `context` block `get-class-members` and `batch-class-members` return. `jarSignature` is described as a signature over the jar's resolved path, modification time and size — good for spotting that the jar behind two responses is no longer the same file in the same state, usable neither as a content identity nor as a cache key. `minecraftVersion`, `mappingType`, `mappingNamespace` and `generatedAt` are described alongside it, including that `minecraftVersion` is `"unknown"` for a dependency artifact and for a jar inside a dependency store whose store-relative path does not name the Minecraft runtime, while a jar outside any such store is read from the first version-shaped token in its path.
|
|
62
|
+
- **Correction to 7.0.0-rc.0.** That release's "Ending the session no longer leaves a worker process behind" entry says the signal path and the supervisor-fault path both "exit non-zero once the worker group is collected or a bounded watchdog expires". That is wrong about the signal path: `SIGHUP`, `SIGINT` and `SIGTERM` run the ordinary cooperative shutdown and the process exits with code 0 once the detached worker group is collected, exactly as when stdin closes. Only an uncaught supervisor fault (`supervisor.fatal`) and a client framing violation (`supervisor.client_framing_fatal`) set exit code 1 on a running session, which is what lets a launcher tell a crash from a clean stop. No code changed; the published 7.0.0-rc.0 entry is left as it stands.
|
|
63
|
+
|
|
64
|
+
## [7.0.0-rc.1] - 2026-08-28
|
|
65
|
+
|
|
66
|
+
### Added
|
|
67
|
+
|
|
68
|
+
- A class-not-found error on a Jar-in-Jar shell now includes a `nestedJars` array listing the shell's inner jars by entry name, alongside `didYouMean`, on both single-tool and `batch-*` error entries. A miss against a shell — for example the Fabric API umbrella JAR — no longer needs a separate `resolve-artifact` call to find which inner module actually holds the class. The array appears only when the artifact carries an inventory, is never empty, and is capped at 64 entries.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- **A dependency's `artifactId` is now derived from a sha256 of the jar's bytes**, replacing a signature built from HTTP freshness headers (sources jars) or local file modification time and size (binary jars) — neither of which reflects the actual content. Each already-cached dependency gets a new `artifactId` and re-indexes once on its first resolve after upgrading (including a fresh decompile for binary-only dependencies); ids you already have keep resolving.
|
|
73
|
+
- **A `-SNAPSHOT` coordinate is now re-checked against its repository on every resolve** instead of being pinned to its first download, since Maven allows republishing a `-SNAPSHOT` under the same name. The check is conditional (no transfer) when the cached copy carries an `ETag` or `Last-Modified`, and an ordinary request otherwise; a republished jar replaces the cached one and gets a new `artifactId`. If the check cannot complete — timeout, server error, rate limit, or similar — the cached bytes are served rather than failing the resolve; a definitive not-found or forbidden response moves resolution to the next configured repository instead. Release versions, and `-SNAPSHOT` jars served from `~/.m2` or the Gradle cache, are unaffected.
|
|
74
|
+
- **A binary-only dependency resolved from local disk now reports different fields.** A coordinate whose module publishes no sources jar anywhere, but whose binary jar is already in `~/.m2` or the Gradle cache, is now answered from that local jar (see Fixed) with `origin: "local-m2"` and a `binaryJarPath` pointing at the local jar, instead of `origin: "decompiled"` with a downloaded copy.
|
|
75
|
+
- **The decompiled-source warning on `get-class-source` now follows whether the source text was actually decompiled**, not the artifact's `origin` label. The only case this changes is the binary-only-from-local-disk dependency above: its text is decompiled even though its origin names where the jar came from, so it now correctly carries the warning.
|
|
76
|
+
- **`manage-cache` now counts each downloaded jar's small on-disk freshness record into that jar's own cache entry** instead of surfacing it as a separate entry, so cache-size and entry-count reporting no longer double-counts it.
|
|
77
|
+
- **Wire contract change.** A `get-class-members` failure on an artifact with no binary jar now reports `issueOrigin: "tool_issue"` when the tool itself picked that artifact (from a version or coordinate), instead of always reporting `"code_issue"` — since nothing in that kind of request lets the caller pick a different jar. A caller who named the artifact directly (an explicit `artifactId` or `target: { kind: "jar", ... }`) still gets `"code_issue"` for a missing binary jar. `verify-mixin-target` gets the same reclassification (see Fixed); `batch-class-members` does not yet. Clients that branch on `issueOrigin` should read it from each response rather than assume a fixed value per error code.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- `get-class-members` and `batch-class-members` now answer a dependency target whose module publishes no sources jar, when its binary jar is already in the local Gradle cache — previously that binary jar was discarded and the read failed with `ERR_CONTEXT_UNRESOLVED`, even though `get-class-source` could read the same target. Bytecode-backed reads now work for binary-only modules from both the Gradle cache and `~/.m2`.
|
|
82
|
+
- Resolving the same dependency coordinate twice no longer re-downloads its jar or mints a new `artifactId`; a cached jar is reused and identified by the sha256 of its bytes, so the id is stable from the first resolve onward.
|
|
83
|
+
- A class-not-found hint no longer blames Minecraft obfuscation for artifacts that were never obfuscated. Dependency and Jar-in-Jar-shell misses previously advised remapping with `mapping="mojang"` even when the caller had already passed it, or when the artifact held no classes of its own; the hint is now suppressed for those artifacts, and whenever the request already named a non-obfuscated mapping. Vanilla artifacts genuinely indexed in obfuscated names still get the hint.
|
|
84
|
+
- `validate-project` with `task="mixin"` and no `version` no longer suggests a Minecraft version it never derived. The error previously carried a directly re-executable recovery call hardcoding `1.21.10`, so replaying it validated a project's mixins against the wrong version. It now points to `list-versions` and uses a `<your-mc-version>` placeholder naming where the real value lives (`gradle.properties`, or `task="project-summary"` with `preferProjectVersion: true`).
|
|
85
|
+
- `get-class-members` and `batch-class-members` now answer a coordinate whose module ships no sources jar anywhere, when a readable binary jar already sits in `~/.m2` or the Gradle cache — previously that local jar was discarded and the same bytes were downloaded again, or the call failed outright if no repository had it either. `~/.m2` and the Gradle cache are both tried, so a damaged copy in one does not hide a good copy in the other; a caller passing `allowDecompile: false` is still refused, since serving that artifact means decompiling it.
|
|
86
|
+
- A repository answering with something that is not a jar (an HTML error page, a truncated response) no longer becomes the resolved artifact — such a response is now checked as a readable archive before being accepted, on both a fresh download and a cache hit, so a cache already poisoned by an earlier version heals itself on the next resolve. A response with no body is likewise refused rather than cached.
|
|
87
|
+
- Reading an error through a `mc://` resource URI now reports the same `issueOrigin` as the equivalent tool call, instead of always reporting `"code_issue"` for a failure the tool call itself classifies as `"tool_issue"`.
|
|
88
|
+
- `verify-mixin-target` no longer blames the caller when the tool itself resolved an artifact with no binary jar from a `target: { kind: "version", ... }`; it now reports `issueOrigin: "tool_issue"` with guidance naming the two ways forward — re-target at a jar path directly, or use a version whose artifact ships one. A caller who passed `target: { kind: "jar", ... }` directly still gets `"code_issue"` for a missing binary jar.
|
|
89
|
+
|
|
10
90
|
## [7.0.0-rc.0] - 2026-08-22
|
|
11
91
|
|
|
12
92
|
### Added
|
|
13
93
|
|
|
14
|
-
- MCP protocol revision `2026-07-28
|
|
15
|
-
-
|
|
16
|
-
- `MCP_MAX_FRAME_BYTES` bounds the JSON-RPC frame size
|
|
94
|
+
- Support for MCP protocol revision `2026-07-28`, served alongside the legacy `initialize` protocol by the same process, so modern and legacy clients can both connect. A modern client calls `server/discover` for `supportedVersions: ["2026-07-28"]`, the advertised capabilities and the server identity, then carries the protocol version, its client capabilities and optional client info in `io.modelcontextprotocol/*` `_meta` on every request. The cacheable methods (`tools/list`, `resources/list`, `resources/read`, `resources/templates/list`, `server/discover`) return `ttlMs` and `cacheScope` for private caching, and every modern result carries `resultType: "complete"` and the server-identity `_meta` echo, including the replies synthesized for queue overflow, worker restart and timeout. A connection selects one era and keeps it: mixing eras is refused with `data.kind: "era_conflict"` or `"missing_meta"`, and an unsupported modern version answers `-32022` with `data.supported` / `data.requested`. The legacy `initialize` protocol stays supported; see the SDK v2 entry under Changed for the five legacy wire differences this release does carry. See `docs/tool-reference.md` → MCP Protocol Support.
|
|
95
|
+
- Memory tuning environment variables: `MCP_SQLITE_CACHE_KB` (artifact-index page cache in KiB, default `8000`) and `MCP_SQLITE_MMAP_SIZE` (bytes, default `268435456`, `0` disables) for the artifact index, which now also keeps temporary tables in memory (`temp_store=MEMORY`), and `MCP_DECOMPILE_MAX_MEMORY_MB` (default `4096`) to cap the Vineflower JVM heap the way `MCP_REMAP_MAX_MEMORY_MB` caps remapping. Defaults preserve existing behavior on typical installations.
|
|
96
|
+
- `MCP_MAX_FRAME_BYTES` bounds the JSON-RPC frame size the stdio transport accepts (default 64 MiB, minimum 1 MiB). Oversized frames are rejected with a diagnostic naming the observed size and the configured limit, header sections are capped at 8 KiB, and line-delimited frames obey the same limit.
|
|
17
97
|
|
|
18
98
|
### Changed
|
|
19
99
|
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
-
|
|
23
|
-
- **
|
|
24
|
-
- **Behavior change
|
|
25
|
-
- **Behavior change.** `
|
|
26
|
-
- **Behavior change — a
|
|
27
|
-
- **
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- **
|
|
31
|
-
-
|
|
32
|
-
- `
|
|
33
|
-
-
|
|
100
|
+
- **Breaking — Node.js 22.13.0 or newer is now required.** `engines.node` is raised from `>=22` to `>=22.13.0`, the first release where `node:sqlite` works unflagged and `StatementSync.iterate` exists. On Node 22.0–22.12 the server fails at startup rather than degrading, so upgrade Node.js before installing. The previous `>=22` floor understated the real requirement.
|
|
101
|
+
- **Breaking — importing this package as a Node library requires migration.** The exported `server` is now an SDK v2 `McpServer` with a different API surface, `buildServer` takes an optional `McpRequestContext`, and the published type declarations resolve Zod 4 types. Clients that talk to the server over stdio are unaffected.
|
|
102
|
+
- The runtime moved from MCP SDK v1 (`@modelcontextprotocol/sdk` 1.27.1, Zod 3) to SDK v2 (`@modelcontextprotocol/server` and `@modelcontextprotocol/client` 2.0.0, Zod 4.4.3). Legacy clients keep byte-compatible wire behavior — advertised `inputSchema` bytes, argument validation and validation-error bytes are unchanged — with five exceptions: (1) `tools/call` with `arguments` omitted now reaches the tool as `{}`, so input-free tools succeed, schemas with required fields answer per-field `ERR_INVALID_INPUT`, and a tool whose schema accepts `{}` but whose handler needs input answers its own error (`json-to-nbt` → `ERR_NBT_INVALID_TYPED_JSON`); (2) `tools/list` entries no longer carry the v1-only `execution: {"taskSupport":"forbidden"}` field; (3) unknown and disabled tool replies no longer consume a queue slot, so reply ordering and overflow outcomes under concurrent load can differ; (4) the unmatched-resource-URI error keeps code `-32602` but its message changed from `MCP error -32602: Resource <uri> not found` to `Resource not found: <uri>`, with the URI also in `data.uri`; (5) `initialize` advertises `resources: { listChanged: false }` and `tools: { listChanged: false }`, where it previously advertised `true` for both.
|
|
103
|
+
- **Wire contract change — both eras.** Advertised capabilities now carry `resources: { listChanged: false }` and `tools: { listChanged: false }`; both were previously `true`. The tool and resource surface is fixed at process start, the server never emits list-changed notifications, and `subscriptions/listen` is rejected in both eras, so the old `true` advertised a stream that could never carry anything. Treat both lists as static for the lifetime of the process and re-read them only across a restart.
|
|
104
|
+
- **Behavior change.** `get-class-members` and `batch-class-members` now inherit the resolved artifact's mapping when the call supplies no explicit `mapping`, matching `get-class-source`. Previously a mojang-mapped artifact answered member reads in the obfuscated namespace — `ownerFqn: "dlp"` with fields `e, f, g` — so the documented `find-class` → `get-class-source` → `get-class-members` flow returned two different namespaces for the same artifact. `context.mappingNamespace` and `minecraftVersion` no longer contradict `returnedNamespace`, and an explicit `mapping` is still never overridden. The `get-class-members.mapping` schema description still reads "default obfuscated" because the advertised schema bytes are pinned for legacy compatibility; the actual rule is documented in `docs/tool-reference.md`.
|
|
105
|
+
- **Behavior change — result counts differ.** `compare-versions` now diffs both jars in the mojang namespace instead of comparing raw obfuscated entries, which are not stable across Minecraft versions and made the old diff compare unrelated symbols. On the measured 1.21.10→1.21.11 pair the unfiltered result moved from `added 236 / removed 0 / unchanged 6386` to `added 616 / removed 380 / unchanged 6006`. Results carry `classes.namespace` and `classes.packageFilter { matchedFrom, matchedTo }`; a filter that matches nothing now warns instead of returning a zeroed result that reads as "no changes", and an unavailable mapping degrades to the obfuscated namespace with a loud warning.
|
|
106
|
+
- **Behavior change — a previously successful call now fails.** `validate-access-widener` and `validate-access-transformer` refuse a `scope: "merged"` resolution that would serve a jar from a different loader than the workspace, answering `ERR_CONTEXT_UNRESOLVED`. Previously a Fabric 1.21.11 request could be answered with a NeoForge 1.21.10 jar, and `validate-access-widener` then reported `valid: true` for a Fabric access widener checked against that jar — a false PASS. Same-loader version drift is still answered, now marked `approximate: true`. `resolve-artifact` and `get-class-source` are unaffected.
|
|
107
|
+
- **Behavior change — a different artifact is resolved.** `inspect-minecraft` now resolves a workspace subject exactly as `resolve-artifact` does for the same directory, instead of reading `minecraft_version` itself and resolving without a mapping. The two tools previously returned different artifacts for one project directory: `task="class-members"` failed to find a mojang class name that `batch-class-members` found on the same class. A call that used to resolve an obfuscated vanilla artifact can now resolve the merged, mojang-mapped one and report a different `artifactId`. An explicit `subject.mapping` or `subject.scope` is still never overridden, and `WORKSPACE_TARGET_OFF=1` restores the previous routing.
|
|
108
|
+
- **Behavior change — a response field reports a different set.** `resolve-method-mapping-exact` now reports the strict candidates its verdict was computed from on `status: "resolved"` and `"ambiguous"`, so `candidates` and `candidateCount` shrink where the simple-name index contributed extra matches. A caller can no longer be shown a `confidence: 1, matchKind: "exact"` candidate on another owner or descriptor next to an `ambiguous` verdict. `status: "not_found"` and `"mapping_unavailable"` keep the wider list, where the near misses are the useful content. `resolved` responses omit `candidates` and return `resolvedSymbol` with `candidateCount: 1`; `ambiguous` responses gain a warning counting how many candidates were rejected by owner and how many by descriptor, plus `ambiguityReasons[]`, which now also appears on `resolve-workspace-symbol` method-ambiguous responses. `find-mapping` still returns the unfiltered list.
|
|
109
|
+
- **Behavior change.** `resolve-method-mapping-exact` is now owner-strict: it requires the full `owner + name + descriptor`, where it previously filtered on the descriptor alone and could pull in a same-signature method from an unrelated class, forcing a false `ambiguous`. Queries whose owner declares exactly one matching method now resolve. The tradeoff: a method the owner **inherits** rather than declares moves from `ambiguous` to `not_found`, because mapping files record declarations and carry no class hierarchy. That case returns a warning naming the queried owner, the classes that do declare the member, and `find-mapping` as the owner-agnostic lookup. Two methods on the same owner remain `ambiguous`.
|
|
110
|
+
- **Behavior change — a different error code.** When a workspace subject's Minecraft version cannot be detected, `inspect-minecraft` now fails with `ERR_WORKSPACE_VERSION_UNRESOLVED`, naming the `projectPath` and carrying `nextAction` and a `suggestedCall`. Previously it surfaced as `ERR_INVALID_INPUT` "Either artifactId or target must be provided.", which described neither the cause nor the fix. `task="class-overview"` and `task="file"`, which answered with a `blocked` summary, now return the typed error too.
|
|
111
|
+
- Modern-era `resources/read` for a URI matching a registered template with no backing artifact now answers JSON-RPC `-32602`, as the revision requires, instead of a successful `resultType: "complete"` result carrying a ProblemDetails document. Applies to the five 404-class error codes. Legacy replies keep the ProblemDetails envelope.
|
|
112
|
+
- Modern-era `tools/list` returns tools in name-ascending order; legacy connections keep the previous registration order.
|
|
113
|
+
- `provenance.mappingArtifact` for Loom-sourced mappings names the file actually merged rather than the alphabetically first one found (for the measured 1.21.10 cache, `mappings-mojang.tiny` instead of `intermediary-v2.tiny`).
|
|
34
114
|
|
|
35
115
|
### Fixed
|
|
36
116
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
- An error
|
|
43
|
-
-
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- `
|
|
49
|
-
-
|
|
50
|
-
- `
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
- `
|
|
64
|
-
-
|
|
65
|
-
- The
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
117
|
+
- Runtime provenance no longer claims a version or loader that was not served. `provenance` describes the jar actually used — `version`, `requestedVersion`, `versionApproximated`, `servedLoader`, `expectedLoader`, `loaderMismatch` — where it previously echoed the requested version back, making a substituted artifact indistinguishable from an exact match. `resolve-artifact` gains `provenance.versionApproximation` and corrects `resolvedFrom.version`.
|
|
118
|
+
- `get-artifact-file` serves root-level and `META-INF/**` jar entries. Delivery was gated on an `assets/` or `data/` prefix, so `fabric.mod.json`, `META-INF/MANIFEST.MF`, `<mod>.mixins.json` and license files answered `ERR_FILE_NOT_FOUND` despite being in the jar — precisely the files you read first when inspecting a mod. Extension-less entries are now classified by checking for valid UTF-8 instead of being assumed binary. The 512 KiB cap, `truncated` flag, `contentOmittedReason` and `ERR_INVALID_INPUT` for traversal-shaped paths are unchanged.
|
|
119
|
+
- `validate-access-transformer` resolves its context from a NeoForge/ModDevGradle workspace. Discovery required an exact Minecraft-version token in the artifact path, but ModDevGradle names artifacts after the loader version (`neoforge-21.11.38-beta-*.jar` for Minecraft 1.21.11), so the canonical NeoForge example project answered `ERR_CONTEXT_UNRESOLVED` even with `projectPath` supplied. Loader-to-Minecraft version equivalence is now recognized and recorded as a provenance note, and resources-only `client-extra` jars are deprioritized.
|
|
120
|
+
- Error hints no longer ask you to supply a parameter you already supplied. Observed on `validate-access-transformer`; fixed for execution-error guidance generally.
|
|
121
|
+
- Typed-NBT rejections now say what is wrong and what to do about it. `ERR_NBT_INVALID_TYPED_JSON` used to report only "Invalid typed NBT JSON document." with no way to learn which node was at fault. It now carries `fieldErrors[0].path` — the RFC 6901 pointer into the document, or `typedJson` for a root-level failure — plus a `nextAction` naming the expected and received types, and `json-to-nbt` / `nbt-apply-json-patch` attach an `exampleCalls` entry pointing at `nbt-to-json` as the reliable way to obtain a valid document. `ERR_NBT_PARSE_FAILED`, `ERR_NBT_ENCODE_FAILED`, `ERR_JSON_PATCH_INVALID`, `ERR_NBT_UNSUPPORTED_FEATURE` and `ERR_JSON_PATCH_CONFLICT` gain default `nextAction` guidance the same way. No message string and no advertised schema changed; the required document shape is documented in `docs/tool-reference.md`.
|
|
122
|
+
- `get-class-source`'s `ERR_CLASS_NOT_FOUND` describes the artifact you asked about. An internal fallback or a nested-jar redirect used to overwrite `details.artifactId`, `error.context.artifactId`, `details.suggestedCall.params.artifactId`, `details.mapping` and `details.qualityFlags` with a different artifact — so a client acting on `mapping` queried the wrong namespace, and a `find-class` retry built from the suggestion searched an index holding neither the class nor its siblings. Those fields now describe the requested artifact; where the lookup ended is reported additively as `details.fallbackArtifactId`, present only when the two differ. A successful fallback still reports the fallback artifact, which is where the source came from.
|
|
123
|
+
- `ERR_CLASS_NOT_FOUND` offers near-miss candidates in the case they were built for. `details.didYouMean` came back empty exactly when the binary fallback had fired, so a typo'd vanilla class name lost its only recovery signal. Candidates are now taken from the requested artifact and unioned with the artifact the lookup ended on, deduplicated by fully-qualified name; a candidate found elsewhere carries an `artifactId` naming where it was found, so an entry without that field always means the artifact you asked about.
|
|
124
|
+
- `get-class-source`'s binary fallback honors `allowDecompile` instead of hardcoding `true`, so passing `allowDecompile: false` no longer costs a full Vineflower pass (measured on a first miss: 22.5 s decompile plus 29.3 s indexing). The cost is unchanged if you did not decline decompilation.
|
|
125
|
+
- `find-class` reports a nested type under its own name. A nested class or enum was reported under its outer fully-qualified name, so searching `Block` returned `net.minecraft.world.level.ClipContext` (the outer class of `ClipContext.Block`) above the real `net.minecraft.world.level.block.Block`. Nested matches now render as `<Outer>.<Nested>` with `nested` and `enclosingClass` fields, and top-level matches sort first. A zero-result response is classified as `partial_coverage` and suggests `get-class-source` when the resolved artifact carries the `partial-source-no-net-minecraft` flag and the query looks like a deobfuscated class name — the one case where the index cannot answer but the binary fallback can.
|
|
126
|
+
- Mapping lookups no longer exhaust the worker heap on a populated Fabric Loom cache. Every `.tiny` file found for a version was read whole into one unbounded index; on a real 1.21.10 cache that meant 13 files totalling 87,744,824 bytes, which crashed the worker and returned `ERR_WORKER_RESTART`. Files are now selected from a header probe, duplicate contents are skipped, and accumulation is bounded by a heap-derived budget overridable with `MCP_LOOM_TINY_MAX_INDEX_ENTRIES`. Measured on that cache: a crash at 86.5 s with 4,256.7 MB peak became a resolved answer at 26.1 s with 2,705.3 MB.
|
|
127
|
+
- Loom mapping lookups no longer report phantom candidates. Merging Tiny v2 files whose namespace order differs re-registered every method a second time under a foreign descriptor, so a method query could return two candidates for one real member, and the phantom could outrank the real record. One measured `resolve-method-mapping-exact` query changed from `ambiguous` with 2 candidates to `resolved` with 1.
|
|
128
|
+
- `subscriptions/listen` is rejected with `-32601` `Method not found` in the modern era, as the contract always documented. A conformant listen request was previously accepted but never answered, which permanently blocked the `validate-project` barrier and every request queued behind it.
|
|
129
|
+
- A cancelled request now releases its slot for every method, not only `validate-project`. `notifications/cancelled` settles the request, clears its deadline, releases the `validate-project` barrier if it held one, and discards a late answer. An `initialize` in flight is excluded.
|
|
130
|
+
- A malformed `initialize` no longer locks the process to the legacy protocol. Any JSON-RPC frame named `initialize` used to commit the one-way era choice before validation, so a bad handshake returned a `-32603` restart error and made every later modern request fail with `era_conflict` until the process was respawned. Such a request is now rejected with `-32602` and `data.kind: "invalid_initialize"` (carrying `data.required[]` and `data.eraSelected: false`), leaving the era unselected and the client free to retry either era.
|
|
131
|
+
- The modern `protocolVersion` value is validated on every request instead of only the one that pins the connection. Previously a later request carrying an unsupported version was served normally — a `tools/call` with `"1999-12-31"` ran its handler — and whether `-32022` fired depended on which method you sent first.
|
|
132
|
+
- A framing violation the reader cannot safely recover from now ends the session with a `supervisor.client_framing_fatal` diagnostic and exit code 1, instead of silently swallowing everything that follows. A `Content-Length` header declaring a body that never arrives previously hung the reader for the process lifetime, and an under-declared length corrupted the next request. Duplicate `Content-Length` headers are rejected rather than resolved last-wins.
|
|
133
|
+
- Line framing recovers after a line-delimited JSON array frame. An array arriving after a `Content-Length` frame was neither consumed as a line nor parsed as a header, so every later frame stayed buffered indefinitely with no error reported. Arrays are now re-dispatched to surface their JSON-RPC schema error; this does not add batch-message support.
|
|
134
|
+
- A `server/discover` sent while the worker is still starting is no longer failed with `-32601` by a later `initialize`.
|
|
135
|
+
- Legacy `tools/call` with a non-object `arguments` returns the v1 error bytes again: a malformed call naming a missing tool no longer returns a successful "not found" envelope, and a registered tool answers `-32603` rather than `-32602`. Modern calls keep `-32602`.
|
|
136
|
+
- Reusing a JSON-RPC id while another request still holds it no longer costs that request its answer. A queue overflow, blocked restart or unknown-tool reply for the reused id used to settle the live request instead, discarding its result and stranding everything queued behind a running `validate-project`.
|
|
137
|
+
- A legacy `initialize` carrying a modern `io.modelcontextprotocol/*` era claim in `params._meta` now completes the legacy handshake instead of failing with a worker restart. Other `_meta` keys pass through unchanged.
|
|
138
|
+
- Version manifest and version-detail fetches abort after `MCP_FETCH_TIMEOUT_MS` with a typed `ERR_REPO_FETCH_FAILED` instead of hanging when a repository stops responding.
|
|
139
|
+
- `manage-cache` opens the artifact index through the integrity-checking recovery path: a corrupt SQLite file is backed up and rebuilt instead of crashing cache inspection, and a missing database is still not created as a side effect of inspection.
|
|
140
|
+
- Tool responses keep their typed error envelopes when SQLite is unavailable: input validation still returns `ERR_INVALID_INPUT`, database-independent tools such as the NBT utilities still return results, and a metrics-recording failure no longer replaces a completed response.
|
|
141
|
+
- The server recovers from a crashed worker instead of going unresponsive. A worker that hit a fatal `uncaughtException` or `unhandledRejection` used to stay alive with recovery bypassed; it now exits so the supervisor can replace it. A restart blocked by stale process-tree cleanup is retried with capped backoff instead of waiting for shutdown.
|
|
142
|
+
- The server no longer leaves orphaned processes behind. A keep-alive timer kept the worker's event loop running after stdin closed, so any launcher that terminated the server without a cooperative shutdown left a resident node process — about 125 MB on the reference host — and each restart added another. The worker now stands down on stdin EOF, and also when the parent process it recorded at startup disappears. Replies still being written after a half-close are delivered in full. A host that immediately closes every child's stdin now backs off exponentially instead of respawning a worker ten times a second, while a genuine crash still gets a prompt replacement. Measured on the reference host over one full test run each way: 20 orphaned workers before the fix, 0 after.
|
|
143
|
+
- Ending the session no longer leaves a worker process behind. `SIGHUP` — what a terminating launcher or a vanishing session sends — was never handled, and an uncaught error in the supervisor went to node's default handler, so either one left the detached worker process group with nobody to collect it. Both now run the ordinary shutdown path, report a `supervisor.fatal` event where applicable, and exit non-zero once the worker group is collected or a bounded watchdog expires.
|
|
144
|
+
- The published package no longer risks shipping internal design documents. `files` listed `docs/**/*.md`, and because an npm `files` allowlist overrides `.gitignore`, a publish from a working tree containing local design notes would have included them. `files` now names the three intended documents explicitly.
|
|
145
|
+
- The automated npm release workflow can no longer publish a prerelease under the `latest` dist-tag: it derives the dist-tag from the package version, so a SemVer prerelease publishes under `rc` and an install without an explicit tag never picks up a release candidate.
|
|
146
|
+
|
|
147
|
+
### Documentation
|
|
148
|
+
|
|
149
|
+
- `docs/tool-reference.md` no longer states a condition on the suggested-call fallback hint that the code does not implement. It documented `"suggested call payload failed schema validation; using fallback examples"` as firing only when a primary suggestion is dropped and no `exampleCalls[]` fallback exists; neither branch checks that, and the sentence also fires for a deliberate placeholder-template drop, where nothing was schema-validated. The wording itself is unchanged, because it is part of the frozen legacy wire surface, and now carries a note saying why.
|
|
150
|
+
- `docs/tool-reference.md` no longer promises that `error.exampleCalls[]` entries are "always-valid" and "safe to re-call as-is". Examples are validated for schema shape only and may be templates; frozen envelopes already publish `<...>` placeholders that would be rejected if replayed verbatim. The guarantee is corrected to what the code provides — right tool, right argument names and types, placeholders possible — with no change to the emitted examples. The `didYouMean` and `resolve-method-mapping-exact` descriptions in the same section are updated for the behavior changes in this release.
|
|
69
151
|
|
|
70
152
|
## [6.3.0] - 2026-07-18
|
|
71
153
|
|
package/README.md
CHANGED
|
@@ -174,6 +174,8 @@ These notes cover high-frequency decisions during onboarding. For the full pitfa
|
|
|
174
174
|
- `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.
|
|
175
175
|
- `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.
|
|
176
176
|
- `validate-project` has a supervisor-owned end-to-end deadline of 120 seconds, including queue time. Set `MCP_VALIDATE_PROJECT_TIMEOUT_MS` to an ASCII-decimal value from `10000` through `600000` to override it. A timeout returns `ERR_TOOL_TIMEOUT`; a running timeout restarts the isolated worker before queued calls resume, while a queue timeout leaves the current worker untouched.
|
|
177
|
+
- Artifact downloads are capped at 512 MiB (`536870912` bytes) each. Set `MCP_MAX_DOWNLOAD_BYTES` to an ASCII-decimal byte count to override it; values below 1 MiB are raised to 1 MiB, and anything else — non-numeric, malformed, or too large to be an exact integer — falls back to the default. The cap is checked twice: against `Content-Length` before any body is read (a header that is not a plain byte count is treated as absent rather than as a breach), and against the bytes actually received, so a chunked or mis-declared response is caught too. A download that exceeds it fails with `ERR_LIMIT_EXCEEDED` and is not retried; the partial file is deleted on a best-effort basis, so a file the server cannot remove may survive the refusal. A cached copy is never served in its place — a cap is a configuration verdict, not a passing outage, and standing a stale artifact in for it would hide the ceiling instead of reporting it. When every repository trips the cap, the terminal `ERR_REPO_FETCH_FAILED` reports it where a caller can actually read it: `hints` carries the sentence naming `MCP_MAX_DOWNLOAD_BYTES` and the byte count to clear, and `context.repoFailureCode` is `ERR_LIMIT_EXCEEDED`.
|
|
178
|
+
- Nested (Jar-in-Jar) entries are capped at 64 MiB (`67108864` bytes) of uncompressed data each. Set `MCP_MAX_NESTED_JAR_ENTRY_BYTES` to override it, on the same rules as the download cap: ASCII decimal digits, values below 1 MiB raised to 1 MiB, anything else falling back to the default. The size is checked against the entry's declared uncompressed size before the read starts and again against the bytes actually streamed. A breach fails that one inner jar with `ERR_LIMIT_EXCEEDED`; the shell jar's other inner jars still resolve.
|
|
177
179
|
- Queued calls resume only after the replacement worker completes initialization replay. If replacement startup or replay fails, queued tool calls terminate with `ERR_WORKER_RESTART` instead of waiting indefinitely. If unresolved process-tree cleanup fills the supervisor's two live-generation slots, new requests fail with the existing restart envelope and unavailable notifications are warning-dropped until cleanup or reconnect. On POSIX, an already-gone process group counts as cleaned up rather than leaving a stale cleanup token.
|
|
178
180
|
- 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.
|
|
179
181
|
- `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.
|
package/dist/cache-registry.d.ts
CHANGED
|
@@ -45,6 +45,16 @@ export declare function pathContainsVersion(path: string, version: string): bool
|
|
|
45
45
|
export type CacheRegistryConfig = {
|
|
46
46
|
cacheDir: string;
|
|
47
47
|
sqlitePath: string;
|
|
48
|
+
/**
|
|
49
|
+
* SQLite page-cache and memory-map tuning for the artifact index, as
|
|
50
|
+
* MCP_SQLITE_CACHE_KB and MCP_SQLITE_MMAP_SIZE resolve them. Optional because
|
|
51
|
+
* a caller with no opinion should get the same defaults `applyPragmas` gives
|
|
52
|
+
* every other reader of the index - but a caller that HAS one is the whole
|
|
53
|
+
* point: without these the registry opened the index on the built-in defaults,
|
|
54
|
+
* so the two variables tuned every consumer except manage-cache.
|
|
55
|
+
*/
|
|
56
|
+
sqliteCacheKb?: number;
|
|
57
|
+
sqliteMmapSize?: number;
|
|
48
58
|
pathRuntimeInfo?: PathRuntimeInfo;
|
|
49
59
|
workspaceContextCache?: WorkspaceContextCache;
|
|
50
60
|
};
|