@adhisang/minecraft-modding-mcp 6.2.0 → 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 +10 -1
- package/README.md +8 -2
- package/dist/entry-tools/analyze-symbol-service.d.ts +4 -4
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +3 -3
- package/dist/entry-tools/inspect-minecraft/internal.js +15 -15
- package/dist/entry-tools/inspect-minecraft-service.d.ts +12 -12
- package/dist/entry-tools/inspect-minecraft-service.js +1 -1
- package/dist/index.js +5 -6
- package/dist/source/class-source.d.ts +1 -0
- package/dist/source/class-source.js +39 -3
- package/dist/source/nested-jars.d.ts +19 -0
- package/dist/source/nested-jars.js +90 -21
- package/dist/source-service.d.ts +1 -0
- package/dist/source-service.js +3 -0
- package/dist/tool-contract-manifest.js +1 -1
- package/dist/tool-guidance.js +112 -6
- package/dist/tool-schemas.d.ts +9 -3
- package/dist/tool-schemas.js +1 -0
- package/docs/README-ja.md +4 -0
- package/docs/tool-reference.md +73 -3
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,16 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [6.3.0] - 2026-07-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `find-class` accepts top-level `projectPath` for workspace-relative `target.kind="workspace"` and dependency targets using `versionFromProject`, and searches simple or qualified class names across nested `.class` inventories when the resolved artifact is a Jar-in-Jar shell such as the Fabric API umbrella JAR. Nested matches retain the existing response shape, deduplicate repeated qualified names, and honor `limit`; top-level matches remain directly usable by the source/member tools.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `inspect-minecraft` now describes `task`, structured workspace `subject.focus`, and each class/search/file focus branch in `tools/list`. A string focus remains invalid instead of being guessed or coerced, but its `ERR_INVALID_INPUT` response now includes three schema-validated `exampleCalls` that preserve valid workspace context and show the exact object shapes for retrying.
|
|
17
|
+
- `find-class` no longer interprets a native dependency or Jar-in-Jar shell miss as evidence of obfuscated Minecraft runtime names, so it does not recommend `mapping="mojang"` for those artifacts. Dotted inner-class names discovered in a shell also continue through `get-class-source` instead of missing the binary `$` entry.
|
|
9
18
|
|
|
10
19
|
## [6.2.0] - 2026-07-11
|
|
11
20
|
|
package/README.md
CHANGED
|
@@ -156,7 +156,7 @@ All six return `result.summary` first and can include `summary.nextActions` when
|
|
|
156
156
|
These notes cover high-frequency decisions during onboarding. For the full pitfall list, exact contracts, migration notes, and environment variables, see [docs/tool-reference.md](docs/tool-reference.md).
|
|
157
157
|
|
|
158
158
|
- `search-class-source` defaults to `queryMode="auto"` and keeps separator queries such as `foo.bar`, `foo_bar`, and `foo$bar` on the indexed path. Use `queryMode="literal"` for an explicit full substring scan.
|
|
159
|
-
- If you do not already have an artifact, prefer `subject.kind="workspace"` for `inspect-minecraft` instead of guessing artifact details.
|
|
159
|
+
- If you do not already have an artifact, prefer `subject.kind="workspace"` for `inspect-minecraft` instead of guessing artifact details. `subject.focus` is an object, not a string: use `{ "kind": "class", "className": "..." }`, `{ "kind": "search", "query": "..." }`, or `{ "kind": "file", "filePath": "..." }`. `task="auto"` dispatches only from `subject.kind` and `focus.kind`; it is not a natural-language planner. Invalid string focus returns class/search/file `exampleCalls`, while missing artifact context keeps the requested task in a retryable `suggestedCall`.
|
|
160
160
|
- `trace-symbol-lifecycle` expects `Class.method` in `symbol`. Keep exact overload matching in the separate `descriptor` field.
|
|
161
161
|
- For unobfuscated releases such as `26.1+`, `check-symbol-exists` and `analyze-symbol task="exists"` validate `mojang` lookups against runtime bytecode when no mapping graph exists, and return `mapping_unavailable` when the runtime JAR itself is unreachable.
|
|
162
162
|
- `analyze-mod` and `validate-project` require structured `subject` objects and canonical `include` groups; stale string-subject or domain-include payloads return `ERR_INVALID_INPUT` with a retryable `suggestedCall`.
|
|
@@ -278,7 +278,7 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading o
|
|
|
278
278
|
| --- | --- |
|
|
279
279
|
| `list-versions` | List available Minecraft versions from Mojang metadata and local cache |
|
|
280
280
|
| `resolve-artifact` | Resolve source artifacts from versions, JAR paths, or Maven coordinates |
|
|
281
|
-
| `find-class` | Find simple or fully-qualified class names
|
|
281
|
+
| `find-class` | Find simple or fully-qualified class names, including classes bundled in nested JARs |
|
|
282
282
|
| `get-class-source` | Read class source from an artifact or resolve the backing artifact on demand |
|
|
283
283
|
| `get-class-members` | List constructors, fields, and methods from bytecode |
|
|
284
284
|
| `search-class-source` | Search indexed class source by symbol, text, or path |
|
|
@@ -287,6 +287,8 @@ Tools for browsing Minecraft versions, resolving source artifacts, and reading o
|
|
|
287
287
|
| `index-artifact` | Rebuild indexed metadata for an existing artifact |
|
|
288
288
|
<!-- END GENERATED TOOL TABLE: source-exploration -->
|
|
289
289
|
|
|
290
|
+
`find-class` accepts either an `artifactId` or the shared object `target` shape. For a workspace-relative dependency, pass `target: { kind: "dependency", group, name, versionFromProject: true }` with top-level `projectPath`. Fabric-style umbrella JARs are searched through their nested `.class` inventories; a top-level class match can then be passed to `get-class-source` or `get-class-members`, which resolve the containing nested JAR. Dotted inner-class matches are also readable through `get-class-source`. An empty result still means that the requested class name is absent from the resolved dependency version.
|
|
291
|
+
|
|
290
292
|
For unobfuscated releases such as `26.1+`, `mapping="mojang"` uses the runtime/decompile path directly and skips Loom source-jar discovery, while `intermediary` and `yarn` fall back to `obfuscated` with a warning.
|
|
291
293
|
|
|
292
294
|
### Version Comparison & Symbol Tracking
|
|
@@ -415,12 +417,16 @@ pnpm test
|
|
|
415
417
|
|
|
416
418
|
Run these when relevant:
|
|
417
419
|
|
|
420
|
+
- `pnpm test:file <path...>` for focused ordinary suites
|
|
421
|
+
- `pnpm test:grep <name-pattern>` for recursive ordinary suites filtered by test name
|
|
418
422
|
- `pnpm test:manual:stdio-smoke` for MCP transport, registration, or manual workflow changes
|
|
419
423
|
- `pnpm test:manual:package-smoke` when checking packaged install and distribution behavior
|
|
420
424
|
- `pnpm test:perf` for search, index, or performance-sensitive changes
|
|
421
425
|
- `pnpm test:coverage` or `pnpm test:coverage:lcov` for coverage checks (`lines=80`, `branches=70`, `functions=80`)
|
|
422
426
|
- `pnpm validate` for the full local validation suite
|
|
423
427
|
|
|
428
|
+
Ordinary `.test.ts` files live in domain directories under `tests/` (for example `source-service/`, `entry-tools/`, `mapping/`, `mixin/`, `integration/mcp-tools/`, `contracts/`, and `utils/`). Keep helper-only modules in `tests/helpers`; manual smoke, performance, resource, and smoke-only files remain in their existing special directories.
|
|
429
|
+
|
|
424
430
|
## License
|
|
425
431
|
|
|
426
432
|
[MIT](LICENSE)
|
|
@@ -90,10 +90,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
|
|
|
90
90
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
91
91
|
include?: string[] | undefined;
|
|
92
92
|
targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
93
|
+
gradleUserHome?: string | undefined;
|
|
93
94
|
maxVersions?: number | undefined;
|
|
94
95
|
sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
95
96
|
classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
96
|
-
gradleUserHome?: string | undefined;
|
|
97
97
|
includeSnapshots?: boolean | undefined;
|
|
98
98
|
fromVersion?: string | undefined;
|
|
99
99
|
toVersion?: string | undefined;
|
|
@@ -114,10 +114,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
|
|
|
114
114
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
115
115
|
include?: string[] | undefined;
|
|
116
116
|
targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
117
|
+
gradleUserHome?: string | undefined;
|
|
117
118
|
maxVersions?: number | undefined;
|
|
118
119
|
sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
119
120
|
classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
120
|
-
gradleUserHome?: string | undefined;
|
|
121
121
|
nameMode?: "auto" | "fqcn" | undefined;
|
|
122
122
|
includeSnapshots?: boolean | undefined;
|
|
123
123
|
fromVersion?: string | undefined;
|
|
@@ -142,10 +142,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
|
|
|
142
142
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
143
143
|
include?: string[] | undefined;
|
|
144
144
|
targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
145
|
+
gradleUserHome?: string | undefined;
|
|
145
146
|
maxVersions?: number | undefined;
|
|
146
147
|
sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
147
148
|
classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
148
|
-
gradleUserHome?: string | undefined;
|
|
149
149
|
includeSnapshots?: boolean | undefined;
|
|
150
150
|
fromVersion?: string | undefined;
|
|
151
151
|
toVersion?: string | undefined;
|
|
@@ -166,10 +166,10 @@ export declare const analyzeSymbolSchema: z.ZodEffects<z.ZodObject<{
|
|
|
166
166
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
167
167
|
include?: string[] | undefined;
|
|
168
168
|
targetMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
169
|
+
gradleUserHome?: string | undefined;
|
|
169
170
|
maxVersions?: number | undefined;
|
|
170
171
|
sourceMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
171
172
|
classNameMapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
172
|
-
gradleUserHome?: string | undefined;
|
|
173
173
|
nameMode?: "auto" | "fqcn" | undefined;
|
|
174
174
|
includeSnapshots?: boolean | undefined;
|
|
175
175
|
fromVersion?: string | undefined;
|
|
@@ -401,6 +401,7 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
|
|
|
401
401
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
402
402
|
preferProjectVersion?: boolean | undefined;
|
|
403
403
|
strictVersion?: boolean | undefined;
|
|
404
|
+
gradleUserHome?: string | undefined;
|
|
404
405
|
artifact?: {
|
|
405
406
|
artifactId: string;
|
|
406
407
|
type: "resolved-id";
|
|
@@ -411,7 +412,6 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
|
|
|
411
412
|
};
|
|
412
413
|
type: "resolve-target";
|
|
413
414
|
} | undefined;
|
|
414
|
-
gradleUserHome?: string | undefined;
|
|
415
415
|
}, {
|
|
416
416
|
kind: "class";
|
|
417
417
|
className: string;
|
|
@@ -420,6 +420,7 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
|
|
|
420
420
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
421
421
|
preferProjectVersion?: boolean | undefined;
|
|
422
422
|
strictVersion?: boolean | undefined;
|
|
423
|
+
gradleUserHome?: string | undefined;
|
|
423
424
|
artifact?: {
|
|
424
425
|
artifactId: string;
|
|
425
426
|
type: "resolved-id";
|
|
@@ -430,7 +431,6 @@ export declare const subjectSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject
|
|
|
430
431
|
};
|
|
431
432
|
type: "resolve-target";
|
|
432
433
|
} | undefined;
|
|
433
|
-
gradleUserHome?: string | undefined;
|
|
434
434
|
}>, z.ZodObject<{
|
|
435
435
|
kind: z.ZodLiteral<"file">;
|
|
436
436
|
filePath: z.ZodString;
|
|
@@ -1044,7 +1044,7 @@ export declare function resolveWorkspaceArtifactReference(deps: InspectMinecraft
|
|
|
1044
1044
|
version?: string;
|
|
1045
1045
|
warnings: string[];
|
|
1046
1046
|
}>;
|
|
1047
|
-
export declare function resolveTask(task: InspectMinecraftTask | undefined, subject: Subject | undefined): "search" | "class-source" | "
|
|
1047
|
+
export declare function resolveTask(task: InspectMinecraftTask | undefined, subject: Subject | undefined): "search" | "class-source" | "file" | "class-overview" | "class-members" | "artifact" | "versions" | "list-files";
|
|
1048
1048
|
export declare function summarizeRequestedSubject(subject: Subject): Record<string, unknown>;
|
|
1049
1049
|
export declare function taskForSubject(subject: Subject): ConcreteInspectMinecraftTask;
|
|
1050
1050
|
export declare function invalidTaskSubjectError(task: ConcreteInspectMinecraftTask, subject: Subject): never;
|
|
@@ -30,27 +30,27 @@ export const artifactRefSchema = z.discriminatedUnion("type", [
|
|
|
30
30
|
]);
|
|
31
31
|
export const workspaceFocusSchema = z.discriminatedUnion("kind", [
|
|
32
32
|
z.object({
|
|
33
|
-
kind: z.literal("class"),
|
|
34
|
-
className: nonEmptyString,
|
|
35
|
-
artifact: artifactRefSchema.optional()
|
|
36
|
-
}),
|
|
33
|
+
kind: z.literal("class").describe("Select class inspection."),
|
|
34
|
+
className: nonEmptyString.describe("Fully-qualified class name to inspect."),
|
|
35
|
+
artifact: artifactRefSchema.optional().describe("Optional explicit artifact override; otherwise resolve it from the workspace.")
|
|
36
|
+
}).describe('Class focus object: {"kind":"class","className":"net.minecraft.world.item.Item"}. task=auto dispatches it to class-overview; select class-source or class-members explicitly when needed.'),
|
|
37
37
|
z.object({
|
|
38
|
-
kind: z.literal("file"),
|
|
39
|
-
filePath: nonEmptyString,
|
|
40
|
-
artifact: artifactRefSchema.optional()
|
|
41
|
-
}),
|
|
38
|
+
kind: z.literal("file").describe("Select an artifact-relative file read."),
|
|
39
|
+
filePath: nonEmptyString.describe("Artifact-relative file path to read."),
|
|
40
|
+
artifact: artifactRefSchema.optional().describe("Optional explicit artifact override; otherwise resolve it from the workspace.")
|
|
41
|
+
}).describe('File focus object: {"kind":"file","filePath":"net/minecraft/world/item/Item.java"}. task=auto dispatches it to file.'),
|
|
42
42
|
z.object({
|
|
43
|
-
kind: z.literal("search"),
|
|
44
|
-
query: nonEmptyString,
|
|
45
|
-
artifact: artifactRefSchema.optional(),
|
|
43
|
+
kind: z.literal("search").describe("Select source search."),
|
|
44
|
+
query: nonEmptyString.describe("Source, symbol, or path query to search for."),
|
|
45
|
+
artifact: artifactRefSchema.optional().describe("Optional explicit artifact override; otherwise resolve it from the workspace."),
|
|
46
46
|
intent: z.enum(["symbol", "text", "path"]).optional(),
|
|
47
47
|
match: z.enum(["exact", "prefix", "contains", "regex"]).optional(),
|
|
48
48
|
symbolKind: z.enum(["class", "interface", "enum", "record", "method", "field"]).optional(),
|
|
49
49
|
packagePrefix: nonEmptyString.optional(),
|
|
50
50
|
fileGlob: nonEmptyString.optional(),
|
|
51
51
|
queryMode: z.enum(["auto", "token", "literal"]).default("auto")
|
|
52
|
-
})
|
|
53
|
-
]);
|
|
52
|
+
}).describe('Search focus object: {"kind":"search","query":"CreativeModeTab"}. task=auto dispatches it to search.')
|
|
53
|
+
]).describe('Structured workspace focus. Object, not string. Choose {kind:"class",className}, {kind:"file",filePath}, or {kind:"search",query}.');
|
|
54
54
|
export const subjectSchema = z.discriminatedUnion("kind", [
|
|
55
55
|
z.object({
|
|
56
56
|
kind: z.literal("version"),
|
|
@@ -108,8 +108,8 @@ export const subjectSchema = z.discriminatedUnion("kind", [
|
|
|
108
108
|
preferProjectVersion: z.boolean().optional(),
|
|
109
109
|
strictVersion: z.boolean().optional(),
|
|
110
110
|
focus: workspaceFocusSchema.optional()
|
|
111
|
-
})
|
|
112
|
-
]);
|
|
111
|
+
}).describe("Workspace subject. Resolves Minecraft artifact context from projectPath; add a structured focus object for class, file, or search work.")
|
|
112
|
+
]).describe("Structured inspection subject. Use kind=workspace with a structured focus object when the workspace should resolve artifact context.");
|
|
113
113
|
export function hasPartialVanillaCoverage(artifact) {
|
|
114
114
|
return artifact?.qualityFlags.includes("partial-source-no-net-minecraft") === true
|
|
115
115
|
|| artifact?.artifactContents.sourceCoverage === "partial";
|
|
@@ -158,6 +158,7 @@ export declare const inspectMinecraftShape: {
|
|
|
158
158
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
159
159
|
preferProjectVersion?: boolean | undefined;
|
|
160
160
|
strictVersion?: boolean | undefined;
|
|
161
|
+
gradleUserHome?: string | undefined;
|
|
161
162
|
artifact?: {
|
|
162
163
|
artifactId: string;
|
|
163
164
|
type: "resolved-id";
|
|
@@ -168,7 +169,6 @@ export declare const inspectMinecraftShape: {
|
|
|
168
169
|
};
|
|
169
170
|
type: "resolve-target";
|
|
170
171
|
} | undefined;
|
|
171
|
-
gradleUserHome?: string | undefined;
|
|
172
172
|
}, {
|
|
173
173
|
kind: "class";
|
|
174
174
|
className: string;
|
|
@@ -177,6 +177,7 @@ export declare const inspectMinecraftShape: {
|
|
|
177
177
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
178
178
|
preferProjectVersion?: boolean | undefined;
|
|
179
179
|
strictVersion?: boolean | undefined;
|
|
180
|
+
gradleUserHome?: string | undefined;
|
|
180
181
|
artifact?: {
|
|
181
182
|
artifactId: string;
|
|
182
183
|
type: "resolved-id";
|
|
@@ -187,7 +188,6 @@ export declare const inspectMinecraftShape: {
|
|
|
187
188
|
};
|
|
188
189
|
type: "resolve-target";
|
|
189
190
|
} | undefined;
|
|
190
|
-
gradleUserHome?: string | undefined;
|
|
191
191
|
}>, z.ZodObject<{
|
|
192
192
|
kind: z.ZodLiteral<"file">;
|
|
193
193
|
filePath: z.ZodString;
|
|
@@ -821,6 +821,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
821
821
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
822
822
|
preferProjectVersion?: boolean | undefined;
|
|
823
823
|
strictVersion?: boolean | undefined;
|
|
824
|
+
gradleUserHome?: string | undefined;
|
|
824
825
|
artifact?: {
|
|
825
826
|
artifactId: string;
|
|
826
827
|
type: "resolved-id";
|
|
@@ -831,7 +832,6 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
831
832
|
};
|
|
832
833
|
type: "resolve-target";
|
|
833
834
|
} | undefined;
|
|
834
|
-
gradleUserHome?: string | undefined;
|
|
835
835
|
}, {
|
|
836
836
|
kind: "class";
|
|
837
837
|
className: string;
|
|
@@ -840,6 +840,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
840
840
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
841
841
|
preferProjectVersion?: boolean | undefined;
|
|
842
842
|
strictVersion?: boolean | undefined;
|
|
843
|
+
gradleUserHome?: string | undefined;
|
|
843
844
|
artifact?: {
|
|
844
845
|
artifactId: string;
|
|
845
846
|
type: "resolved-id";
|
|
@@ -850,7 +851,6 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
850
851
|
};
|
|
851
852
|
type: "resolve-target";
|
|
852
853
|
} | undefined;
|
|
853
|
-
gradleUserHome?: string | undefined;
|
|
854
854
|
}>, z.ZodObject<{
|
|
855
855
|
kind: z.ZodLiteral<"file">;
|
|
856
856
|
filePath: z.ZodString;
|
|
@@ -1329,7 +1329,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1329
1329
|
includeSnapshots: boolean;
|
|
1330
1330
|
limit?: number | undefined;
|
|
1331
1331
|
cursor?: string | undefined;
|
|
1332
|
-
task?: "search" | "class-source" | "
|
|
1332
|
+
task?: "search" | "class-source" | "file" | "auto" | "class-overview" | "class-members" | "artifact" | "versions" | "list-files" | undefined;
|
|
1333
1333
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
1334
1334
|
subject?: {
|
|
1335
1335
|
kind: "version";
|
|
@@ -1366,6 +1366,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1366
1366
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
1367
1367
|
preferProjectVersion?: boolean | undefined;
|
|
1368
1368
|
strictVersion?: boolean | undefined;
|
|
1369
|
+
gradleUserHome?: string | undefined;
|
|
1369
1370
|
artifact?: {
|
|
1370
1371
|
artifactId: string;
|
|
1371
1372
|
type: "resolved-id";
|
|
@@ -1376,7 +1377,6 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1376
1377
|
};
|
|
1377
1378
|
type: "resolve-target";
|
|
1378
1379
|
} | undefined;
|
|
1379
|
-
gradleUserHome?: string | undefined;
|
|
1380
1380
|
} | {
|
|
1381
1381
|
kind: "file";
|
|
1382
1382
|
filePath: string;
|
|
@@ -1468,7 +1468,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1468
1468
|
}, {
|
|
1469
1469
|
limit?: number | undefined;
|
|
1470
1470
|
cursor?: string | undefined;
|
|
1471
|
-
task?: "search" | "class-source" | "
|
|
1471
|
+
task?: "search" | "class-source" | "file" | "auto" | "class-overview" | "class-members" | "artifact" | "versions" | "list-files" | undefined;
|
|
1472
1472
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
1473
1473
|
subject?: {
|
|
1474
1474
|
kind: "version";
|
|
@@ -1505,6 +1505,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1505
1505
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
1506
1506
|
preferProjectVersion?: boolean | undefined;
|
|
1507
1507
|
strictVersion?: boolean | undefined;
|
|
1508
|
+
gradleUserHome?: string | undefined;
|
|
1508
1509
|
artifact?: {
|
|
1509
1510
|
artifactId: string;
|
|
1510
1511
|
type: "resolved-id";
|
|
@@ -1515,7 +1516,6 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1515
1516
|
};
|
|
1516
1517
|
type: "resolve-target";
|
|
1517
1518
|
} | undefined;
|
|
1518
|
-
gradleUserHome?: string | undefined;
|
|
1519
1519
|
} | {
|
|
1520
1520
|
kind: "file";
|
|
1521
1521
|
filePath: string;
|
|
@@ -1609,7 +1609,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1609
1609
|
includeSnapshots: boolean;
|
|
1610
1610
|
limit?: number | undefined;
|
|
1611
1611
|
cursor?: string | undefined;
|
|
1612
|
-
task?: "search" | "class-source" | "
|
|
1612
|
+
task?: "search" | "class-source" | "file" | "auto" | "class-overview" | "class-members" | "artifact" | "versions" | "list-files" | undefined;
|
|
1613
1613
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
1614
1614
|
subject?: {
|
|
1615
1615
|
kind: "version";
|
|
@@ -1646,6 +1646,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1646
1646
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
1647
1647
|
preferProjectVersion?: boolean | undefined;
|
|
1648
1648
|
strictVersion?: boolean | undefined;
|
|
1649
|
+
gradleUserHome?: string | undefined;
|
|
1649
1650
|
artifact?: {
|
|
1650
1651
|
artifactId: string;
|
|
1651
1652
|
type: "resolved-id";
|
|
@@ -1656,7 +1657,6 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1656
1657
|
};
|
|
1657
1658
|
type: "resolve-target";
|
|
1658
1659
|
} | undefined;
|
|
1659
|
-
gradleUserHome?: string | undefined;
|
|
1660
1660
|
} | {
|
|
1661
1661
|
kind: "file";
|
|
1662
1662
|
filePath: string;
|
|
@@ -1748,7 +1748,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1748
1748
|
}, {
|
|
1749
1749
|
limit?: number | undefined;
|
|
1750
1750
|
cursor?: string | undefined;
|
|
1751
|
-
task?: "search" | "class-source" | "
|
|
1751
|
+
task?: "search" | "class-source" | "file" | "auto" | "class-overview" | "class-members" | "artifact" | "versions" | "list-files" | undefined;
|
|
1752
1752
|
detail?: "summary" | "standard" | "full" | undefined;
|
|
1753
1753
|
subject?: {
|
|
1754
1754
|
kind: "version";
|
|
@@ -1785,6 +1785,7 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1785
1785
|
scope?: "vanilla" | "merged" | "loader" | undefined;
|
|
1786
1786
|
preferProjectVersion?: boolean | undefined;
|
|
1787
1787
|
strictVersion?: boolean | undefined;
|
|
1788
|
+
gradleUserHome?: string | undefined;
|
|
1788
1789
|
artifact?: {
|
|
1789
1790
|
artifactId: string;
|
|
1790
1791
|
type: "resolved-id";
|
|
@@ -1795,7 +1796,6 @@ export declare const inspectMinecraftSchema: z.ZodEffects<z.ZodObject<{
|
|
|
1795
1796
|
};
|
|
1796
1797
|
type: "resolve-target";
|
|
1797
1798
|
} | undefined;
|
|
1798
|
-
gradleUserHome?: string | undefined;
|
|
1799
1799
|
} | {
|
|
1800
1800
|
kind: "file";
|
|
1801
1801
|
filePath: string;
|
|
@@ -14,7 +14,7 @@ import { handleListFiles } from "./inspect-minecraft/handlers/list-files.js";
|
|
|
14
14
|
const INCLUDE_GROUPS = ["warnings", "provenance", "candidates", "members", "descriptors", "source", "files", "samples", "artifact", "timings"];
|
|
15
15
|
const nonEmptyString = z.string().trim().min(1);
|
|
16
16
|
export const inspectMinecraftShape = {
|
|
17
|
-
task: z.enum(TASKS).optional(),
|
|
17
|
+
task: z.enum(TASKS).optional().describe("Workflow task. auto dispatches only from subject.kind and workspace focus.kind; it is not a natural-language planner."),
|
|
18
18
|
subject: subjectSchema.optional(),
|
|
19
19
|
includeSnapshots: z.boolean().default(false),
|
|
20
20
|
detail: detailSchema.optional(),
|
package/dist/index.js
CHANGED
|
@@ -114,7 +114,7 @@ const workspaceMappingService = new WorkspaceMappingService();
|
|
|
114
114
|
const inspectMinecraftService = new InspectMinecraftService({
|
|
115
115
|
listVersions: (input) => sourceService.listVersions(input),
|
|
116
116
|
resolveArtifact: (input) => sourceService.resolveArtifact(input),
|
|
117
|
-
findClass: (input) =>
|
|
117
|
+
findClass: (input) => sourceService.findClassIncludingNested(input),
|
|
118
118
|
checkSymbolExists: (input) => sourceService.checkSymbolExists(input),
|
|
119
119
|
getClassSource: (input) => sourceService.getClassSource(input),
|
|
120
120
|
getClassMembers: (input) => sourceService.getClassMembers(input),
|
|
@@ -472,7 +472,7 @@ expertTool("list-versions", "List available Minecraft versions from Mojang manif
|
|
|
472
472
|
limit: input.limit
|
|
473
473
|
})));
|
|
474
474
|
registerToolSchema("list-versions", listVersionsSchema);
|
|
475
|
-
server.tool("inspect-minecraft", "Top-level workflow tool for version discovery, artifact resolution, class inspection, source search, file reads, and file listings.", inspectMinecraftShape, { readOnlyHint: true }, async (args) => runTool("inspect-minecraft", args, inspectMinecraftSchema, async (input) => inspectMinecraftService.execute(input)));
|
|
475
|
+
server.tool("inspect-minecraft", "Top-level workflow tool for version discovery, artifact resolution, class inspection, source search, file reads, and file listings. Workspace subject.focus is a structured class/file/search object, never a string; task=auto dispatches from subject.kind and focus.kind.", inspectMinecraftShape, { readOnlyHint: true }, async (args) => runTool("inspect-minecraft", args, inspectMinecraftSchema, async (input) => inspectMinecraftService.execute(input)));
|
|
476
476
|
registerToolSchema("inspect-minecraft", inspectMinecraftSchema);
|
|
477
477
|
server.tool("analyze-symbol", "Top-level workflow tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview. subject.kind='symbol' auto-detects class/field/method from the selector.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
|
|
478
478
|
registerToolSchema("analyze-symbol", analyzeSymbolSchema);
|
|
@@ -542,15 +542,14 @@ async function resolveFlatArtifactId(input) {
|
|
|
542
542
|
if (target?.kind === "artifact" && target.artifactId) {
|
|
543
543
|
return target.artifactId;
|
|
544
544
|
}
|
|
545
|
+
const projectPath = input.projectPath ?? target?.projectPath;
|
|
545
546
|
const resolved = await sourceService.resolveArtifact({
|
|
546
547
|
target: target,
|
|
547
|
-
...(
|
|
548
|
-
? { projectPath: target.projectPath }
|
|
549
|
-
: {})
|
|
548
|
+
...(projectPath ? { projectPath } : {})
|
|
550
549
|
});
|
|
551
550
|
return resolved.artifactId;
|
|
552
551
|
}
|
|
553
|
-
expertTool("find-class", "Resolve a simple or qualified class name to fully-qualified class names within an artifact. Use this before get-class-source when you only have a simple name.", findClassShape, { readOnlyHint: true }, async (args) => runTool("find-class", args, findClassSchema, async (input) => sourceService.
|
|
552
|
+
expertTool("find-class", "Resolve a simple or qualified class name to fully-qualified class names within an artifact. Use this before get-class-source when you only have a simple name.", findClassShape, { readOnlyHint: true }, async (args) => runTool("find-class", args, findClassSchema, async (input) => sourceService.findClassIncludingNested({
|
|
554
553
|
className: input.className,
|
|
555
554
|
artifactId: await resolveFlatArtifactId(input),
|
|
556
555
|
limit: input.limit
|
|
@@ -59,5 +59,6 @@ export declare function buildDecompiledFallback(svc: SourceService, artifactId:
|
|
|
59
59
|
truncated: boolean;
|
|
60
60
|
} | undefined;
|
|
61
61
|
export declare function findClass(svc: SourceService, input: FindClassInput): FindClassOutput;
|
|
62
|
+
export declare function findClassIncludingNested(svc: SourceService, input: FindClassInput): Promise<FindClassOutput>;
|
|
62
63
|
export declare function getClassSource(svc: SourceService, input: GetClassSourceInput): Promise<GetClassSourceOutput>;
|
|
63
64
|
export declare function getClassMembers(svc: SourceService, input: GetClassMembersInput): Promise<GetClassMembersOutput>;
|
|
@@ -8,7 +8,7 @@ import { buildClassSourceSnippet } from "./class-source/snippet-builder.js";
|
|
|
8
8
|
import { remapAndCountMembers, sliceMembersWithLimit, projectMembersForWire, projectMembersByLevel } from "./class-source/members-builder.js";
|
|
9
9
|
import { collectDidYouMeanCandidates } from "./did-you-mean.js";
|
|
10
10
|
import { matchesMemberPattern } from "./member-pattern.js";
|
|
11
|
-
import { resolveUniqueNestedJarForClass } from "./nested-jars.js";
|
|
11
|
+
import { findNestedJarClasses, resolveUniqueNestedJarForClass } from "./nested-jars.js";
|
|
12
12
|
import { buildPageContextKey, encodeOffsetCursor, resolveCursorOffset } from "../page-cursor.js";
|
|
13
13
|
import { dedupeQualityFlags, normalizeMapping, normalizeOptionalString, normalizePathStyle } from "./shared-utils.js";
|
|
14
14
|
import { isUnobfuscatedVersion } from "../version-service.js";
|
|
@@ -82,6 +82,14 @@ function obfuscatedNamespaceHint(className) {
|
|
|
82
82
|
function hasPartialNetMinecraftCoverage(qualityFlags) {
|
|
83
83
|
return qualityFlags.includes("partial-source-no-net-minecraft");
|
|
84
84
|
}
|
|
85
|
+
function shouldSuggestObfuscatedMapping(artifact, className) {
|
|
86
|
+
const nativeDependency = artifact.provenance?.dependencyResolution != null;
|
|
87
|
+
const shellArtifact = artifact.qualityFlags.includes("shell-jar");
|
|
88
|
+
return (artifact.mappingApplied === "obfuscated" &&
|
|
89
|
+
!nativeDependency &&
|
|
90
|
+
!shellArtifact &&
|
|
91
|
+
looksLikeDeobfuscatedClassName(className));
|
|
92
|
+
}
|
|
85
93
|
function classNameToClassPath(className) {
|
|
86
94
|
const normalized = normalizePathStyle(className.trim()).replace(/\//g, ".");
|
|
87
95
|
const segments = normalized.split(".").filter((segment) => segment.length > 0);
|
|
@@ -398,7 +406,7 @@ export function findClass(svc, input) {
|
|
|
398
406
|
if (filteredMatches.length === 0 && partialVanillaLookup) {
|
|
399
407
|
warnings.push(`Artifact source coverage is partial and excludes net.minecraft; returning non-vanilla matches for "${className}" would be misleading. Use get-class-source/get-class-members for binary fallback or get-class-api-matrix for mapped API inspection.`);
|
|
400
408
|
}
|
|
401
|
-
if (filteredMatches.length === 0 && artifact
|
|
409
|
+
if (filteredMatches.length === 0 && shouldSuggestObfuscatedMapping(artifact, className)) {
|
|
402
410
|
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
403
411
|
}
|
|
404
412
|
return { matches: filteredMatches, total: filteredMatches.length, warnings };
|
|
@@ -432,11 +440,39 @@ export function findClass(svc, input) {
|
|
|
432
440
|
if (filteredMatches.length === 0 && partialVanillaLookup) {
|
|
433
441
|
warnings.push(`Artifact source coverage is partial and excludes net.minecraft; returning non-vanilla matches for "${className}" would be misleading. Use get-class-source/get-class-members for binary fallback or get-class-api-matrix for mapped API inspection.`);
|
|
434
442
|
}
|
|
435
|
-
if (filteredMatches.length === 0 && artifact
|
|
443
|
+
if (filteredMatches.length === 0 && shouldSuggestObfuscatedMapping(artifact, className)) {
|
|
436
444
|
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
437
445
|
}
|
|
438
446
|
return { matches: filteredMatches, total: filteredMatches.length, warnings };
|
|
439
447
|
}
|
|
448
|
+
export async function findClassIncludingNested(svc, input) {
|
|
449
|
+
const indexed = findClass(svc, input);
|
|
450
|
+
if (indexed.total > 0) {
|
|
451
|
+
return indexed;
|
|
452
|
+
}
|
|
453
|
+
const artifact = svc.getArtifact(input.artifactId.trim());
|
|
454
|
+
const inventory = artifact.provenance?.nestedJars;
|
|
455
|
+
if (!artifact.qualityFlags.includes("shell-jar") ||
|
|
456
|
+
!artifact.binaryJarPath ||
|
|
457
|
+
!inventory ||
|
|
458
|
+
inventory.length === 0) {
|
|
459
|
+
return indexed;
|
|
460
|
+
}
|
|
461
|
+
const limit = Math.max(1, Math.min(input.limit ?? 20, 200));
|
|
462
|
+
const matches = await findNestedJarClasses({
|
|
463
|
+
cacheDir: svc.config.cacheDir,
|
|
464
|
+
outerJarPath: artifact.binaryJarPath,
|
|
465
|
+
outerSignature: artifact.artifactId,
|
|
466
|
+
inventory,
|
|
467
|
+
className: input.className,
|
|
468
|
+
limit
|
|
469
|
+
});
|
|
470
|
+
return {
|
|
471
|
+
matches,
|
|
472
|
+
total: matches.length,
|
|
473
|
+
warnings: indexed.warnings
|
|
474
|
+
};
|
|
475
|
+
}
|
|
440
476
|
export async function getClassSource(svc, input) {
|
|
441
477
|
const className = input.className.trim();
|
|
442
478
|
if (!className) {
|
|
@@ -9,6 +9,25 @@ export interface NestedJarMatch {
|
|
|
9
9
|
entryName: string;
|
|
10
10
|
extractedPath: string;
|
|
11
11
|
}
|
|
12
|
+
export interface NestedClassMatch {
|
|
13
|
+
qualifiedName: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
line: number;
|
|
16
|
+
symbolKind: "class";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Finds exact class-name matches across a shell's nested bytecode inventory.
|
|
20
|
+
* Results use the same binary-backed class representation as entry-tool search:
|
|
21
|
+
* an inferred outer Java path, line 1, and the broad class symbol kind.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findNestedJarClasses(args: {
|
|
24
|
+
cacheDir: string;
|
|
25
|
+
outerJarPath: string;
|
|
26
|
+
outerSignature: string;
|
|
27
|
+
inventory: string[];
|
|
28
|
+
className: string;
|
|
29
|
+
limit: number;
|
|
30
|
+
}): Promise<NestedClassMatch[]>;
|
|
12
31
|
/**
|
|
13
32
|
* Shell detection with both required signals: near-zero own classes AND
|
|
14
33
|
* bundled nested jars (META-INF/jars scan or fabric.mod.json "jars"
|
|
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { buildSuggestedCall } from "../build-suggested-call.js";
|
|
5
5
|
import { ERROR_CODES, createError } from "../errors.js";
|
|
6
6
|
import { collectNestedJars } from "../mod-analyzer.js";
|
|
7
|
+
import { isSecureJarEntryPath } from "../path-resolver.js";
|
|
7
8
|
import { listJarEntries, readJarEntryAsBuffer, readJarEntryAsUtf8 } from "../source-jar-reader.js";
|
|
8
9
|
/**
|
|
9
10
|
* A jar with at most this many own `.class` entries can qualify as a
|
|
@@ -29,6 +30,84 @@ function rememberClassSet(key, value) {
|
|
|
29
30
|
classSetCache.delete(oldest);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
async function loadNestedJarClassSet(args) {
|
|
34
|
+
let extractedPath;
|
|
35
|
+
try {
|
|
36
|
+
extractedPath = await extractNestedJar(args.cacheDir, args.outerJarPath, args.outerSignature, args.entryName);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
let classSet = classSetCache.get(extractedPath);
|
|
42
|
+
if (!classSet) {
|
|
43
|
+
try {
|
|
44
|
+
const entries = await listJarEntries(extractedPath);
|
|
45
|
+
classSet = new Set(entries.filter((entry) => entry.endsWith(".class") && isSecureJarEntryPath(entry)));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
rememberClassSet(extractedPath, classSet);
|
|
51
|
+
}
|
|
52
|
+
return { extractedPath, classSet };
|
|
53
|
+
}
|
|
54
|
+
function nestedClassMatch(entry) {
|
|
55
|
+
const internalName = entry.slice(0, -".class".length);
|
|
56
|
+
if (internalName.startsWith("META-INF/versions/")) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const binarySimpleName = internalName.split("/").at(-1) ?? internalName;
|
|
60
|
+
if (binarySimpleName === "module-info" || binarySimpleName === "package-info") {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const innerSegments = binarySimpleName.split("$").slice(1);
|
|
64
|
+
if (innerSegments.some((segment) => segment.length === 0 || /^\d/.test(segment))) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
qualifiedName: internalName.replaceAll("/", ".").replaceAll("$", "."),
|
|
69
|
+
filePath: `${internalName.split("$")[0]}.java`,
|
|
70
|
+
line: 1,
|
|
71
|
+
symbolKind: "class"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Finds exact class-name matches across a shell's nested bytecode inventory.
|
|
76
|
+
* Results use the same binary-backed class representation as entry-tool search:
|
|
77
|
+
* an inferred outer Java path, line 1, and the broad class symbol kind.
|
|
78
|
+
*/
|
|
79
|
+
export async function findNestedJarClasses(args) {
|
|
80
|
+
const normalizedQuery = args.className.trim().replaceAll("/", ".").replaceAll("$", ".");
|
|
81
|
+
const isQualified = normalizedQuery.includes(".");
|
|
82
|
+
const matches = new Map();
|
|
83
|
+
inventory: for (const entryName of args.inventory) {
|
|
84
|
+
const loaded = await loadNestedJarClassSet({
|
|
85
|
+
cacheDir: args.cacheDir,
|
|
86
|
+
outerJarPath: args.outerJarPath,
|
|
87
|
+
outerSignature: args.outerSignature,
|
|
88
|
+
entryName
|
|
89
|
+
});
|
|
90
|
+
if (!loaded) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
for (const classEntry of [...loaded.classSet].sort((left, right) => left.localeCompare(right))) {
|
|
94
|
+
const match = nestedClassMatch(classEntry);
|
|
95
|
+
if (!match) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const simpleName = match.qualifiedName.split(".").at(-1) ?? match.qualifiedName;
|
|
99
|
+
if ((isQualified && match.qualifiedName !== normalizedQuery) ||
|
|
100
|
+
(!isQualified && simpleName !== normalizedQuery)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
matches.set(match.qualifiedName, match);
|
|
104
|
+
if (isQualified || matches.size >= args.limit) {
|
|
105
|
+
break inventory;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return [...matches.values()];
|
|
110
|
+
}
|
|
32
111
|
/**
|
|
33
112
|
* Shell detection with both required signals: near-zero own classes AND
|
|
34
113
|
* bundled nested jars (META-INF/jars scan or fabric.mod.json "jars"
|
|
@@ -128,29 +207,19 @@ export async function extractNestedJar(cacheDir, outerJarPath, outerSignature, e
|
|
|
128
207
|
*/
|
|
129
208
|
export async function findNestedJarsContainingClass(args) {
|
|
130
209
|
const classEntry = `${args.internalName}.class`;
|
|
210
|
+
const qualifiedName = args.internalName.replaceAll("/", ".").replaceAll("$", ".");
|
|
131
211
|
const matches = [];
|
|
132
212
|
for (const entryName of args.inventory) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const entries = await listJarEntries(extractedPath);
|
|
145
|
-
classSet = new Set(entries.filter((entry) => entry.endsWith(".class")));
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
rememberClassSet(extractedPath, classSet);
|
|
151
|
-
}
|
|
152
|
-
if (classSet.has(classEntry)) {
|
|
153
|
-
matches.push({ entryName, extractedPath });
|
|
213
|
+
const loaded = await loadNestedJarClassSet({
|
|
214
|
+
cacheDir: args.cacheDir,
|
|
215
|
+
outerJarPath: args.outerJarPath,
|
|
216
|
+
outerSignature: args.outerSignature,
|
|
217
|
+
entryName
|
|
218
|
+
});
|
|
219
|
+
const containsClass = loaded?.classSet.has(classEntry) || [...(loaded?.classSet ?? [])].some((entry) => entry.endsWith(".class") &&
|
|
220
|
+
entry.slice(0, -".class".length).replaceAll("/", ".").replaceAll("$", ".") === qualifiedName);
|
|
221
|
+
if (loaded && containsClass) {
|
|
222
|
+
matches.push({ entryName, extractedPath: loaded.extractedPath });
|
|
154
223
|
}
|
|
155
224
|
}
|
|
156
225
|
return matches;
|
package/dist/source-service.d.ts
CHANGED
|
@@ -670,6 +670,7 @@ export declare class SourceService {
|
|
|
670
670
|
traceSymbolLifecycle(input: TraceSymbolLifecycleInput): Promise<TraceSymbolLifecycleOutput>;
|
|
671
671
|
diffClassSignatures(input: DiffClassSignaturesInput): Promise<DiffClassSignaturesOutput>;
|
|
672
672
|
findClass(input: FindClassInput): FindClassOutput;
|
|
673
|
+
findClassIncludingNested(input: FindClassInput): Promise<FindClassOutput>;
|
|
673
674
|
getClassSource(input: GetClassSourceInput): Promise<GetClassSourceOutput>;
|
|
674
675
|
getClassMembers(input: GetClassMembersInput): Promise<GetClassMembersOutput>;
|
|
675
676
|
validateMixin(input: ValidateMixinInput, options?: ValidateMixinOptions): Promise<ValidateMixinOutput>;
|
package/dist/source-service.js
CHANGED
|
@@ -215,6 +215,9 @@ export class SourceService {
|
|
|
215
215
|
findClass(input) {
|
|
216
216
|
return classSource.findClass(this, input);
|
|
217
217
|
}
|
|
218
|
+
async findClassIncludingNested(input) {
|
|
219
|
+
return classSource.findClassIncludingNested(this, input);
|
|
220
|
+
}
|
|
218
221
|
async getClassSource(input) {
|
|
219
222
|
return classSource.getClassSource(this, input);
|
|
220
223
|
}
|
|
@@ -32,7 +32,7 @@ const SECTION_ROWS = {
|
|
|
32
32
|
en: [
|
|
33
33
|
"| `list-versions` | List available Minecraft versions from Mojang metadata and local cache |",
|
|
34
34
|
"| `resolve-artifact` | Resolve source artifacts from versions, JAR paths, or Maven coordinates |",
|
|
35
|
-
"| `find-class` | Find simple or fully-qualified class names
|
|
35
|
+
"| `find-class` | Find simple or fully-qualified class names, including classes bundled in nested JARs |",
|
|
36
36
|
"| `get-class-source` | Read class source from an artifact or resolve the backing artifact on demand |",
|
|
37
37
|
"| `get-class-members` | List constructors, fields, and methods from bytecode |",
|
|
38
38
|
"| `search-class-source` | Search indexed class source by symbol, text, or path |",
|
package/dist/tool-guidance.js
CHANGED
|
@@ -656,7 +656,103 @@ function gatedGuidance(tool, hints, params) {
|
|
|
656
656
|
primaryDropped: !validated.suggestedCall
|
|
657
657
|
};
|
|
658
658
|
}
|
|
659
|
+
function buildInspectWorkspaceSubject(source) {
|
|
660
|
+
const subject = {
|
|
661
|
+
kind: "workspace",
|
|
662
|
+
projectPath: asNonEmptyString(source.projectPath) ?? "<workspace-path>"
|
|
663
|
+
};
|
|
664
|
+
if (typeof source.mapping === "string" &&
|
|
665
|
+
["obfuscated", "mojang", "intermediary", "yarn"].includes(source.mapping)) {
|
|
666
|
+
subject.mapping = source.mapping;
|
|
667
|
+
}
|
|
668
|
+
if (typeof source.scope === "string" &&
|
|
669
|
+
["vanilla", "merged", "loader"].includes(source.scope)) {
|
|
670
|
+
subject.scope = source.scope;
|
|
671
|
+
}
|
|
672
|
+
const gradleUserHome = asNonEmptyString(source.gradleUserHome);
|
|
673
|
+
if (gradleUserHome) {
|
|
674
|
+
subject.gradleUserHome = gradleUserHome;
|
|
675
|
+
}
|
|
676
|
+
if (typeof source.preferProjectVersion === "boolean") {
|
|
677
|
+
subject.preferProjectVersion = source.preferProjectVersion;
|
|
678
|
+
}
|
|
679
|
+
if (typeof source.strictVersion === "boolean") {
|
|
680
|
+
subject.strictVersion = source.strictVersion;
|
|
681
|
+
}
|
|
682
|
+
return subject;
|
|
683
|
+
}
|
|
684
|
+
function inspectTaskForFocus(task, focusKind) {
|
|
685
|
+
if (focusKind === "class" &&
|
|
686
|
+
(task === "auto" || task === "class-overview" || task === "class-source" || task === "class-members")) {
|
|
687
|
+
return task;
|
|
688
|
+
}
|
|
689
|
+
if (focusKind === "search" && (task === "auto" || task === "search")) {
|
|
690
|
+
return task;
|
|
691
|
+
}
|
|
692
|
+
if (focusKind === "file" && (task === "auto" || task === "file")) {
|
|
693
|
+
return task;
|
|
694
|
+
}
|
|
695
|
+
return "auto";
|
|
696
|
+
}
|
|
697
|
+
function buildInspectMinecraftInvalidFocusGuidance(normalizedInput) {
|
|
698
|
+
const input = asObjectRecord(normalizedInput);
|
|
699
|
+
const originalSubject = asObjectRecord(input?.subject);
|
|
700
|
+
if (originalSubject?.kind !== "workspace" ||
|
|
701
|
+
originalSubject.focus === undefined ||
|
|
702
|
+
asObjectRecord(originalSubject.focus)) {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
const requestedTask = asNonEmptyString(input?.task);
|
|
706
|
+
const workspaceSubject = buildInspectWorkspaceSubject(originalSubject);
|
|
707
|
+
const examples = [
|
|
708
|
+
{
|
|
709
|
+
params: {
|
|
710
|
+
task: inspectTaskForFocus(requestedTask, "class"),
|
|
711
|
+
subject: {
|
|
712
|
+
...workspaceSubject,
|
|
713
|
+
focus: { kind: "class", className: "<fully-qualified-class-name>" }
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
reason: "Use class focus for class overview, source, or member inspection."
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
params: {
|
|
720
|
+
task: inspectTaskForFocus(requestedTask, "search"),
|
|
721
|
+
subject: {
|
|
722
|
+
...workspaceSubject,
|
|
723
|
+
focus: { kind: "search", query: "<search-query>" }
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
reason: "Use search focus for symbol, text, or path search."
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
params: {
|
|
730
|
+
task: inspectTaskForFocus(requestedTask, "file"),
|
|
731
|
+
subject: {
|
|
732
|
+
...workspaceSubject,
|
|
733
|
+
focus: { kind: "file", filePath: "<artifact-relative-file-path>" }
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
reason: "Use file focus for an artifact-relative file read."
|
|
737
|
+
}
|
|
738
|
+
];
|
|
739
|
+
const validated = buildSuggestedCall({
|
|
740
|
+
tool: "inspect-minecraft",
|
|
741
|
+
params: undefined,
|
|
742
|
+
examples
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
hints: [
|
|
746
|
+
"inspect-minecraft subject.focus must be a structured object, not a string.",
|
|
747
|
+
"Choose focus.kind=class with className, search with query, or file with filePath; task=auto dispatches from that kind and does not interpret prose."
|
|
748
|
+
],
|
|
749
|
+
...(validated.exampleCalls ? { exampleCalls: validated.exampleCalls } : {})
|
|
750
|
+
};
|
|
751
|
+
}
|
|
659
752
|
export function buildInvalidInputGuidance(tool, normalizedInput) {
|
|
753
|
+
if (tool === "inspect-minecraft") {
|
|
754
|
+
return buildInspectMinecraftInvalidFocusGuidance(normalizedInput);
|
|
755
|
+
}
|
|
660
756
|
if (tool === "validate-mixin") {
|
|
661
757
|
return gatedGuidance(tool, [
|
|
662
758
|
"validate-mixin.input must be an object with input.mode = \"inline\" | \"path\" | \"paths\" | \"config\" | \"project\".",
|
|
@@ -719,6 +815,13 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
719
815
|
}
|
|
720
816
|
if (isAppError(caughtError)) {
|
|
721
817
|
const { suggestedCall, exampleCalls, primaryDropped } = extractValidatedSuggestionAndExamples(caughtError.details);
|
|
818
|
+
const invalidInputGuidance = context?.tool === "inspect-minecraft" && caughtError.code === ERROR_CODES.INVALID_INPUT
|
|
819
|
+
? buildInvalidInputGuidance(context.tool, context.normalizedInput)
|
|
820
|
+
: undefined;
|
|
821
|
+
const effectiveSuggestedCall = invalidInputGuidance
|
|
822
|
+
? invalidInputGuidance.suggestedCall
|
|
823
|
+
: suggestedCall;
|
|
824
|
+
const effectiveExampleCalls = invalidInputGuidance?.exampleCalls ?? exampleCalls;
|
|
722
825
|
const sanitizedContext = extractAllowlistedContext(caughtError.details);
|
|
723
826
|
const extractedDidYouMean = extractDidYouMean(caughtError.details);
|
|
724
827
|
let failedStage = extractFailedStageFromDetails(caughtError.details);
|
|
@@ -727,10 +830,13 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
727
830
|
&& caughtError.code === ERROR_CODES.INVALID_INPUT) {
|
|
728
831
|
failedStage = "input-validation";
|
|
729
832
|
}
|
|
730
|
-
const baseHints =
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
833
|
+
const baseHints = [
|
|
834
|
+
...(toHints(caughtError.details) ?? []),
|
|
835
|
+
...(invalidInputGuidance?.hints ?? [])
|
|
836
|
+
];
|
|
837
|
+
const hintsWithFallback = primaryDropped && !effectiveSuggestedCall
|
|
838
|
+
? [...baseHints, VALIDATION_FALLBACK_HINT]
|
|
839
|
+
: baseHints.length > 0 ? baseHints : undefined;
|
|
734
840
|
return {
|
|
735
841
|
type: `https://minecraft-modding-mcp.dev/problems/${caughtError.code.toLowerCase()}`,
|
|
736
842
|
title: "Tool execution error",
|
|
@@ -742,8 +848,8 @@ export function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
742
848
|
issueOrigin: issueOriginForErrorCode(caughtError.code),
|
|
743
849
|
fieldErrors: extractFieldErrorsFromDetails(caughtError.details),
|
|
744
850
|
hints: hintsWithFallback,
|
|
745
|
-
...(
|
|
746
|
-
...(
|
|
851
|
+
...(effectiveSuggestedCall ? { suggestedCall: effectiveSuggestedCall } : {}),
|
|
852
|
+
...(effectiveExampleCalls ? { exampleCalls: effectiveExampleCalls } : {}),
|
|
747
853
|
...(extractedDidYouMean ? { didYouMean: extractedDidYouMean } : {}),
|
|
748
854
|
...(failedStage ? { failedStage } : {}),
|
|
749
855
|
...(sanitizedContext ? { context: sanitizedContext } : {})
|
package/dist/tool-schemas.d.ts
CHANGED
|
@@ -2529,6 +2529,7 @@ export declare const findClassShape: {
|
|
|
2529
2529
|
kind: "artifact";
|
|
2530
2530
|
artifactId: string;
|
|
2531
2531
|
}>]>>;
|
|
2532
|
+
projectPath: z.ZodOptional<z.ZodString>;
|
|
2532
2533
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
2533
2534
|
};
|
|
2534
2535
|
export declare const findClassSchema: z.ZodEffects<z.ZodObject<{
|
|
@@ -2601,9 +2602,11 @@ export declare const findClassSchema: z.ZodEffects<z.ZodObject<{
|
|
|
2601
2602
|
kind: "artifact";
|
|
2602
2603
|
artifactId: string;
|
|
2603
2604
|
}>]>>;
|
|
2605
|
+
projectPath: z.ZodOptional<z.ZodString>;
|
|
2604
2606
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
2605
2607
|
}, "strip", z.ZodTypeAny, {
|
|
2606
2608
|
className: string;
|
|
2609
|
+
projectPath?: string | undefined;
|
|
2607
2610
|
artifactId?: string | undefined;
|
|
2608
2611
|
limit?: number | undefined;
|
|
2609
2612
|
target?: {
|
|
@@ -2631,6 +2634,7 @@ export declare const findClassSchema: z.ZodEffects<z.ZodObject<{
|
|
|
2631
2634
|
} | undefined;
|
|
2632
2635
|
}, {
|
|
2633
2636
|
className: string;
|
|
2637
|
+
projectPath?: string | undefined;
|
|
2634
2638
|
artifactId?: string | undefined;
|
|
2635
2639
|
limit?: number | undefined;
|
|
2636
2640
|
target?: {
|
|
@@ -2658,6 +2662,7 @@ export declare const findClassSchema: z.ZodEffects<z.ZodObject<{
|
|
|
2658
2662
|
} | undefined;
|
|
2659
2663
|
}>, {
|
|
2660
2664
|
className: string;
|
|
2665
|
+
projectPath?: string | undefined;
|
|
2661
2666
|
artifactId?: string | undefined;
|
|
2662
2667
|
limit?: number | undefined;
|
|
2663
2668
|
target?: {
|
|
@@ -2685,6 +2690,7 @@ export declare const findClassSchema: z.ZodEffects<z.ZodObject<{
|
|
|
2685
2690
|
} | undefined;
|
|
2686
2691
|
}, {
|
|
2687
2692
|
className: string;
|
|
2693
|
+
projectPath?: string | undefined;
|
|
2688
2694
|
artifactId?: string | undefined;
|
|
2689
2695
|
limit?: number | undefined;
|
|
2690
2696
|
target?: {
|
|
@@ -3596,8 +3602,8 @@ export declare const traceSymbolLifecycleSchema: z.ZodObject<{
|
|
|
3596
3602
|
descriptor?: string | undefined;
|
|
3597
3603
|
mapping?: "obfuscated" | "mojang" | "intermediary" | "yarn" | undefined;
|
|
3598
3604
|
sourcePriority?: "loom-first" | "maven-first" | undefined;
|
|
3599
|
-
maxVersions?: number | undefined;
|
|
3600
3605
|
gradleUserHome?: string | undefined;
|
|
3606
|
+
maxVersions?: number | undefined;
|
|
3601
3607
|
includeSnapshots?: boolean | undefined;
|
|
3602
3608
|
fromVersion?: string | undefined;
|
|
3603
3609
|
toVersion?: string | undefined;
|
|
@@ -4053,10 +4059,10 @@ export declare const nbtToJsonSchema: z.ZodObject<{
|
|
|
4053
4059
|
compression: z.ZodDefault<z.ZodEnum<["none", "gzip", "auto"]>>;
|
|
4054
4060
|
}, "strip", z.ZodTypeAny, {
|
|
4055
4061
|
nbtBase64: string;
|
|
4056
|
-
compression: "
|
|
4062
|
+
compression: "auto" | "none" | "gzip";
|
|
4057
4063
|
}, {
|
|
4058
4064
|
nbtBase64: string;
|
|
4059
|
-
compression?: "
|
|
4065
|
+
compression?: "auto" | "none" | "gzip" | undefined;
|
|
4060
4066
|
}>;
|
|
4061
4067
|
export declare const nbtApplyJsonPatchShape: {
|
|
4062
4068
|
typedJson: z.ZodUnknown;
|
package/dist/tool-schemas.js
CHANGED
|
@@ -371,6 +371,7 @@ export const findClassShape = {
|
|
|
371
371
|
className: nonEmptyString.describe("Simple name (e.g. Blocks) or fully-qualified name (e.g. net.minecraft.world.level.block.Blocks)"),
|
|
372
372
|
artifactId: optionalNonEmptyString,
|
|
373
373
|
target: sourceLookupTargetSchema.optional().describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
374
|
+
projectPath: optionalNonEmptyString.describe("Workspace root for dependency or workspace target resolution."),
|
|
374
375
|
limit: optionalPositiveInt.describe("default 20, max 200")
|
|
375
376
|
};
|
|
376
377
|
export const findClassSchema = z.object(findClassShape).superRefine(requireExactlyOneArtifactRef);
|
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,12 +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
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`.
|
|
41
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`.
|
|
42
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.
|
|
43
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.
|
|
@@ -59,6 +60,75 @@ Start here when you are not sure which tool to reach for. In every row, the left
|
|
|
59
60
|
- Heavy analysis tools are serialized in-process to protect stdio stability. Queue overflow returns `ERR_LIMIT_EXCEEDED`.
|
|
60
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.
|
|
61
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
|
+
|
|
62
132
|
## Workspace and dependency target shapes
|
|
63
133
|
|
|
64
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.
|
|
@@ -288,7 +358,7 @@ These environment variables are read once at worker startup and provide rollback
|
|
|
288
358
|
| Env | Effect | Acceptance test |
|
|
289
359
|
|---|---|---|
|
|
290
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`) |
|
|
291
|
-
| `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`) |
|
|
292
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`) |
|
|
293
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`) |
|
|
294
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`) |
|
|
@@ -419,7 +489,7 @@ If callers accidentally append an inline signature suffix to `trace-symbol-lifec
|
|
|
419
489
|
|
|
420
490
|
For decompile-only `ERR_MAPPING_NOT_APPLIED` failures, error details include `artifactOrigin`, `nextAction`, and `suggestedCall` so clients can recover without guessing.
|
|
421
491
|
|
|
422
|
-
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.
|
|
423
493
|
|
|
424
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.
|
|
425
495
|
|
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",
|