@adhisang/minecraft-modding-mcp 6.1.1 → 6.3.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 +40 -1
- package/README.md +12 -4
- package/dist/cache-registry.d.ts +1 -1
- package/dist/cache-registry.js +3 -0
- package/dist/entry-tools/analyze-mod-service.d.ts +12 -6
- package/dist/entry-tools/analyze-mod-service.js +37 -3
- package/dist/entry-tools/analyze-symbol-service.d.ts +6 -4
- package/dist/entry-tools/analyze-symbol-service.js +37 -2
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +7 -3
- package/dist/entry-tools/inspect-minecraft/internal.js +43 -15
- package/dist/entry-tools/inspect-minecraft-service.d.ts +12 -12
- package/dist/entry-tools/inspect-minecraft-service.js +1 -1
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/error-mapping.d.ts +13 -0
- package/dist/error-mapping.js +35 -2
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/index.js +39 -20
- package/dist/mapping/internal-types.d.ts +7 -0
- package/dist/mapping/types.d.ts +18 -0
- package/dist/mapping-service.js +16 -3
- package/dist/minecraft-explorer-service.d.ts +4 -0
- package/dist/minecraft-explorer-service.js +156 -17
- package/dist/mod-analyzer.d.ts +7 -0
- package/dist/mod-analyzer.js +28 -7
- package/dist/source/artifact-resolver.d.ts +2 -0
- package/dist/source/artifact-resolver.js +24 -1
- package/dist/source/class-source/members-builder.d.ts +4 -0
- package/dist/source/class-source/members-builder.js +3 -1
- package/dist/source/class-source.d.ts +3 -1
- package/dist/source/class-source.js +192 -19
- package/dist/source/did-you-mean.d.ts +14 -0
- package/dist/source/did-you-mean.js +79 -0
- package/dist/source/file-access.js +159 -3
- package/dist/source/indexer.js +72 -2
- package/dist/source/lifecycle/runtime-check.js +9 -5
- package/dist/source/nested-jars.d.ts +78 -0
- package/dist/source/nested-jars.js +267 -0
- package/dist/source/workspace-target.js +5 -2
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +82 -0
- package/dist/source-service.d.ts +37 -0
- package/dist/source-service.js +52 -6
- package/dist/stage-emitter.js +24 -8
- package/dist/stdio-supervisor.d.ts +92 -9
- package/dist/stdio-supervisor.js +915 -103
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-guidance.js +115 -7
- package/dist/tool-schemas.d.ts +1343 -149
- package/dist/tool-schemas.js +39 -7
- package/dist/types.d.ts +23 -0
- package/dist/workspace-mapping-service.d.ts +1 -0
- package/dist/workspace-mapping-service.js +120 -8
- package/docs/README-ja.md +4 -0
- package/docs/tool-reference.md +92 -6
- package/package.json +5 -5
package/dist/tool-schemas.js
CHANGED
|
@@ -161,6 +161,7 @@ export const getClassMembersShape = {
|
|
|
161
161
|
access: memberAccessSchema.default("public"),
|
|
162
162
|
includeSynthetic: z.boolean().default(false),
|
|
163
163
|
includeInherited: z.boolean().default(false),
|
|
164
|
+
includeAnnotations: z.boolean().default(false).describe("Opt-in: include runtime-visible member annotations (e.g. @Deprecated) on each member."),
|
|
164
165
|
memberPattern: optionalNonEmptyString.describe(MEMBER_PATTERN_DESCRIPTION),
|
|
165
166
|
projection: memberProjectionSchema.optional().describe(MEMBER_PROJECTION_DESCRIPTION),
|
|
166
167
|
maxMembers: optionalPositiveInt.describe("default 150, max 5000. Page beyond the first 150 with cursor."),
|
|
@@ -348,8 +349,35 @@ export const batchMappingsShape = {
|
|
|
348
349
|
entries: z.array(batchMappingsEntrySchema).min(1).max(50)
|
|
349
350
|
};
|
|
350
351
|
export const batchMappingsSchema = z.object(batchMappingsShape);
|
|
352
|
+
// Exactly one of artifactId/target addresses the artifact on flat tools.
|
|
353
|
+
// `target` is additive: existing flat-artifactId calls stay valid.
|
|
354
|
+
function requireExactlyOneArtifactRef(value, ctx) {
|
|
355
|
+
if (value.artifactId && value.target) {
|
|
356
|
+
ctx.addIssue({
|
|
357
|
+
code: z.ZodIssueCode.custom,
|
|
358
|
+
path: ["target"],
|
|
359
|
+
message: "artifactId and target are mutually exclusive."
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (!value.artifactId && !value.target) {
|
|
363
|
+
ctx.addIssue({
|
|
364
|
+
code: z.ZodIssueCode.custom,
|
|
365
|
+
path: ["artifactId"],
|
|
366
|
+
message: "Either artifactId or target must be provided."
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export const findClassShape = {
|
|
371
|
+
className: nonEmptyString.describe("Simple name (e.g. Blocks) or fully-qualified name (e.g. net.minecraft.world.level.block.Blocks)"),
|
|
372
|
+
artifactId: optionalNonEmptyString,
|
|
373
|
+
target: sourceLookupTargetSchema.optional().describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
374
|
+
projectPath: optionalNonEmptyString.describe("Workspace root for dependency or workspace target resolution."),
|
|
375
|
+
limit: optionalPositiveInt.describe("default 20, max 200")
|
|
376
|
+
};
|
|
377
|
+
export const findClassSchema = z.object(findClassShape).superRefine(requireExactlyOneArtifactRef);
|
|
351
378
|
export const searchClassSourceShape = {
|
|
352
|
-
artifactId:
|
|
379
|
+
artifactId: optionalNonEmptyString,
|
|
380
|
+
target: sourceLookupTargetSchema.optional().describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
353
381
|
query: nonEmptyString,
|
|
354
382
|
intent: searchIntentSchema.optional(),
|
|
355
383
|
match: searchMatchSchema.optional(),
|
|
@@ -366,6 +394,7 @@ export const searchClassSourceShape = {
|
|
|
366
394
|
include: responseIncludeParam
|
|
367
395
|
};
|
|
368
396
|
export const searchClassSourceSchema = z.object(searchClassSourceShape).superRefine((value, ctx) => {
|
|
397
|
+
requireExactlyOneArtifactRef(value, ctx);
|
|
369
398
|
if (value.symbolKind && value.intent && value.intent !== "symbol") {
|
|
370
399
|
ctx.addIssue({
|
|
371
400
|
code: z.ZodIssueCode.custom,
|
|
@@ -375,20 +404,22 @@ export const searchClassSourceSchema = z.object(searchClassSourceShape).superRef
|
|
|
375
404
|
}
|
|
376
405
|
});
|
|
377
406
|
export const getArtifactFileShape = {
|
|
378
|
-
artifactId:
|
|
407
|
+
artifactId: optionalNonEmptyString,
|
|
408
|
+
target: sourceLookupTargetSchema.optional().describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
379
409
|
filePath: nonEmptyString,
|
|
380
410
|
maxBytes: optionalPositiveInt
|
|
381
411
|
};
|
|
382
|
-
export const getArtifactFileSchema = z.object(getArtifactFileShape);
|
|
412
|
+
export const getArtifactFileSchema = z.object(getArtifactFileShape).superRefine(requireExactlyOneArtifactRef);
|
|
383
413
|
export const listArtifactFilesShape = {
|
|
384
|
-
artifactId:
|
|
414
|
+
artifactId: optionalNonEmptyString,
|
|
415
|
+
target: sourceLookupTargetSchema.optional().describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
385
416
|
prefix: optionalNonEmptyString,
|
|
386
417
|
limit: optionalPositiveInt,
|
|
387
418
|
cursor: optionalNonEmptyString,
|
|
388
419
|
detail: detailParam("standard"),
|
|
389
420
|
include: responseIncludeParam
|
|
390
421
|
};
|
|
391
|
-
export const listArtifactFilesSchema = z.object(listArtifactFilesShape);
|
|
422
|
+
export const listArtifactFilesSchema = z.object(listArtifactFilesShape).superRefine(requireExactlyOneArtifactRef);
|
|
392
423
|
export const traceSymbolLifecycleShape = {
|
|
393
424
|
symbol: nonEmptyString.describe("fully.qualified.Class.method"),
|
|
394
425
|
descriptor: optionalDescriptorString.describe('optional JVM descriptor, e.g. "(I)V". Empty strings are treated as omitted.'),
|
|
@@ -719,10 +750,11 @@ export const jsonToNbtShape = {
|
|
|
719
750
|
};
|
|
720
751
|
export const jsonToNbtSchema = z.object(jsonToNbtShape);
|
|
721
752
|
export const indexArtifactShape = {
|
|
722
|
-
artifactId:
|
|
753
|
+
artifactId: optionalNonEmptyString,
|
|
754
|
+
target: sourceLookupTargetSchema.optional().describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
723
755
|
force: z.boolean().default(false)
|
|
724
756
|
};
|
|
725
|
-
export const indexArtifactSchema = z.object(indexArtifactShape);
|
|
757
|
+
export const indexArtifactSchema = z.object(indexArtifactShape).superRefine(requireExactlyOneArtifactRef);
|
|
726
758
|
export const validateMixinShape = {
|
|
727
759
|
input: z.discriminatedUnion("mode", [
|
|
728
760
|
z.object({
|
package/dist/types.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface DependencyResolutionProvenance {
|
|
|
60
60
|
candidatesSeen?: string[];
|
|
61
61
|
attempts?: string[];
|
|
62
62
|
cacheHit: boolean;
|
|
63
|
+
/** Set when a submodule version was adopted from the cached umbrella POM. */
|
|
64
|
+
submoduleVersionSource?: "umbrella-pom";
|
|
63
65
|
}
|
|
64
66
|
export interface ArtifactProvenance {
|
|
65
67
|
target: SourceTargetInput;
|
|
@@ -76,6 +78,27 @@ export interface ArtifactProvenance {
|
|
|
76
78
|
workspaceResolution?: WorkspaceResolutionProvenance;
|
|
77
79
|
dependencyResolution?: DependencyResolutionProvenance;
|
|
78
80
|
warnings?: string[];
|
|
81
|
+
/**
|
|
82
|
+
* In-archive paths of bundled Jar-in-Jar nested jars, recorded once at
|
|
83
|
+
* ingest for shell jars (near-zero own classes). Class-family lookups
|
|
84
|
+
* redirect into these instead of dead-ending in decompilation.
|
|
85
|
+
*/
|
|
86
|
+
nestedJars?: string[];
|
|
87
|
+
/**
|
|
88
|
+
* Set on responses that were served by redirecting a class lookup into a
|
|
89
|
+
* nested jar of a shell artifact.
|
|
90
|
+
*/
|
|
91
|
+
nestedJar?: {
|
|
92
|
+
entryName: string;
|
|
93
|
+
shellArtifactId: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Additional sources jars indexed into this artifact alongside
|
|
97
|
+
* resolvedFrom.sourceJarPath — the other half of a Loom split-source pair
|
|
98
|
+
* (minecraft-common / minecraft-clientOnly), so client-only classes are not
|
|
99
|
+
* lost to single-jar selection.
|
|
100
|
+
*/
|
|
101
|
+
companionSourceJars?: string[];
|
|
79
102
|
}
|
|
80
103
|
export interface RuntimeValidationProvenance<TMapping extends RuntimeValidationNamespace = RuntimeValidationNamespace> {
|
|
81
104
|
version: string;
|
|
@@ -59,21 +59,39 @@ function buildDependencyPropertyKeys(group, name) {
|
|
|
59
59
|
const camelName = camelCaseDependencyName(name);
|
|
60
60
|
const groupSegment = lastGroupSegment(group);
|
|
61
61
|
const camelGroupName = camelCaseDependencyName(`${groupSegment}_${name}`);
|
|
62
|
+
const snakeName = name.replace(/-/g, "_");
|
|
63
|
+
const snakeGroupSegment = groupSegment.replace(/-/g, "_");
|
|
64
|
+
// Hyphenated artifact names (e.g. fabric-api) are declared in
|
|
65
|
+
// gradle.properties as snake_case keys (fabric_api_version), so the base
|
|
66
|
+
// enumeration must probe the snake_case transforms too — including for the
|
|
67
|
+
// umbrella artifact itself, which never reaches the umbrella fallback below.
|
|
62
68
|
const keys = [
|
|
63
69
|
`${name}_version`,
|
|
70
|
+
`${snakeName}_version`,
|
|
64
71
|
`${camelName}Version`,
|
|
65
72
|
`${groupSegment}_${name}_version`,
|
|
73
|
+
`${snakeGroupSegment}_${snakeName}_version`,
|
|
66
74
|
`${camelGroupName}Version`
|
|
67
75
|
];
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
return dedupeKeys(keys);
|
|
77
|
+
}
|
|
78
|
+
// Umbrella properties (fabric_api_version / fabricApiVersion) declare the
|
|
79
|
+
// UMBRELLA package's version, not a submodule's. They are never adopted as a
|
|
80
|
+
// submodule version directly — the umbrella version differs from every
|
|
81
|
+
// submodule version (e.g. fabric-api 0.153.0+26.2 vs
|
|
82
|
+
// fabric-screen-handler-api-v1 2.0.5+...). They only locate the cached
|
|
83
|
+
// umbrella POM, which names the real per-submodule versions.
|
|
84
|
+
function buildUmbrellaPropertyKeys(group, name) {
|
|
85
|
+
const groupSegment = lastGroupSegment(group);
|
|
86
|
+
if (groupSegment === name) {
|
|
87
|
+
return [];
|
|
76
88
|
}
|
|
89
|
+
return dedupeKeys([
|
|
90
|
+
`${groupSegment.replace(/-/g, "_")}_version`,
|
|
91
|
+
`${camelCaseDependencyName(groupSegment)}Version`
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
function dedupeKeys(keys) {
|
|
77
95
|
const seen = new Set();
|
|
78
96
|
const deduped = [];
|
|
79
97
|
for (const key of keys) {
|
|
@@ -84,6 +102,89 @@ function buildDependencyPropertyKeys(group, name) {
|
|
|
84
102
|
}
|
|
85
103
|
return deduped;
|
|
86
104
|
}
|
|
105
|
+
function readPomDependencyVersion(pomContent, group, name) {
|
|
106
|
+
const blocks = pomContent.match(/<dependency>[\s\S]*?<\/dependency>/g) ?? [];
|
|
107
|
+
for (const block of blocks) {
|
|
108
|
+
const groupId = block.match(/<groupId>\s*([^<]+?)\s*<\/groupId>/)?.[1];
|
|
109
|
+
const artifactId = block.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
|
|
110
|
+
if (groupId !== group || artifactId !== name) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// A matching block without a literal <version> (e.g. a managed entry
|
|
114
|
+
// relying on inheritance) must not end the scan — a later block for the
|
|
115
|
+
// same artifact may carry the concrete version.
|
|
116
|
+
const version = block.match(/<version>\s*([^<]+?)\s*<\/version>/)?.[1];
|
|
117
|
+
if (version) {
|
|
118
|
+
return version;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
// When modules-2 caches several versions of a submodule, the cached umbrella
|
|
124
|
+
// POM (modules-2/files-2.1/<group>/<umbrella>/<version>/<hash>/<umbrella>-<version>.pom)
|
|
125
|
+
// is the only local evidence of which one the declared umbrella version maps
|
|
126
|
+
// to. Adoption is fail-closed: no umbrella property, no readable POM, no
|
|
127
|
+
// matching <dependency> entry, or a POM version absent from the cached
|
|
128
|
+
// candidates all leave the resolution unresolved instead of guessing.
|
|
129
|
+
async function adoptSubmoduleVersionFromUmbrellaPom(args) {
|
|
130
|
+
const { group, name, propsContent, candidates, candidatesSeen, attempts } = args;
|
|
131
|
+
const groupSegment = lastGroupSegment(group);
|
|
132
|
+
const umbrellaKeys = buildUmbrellaPropertyKeys(group, name);
|
|
133
|
+
if (umbrellaKeys.length === 0) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
let umbrellaVersion;
|
|
137
|
+
if (propsContent !== undefined) {
|
|
138
|
+
for (const key of umbrellaKeys) {
|
|
139
|
+
attempts.push(`umbrella-property:${key}`);
|
|
140
|
+
const value = readPropertyValue(propsContent, key);
|
|
141
|
+
if (value && isSafeMavenVersionToken(value)) {
|
|
142
|
+
umbrellaVersion = value;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!umbrellaVersion) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const umbrellaDir = resolve(resolveGradleUserHome(), "caches", "modules-2", "files-2.1", group, groupSegment, umbrellaVersion);
|
|
151
|
+
attempts.push(`umbrella-pom:${umbrellaDir}`);
|
|
152
|
+
let hashDirs = [];
|
|
153
|
+
try {
|
|
154
|
+
hashDirs = await readdir(umbrellaDir);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const pomFileName = `${groupSegment}-${umbrellaVersion}.pom`;
|
|
160
|
+
for (const hashDir of hashDirs) {
|
|
161
|
+
const pomPath = resolve(umbrellaDir, hashDir, pomFileName);
|
|
162
|
+
let pomContent;
|
|
163
|
+
try {
|
|
164
|
+
pomContent = await readFile(pomPath, "utf8");
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const version = readPomDependencyVersion(pomContent, group, name);
|
|
170
|
+
if (!version || !isSafeMavenVersionToken(version)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!candidates.includes(version)) {
|
|
174
|
+
attempts.push(`umbrella-pom:${pomPath}:names-uncached-version:${version}`);
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
resolved: true,
|
|
179
|
+
version,
|
|
180
|
+
source: `umbrella-pom:${pomPath}`,
|
|
181
|
+
candidatesSeen,
|
|
182
|
+
attempts,
|
|
183
|
+
submoduleVersionSource: "umbrella-pom"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
87
188
|
function readPropertyValue(content, key) {
|
|
88
189
|
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
89
190
|
const pattern = new RegExp(`^\\s*${escaped}\\s*=\\s*(.+?)\\s*$`, "m");
|
|
@@ -326,6 +427,17 @@ export class WorkspaceMappingService {
|
|
|
326
427
|
return { resolved: false, candidatesSeen, attempts };
|
|
327
428
|
}
|
|
328
429
|
if (sorted.length > 1) {
|
|
430
|
+
const pomAdoption = await adoptSubmoduleVersionFromUmbrellaPom({
|
|
431
|
+
group,
|
|
432
|
+
name,
|
|
433
|
+
propsContent,
|
|
434
|
+
candidates: sorted,
|
|
435
|
+
candidatesSeen,
|
|
436
|
+
attempts
|
|
437
|
+
});
|
|
438
|
+
if (pomAdoption) {
|
|
439
|
+
return pomAdoption;
|
|
440
|
+
}
|
|
329
441
|
return { resolved: false, candidatesSeen, attempts };
|
|
330
442
|
}
|
|
331
443
|
const chosen = sorted[0];
|
package/docs/README-ja.md
CHANGED
|
@@ -405,12 +405,16 @@ pnpm test
|
|
|
405
405
|
|
|
406
406
|
必要に応じて実行:
|
|
407
407
|
|
|
408
|
+
- `pnpm test:file <path...>`: 通常テストをファイル指定で絞り込む場合
|
|
409
|
+
- `pnpm test:grep <name-pattern>`: 通常テストを再帰的に集め、テスト名で絞り込む場合
|
|
408
410
|
- `pnpm test:manual:stdio-smoke`: MCP トランスポート、登録、手動ワークフローの変更時
|
|
409
411
|
- `pnpm test:manual:package-smoke`: パッケージインストールや配布形態の検証時
|
|
410
412
|
- `pnpm test:perf`: 検索、インデックス、性能に影響する変更時
|
|
411
413
|
- `pnpm test:coverage` または `pnpm test:coverage:lcov`: カバレッジ確認時(`lines=80`, `branches=70`, `functions=80`)
|
|
412
414
|
- `pnpm validate`: ローカルの完全検証スイートを実行する場合
|
|
413
415
|
|
|
416
|
+
通常の `.test.ts` ファイルは `tests/` 配下のドメイン別ディレクトリ(例: `source-service/`, `entry-tools/`, `mapping/`, `mixin/`, `integration/mcp-tools/`, `contracts/`, `utils/`)に置きます。ヘルパー専用モジュールは `tests/helpers` に残し、手動 smoke、性能、リソース、smoke 専用ファイルは既存の専用ディレクトリに残します。
|
|
417
|
+
|
|
414
418
|
## ライセンス
|
|
415
419
|
|
|
416
420
|
[MIT](../LICENSE)
|
package/docs/tool-reference.md
CHANGED
|
@@ -32,11 +32,13 @@ Start here when you are not sure which tool to reach for. In every row, the left
|
|
|
32
32
|
- Start with the top-level workflow tools when possible. `inspect-minecraft`, `analyze-symbol`, `compare-minecraft`, `analyze-mod`, `validate-project`, and `manage-cache` cover the common workflows and return summary-first results with follow-up hints.
|
|
33
33
|
- `resolve-artifact` uses `target: { kind, value }`. `kind` is one of `"version"`, `"jar"`, `"coordinate"`, `"workspace"`, or `"dependency"` (see "Workspace and dependency target shapes" below).
|
|
34
34
|
- `get-class-source` and `get-class-members` use `target: { kind, value }` — the same `kind`-based shape as `resolve-artifact` (`"version"`, `"jar"`, `"coordinate"`, `"workspace"`, `"dependency"`), plus `target: { kind: "artifact", artifactId }` to reuse an already-resolved artifact.
|
|
35
|
-
- `find-class`, `search-class-source`, `list-artifact-files`, and `
|
|
35
|
+
- `find-class`, `search-class-source`, `get-artifact-file`, `list-artifact-files`, and `index-artifact` accept either an `artifactId` or the shared object `target` shape. `find-class` also accepts top-level `projectPath`, which supplies the workspace context required by `target.kind="workspace"` and dependency targets using `versionFromProject`. For another flat tool whose target needs workspace context, resolve the artifact first and pass its `artifactId`.
|
|
36
36
|
- `validate-mixin` and `validate-project task="mixin"` use `input.mode="inline" | "path" | "paths" | "config" | "project"`.
|
|
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 the index has nothing usable. All same-simple-name FQNs are enumerated rather than collapsed.
|
|
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`.
|
|
40
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`.
|
|
41
43
|
- 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.
|
|
42
44
|
- `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.
|
|
@@ -58,6 +60,75 @@ Start here when you are not sure which tool to reach for. In every row, the left
|
|
|
58
60
|
- Heavy analysis tools are serialized in-process to protect stdio stability. Queue overflow returns `ERR_LIMIT_EXCEEDED`.
|
|
59
61
|
- All tools and JSON resources use the standard `{ result?, error?, meta }` envelope. `class-source` and `artifact-file` resources return raw text on success and structured JSON on failure.
|
|
60
62
|
|
|
63
|
+
## inspect-minecraft workspace focus
|
|
64
|
+
|
|
65
|
+
Use `subject.kind="workspace"` when `inspect-minecraft` should resolve Minecraft artifact context from a project. Its `focus` is a structured object, not a string:
|
|
66
|
+
|
|
67
|
+
| Focus shape | `task="auto"` dispatch | Explicit tasks |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `focus: { kind: "class", className: "..." }` | `class-overview` | `class-overview`, `class-source`, `class-members` |
|
|
70
|
+
| `focus: { kind: "search", query: "..." }` | `search` | `search` |
|
|
71
|
+
| `focus: { kind: "file", filePath: "..." }` | `file` | `file` |
|
|
72
|
+
|
|
73
|
+
`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
|
+
Class source from a workspace:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"tool": "inspect-minecraft",
|
|
80
|
+
"arguments": {
|
|
81
|
+
"task": "class-source",
|
|
82
|
+
"subject": {
|
|
83
|
+
"kind": "workspace",
|
|
84
|
+
"projectPath": "/path/to/workspace",
|
|
85
|
+
"focus": {
|
|
86
|
+
"kind": "class",
|
|
87
|
+
"className": "net.minecraft.world.item.Item"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Source search from a workspace:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"tool": "inspect-minecraft",
|
|
99
|
+
"arguments": {
|
|
100
|
+
"task": "auto",
|
|
101
|
+
"subject": {
|
|
102
|
+
"kind": "workspace",
|
|
103
|
+
"projectPath": "/path/to/workspace",
|
|
104
|
+
"focus": {
|
|
105
|
+
"kind": "search",
|
|
106
|
+
"query": "CreativeModeTab"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Artifact-relative file read from a workspace:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"tool": "inspect-minecraft",
|
|
118
|
+
"arguments": {
|
|
119
|
+
"task": "auto",
|
|
120
|
+
"subject": {
|
|
121
|
+
"kind": "workspace",
|
|
122
|
+
"projectPath": "/path/to/workspace",
|
|
123
|
+
"focus": {
|
|
124
|
+
"kind": "file",
|
|
125
|
+
"filePath": "net/minecraft/world/item/Item.java"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
61
132
|
## Workspace and dependency target shapes
|
|
62
133
|
|
|
63
134
|
`resolve-artifact`, `get-class-source`, and `get-class-members` accept two synthesizing `target.kind` values in addition to the canonical `"version"` / `"jar"` / `"coordinate"` shapes. The synthesizer rewrites the call into one of those canonical shapes before downstream resolution, so behaviour from the resolver onward is unchanged.
|
|
@@ -92,18 +163,27 @@ same dependency, call `resolve-artifact` once and reuse the returned
|
|
|
92
163
|
|
|
93
164
|
Workspace detection is memoised in a process-resident `WorkspaceContextCache` (16-entry LRU, 5-minute TTL). The cache is observable through `manage-cache` with `cacheKinds: ["workspace"]`, and individual entries can be invalidated via `selector.projectPath`.
|
|
94
165
|
|
|
95
|
-
`target.kind="dependency"` resolution probes
|
|
166
|
+
`target.kind="dependency"` resolution probes up to six de-duplicated `gradle.properties` keys in order — `name_version`, `snake_case(name)_version`, `camelCaseVersion`, `lastSegment(group)_name_version`, `snake_case(lastSegment(group)_name)_version`, and `camelCase(lastSegment(group)_name)Version`. Hyphens become underscores in the snake_case forms (`fabric-api` probes `fabric_api_version`); for hyphen-less names the snake_case forms deduplicate into the raw keys, leaving four. The probe falls back to the modules-2 cache layout `~/.gradle/caches/modules-2/files-2.1/<group>/<name>/`. Version tokens that contain path separators, `..`, NUL, control characters, or any character outside `[A-Za-z0-9._+-]` are rejected; in `gradle.properties` the rejection is recorded under `attempts[]` as `gradle.properties:<key>:rejected-unsafe-version` and the next key is tried. Snapshot and dev directories are excluded by default. The modules-2 fallback resolves directly when exactly one valid entry remains. When several entries remain and the dependency is a submodule of an umbrella package (artifact name differs from the group's last segment, e.g. `net.fabricmc.fabric-api:fabric-screen-handler-api-v1`), the declared umbrella version property (`fabric_api_version` / `fabricApiVersion`) locates the cached umbrella POM and the submodule adopts the version that POM names — the resolving response records `provenance.submoduleVersionSource: "umbrella-pom"` with the POM path in `provenance.source` (workspace-context-cache hits within the TTL return the cached version with `provenance.source: "workspace-context-cache"` instead). Umbrella properties are never adopted verbatim as a submodule's version. Every remaining ambiguity raises `ERR_DEPENDENCY_VERSION_UNRESOLVED` with `candidatesSeen` so a global cache cannot supply a version the workspace did not declare.
|
|
96
167
|
|
|
97
168
|
## Common Pitfalls
|
|
98
169
|
|
|
170
|
+
- 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`.
|
|
172
|
+
|
|
173
|
+
- 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.
|
|
99
174
|
- `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.
|
|
100
175
|
- Mojang binary-remap cache entries live under `<cacheDir>/remapped`. `manage-cache` lists valid files plus corrupt directories and leftover temp entries under `cacheKinds: ["binary-remap"]`; corrupt entries carry `status: "corrupt"` and `meta.artifactId`, so callers can preview or apply cleanup with `selector.artifactId`.
|
|
101
|
-
- `
|
|
176
|
+
- Jar-in-Jar shell jars (near-zero own classes with bundled `META-INF/jars/*.jar`, e.g. the Fabric API umbrella jar) no longer dead-end in `ERR_DECOMPILER_FAILED`. `resolve-artifact` detects the shell, skips decompilation, and returns the artifact with `qualityFlags: ["shell-jar"]` and the bundled inventory in `provenance.nestedJars`; `analyze-mod-jar` reports the same inventory as `nestedJars`. `get-class-source` / `get-class-members` lookups against a shell automatically redirect to the single nested jar containing the class and mark the response with `provenance.nestedJar` (`entryName` + `shellArtifactId`); a class found in several nested jars raises `ERR_NESTED_JAR_AMBIGUOUS` with `nestedJarCandidates` and per-candidate `suggestedCall` examples instead of picking one. Class-not-found errors on a shell carry the `nestedJars` inventory. The redirect is deliberately single-level: a nested jar that is itself a shell surfaces its own inventory when resolved directly as its own artifact. Extracted nested jars are content-addressed under `<cacheDir>/nested-jars`, observable through `manage-cache` with `cacheKinds: ["nested-jar"]`.
|
|
177
|
+
- `list-artifact-files` indexes Java source paths only — `assets/` and `data/` prefixes list nothing. Text files under those prefixes ARE retrievable by exact path with `get-artifact-file`: when the index has no row, the tool reads the entry directly from the backing jar (`deliveryMode: "jar-read-through"` marks such responses). This works for any artifact with a backing jar — vanilla client jars and mod jars alike; for Jar-in-Jar shells it reads the shell's own entries only (resolve a nested jar as its own artifact to reach its resources). Read-through delivery is text-only with a 512 KiB per-file cap (`truncated: true` beyond it); binary entries (e.g. `.png`, `.ogg`) answer with size metadata plus `contentOmittedReason` instead of content. Traversal-shaped paths (`..`, absolute) are rejected with `ERR_INVALID_INPUT`, and a miss returns `ERR_FILE_NOT_FOUND` with `nearbyPaths` naming same-named entries elsewhere in the jar (directory layouts move between versions, e.g. `assets/minecraft/models/item/*` → `assets/minecraft/items/*`).
|
|
102
178
|
- `search-class-source` defaults to `queryMode="auto"`. Use `queryMode="literal"` for explicit substring scans. `match="regex"` enforces `query.length <= 200` and caps results at `100`.
|
|
103
179
|
- `search-class-source` returns compact hits only. Use `get-artifact-file` or `get-class-source` to inspect returned files.
|
|
104
180
|
- `find-class` and `get-class-source` on `mapping="obfuscated"` expect Mojang obfuscated names. Deobfuscated queries warn and usually need `mapping="mojang"` or a `find-mapping` step first.
|
|
181
|
+
- `get-class-members` exposes `annotationDefault` on annotation-type members (the `default` value of an `@interface` member) whenever the classfile carries it, and accepts `includeAnnotations: true` to additionally list runtime-visible member annotations such as `@java.lang.Deprecated` per member. The leaner `names`/`signatures` projections drop annotation fields. `analyze-mod` `task="members"` includes both without a flag.
|
|
182
|
+
- On unobfuscated versions, `find-mapping`, `resolve-method-mapping-exact`, and `check-symbol-exists` report two structured `mappingContext` flags instead of per-response warning sentences (`get-class-api-matrix` reports a top-level `unobfuscatedRuntime: true` — its non-mojang columns are empty by design there): `unobfuscatedRuntime: true` replaces "Version X is unobfuscated; mapping graph is empty because the runtime already uses deobfuscated names.", and `runtimeValidated: true` replaces "Version X is unobfuscated; validated symbol existence against runtime bytecode." Do not pattern-match the old sentences.
|
|
105
183
|
- `check-symbol-exists` defaults to strict FQCN class lookup. Use `nameMode="auto"` for short class names.
|
|
184
|
+
- On unobfuscated versions, every `not_found` and `mapping_unavailable` verdict from the mapping graph — classes and fields included, not just methods — is re-validated against runtime bytecode before being returned, so a graph that lacks a record for a real symbol (e.g. `EntityType.ITEM`) no longer produces false negatives. Genuinely-missing symbols still return `not_found` after the runtime check. Bytecode-derived response contexts report `mappingNamespace: "mojang"` on unobfuscated versions instead of a hardcoded `"obfuscated"`.
|
|
106
185
|
- `check-symbol-exists` can use `signatureMode="name-only"` for overload discovery, but exact `descriptor` matching is still the most reliable path.
|
|
186
|
+
- Inherited-member expansion (`get-class-members` with `includeInherited: true`, and the `check-symbol-exists` runtime-bytecode fallback that reuses it) suppresses super-class and interface resolution warnings for platform packages that are never inside a Minecraft jar (`java.*`, `javax.*`, `jdk.*`, `sun.*`, `com.mojang.serialization.*`). A warning about any other unresolved super type — including `com.mojang.blaze3d.*` — is a real signal.
|
|
107
187
|
- `analyze-symbol task="api-overview"` inherits `sourceMapping` as the default `classNameMapping`; it falls back to `obfuscated` only when neither value is provided.
|
|
108
188
|
- `find-mapping` accepts short class ids such as `dhl` only when `sourceMapping="obfuscated"`. Other class lookup paths still validate class names as fully-qualified.
|
|
109
189
|
- `find-mapping` still rejects the public namespace name `official`, but upstream Tiny files that use `official` internally are now bridged to the supported `obfuscated` graph automatically.
|
|
@@ -247,7 +327,8 @@ Translate symbols across mapping namespaces with one shared Minecraft version. P
|
|
|
247
327
|
|
|
248
328
|
`ProblemDetails.code` may carry the codes below in addition to the existing tool-specific values.
|
|
249
329
|
|
|
250
|
-
- `ERR_WORKER_RESTART` — Synthetic envelope produced
|
|
330
|
+
- `ERR_WORKER_RESTART` — Synthetic envelope produced when the worker exits during `tools/call`, replacement startup or initialization replay fails, or live process-tree cleanup leaves the supervisor unable to start a worker. The response is a `CallToolResult` with `isError: true` and `structuredContent.error.code === "ERR_WORKER_RESTART"`. `structuredContent.meta.synthetic === true` and `meta.restart` records `tool` / `durationMs` / `lastStage` / `lastStageElapsedMs` / `lastStageMeta` / `exit` / `retryRecommendation` so the caller can decide whether to retry, narrow the input, clear caches, or report a bug. Non-`tools/call` requests receive the legacy raw JSON-RPC `-32603` envelope.
|
|
331
|
+
- `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.
|
|
251
332
|
- `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.
|
|
252
333
|
- `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.
|
|
253
334
|
- `ERR_WORKSPACE_VERSION_UNRESOLVED` — Raised by `resolve-artifact`, `get-class-source`, and `get-class-members` 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.
|
|
@@ -264,6 +345,9 @@ The optional `error.exampleCalls?: Array<{ tool: string; params: Record<string,
|
|
|
264
345
|
## Meta fields
|
|
265
346
|
|
|
266
347
|
- `meta.restart` (synthetic worker-restart responses only) — emitted exclusively when the supervisor returns an `ERR_WORKER_RESTART` envelope. Shape: `{ tool, durationMs, lastStage, lastStageElapsedMs, lastStageMeta, exit: { code, signal }, retryRecommendation }`. `retryRecommendation` is one of `"narrow-query" | "clear-cache" | "report-bug" | "same-request"`, decided by the supervisor from `lastStage`, `exit.signal`, and the recent restart cadence (3 restarts within 60 s of the same tool → `"report-bug"`).
|
|
348
|
+
- `meta.timeout` (`ERR_TOOL_TIMEOUT` only) — exact shape: `{ tool: "validate-project", phase: "queue" | "running", durationMs, deadlineMs, lastStage, lastStageElapsedMs, lastStageMeta, redactedToolArgs, redactedToolArgsModified, retryRecommendation, workerRestartInitiated }`. Stage and argument diagnostics use the supervisor's bounded redaction rules. `workerRestartInitiated` reports initiation only; it does not claim that replacement startup or initialization replay completed.
|
|
349
|
+
- `meta.queue` (supervisor queue overflow only) — `{ reason: "supervisor-request-queue", maxQueued: 2, queuedCount: 2 }`. The FIFO retains at most two total worker-bound requests; a queued `validate-project` barrier consumes one slot, while a running barrier is outside the FIFO. Overflowing `tools/call` receives a synthetic `ERR_LIMIT_EXCEEDED` result. An overflowing non-tool request receives raw JSON-RPC `-32000` with message `"MCP supervisor request queue is full."`; notifications do not consume slots.
|
|
350
|
+
- Replacement startup/replay uses a 10–30 second internal watchdog and bounded retry backoff. Successful replay releases queued work. Spawn, pre-ready, replay, or watchdog failure terminalizes queued requests with the existing worker-restart response shapes. At the two-slot live-generation cap, new worker-bound requests, including `initialize`, fail immediately instead of entering the FIFO; notifications that cannot be delivered are emitted as structured supervisor warnings and dropped. Tree-cleanup helper attempts are bounded to five seconds so recovery and shutdown cannot wait forever on `taskkill` or an equivalent platform operation. On POSIX, `ESRCH` while signaling the saved process group means the group is already gone and is treated as completed cleanup.
|
|
267
351
|
- `meta.stageBudgetExhausted` (`ERR_STAGE_BUDGET_PRE_PARSE` errors only) — set to `true` to flag that the failure is budget-driven rather than a genuine resolve / mapping-health / parse error. Pair with `failedStage` and `error.detail` (`"Stage <name> exhausted budget before parse completed."`) for diagnostics. The companion fields `meta.budgetMs` (the stage budget that was exceeded) and `meta.elapsedMs` (the actual stage elapsed time) are emitted alongside it on the same envelope so callers can decide between retry, input-shrink (`mixinConfigPath`), or `MIXIN_STAGE_BUDGETS_OFF=1` rollback without parsing message text.
|
|
268
352
|
- `validate-mixin` batch-mode entries (`results[i]`, when invoked with `input.mode = "paths" | "config" | "project"`) preserve typed error metadata when an entry fails: optional `errorCode` (e.g. `"ERR_STAGE_BUDGET_PRE_PARSE"`) and `errorDetails` (the `failedStage` / `stageBudgetExhausted` / `budgetMs` / `elapsedMs` shape from the underlying AppError) sit alongside the legacy `error` string. The shape is additive — callers that only read `error` continue to see the same human-readable message.
|
|
269
353
|
|
|
@@ -274,7 +358,7 @@ These environment variables are read once at worker startup and provide rollback
|
|
|
274
358
|
| Env | Effect | Acceptance test |
|
|
275
359
|
|---|---|---|
|
|
276
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-validate-mixin-budget.test.ts` (`MIXIN_STAGE_BUDGETS_OFF=1 disables all budgets`) |
|
|
277
|
-
| `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-supervisor.test.ts` (`buildWorkerRestartReply returns raw -32603 when structuredRestartDisabled`) |
|
|
361
|
+
| `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`) |
|
|
278
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`) |
|
|
279
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-workspace-target.test.ts` (`synthesizeWorkspaceTarget rejects target.kind=workspace when WORKSPACE_TARGET_OFF is set`) |
|
|
280
364
|
| `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`) |
|
|
@@ -294,6 +378,7 @@ These environment variables are read once at worker startup and provide rollback
|
|
|
294
378
|
- `analyze-symbol` `subject.kind` accepts `"symbol"` to auto-detect the concrete kind from the selector (owner+descriptor ⇒ method, owner only ⇒ field, otherwise ⇒ class); the inferred kind is surfaced as a warning. For `task="api-overview"` the inferred kind is always class.
|
|
295
379
|
- Start with `compare-minecraft` for version-pair, class diff, registry diff, and migration-summary flows before using `compare-versions`, `diff-class-signatures`, or `get-registry-data` directly.
|
|
296
380
|
- Start with `analyze-mod` for metadata-first mod inspection and safe remap preview/apply flows before using `analyze-mod-jar`, `decompile-mod-jar`, `get-mod-class-source`, `search-mod-source`, or `remap-mod-jar` directly.
|
|
381
|
+
- `analyze-mod` `task="members"` (subject `{ kind: "class", jarPath, className }`) reads a mod class's constructors/fields/methods (all access levels, including private and protected) straight from bytecode — no decompiler runs on this path, unlike `task="class-source"` which costs a full decompile. Responses mark `extractionMethod: "bytecode-only"`; a class missing from the jar returns `ERR_CLASS_NOT_FOUND`.
|
|
297
382
|
- Start with `validate-project` for workspace summaries and direct Mixin, Access Widener, or Access Transformer validation before using `validate-mixin`, `validate-access-widener`, or `validate-access-transformer` directly.
|
|
298
383
|
- `validate-project task="project-summary"` discovers mixins and access wideners by default. Add `discover: ["access-transformers"]` when you also want Access Transformer files included in the workspace summary.
|
|
299
384
|
- `validate-project task="project-summary"` returns an additive `tasks` field alongside the existing aggregate `result.summary` / `result.project` blocks. The headline `result.summary.status` is unchanged; the new field reports per-probe status so a `status: "blocked"` headline still preserves which probes succeeded.
|
|
@@ -404,7 +489,7 @@ If callers accidentally append an inline signature suffix to `trace-symbol-lifec
|
|
|
404
489
|
|
|
405
490
|
For decompile-only `ERR_MAPPING_NOT_APPLIED` failures, error details include `artifactOrigin`, `nextAction`, and `suggestedCall` so clients can recover without guessing.
|
|
406
491
|
|
|
407
|
-
If `find-class` or `get-class-source` returns no hit on an
|
|
492
|
+
If `find-class` or `get-class-source` returns no hit on an obfuscated Minecraft runtime artifact for names like `net.minecraft.world.item.Item`, the tool warns that `obfuscated` means Mojang's runtime names and recommends retrying with `mapping="mojang"` or translating via `find-mapping`. `find-class` does not issue that advice for dependency-resolved or Jar-in-Jar shell artifacts, whose native names are not evidence of Minecraft obfuscation.
|
|
408
493
|
|
|
409
494
|
Method descriptor precision is best on Tiny-backed paths (`intermediary` and `yarn`). For `obfuscated <-> mojang`, Mojang `client_mappings` do not carry JVM descriptors, so descriptor queries may fall back to name matching and emit a warning.
|
|
410
495
|
|
|
@@ -477,6 +562,7 @@ Path-based overrides treat blank values and the literal strings `undefined` and
|
|
|
477
562
|
| Variable | Default | Description |
|
|
478
563
|
| --- | --- | --- |
|
|
479
564
|
| `MCP_SUPERVISOR_DEBUG` | unset | Set to `1` to emit verbose stdio supervisor diagnostics |
|
|
565
|
+
| `MCP_VALIDATE_PROJECT_TIMEOUT_MS` | `120000` | End-to-end `validate-project` deadline, including queue time. Accepts ASCII decimal digits only and must be from `10000` through `600000`; invalid values use the default. |
|
|
480
566
|
|
|
481
567
|
Internal worker-mode environment variables are reserved for the transport implementation and are intentionally omitted from the public reference.
|
|
482
568
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhisang/minecraft-modding-mcp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.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",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"prepack": "npm run build",
|
|
24
24
|
"start": "node dist/cli.js",
|
|
25
25
|
"check": "tsc --noEmit -p tsconfig.json",
|
|
26
|
-
"test": "node
|
|
26
|
+
"test": "node scripts/run-tests.mjs",
|
|
27
27
|
"test:file": "node --test --import tsx",
|
|
28
|
-
"test:grep": "node
|
|
29
|
-
"test:coverage": "node
|
|
30
|
-
"test:coverage:lcov": "node
|
|
28
|
+
"test:grep": "node scripts/run-tests.mjs --test-name-pattern",
|
|
29
|
+
"test:coverage": "node scripts/run-tests.mjs --coverage",
|
|
30
|
+
"test:coverage:lcov": "node scripts/run-tests.mjs --coverage --lcov coverage/lcov.info",
|
|
31
31
|
"test:perf": "node --test --test-concurrency=1 --import tsx tests/perf/*.perf.ts",
|
|
32
32
|
"test:perf:update-baseline": "UPDATE_PERF_BASELINE=1 node --test --test-concurrency=1 --import tsx tests/perf/*.perf.ts",
|
|
33
33
|
"test:perf:strict": "STRICT_PERF=1 node --test --test-concurrency=1 --import tsx tests/perf/*.perf.ts",
|