@adhisang/minecraft-modding-mcp 6.3.0 → 7.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +13 -3
- package/dist/cache-policy.d.ts +71 -0
- package/dist/cache-policy.js +83 -0
- package/dist/cache-registry.js +6 -6
- package/dist/cli.js +74 -3
- package/dist/compat-stdio-transport.d.ts +1 -1
- package/dist/compat-stdio-transport.js +13 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -2
- package/dist/decompiler/vineflower.d.ts +1 -0
- package/dist/decompiler/vineflower.js +8 -5
- package/dist/entry-tools/analyze-mod-service.d.ts +70 -136
- package/dist/entry-tools/analyze-symbol-service.d.ts +112 -150
- package/dist/entry-tools/compare-minecraft-service.d.ts +59 -145
- package/dist/entry-tools/entry-tool-schema.d.ts +38 -4
- package/dist/entry-tools/entry-tool-schema.js +4 -1
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +235 -799
- package/dist/entry-tools/inspect-minecraft/internal.js +50 -13
- package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
- package/dist/entry-tools/manage-cache-service.d.ts +81 -91
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +164 -592
- package/dist/entry-tools/verify-mixin-target-service.d.ts +3 -19
- package/dist/era-classifier.d.ts +161 -0
- package/dist/era-classifier.js +292 -0
- package/dist/error-mapping.js +9 -2
- package/dist/index.d.ts +42 -4
- package/dist/index.js +636 -473
- package/dist/java-process.d.ts +2 -0
- package/dist/java-process.js +22 -2
- package/dist/json-rpc-framing.d.ts +77 -1
- package/dist/json-rpc-framing.js +249 -13
- package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
- package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
- package/dist/mapping/loaders/tiny-loom.js +45 -33
- package/dist/mapping/loaders/tiny-maven.js +6 -11
- package/dist/mapping/parsers/tiny.d.ts +57 -0
- package/dist/mapping/parsers/tiny.js +99 -22
- package/dist/mapping-service.d.ts +19 -0
- package/dist/mapping-service.js +93 -9
- package/dist/mcp-helpers.d.ts +19 -2
- package/dist/mcp-helpers.js +48 -6
- package/dist/minecraft-explorer-service.d.ts +1 -1
- package/dist/mixin/types.d.ts +8 -0
- package/dist/mod-analyzer.js +7 -7
- package/dist/mod-decompile-service.js +1 -0
- package/dist/nbt/java-nbt-codec.js +12 -2
- package/dist/nbt/json-patch.js +14 -3
- package/dist/nbt/pipeline.js +40 -3
- package/dist/nbt/typed-json.js +26 -1
- package/dist/registration-adapter.d.ts +32 -0
- package/dist/registration-adapter.js +52 -0
- package/dist/request-context.d.ts +7 -0
- package/dist/request-context.js +9 -0
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +25 -19
- package/dist/server-identity.d.ts +27 -0
- package/dist/server-identity.js +26 -0
- package/dist/source/access-validate.js +53 -0
- package/dist/source/artifact-resolver.d.ts +69 -1
- package/dist/source/artifact-resolver.js +215 -14
- package/dist/source/class-source.d.ts +21 -0
- package/dist/source/class-source.js +125 -28
- package/dist/source/did-you-mean.d.ts +12 -1
- package/dist/source/did-you-mean.js +6 -2
- package/dist/source/file-access.js +150 -46
- package/dist/source/indexer.js +1 -0
- package/dist/source/shared-utils.d.ts +21 -0
- package/dist/source/shared-utils.js +23 -0
- package/dist/source-service.d.ts +11 -0
- package/dist/stdio-supervisor.d.ts +357 -2
- package/dist/stdio-supervisor.js +1031 -80
- package/dist/storage/db.d.ts +2 -1
- package/dist/storage/db.js +15 -8
- package/dist/synthetic-decorator.d.ts +24 -0
- package/dist/synthetic-decorator.js +48 -0
- package/dist/tool-guidance.d.ts +17 -1
- package/dist/tool-guidance.js +309 -11
- package/dist/tool-schema-registry.d.ts +2 -0
- package/dist/tool-schema-registry.js +4 -0
- package/dist/tool-schemas.d.ts +2212 -3919
- package/dist/tool-schemas.js +33 -7
- package/dist/types.d.ts +35 -0
- package/dist/v1-parity-schemas.d.ts +7 -0
- package/dist/v1-parity-schemas.js +5584 -0
- package/dist/version-diff-service.d.ts +33 -0
- package/dist/version-diff-service.js +148 -3
- package/dist/version-service.js +36 -14
- package/dist/warning-details.js +18 -1
- package/docs/README-ja.md +5 -3
- package/docs/tool-reference.md +194 -19
- package/package.json +8 -5
package/docs/tool-reference.md
CHANGED
|
@@ -37,9 +37,10 @@ Start here when you are not sure which tool to reach for. In every row, the left
|
|
|
37
37
|
- Positive integer tool arguments accept numeric strings such as `"10"` for documented top-level parameters.
|
|
38
38
|
- When a parameter has a fixed safe default, `tools/list` exposes it through the JSON Schema `default` field so clients can rely on schema metadata instead of prose notes.
|
|
39
39
|
- Retryable `suggestedCall` payloads omit parameters when the supplied value already matches the tool default, keeping recovery calls smaller without changing behavior.
|
|
40
|
-
- `ERR_CLASS_NOT_FOUND` errors from the class tools carry a top-level `didYouMean` array (parallel to `suggestedCall`) with ranked near-miss candidates from the artifact's symbol index: each entry is `{ className, matchReason }` where `matchReason` is `"exact-simple-name"` (same simple name in another package — the moved-class case, ranked first), `"case-insensitive"`, or `"edit-distance:N"`. Candidates are hints, never assertions that the class exists at the suggested location; the array is empty when
|
|
40
|
+
- `ERR_CLASS_NOT_FOUND` errors from the class tools carry a top-level `didYouMean` array (parallel to `suggestedCall`) with ranked near-miss candidates from the artifact's symbol index: each entry is `{ className, matchReason }` where `matchReason` is `"exact-simple-name"` (same simple name in another package — the moved-class case, ranked first), `"case-insensitive"`, or `"edit-distance:N"`. Candidates come from the symbol index of the artifact the caller REQUESTED first, followed by any the artifact the lookup ended on contributes, deduplicated by FQN. A candidate found outside the requested artifact carries an extra `artifactId` naming where it was found; an entry WITHOUT that field is always from the artifact the caller asked about. The union matters because the requested artifact is, in the partial-source fallback scenario, the one with no `net.minecraft` symbols — which is why the fallback fired — so collecting from it alone returns `[]` in exactly the case the fallback exists to serve. Candidates are hints, never assertions that the class exists at the suggested location; the array is empty when neither index has anything usable. All same-simple-name FQNs are enumerated rather than collapsed. When an internal redirect resolved a different artifact while trying to answer the call — the binary fallback, or the nested-jar redirect that follows a shell jar's bundled inner jar — the error carries `details.fallbackArtifactId` naming it, alongside `details.binaryFallbackAttempted` for the binary case. `details.artifactId`, `details.mapping` and `details.qualityFlags` all describe the REQUESTED artifact, so identity, namespace and quality never disagree about which artifact is being reported. `fallbackArtifactId` and `binaryFallbackAttempted` are internal `AppError.details` fields for diagnosis and are NOT published on the wire; the envelope reports the requested artifact as `error.context.artifactId`.
|
|
41
41
|
- `find-class` searches the nested `.class` inventories of Jar-in-Jar shell artifacts such as the Fabric API umbrella JAR. It accepts simple or qualified names, returns dotted names for inner classes, deduplicates a class bundled more than once, and honors `limit`. The returned source path is inferred from the outer class. For top-level matches, `get-class-source` and `get-class-members` resolve the actual containing nested JAR before reading content; dotted inner-class matches are also readable through `get-class-source`.
|
|
42
42
|
- Source-oriented tools expose `artifactContents` so callers can tell whether the backing artifact is a `source-jar` or a `decompiled-binary`. `get-class-source`, `get-class-members`, `search-class-source`, and `get-artifact-file` also expose `returnedNamespace`.
|
|
43
|
+
- When `mapping` is omitted, `get-class-source`, `get-class-members`, and `batch-class-members` all inherit the mapping the target artifact was resolved with, and report it as `returnedNamespace`. The `mapping` parameter's advertised schema description still says the default is `obfuscated`; that wording is pinned by the frozen legacy `inputSchema` bytes and is accurate only for targets that carry no resolved mapping of their own. An explicit `mapping` is never overridden.
|
|
43
44
|
- Cache-backed source, mapping, validation, batch, and workflow tools accept `gradleUserHome?: string` when they need Loom cache data. Use it for builds that used an isolated `GRADLE_USER_HOME`; the server searches `<gradleUserHome>/loom-cache` and `<gradleUserHome>/caches/fabric-loom` before the MCP process default. The value selects a Gradle User Home, not arbitrary Loom cache roots.
|
|
44
45
|
- `get-class-members` returns `decompiledFallback` (with `constructors`, `fields`, `methods`, each entry is `{ name, line, kind }`) and `decompiledMemberCounts` whenever bytecode enumeration yields zero but the decompiled source for the class is already indexed. The bytecode-derived `members` / `counts` are preserved as-is; the fallback is additive and carries no descriptor or access modifier. `qualityFlags` gains `"members-from-decompiled-source"` in that case. Use `get-class-source` for descriptors and full context.
|
|
45
46
|
- `get-class-members` also returns an additive `status: "ok" | "members_unavailable" | "partial"` field so callers can distinguish "really 0 members" from "extraction unavailable":
|
|
@@ -72,6 +73,8 @@ Use `subject.kind="workspace"` when `inspect-minecraft` should resolve Minecraft
|
|
|
72
73
|
|
|
73
74
|
`task="auto"` is structured dispatch based on `subject.kind` and `focus.kind`; it is not a natural-language planner and does not interpret prose. A string `focus` remains invalid and returns `ERR_INVALID_INPUT` with three schema-validated class/search/file `exampleCalls`; the server never guesses which object shape the text meant.
|
|
74
75
|
|
|
76
|
+
A workspace subject resolves through `target.kind="workspace"`, the same synthesizing target `resolve-artifact` uses for a directory, so version detection, the project's compile mapping, and the loader-derived scope all apply and `inspect-minecraft` and `resolve-artifact` return the SAME artifact for the same project. Previously `inspect-minecraft` re-derived a `{ kind: "version" }` target with no mapping; because the mapping feeds the remap gate and the mapping variant is hashed into the `artifactId`, that resolved a DIFFERENT artifact — and skipped Loom source-jar discovery. An explicit `subject.mapping` / `subject.scope` still wins, and `WORKSPACE_TARGET_OFF=1` restores the previous version-target routing.
|
|
77
|
+
|
|
75
78
|
Class source from a workspace:
|
|
76
79
|
|
|
77
80
|
```json
|
|
@@ -168,7 +171,7 @@ Workspace detection is memoised in a process-resident `WorkspaceContextCache` (1
|
|
|
168
171
|
## Common Pitfalls
|
|
169
172
|
|
|
170
173
|
- Flat-`artifactId` tools (`find-class`, `get-artifact-file`, `list-artifact-files`, `search-class-source`, `index-artifact`) also accept the shared `target` shape (`{ kind: "version" | "jar" | "coordinate" | "artifact" | "dependency", ... }`) instead of `artifactId` — exactly one of the two must be supplied. The target is resolved (and ingested if needed) before the lookup, so a fresh `resolve-artifact` round-trip is unnecessary. `{ kind: "artifact", artifactId }` passes through directly. Targets that need workspace context beyond their own fields — `kind: "workspace"`, or `kind: "dependency"` without an explicit `version` — cannot supply a `projectPath` through these tools; resolve them with `resolve-artifact` first and pass the `artifactId`.
|
|
171
|
-
- `analyze-symbol` infers an omitted `version` from `projectPath` (gradle.properties `minecraft_version`/`mc_version`); the response then carries `versionInference { version, source }` and a warning. An explicit `version` always wins. `inspect-minecraft` direct subjects without `subject.artifact` auto-resolve only when exactly one workspace is known to the process (provenance warning attached); several candidates are refused with `workspaceCandidates`.
|
|
174
|
+
- `analyze-symbol` infers an omitted `version` from `projectPath` (gradle.properties `minecraft_version`/`mc_version`); the response then carries `versionInference { version, source }` and a warning. An explicit `version` always wins. `inspect-minecraft` direct subjects without `subject.artifact` auto-resolve only when exactly one workspace is known to the process (provenance warning attached); several candidates are refused with `workspaceCandidates`. That unique workspace is then resolved through `target.kind="workspace"` like any other workspace subject, so it picks up the project's compile mapping and loader scope; the separately detected Minecraft version is retained only as the "is this workspace usable at all" guard.
|
|
172
175
|
|
|
173
176
|
- Loom split-source workspaces publish a version as a `minecraft-common` / `minecraft-clientOnly` sources-jar pair with no merged jar. Version-target resolution indexes both halves (the companion jar appears in `provenance.companionSourceJars`), so client-only classes are queryable under the merged scope. If a class still cannot be found, the error's `exampleCalls` carries a `scope: "vanilla"` retry — the decompiled client jar also contains client-only classes.
|
|
174
177
|
- `mapping="mojang"` requires source-backed artifacts on legacy obfuscated versions. For unobfuscated releases such as `26.1+`, the runtime/decompile path is accepted directly for version and versioned-coordinate targets. When source jars are not available but the version's Mojang tiny mappings, the tiny-remapper jar, and `MappingService.checkMappingHealth` are all healthy, `resolve-artifact` with `target.kind="version"` will transparently tiny-remap the binary jar (`obfuscated -> mojang`) and decompile the result, and the response carries `qualityFlags` `"binary-remapped"` and `"decompiled"` plus `provenance.transformChain` `"binary-remap:obf->mojang"` and `"decompile:vineflower"`. Coordinate and jar targets are not eligible for this fallback and still surface `ERR_MAPPING_NOT_APPLIED`. Source-backed and obfuscated artifacts keep their existing `artifactId` hashes — only the new mojang-remapped variant lives in a separate cache slot.
|
|
@@ -305,7 +308,7 @@ Every per-entry `suggestedCall` is validated through the same `tool-schema-regis
|
|
|
305
308
|
|
|
306
309
|
If the **shared resolution itself** fails (e.g. `target.kind="version"` with an unknown version), the batch returns a top-level error envelope (no `results[]`). `failFast` does not apply because no entries ran.
|
|
307
310
|
|
|
308
|
-
Rollback: `BATCH_TOOLS_OFF=1` removes all four batch tools from `tools/list`. Direct `tools/call` for any of them
|
|
311
|
+
Rollback: `BATCH_TOOLS_OFF=1` removes all four batch tools from `tools/list`. Direct `tools/call` for any of them is answered per era: legacy clients receive a SUCCESSFUL tool-result envelope (`{ content: [{ type: "text", text: "MCP error -32602: Tool <name> not found" }], isError: true }`, no `structuredContent` key), while modern clients receive a raw JSON-RPC `-32602` error (see `## MCP Protocol Support` → Rejection and error table). Neither shape carries `ProblemDetails`, so callers cannot rely on `error.code === "ERR_*"` for disabled tools.
|
|
309
312
|
|
|
310
313
|
### batch-class-source
|
|
311
314
|
|
|
@@ -323,6 +326,34 @@ Probe symbol existence for many entries against one shared Minecraft-version art
|
|
|
323
326
|
|
|
324
327
|
Translate symbols across mapping namespaces with one shared Minecraft version. Per-entry: `{ kind, name, owner?, descriptor?, sourceMapping, targetMapping, signatureMode?, disambiguation?, maxCandidates? }`. Shared: top-level `version` (required) plus `sourcePriority` / `projectPath`. Per-entry `version` is rejected with an `unrecognized_keys` zod issue; the batch shape is intentionally single-version. There is no shared artifact resolution — each entry hits the mapping graph directly — so `summary.sharedArtifactId` is omitted.
|
|
325
328
|
|
|
329
|
+
## Typed NBT document shape
|
|
330
|
+
|
|
331
|
+
`json-to-nbt` and `nbt-apply-json-patch` take a `typedJson` argument whose advertised JSON Schema is the empty object `{}`. Those bytes are pinned for legacy wire parity and cannot change, so the required shape is documented here instead — the same workaround already applied to `get-class-members.mapping`. A `typedJson` that does not match is rejected with `ERR_NBT_INVALID_TYPED_JSON`.
|
|
332
|
+
|
|
333
|
+
A document is:
|
|
334
|
+
|
|
335
|
+
```jsonc
|
|
336
|
+
{
|
|
337
|
+
"rootName": "Level", // string; the NBT root compound's name (may be "")
|
|
338
|
+
"root": { "type": "compound", "value": { /* nodes */ } }
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Every node is `{ "type": <nbt-type>, "value": <payload> }`, where `<nbt-type>` is one of `byte`, `short`, `int`, `long`, `float`, `double`, `byteArray`, `string`, `list`, `compound`, `intArray`, `longArray`. Payloads by type:
|
|
343
|
+
|
|
344
|
+
| `type` | `value` | Notes |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| `byte` / `short` / `int` | number | Integer, in the JVM range for that width (`byte` is signed, −128..127). |
|
|
347
|
+
| `long` | **string** | Decimal digits, `-9223372036854775808`..`9223372036854775807`. Never a JSON number. |
|
|
348
|
+
| `float` / `double` | number, or the strings `"NaN"` / `"Infinity"` / `"-Infinity"` | A raw non-finite JSON number is rejected; use the string sentinels. |
|
|
349
|
+
| `string` | string | |
|
|
350
|
+
| `byteArray` / `intArray` | number[] | Each element must fit the element width. |
|
|
351
|
+
| `longArray` | string[] | Same decimal-string rule as `long`. |
|
|
352
|
+
| `list` | node[] | Also carries `"elementType"`: any node type, and every element's `type` must equal it. `"end"` is allowed only for an empty list. |
|
|
353
|
+
| `compound` | object of name → node | Keys are the NBT tag names. |
|
|
354
|
+
|
|
355
|
+
Rejections name the offending node twice: `error.fieldErrors[0].path` is the RFC6901 JSON pointer into the document (for example `/root/value/health/value`, `typedJson` when the whole document is wrong), and `error.hints` states the expected and received types plus this shape. `error.exampleCalls[0]` points at `nbt-to-json`, which produces a document this format accepts from any real NBT payload — the reliable way to obtain a valid `typedJson` to edit.
|
|
356
|
+
|
|
326
357
|
## Errors
|
|
327
358
|
|
|
328
359
|
`ProblemDetails.code` may carry the codes below in addition to the existing tool-specific values.
|
|
@@ -331,14 +362,18 @@ Translate symbols across mapping namespaces with one shared Minecraft version. P
|
|
|
331
362
|
- `ERR_TOOL_TIMEOUT` — Synthetic `CallToolResult` produced only for `validate-project` when its supervisor-owned end-to-end deadline expires. The default is 120,000 ms and includes supervisor queue time. `meta.timeout.phase` is `"queue"` when the call expired before dispatch and `"running"` after dispatch. A queue timeout does not restart the worker; a running timeout isolates the worker process tree, initiates replacement, and sets `meta.timeout.workerRestartInitiated: true`. Cancellation suppresses the timeout result while retaining the deadline for worker cleanup.
|
|
332
363
|
- `ERR_MIXIN_PARSE_FAILED` — Reserved code for `validate-mixin` parse-stage failures in single / inline mode. Today the runtime parser is permissive (no hard parse failure path), but emitting this code keeps the contract stable for future strict-parse modes.
|
|
333
364
|
- `ERR_STAGE_BUDGET_PRE_PARSE` — Raised when a pre-parse stage (`resolve`, `mapping-health`, or `parse` itself) exhausts its independent soft-deadline before validation can proceed. The error carries `failedStage: <stage name>`, `meta.stageBudgetExhausted: true`, and the `budgetMs` / `elapsedMs` for the offending stage. Recovery: shrink `mixinConfigPath` (e.g. validate one config file at a time) or set `MIXIN_STAGE_BUDGETS_OFF=1` to disable budgets for diagnostic reruns.
|
|
334
|
-
- `ERR_WORKSPACE_VERSION_UNRESOLVED` — Raised by `resolve-artifact`, `get-class-source`,
|
|
365
|
+
- `ERR_WORKSPACE_VERSION_UNRESOLVED` — Raised by `resolve-artifact`, `get-class-source`, `get-class-members`, and `inspect-minecraft` (workspace subject, every artifact-context task) when `target.kind="workspace"` cannot detect a Minecraft version from `gradle.properties`. Both `strict: true` and `strict: false` raise; `details.strict` records which value was supplied. The error carries `details.projectPath` and a `suggestedCall` with `target: { kind: "version", value: "<your-mc-version>" }` so the caller can fall back to an explicit version target.
|
|
335
366
|
- `ERR_DEPENDENCY_VERSION_UNRESOLVED` — Raised by the same three tools when `target.kind="dependency"` cannot infer a version from `gradle.properties` or `~/.gradle/caches/modules-2/files-2.1/<group>/<name>/`. Multiple non-snapshot entries in modules-2 also raise this error: the synthesizer refuses to pick without project-specific evidence, sets `details.ambiguous=true`, and lists the cached versions in `details.candidatesSeen`. The error carries `details.attempts` (the gradle.properties keys that were probed) plus a `suggestedCall` with `target: { kind: "dependency", group, name, version: "<your-version>" }`.
|
|
336
367
|
|
|
337
368
|
### `suggestedCall` schema validation gate
|
|
338
369
|
|
|
339
|
-
Every `ProblemDetails.suggestedCall` payload an agent receives is validated against the registered `zod` schema for the named tool BEFORE it leaves the process. Invalid payloads (e.g. a `target.value` that holds a JSON-stringified inner target — the bug in v3 chains where round-tripping the suggestion produced `ERR_INVALID_INPUT`) are dropped from the published envelope.
|
|
370
|
+
Every `ProblemDetails.suggestedCall` payload an agent receives is validated against the registered `zod` schema for the named tool BEFORE it leaves the process. Invalid payloads (e.g. a `target.value` that holds a JSON-stringified inner target — the bug in v3 chains where round-tripping the suggestion produced `ERR_INVALID_INPUT`) are dropped from the published envelope.
|
|
371
|
+
|
|
372
|
+
The gate drops a primary suggestion for TWO reasons, and both are reported the same way. Either the payload failed schema validation, or — the common case — the guidance handed the gate a TEMPLATE whose values are still `<...>` placeholders, which is by design and never reached the schema at all. Whenever a primary suggestion is dropped and no `suggestedCall` survives, `error.hints` gains the literal sentence `"suggested call payload failed schema validation; using fallback examples"`. Read that sentence as "no directly-replayable suggestion is being published", not as evidence that a schema check failed: it is emitted for the placeholder-template case too. It also does NOT mean the envelope is empty of guidance — it coexists with `exampleCalls[]`, and in practice every recorded tool-contract envelope that carries the sentence also carries `exampleCalls`. Branch on the presence of `suggestedCall` / `exampleCalls`, never on this text.
|
|
340
373
|
|
|
341
|
-
The
|
|
374
|
+
The wording is deliberately retained verbatim even though it is inaccurate for the placeholder case: it is frozen inside the pre-migration envelope evidence under `tests/fixtures/premigration/` (six tool-contract envelope samples and four ProblemDetails goldens), which was captured against an untouched pre-migration build and cannot be re-recorded. Rewording the sentence — or splitting the single drop marker into two — breaks those goldens with no way to regenerate them. Fix the reader, not the string.
|
|
375
|
+
|
|
376
|
+
The optional `error.exampleCalls?: Array<{ tool: string; params: Record<string, unknown>; reason: string }>` field carries one or more alternative payloads when the primary is dropped. Each entry is **schema-valid**: it names a real tool and passes that tool's registered schema, so the argument names and types are right. It is NOT necessarily replayable as-is. Examples are explicitly permitted to be TEMPLATES — the same `<...>` placeholders that cause a primary `suggestedCall` to be dropped are allowed to survive here, because an example's job is to show the shape when no fillable suggestion exists. Recorded envelopes carry such templates today (the `analyze-mod` sample publishes `subject.jarPath: "<mod-jar-path>"`, and the typed-NBT recovery example publishes `nbtBase64: "<base64-encoded-nbt-payload>"`, which the NBT pipeline would reject as invalid base64 if sent verbatim). Scan the params for `<...>` values and fill them before sending; only `suggestedCall` is guaranteed placeholder-free.
|
|
342
377
|
|
|
343
378
|
`suggestedCall.params` is published **byte-identical** to the caller-supplied object — the gate validates emit-vs-drop only and never injects schema defaults into the published payload. Internal consumers that want the schema-normalized form read `validateToolParams(name, params).data` directly from `tool-schema-registry`.
|
|
344
379
|
|
|
@@ -357,17 +392,17 @@ These environment variables are read once at worker startup and provide rollback
|
|
|
357
392
|
|
|
358
393
|
| Env | Effect | Acceptance test |
|
|
359
394
|
|---|---|---|
|
|
360
|
-
| `MIXIN_STAGE_BUDGETS_OFF=1` | Sets every `validate-mixin` stage budget (including the per-target soft cap) to `Number.POSITIVE_INFINITY`. Restores the pre-budget run-to-completion behaviour. | `tests/source-service
|
|
395
|
+
| `MIXIN_STAGE_BUDGETS_OFF=1` | Sets every `validate-mixin` stage budget (including the per-target soft cap) to `Number.POSITIVE_INFINITY`. Restores the pre-budget run-to-completion behaviour. | `tests/source-service/validate-mixin-budget.test.ts` (`MIXIN_STAGE_BUDGETS_OFF=1 disables all budgets`) |
|
|
361
396
|
| `SUPERVISOR_STRUCTURED_RESTART_OFF=1` | Suppresses synthetic `CallToolResult` envelopes; `tools/call` requests killed by a worker exit fall back to the legacy raw JSON-RPC `-32603` error. | `tests/stdio/stdio-supervisor.test.ts` (`buildWorkerRestartReply returns raw -32603 when structuredRestartDisabled`) |
|
|
362
|
-
| `MIXIN_STAGE_PROGRESS_OFF=1` | Replaces the worker stage emitter with a no-op so `$/stageUpdate` notifications are never sent. Use as a fallback when the active SDK build does not surface `extra.requestId`. | `tests/stage-emitter.test.ts` (`makeStageEmitter is a no-op when disabled option is true`) |
|
|
363
|
-
| `WORKSPACE_TARGET_OFF=1` | Rejects `target.kind="workspace"` on `resolve-artifact`, `get-class-source`, and `get-class-members` with `ERR_INVALID_INPUT`. Restores the pre-workspace-target behaviour where callers must always supply `target.kind="version"`/`"jar"`/`"coordinate"`. | `tests/source-service
|
|
364
|
-
| `DEPENDENCY_TARGET_OFF=1` | Rejects `target.kind="dependency"` on the same three tools with `ERR_INVALID_INPUT`. | `tests/source-service
|
|
365
|
-
| `WORKSPACE_FALLBACK_LEGACY=1` | Forces the `ERR_MAPPING_NOT_APPLIED` `suggestedCall` back to the pre-workspace shape (`{ target, mapping: "obfuscated" }` with the legacy scope flip on `vanilla`+`mojang`). Use when an integration relies on the legacy retry payload. | `tests/source-service
|
|
366
|
-
| `VALIDATE_PROJECT_TASKS_OFF=1` | Omits the additive `tasks` per-probe status report from `validate-project task="project-summary"` results. The headline `result.summary.status`, `result.project`, and `result.workspace` blocks are unchanged. Use as a rollback path while the per-probe contract stabilizes. | `tests/entry-tools-validate-project-tasks.test.ts` (`validate-project tasks A5: VALIDATE_PROJECT_TASKS_OFF=1 omits the tasks field`) |
|
|
367
|
-
| `MEMBERS_STATUS_LEGACY=1` | Omits the additive `status` / `unavailableReason` / `suggestedCall` fields from `get-class-members` results. Restores the pre-status response shape for callers that pre-date the new enum. | `tests/source-service
|
|
368
|
-
| `VERIFY_MIXIN_TARGET_OFF=1` | Removes `verify-mixin-target` from `tools/list` and rejects direct invocations with `ERR_INVALID_INPUT`. Use as a rollback path while the accessor-inference rules stabilize. | `tests/entry-tools-verify-mixin-target.test.ts` (`C11: VERIFY_MIXIN_TARGET_OFF=1 hides the tool from tools/list and rejects direct calls`) |
|
|
369
|
-
| `SUGGESTED_CALL_VALIDATE_OFF=1` | Bypasses the `ProblemDetails.suggestedCall` schema validation gate. Raw caller-supplied payloads are emitted unchanged (matching the pre-gate behaviour); `error.hints` does not gain the fallback line. Use only as an emergency rollback if the gate causes unexpected drops in production. | `tests/build-suggested-call.test.ts` (`D11: SUGGESTED_CALL_VALIDATE_OFF=1 bypasses validation`) |
|
|
370
|
-
| `BATCH_TOOLS_OFF=1` | Removes the 4 batch lookup tools (`batch-class-source`, `batch-class-members`, `batch-symbol-exists`, `batch-mappings`) from `tools/list`. Direct calls
|
|
397
|
+
| `MIXIN_STAGE_PROGRESS_OFF=1` | Replaces the worker stage emitter with a no-op so `$/stageUpdate` notifications are never sent. Use as a fallback when the active SDK build does not surface `extra.requestId`. | `tests/utils/stage-emitter.test.ts` (`makeStageEmitter is a no-op when disabled option is true`) |
|
|
398
|
+
| `WORKSPACE_TARGET_OFF=1` | Rejects `target.kind="workspace"` on `resolve-artifact`, `get-class-source`, and `get-class-members` with `ERR_INVALID_INPUT`. Restores the pre-workspace-target behaviour where callers must always supply `target.kind="version"`/`"jar"`/`"coordinate"`. | `tests/source-service/workspace-target.test.ts` (`synthesizeWorkspaceTarget rejects target.kind=workspace when WORKSPACE_TARGET_OFF is set`) |
|
|
399
|
+
| `DEPENDENCY_TARGET_OFF=1` | Rejects `target.kind="dependency"` on the same three tools with `ERR_INVALID_INPUT`. | `tests/source-service/dependency-target.test.ts` (`synthesizeDependencyTarget rejects target.kind=dependency when DEPENDENCY_TARGET_OFF is set`) |
|
|
400
|
+
| `WORKSPACE_FALLBACK_LEGACY=1` | Forces the `ERR_MAPPING_NOT_APPLIED` `suggestedCall` back to the pre-workspace shape (`{ target, mapping: "obfuscated" }` with the legacy scope flip on `vanilla`+`mojang`). Use when an integration relies on the legacy retry payload. | `tests/source-service/mapping-not-applied-fallback.test.ts` (`buildMappingFallbackSuggestedCall returns the legacy obfuscated retry when WORKSPACE_FALLBACK_LEGACY is set`) |
|
|
401
|
+
| `VALIDATE_PROJECT_TASKS_OFF=1` | Omits the additive `tasks` per-probe status report from `validate-project task="project-summary"` results. The headline `result.summary.status`, `result.project`, and `result.workspace` blocks are unchanged. Use as a rollback path while the per-probe contract stabilizes. | `tests/entry-tools/validate-project/validate-project-tasks.test.ts` (`validate-project tasks A5: VALIDATE_PROJECT_TASKS_OFF=1 omits the tasks field`) |
|
|
402
|
+
| `MEMBERS_STATUS_LEGACY=1` | Omits the additive `status` / `unavailableReason` / `suggestedCall` fields from `get-class-members` results. Restores the pre-status response shape for callers that pre-date the new enum. | `tests/source-service/get-class-members-status.test.ts` (`B7: MEMBERS_STATUS_LEGACY=1 strips the new fields`) |
|
|
403
|
+
| `VERIFY_MIXIN_TARGET_OFF=1` | Removes `verify-mixin-target` from `tools/list` and rejects direct invocations with `ERR_INVALID_INPUT`. Use as a rollback path while the accessor-inference rules stabilize. | `tests/entry-tools/verify-mixin-target/verify-mixin-target.test.ts` (`C11: VERIFY_MIXIN_TARGET_OFF=1 hides the tool from tools/list and rejects direct calls`) |
|
|
404
|
+
| `SUGGESTED_CALL_VALIDATE_OFF=1` | Bypasses the `ProblemDetails.suggestedCall` schema validation gate. Raw caller-supplied payloads are emitted unchanged (matching the pre-gate behaviour); `error.hints` does not gain the fallback line. Use only as an emergency rollback if the gate causes unexpected drops in production. | `tests/contracts/build-suggested-call.test.ts` (`D11: SUGGESTED_CALL_VALIDATE_OFF=1 bypasses validation`) |
|
|
405
|
+
| `BATCH_TOOLS_OFF=1` | Removes the 4 batch lookup tools (`batch-class-source`, `batch-class-members`, `batch-symbol-exists`, `batch-mappings`) from `tools/list`. Direct calls answer per era: legacy gets the successful `isError: true` "Tool not found" envelope, modern gets a raw JSON-RPC `-32602` (no `ProblemDetails` in either shape; see `## MCP Protocol Support`). Use as an emergency rollback while the batch contract stabilizes. | `tests/manual/stdio-client-smoke.manual.ts` (`runBatchToolsOffProbe`); `tests/stdio/stdio-error-code-inventory.test.ts` |
|
|
371
406
|
|
|
372
407
|
|
|
373
408
|
## Migration Notes
|
|
@@ -445,6 +480,134 @@ JSON resources follow the same `result/error/meta` pattern. Text resources retur
|
|
|
445
480
|
|
|
446
481
|
The same JSON envelope is mirrored in MCP `structuredContent` for SDK-aware clients, and failures also set `isError=true`.
|
|
447
482
|
|
|
483
|
+
The application-level `meta` field above is distinct from protocol-level `_meta`. Modern-era protocol fields (`resultType`, `ttlMs`, `cacheScope`, and the `io.modelcontextprotocol/serverInfo` identity echo in result `_meta`) sit at the MCP protocol result level, outside the application envelope, and appear only for modern-era clients (see `## MCP Protocol Support`).
|
|
484
|
+
|
|
485
|
+
## MCP Protocol Support
|
|
486
|
+
|
|
487
|
+
The stdio server implements MCP protocol revision `2026-07-28` and keeps the legacy initialize-based protocol fully supported in the same process. One process serves exactly one era; the first valid era signal selects it.
|
|
488
|
+
|
|
489
|
+
### Era selection
|
|
490
|
+
|
|
491
|
+
- A process starts with no era selected.
|
|
492
|
+
- `initialize` selects the LEGACY era. Any `io.modelcontextprotocol/*` era-claim keys inside `initialize` `params._meta` are ignored for classification and stripped before the frame reaches the worker (other `_meta` keys pass through), so a hybrid client that attaches a modern claim to `initialize` still completes the legacy handshake.
|
|
493
|
+
- A request whose `params._meta` carries BOTH `io.modelcontextprotocol/protocolVersion` (string) and `io.modelcontextprotocol/clientCapabilities` (object) selects the MODERN era. Selection is shallow: the era signal is a shape check only. The version VALUE is validated by the SUPERVISOR on EVERY modern request, so an unsupported version string still locks modern and then answers `-32022` — on that request and on every later one, regardless of which method pinned the connection.
|
|
494
|
+
- The lock is one-way and survives internal worker restarts. There is no era-switch method; switching eras requires a fresh process (see the recovery sequence below).
|
|
495
|
+
- `server/discover` is era-neutral: it never selects an era. Per-state outcomes are in the rejection table.
|
|
496
|
+
- An ordinary request received before any era signal is rejected with `-32602` `data.kind: "missing_meta"`. A notification received before any era signal is consumed without a response and without forwarding.
|
|
497
|
+
|
|
498
|
+
### Legacy era (initialize handshake)
|
|
499
|
+
|
|
500
|
+
Supported protocol versions: `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, `2024-10-07`. Each is echoed verbatim by `initialize`. Any other requested version negotiates down to `2025-11-25`.
|
|
501
|
+
|
|
502
|
+
The supervisor caches the completed `initialize` / `notifications/initialized` pair and replays it into a replacement worker before queued requests are released; replay failure is a startup failure (see `ERR_WORKER_RESTART` in `## Errors`).
|
|
503
|
+
|
|
504
|
+
The legacy wire contract is byte-compatible with the pre-migration (SDK v1) server, with these recorded exceptions:
|
|
505
|
+
|
|
506
|
+
- `tools/call` with `arguments` omitted reaches the application validator as `{}`. Three outcome arms: (a) input-free tools succeed (`get-runtime-metrics`); (b) required-field schemas answer per-field `ERR_INVALID_INPUT` (`analyze-mod`); (c) a `{}`-accepting schema whose handler requires input answers that tool's own handler-level ProblemDetails — `json-to-nbt` answers `ERR_NBT_INVALID_TYPED_JSON` (status 400, `isError: true`) instead of the pre-migration validation-layer `ERR_INVALID_INPUT`.
|
|
507
|
+
- Legacy `tools/list` entries omit the v1-only `execution: {"taskSupport":"forbidden"}` field (SDK v2 does not emit it); every other advertised field, including the `inputSchema` bytes, is identical to the pre-migration snapshots.
|
|
508
|
+
- An unknown or disabled tool is answered immediately at the supervisor, before queueing: the reply bytes are identical to v1, but the reply consumes no queue slot and no worker round-trip, so reply ordering and queue-overflow outcomes can differ from v1 under concurrent load.
|
|
509
|
+
- The unmatched-resource-URI error keeps the raw JSON-RPC `-32602` code, but its message changed from the pre-migration `MCP error -32602: Resource <uri> not found` to `Resource not found: <uri>`, and the error carries a `data.uri` field (SDK v2 wording).
|
|
510
|
+
- The `initialize` result advertises `resources: { listChanged: false }` and `tools: { listChanged: false }`, where the pre-migration server advertised `true` for both. The suppression is deliberate and is NOT era-gated — both eras read one `getCapabilities()`, so gating it would leave one process advertising two different contracts; the reasoning is under the modern-era capability paragraph below. Every other byte of the `initialize` result is unchanged.
|
|
511
|
+
|
|
512
|
+
### Modern era (2026-07-28, stateless)
|
|
513
|
+
|
|
514
|
+
Modern clients never send `initialize`. Every request carries `params._meta`:
|
|
515
|
+
|
|
516
|
+
| `_meta` key | Requirement | Value |
|
|
517
|
+
| --- | --- | --- |
|
|
518
|
+
| `io.modelcontextprotocol/protocolVersion` | required | `"2026-07-28"` |
|
|
519
|
+
| `io.modelcontextprotocol/clientCapabilities` | required | object (for example `{}`) |
|
|
520
|
+
| `io.modelcontextprotocol/clientInfo` | optional | `{ name, version }` |
|
|
521
|
+
|
|
522
|
+
Recommended bootstrap probe (era-neutral — it never locks an era; per-state outcomes are under the rejection table):
|
|
523
|
+
|
|
524
|
+
```json
|
|
525
|
+
{
|
|
526
|
+
"jsonrpc": "2.0",
|
|
527
|
+
"id": 1,
|
|
528
|
+
"method": "server/discover",
|
|
529
|
+
"params": {
|
|
530
|
+
"_meta": {
|
|
531
|
+
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
|
532
|
+
"io.modelcontextprotocol/clientCapabilities": {}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The discover result carries `supportedVersions: ["2026-07-28"]` (the modern per-request set — the legacy matrix is advertised through `initialize` negotiation, not through discover), `capabilities`, the server identity in `result._meta["io.modelcontextprotocol/serverInfo"]`, `resultType`, and the cache fields. `capabilities` advertises `resources: { listChanged: false }` and `tools: { listChanged: false }`, in that key order, and the legacy `initialize` result advertises the same payload — the two eras never disagree. The `false` is a deliberate suppression, not an SDK default: the SDK advertises `listChanged: true` for any registered tool or resource surface unless the server overrides it, and this server never emits `notifications/tools/list_changed` or `notifications/resources/list_changed` — the tool and resource surface is fixed at process start by environment flags — and there is no way to subscribe to those notifications either (see `subscriptions/listen` below). Treat both lists as static for the lifetime of the process; re-read them only across a restart.
|
|
539
|
+
|
|
540
|
+
Probe fallback rule for clients: fall back to the legacy `initialize` handshake on ANY unrecognized probe error or on a probe timeout — never key the fallback to one specific error code. Back-to-back pipelining is safe: a modern-claim `server/discover` immediately followed by `initialize` in one stdin chunk is admitted atomically in stdin order — the discover is answered with a DiscoverResult, the initialize negotiates, and the process ends legacy-locked. One narrow exception: when the worker is down (restarting) as that pair arrives, the replayed `initialize` pins the replacement worker first and the queued discover is answered `-32601`; the any-error fallback rule covers this case.
|
|
541
|
+
|
|
542
|
+
Every modern result — tool calls, resource reads, list methods, `server/discover`, and supervisor-synthesized results — carries `resultType: "complete"` and echoes the same server identity as discover in `result._meta["io.modelcontextprotocol/serverInfo"]`. `input_required` is never emitted. Raw JSON-RPC error responses never carry result-only fields.
|
|
543
|
+
|
|
544
|
+
Absent modern surfaces: `prompts/list` is not advertised and answers `-32601`. `ping`, `logging/setLevel`, `tasks/list`, and `tasks/get` answer `-32601` in the modern era; legacy `ping` keeps its automatic `{}` pong. `subscriptions/listen` is intentionally absent in BOTH eras: no subscription capability is advertised — which is why `listChanged` is advertised as `false` above rather than `true` — the worker runs with `maxSubscriptions: 0`, the rejection is `-32601`, and it is non-retryable: there is no live update stream to wait for, and nothing would be pushed onto one. The server never sends `notifications/message`; logging goes to stderr.
|
|
545
|
+
|
|
546
|
+
### Cache metadata (modern era only)
|
|
547
|
+
|
|
548
|
+
Both `ttlMs` and `cacheScope` are returned by exactly these cacheable methods: `tools/list`, `resources/list`, `resources/read`, `resources/templates/list`, `server/discover`, and `prompts/list` were it exposed (it is not). This is the protocol's five-method cacheable list plus `server/discover` (SDK/schema default row). Values:
|
|
549
|
+
|
|
550
|
+
| Surface | `cacheScope` | `ttlMs` |
|
|
551
|
+
| --- | --- | ---: |
|
|
552
|
+
| `tools/list` | `private` | `0` |
|
|
553
|
+
| `server/discover` | `private` | `0` |
|
|
554
|
+
| `resources/list`, `resources/templates/list` | `private` | `3600000` |
|
|
555
|
+
| `mc://versions/list` read | `private` | `300000` |
|
|
556
|
+
| `mc://metrics` read | `private` | `0` |
|
|
557
|
+
| class source, artifact, mapping, member, and artifact-metadata reads | `private` | `60000` |
|
|
558
|
+
| any successful read whose content is a ProblemDetails failure envelope | `private` | `0` |
|
|
559
|
+
|
|
560
|
+
The ProblemDetails row takes precedence over the resource-class rows; otherwise an exact-URI row beats a class row. Legacy responses carry no cache fields, no `resultType`, and no protocol `_meta` identity echo.
|
|
561
|
+
|
|
562
|
+
### Version negotiation
|
|
563
|
+
|
|
564
|
+
| Client sends | Outcome |
|
|
565
|
+
| --- | --- |
|
|
566
|
+
| `initialize` with `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, or `2024-10-07` | verbatim echo; legacy era |
|
|
567
|
+
| `initialize` with any other version string | negotiates down to `2025-11-25`; legacy era |
|
|
568
|
+
| modern `_meta` with `protocolVersion: "2026-07-28"` | served; modern era |
|
|
569
|
+
| modern `_meta` with any other version string | LOCKS modern, then answers `-32022` with `data: { supported: ["2026-07-28"], requested }` — retry with a version from `data.supported`; do NOT fall back to `initialize` (the process is already modern-locked and would answer `-32601` `era_conflict`) |
|
|
570
|
+
|
|
571
|
+
### Rejection and error table
|
|
572
|
+
|
|
573
|
+
`-32602` reaches clients in distinct shapes. Branch on `error.data.kind` when present; the remaining shapes are distinguished by message family (most are data-less; the resource-miss row carries `data.uri`):
|
|
574
|
+
|
|
575
|
+
| Producer | State / trigger | Method scope | Code | Discriminator |
|
|
576
|
+
| --- | --- | --- | --- | --- |
|
|
577
|
+
| supervisor | no era selected; request without a valid era signal | any non-`server/discover` request | `-32602` | `data.kind: "missing_meta"` with `data.missing[]` (plus `data.invalid[]` for wrong-typed keys); the message names both recovery paths |
|
|
578
|
+
| supervisor | modern-locked; claim-less request | any request, including `server/discover` | `-32602` | `data.kind: "missing_meta"`; the message names the required keys |
|
|
579
|
+
| worker (SDK) | modern-locked; shallow-valid claim with a deep-invalid `_meta` value | forwarded requests | `-32602` | data-less; message starts `Invalid _meta envelope for protocol revision 2026-07-28:` (pinned example: `Invalid _meta envelope for protocol revision 2026-07-28: Invalid input: expected object, received number`) |
|
|
580
|
+
| worker (SDK) | params-schema violation, for example non-object `tools/call` `arguments` | both eras | `-32602` | data-less; message starts `Invalid tools/call request:`; the request never reaches the application validator |
|
|
581
|
+
| worker (SDK) | unmatched resource URI | `resources/read`, both eras | `-32602` | message `Resource not found: <uri>` with `data.uri` (the pre-migration server answered `MCP error -32602: Resource <uri> not found` without `data` — code unchanged, message family changed) |
|
|
582
|
+
| worker (SDK) | unknown or disabled tool, MODERN era | `tools/call` | `-32602` | raw JSON-RPC error (sanctioned modern contract; the modern era never had a v1 contract here) |
|
|
583
|
+
| supervisor | unknown or disabled tool, LEGACY era | `tools/call` | none | SUCCESSFUL `CallToolResult` with `isError: true`, text `MCP error -32602: Tool <name> not found`, and NO `structuredContent` key — the missing `structuredContent` is the discriminator against a genuine tool failure |
|
|
584
|
+
| supervisor | `initialize` that is not a valid MCP initialize request | `initialize` | `-32602` | `data.kind: "invalid_initialize"` with `data.required[]` and `data.eraSelected: false` — the era is NOT locked, so the client may retry a well-formed `initialize` or select the modern era instead |
|
|
585
|
+
| supervisor | modern-locked; `initialize` arrives | `initialize` | `-32601` | `data.kind: "era_conflict"`, `selectedEra: "modern"`, `requestedEra: "legacy"`, `supported[]` = all six versions |
|
|
586
|
+
| supervisor | legacy-locked; modern-claim request arrives | any modern-claim request | `-32600` | `data.kind: "era_conflict"`, `selectedEra: "legacy"`, `requestedEra: "modern"` |
|
|
587
|
+
| supervisor / worker | `subscriptions/listen` | both eras | `-32601` | plain `Method not found` (no data). Non-legacy states reject at supervisor admission; a modern-signal listen still era-locks first, and a claim-less listen in non-legacy states fails the envelope check with `-32602` before the method rejection |
|
|
588
|
+
| supervisor | modern request with an unsupported `protocolVersion` value | every modern request | `-32022` | `data.supported: ["2026-07-28"]`, `data.requested` |
|
|
589
|
+
| supervisor | queue overflow, non-`tools/call` request | requests | `-32000` | message `MCP supervisor request queue is full.` (`tools/call` overflow receives the `ERR_LIMIT_EXCEEDED` `isError` result instead — see `## Meta fields`) |
|
|
590
|
+
|
|
591
|
+
`server/discover` per state: a valid modern-claim discover never selects or changes the era. It receives a DiscoverResult in the unselected and modern-locked states; in the legacy-locked state it is forwarded to the legacy-pinned worker, which answers `-32601` (the probe's any-error fallback rule covers this). A claim-less discover: unselected → `-32602` `missing_meta`; modern-locked → `-32602` `missing_meta`; legacy-locked → forwarded → `-32601`.
|
|
592
|
+
|
|
593
|
+
Era-conflict messages embed a launcher-neutral recovery sequence: close this transport, terminate and respawn the configured server command as a fresh stdio process, discard or re-issue any pending request ids, then perform the wanted era's opening (`initialize` + `notifications/initialized`, or a request carrying the required `io.modelcontextprotocol/*` `_meta` envelope).
|
|
594
|
+
|
|
595
|
+
Notification variants: a modern-era notification without a valid claim (for example a claim-less `notifications/cancelled`) is never forwarded to the worker; it is dropped with a logged `supervisor.notification_dropped` warning and, like every notification, receives no response. Supervisor-side bookkeeping still applies: a claim-less cancellation still cancels ANY tracked in-flight request (not only `validate-project`), releasing its pending slot, its deadline timer, and the `validate-project` barrier if it held one, and the eventual worker response is suppressed per MCP cancellation semantics via the ordinary response-finality tombstone. An `initialize` in flight is the one exception — its handshake lifecycle owns that entry.
|
|
596
|
+
|
|
597
|
+
### Synthetic terminal responses and retry semantics
|
|
598
|
+
|
|
599
|
+
Supervisor-synthesized replies (queue overflow, worker restart, `validate-project` timeout, startup failure — shapes in `## Errors` and `## Meta fields`) are FINAL for that request instance: a late worker answer for the same id is discarded, so a client sees exactly one response per id. Retrying with the SAME id after a synthetic terminal reply is legal — the retry re-forwards and clears the finality bookkeeping. Era rejections synthesize no such finality (they answer without forwarding), and dropped notifications receive no response at all. Modern-era synthetic results are decorated like ordinary modern results (`resultType: "complete"` plus the identity `_meta` echo); legacy synthetic results remain byte-compatible with the pre-migration shapes.
|
|
600
|
+
|
|
601
|
+
### Framing
|
|
602
|
+
|
|
603
|
+
Standard framing is newline-delimited JSON-RPC (the MCP stdio standard). `Content-Length` header framing is a LOCAL, NONSTANDARD compatibility extension — it is not part of MCP. The reader auto-detects both and may switch mid-stream; every response, including synthetic replies and replies released after a worker restart, uses the framing of the request it answers. `MCP_MAX_FRAME_BYTES` bounds accepted frames (see `## Environment Variables`).
|
|
604
|
+
|
|
605
|
+
### Adopted-policy note
|
|
606
|
+
|
|
607
|
+
The 2026-07-28 revision does not mandate specific mixed-era conflict rules or unselected-state behavior for one stdio process. The era-conflict codes, `missing_meta` rejections, one-way lock, and discover neutrality documented here are this server's adopted policy, pinned by the committed wire suites (`tests/stdio/stdio-supervisor-era-state.test.ts`, `tests/stdio/stdio-supervisor-era-wire.test.ts`, `tests/stdio/stdio-supervisor-era-lifecycle.test.ts`). If a future protocol revision defines normative rules for these cases, those rules supersede this policy.
|
|
608
|
+
|
|
609
|
+
Tooling note: `pnpm test:manual:stdio-smoke` runs against the production supervisor by default; its direct-worker bridge fallback mode bypasses the supervisor and therefore observes the worker-level raw `-32602` for disabled tools instead of the restored legacy `isError` envelope.
|
|
610
|
+
|
|
448
611
|
## Mapping Policy
|
|
449
612
|
|
|
450
613
|
### Namespace Definitions
|
|
@@ -495,6 +658,12 @@ Method descriptor precision is best on Tiny-backed paths (`intermediary` and `ya
|
|
|
495
658
|
|
|
496
659
|
Use `resolve-method-mapping-exact` when candidate ranking is not enough and the workflow needs strict `owner + name + descriptor` certainty. It is a strict shortcut for `find-mapping` (kind=method, signatureMode=exact) that additionally requires a **complete** descriptor projection — it returns `mapping_unavailable` when the descriptor's class references cannot all be projected to the target namespace, whereas `find-mapping`'s exact mode resolves those leniently. Prefer `find-mapping kind=method signatureMode=exact` unless you specifically need that strict-completeness guarantee.
|
|
497
660
|
|
|
661
|
+
`resolve-method-mapping-exact` is **owner-strict**: the strict set is the candidates matching the query's full advertised triple `owner + name + descriptor`. The owner is compared after projecting it along the same mapping path the candidates travelled, so it is checked in the target namespace, not as a raw string. A same-signature method on a different class is therefore rejected rather than turned into an ambiguous verdict. The cost is that a method the owner INHERITS rather than declares now returns `not_found`: the mapping formats record declarations and carry no class hierarchy. That case is never silent — the result carries a warning naming the owner, the classes that do declare the member, and `find-mapping` as the owner-agnostic lookup.
|
|
662
|
+
|
|
663
|
+
`resolve-method-mapping-exact` reports the candidate set its verdict was computed FROM, on every status. `candidates` and `candidateCount` are the strict matches — never the wider name-matched list, which previously put a `confidence: 1, matchKind: "exact"` candidate the strict filter had already rejected next to an ambiguous verdict with nothing to distinguish it. On `status: "resolved"` that set is the single match, so `candidates` duplicates `resolvedSymbol` and the default response projection omits it, leaving `resolvedSymbol` and `candidateCount: 1`. Candidates the strict filter dropped are accounted for in `warnings` as counts BY REASON (how many by owner, how many by descriptor); use `find-mapping` when you want the unfiltered list. An ambiguous verdict also carries `ambiguityReasons[]`, the same field `find-mapping` populates. The tool never picks a winner: `status: "resolved"` means exactly one strict match existed.
|
|
664
|
+
|
|
665
|
+
`resolve-workspace-symbol` delegates its `kind: "method"` lookups to `resolve-method-mapping-exact` and spreads the result, so its method responses carry the same owner-strict semantics and the same `ambiguityReasons[]` on `status: "ambiguous"`. The field is present only on that status and always holds at least one reason, so it never serializes as an empty array.
|
|
666
|
+
|
|
498
667
|
Use `find-mapping` `disambiguation.ownerHint` and `disambiguation.descriptorHint` to narrow ambiguous candidate sets.
|
|
499
668
|
|
|
500
669
|
Use `resolve-workspace-symbol` when you need compile-visible names from actual Gradle Loom mappings in a workspace.
|
|
@@ -511,6 +680,8 @@ Path-based overrides treat blank values and the literal strings `undefined` and
|
|
|
511
680
|
| --- | --- | --- |
|
|
512
681
|
| `MCP_CACHE_DIR` | `~/.cache/minecraft-modding-mcp` | Cache root for downloads and SQLite |
|
|
513
682
|
| `MCP_SQLITE_PATH` | `<cacheDir>/source-cache.db` | SQLite database path |
|
|
683
|
+
| `MCP_SQLITE_CACHE_KB` | `8000` | SQLite page-cache size in KiB (applied as a negative `cache_size` pragma) |
|
|
684
|
+
| `MCP_SQLITE_MMAP_SIZE` | `268435456` | SQLite `mmap_size` in bytes; `0` disables memory-mapped I/O |
|
|
514
685
|
| `MCP_SOURCE_REPOS` | Maven Central + Fabric + Forge + NeoForge | Comma-separated Maven repository URLs |
|
|
515
686
|
| `MCP_LOCAL_M2` | `~/.m2/repository` | Local Maven repository path |
|
|
516
687
|
| `MCP_ENABLE_INDEXED_SEARCH` | `true` | Enable indexed query path for `search-class-source` |
|
|
@@ -524,6 +695,8 @@ Path-based overrides treat blank values and the literal strings `undefined` and
|
|
|
524
695
|
| `MCP_MAX_CONTENT_BYTES` | `1000000` | Maximum bytes for file read operations |
|
|
525
696
|
| `MCP_MAX_SEARCH_HITS` | `200` | Maximum search result count |
|
|
526
697
|
| `MCP_SEARCH_SCAN_PAGE_SIZE` | `250` | Page size used by literal scan fallbacks |
|
|
698
|
+
| `MCP_SEARCH_SCAN_MAX_BYTES` | `67108864` | Maximum bytes read by literal scan fallbacks before the scan stops |
|
|
699
|
+
| `MCP_LOOM_TINY_MAX_INDEX_ENTRIES` | derived from the live V8 heap limit | Hard cap on index slots one Loom `.tiny` load may accumulate before it stops and warns instead of exhausting the heap. Unset, the budget is derived from free heap (so raising `--max-old-space-size` raises it automatically) and clamped to 1,000,000–64,000,000 slots. |
|
|
527
700
|
| `MCP_INDEX_INSERT_CHUNK_SIZE` | `200` | Batch size for SQLite index inserts |
|
|
528
701
|
| `MCP_MAX_ARTIFACTS` | `200` | Maximum cached artifacts |
|
|
529
702
|
| `MCP_MAX_CACHE_BYTES` | `2147483648` | Maximum total cache size in bytes |
|
|
@@ -535,8 +708,9 @@ Path-based overrides treat blank values and the literal strings `undefined` and
|
|
|
535
708
|
|
|
536
709
|
| Variable | Default | Description |
|
|
537
710
|
| --- | --- | --- |
|
|
538
|
-
| `MCP_FETCH_TIMEOUT_MS` | `15000` | HTTP request timeout in milliseconds |
|
|
711
|
+
| `MCP_FETCH_TIMEOUT_MS` | `15000` | HTTP request timeout in milliseconds (also bounds version-manifest fetches) |
|
|
539
712
|
| `MCP_FETCH_RETRIES` | `2` | HTTP request retry count |
|
|
713
|
+
| `MCP_MAX_FRAME_BYTES` | `67108864` | Maximum accepted JSON-RPC frame size in bytes for the stdio supervisor and worker transport (clamped to at least 1 MiB); oversized frames are rejected with a diagnostic and skipped |
|
|
540
714
|
|
|
541
715
|
### Decompilation and Remapping
|
|
542
716
|
|
|
@@ -548,6 +722,7 @@ Path-based overrides treat blank values and the literal strings `undefined` and
|
|
|
548
722
|
| `MCP_TINY_REMAPPER_VERSION` | `0.10.3` | tiny-remapper version to auto-download when no JAR path override is set |
|
|
549
723
|
| `MCP_REMAP_TIMEOUT_MS` | `600000` | Remap operation timeout in milliseconds |
|
|
550
724
|
| `MCP_REMAP_MAX_MEMORY_MB` | `4096` | Maximum JVM heap for remap operations |
|
|
725
|
+
| `MCP_DECOMPILE_MAX_MEMORY_MB` | `4096` | Maximum JVM heap for Vineflower decompile operations |
|
|
551
726
|
|
|
552
727
|
### NBT Limits
|
|
553
728
|
|
|
@@ -570,8 +745,8 @@ Internal worker-mode environment variables are reserved for the transport implem
|
|
|
570
745
|
|
|
571
746
|
| Component | Technology |
|
|
572
747
|
| --- | --- |
|
|
573
|
-
| Runtime | Node.js 22+ |
|
|
574
|
-
| Transport | stdio
|
|
748
|
+
| Runtime | Node.js 22.13.0+ |
|
|
749
|
+
| Transport | stdio, MCP protocol revision `2026-07-28` plus the legacy initialize protocol (dual-era, see `## MCP Protocol Support`); newline framing standard, `Content-Length` as a local extension |
|
|
575
750
|
| Storage | SQLite for artifact metadata, source indexing, and cache bookkeeping |
|
|
576
751
|
| Decompilation | [Vineflower](https://github.com/Vineflower/vineflower) |
|
|
577
752
|
| Remapping | [tiny-remapper](https://github.com/FabricMC/tiny-remapper) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhisang/minecraft-modding-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-rc.0",
|
|
4
4
|
"description": "MCP server for AI-assisted Minecraft modding: inspect decompiled source, resolve Mojang/Yarn/Intermediary mappings, diff versions, analyze Fabric/Forge/NeoForge mod JARs, and validate Mixin, Access Widener, and Access Transformer files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"README.md",
|
|
15
15
|
"LICENSE",
|
|
16
16
|
"CHANGELOG.md",
|
|
17
|
-
"docs
|
|
17
|
+
"docs/README-ja.md",
|
|
18
|
+
"docs/examples.md",
|
|
19
|
+
"docs/tool-reference.md"
|
|
18
20
|
],
|
|
19
21
|
"scripts": {
|
|
20
22
|
"dev": "tsx src/cli.ts",
|
|
@@ -61,16 +63,17 @@
|
|
|
61
63
|
"bugs": "https://github.com/adhi-jp/minecraft-modding-mcp/issues",
|
|
62
64
|
"homepage": "https://github.com/adhi-jp/minecraft-modding-mcp#readme",
|
|
63
65
|
"engines": {
|
|
64
|
-
"node": ">=22"
|
|
66
|
+
"node": ">=22.13.0"
|
|
65
67
|
},
|
|
66
68
|
"dependencies": {
|
|
67
|
-
"@modelcontextprotocol/
|
|
69
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
68
70
|
"fast-glob": "^3.3.3",
|
|
69
71
|
"smol-toml": "^1.3.0",
|
|
70
72
|
"yauzl": "^3.2.0",
|
|
71
|
-
"zod": "
|
|
73
|
+
"zod": "4.4.3"
|
|
72
74
|
},
|
|
73
75
|
"devDependencies": {
|
|
76
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
74
77
|
"@types/node": "^22.13.5",
|
|
75
78
|
"@types/yauzl": "^2.10.3",
|
|
76
79
|
"tsx": "^4.19.2",
|