@adhisang/minecraft-modding-mcp 2.0.0 → 2.1.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 +44 -0
- package/README.md +109 -29
- package/dist/cli.js +31 -4
- package/dist/compat-stdio-transport.d.ts +2 -7
- package/dist/compat-stdio-transport.js +12 -154
- package/dist/index.js +392 -33
- package/dist/json-rpc-framing.d.ts +22 -0
- package/dist/json-rpc-framing.js +168 -0
- package/dist/mapping-pipeline-service.js +9 -1
- package/dist/mapping-service.d.ts +9 -0
- package/dist/mapping-service.js +183 -60
- package/dist/minecraft-explorer-service.d.ts +0 -1
- package/dist/minecraft-explorer-service.js +119 -23
- package/dist/mixin-validator.d.ts +24 -2
- package/dist/mixin-validator.js +223 -98
- package/dist/mod-decompile-service.d.ts +5 -0
- package/dist/mod-decompile-service.js +40 -5
- package/dist/mod-remap-service.js +142 -30
- package/dist/path-resolver.js +41 -4
- package/dist/registry-service.d.ts +10 -1
- package/dist/registry-service.js +154 -22
- package/dist/search-hit-accumulator.js +23 -2
- package/dist/source-jar-reader.js +16 -2
- package/dist/source-resolver.js +6 -7
- package/dist/source-service.d.ts +42 -4
- package/dist/source-service.js +781 -127
- package/dist/stdio-supervisor.d.ts +46 -0
- package/dist/stdio-supervisor.js +349 -0
- package/dist/storage/files-repo.d.ts +3 -9
- package/dist/storage/files-repo.js +66 -43
- package/dist/symbols/symbol-extractor.js +6 -4
- package/dist/tool-execution-gate.d.ts +15 -0
- package/dist/tool-execution-gate.js +58 -0
- package/dist/version-diff-service.js +10 -5
- package/dist/version-service.js +7 -2
- package/dist/workspace-mapping-service.js +12 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { analyzeModJar } from "./mod-analyzer.js";
|
|
|
12
12
|
import { remapModJar } from "./mod-remap-service.js";
|
|
13
13
|
import { registerResources } from "./resources.js";
|
|
14
14
|
import { SourceService } from "./source-service.js";
|
|
15
|
+
import { ToolExecutionGate } from "./tool-execution-gate.js";
|
|
15
16
|
if (!process.env.NODE_ENV) {
|
|
16
17
|
process.env.NODE_ENV = "production";
|
|
17
18
|
}
|
|
@@ -28,6 +29,16 @@ const SOURCE_MODES = ["metadata", "snippet", "full"];
|
|
|
28
29
|
const ARTIFACT_SCOPES = ["vanilla", "merged", "loader"];
|
|
29
30
|
const DECODE_COMPRESSIONS = ["none", "gzip", "auto"];
|
|
30
31
|
const ENCODE_COMPRESSIONS = ["none", "gzip"];
|
|
32
|
+
const HEAVY_TOOL_NAMES = new Set([
|
|
33
|
+
"trace-symbol-lifecycle",
|
|
34
|
+
"diff-class-signatures",
|
|
35
|
+
"compare-versions",
|
|
36
|
+
"find-mapping",
|
|
37
|
+
"resolve-method-mapping-exact",
|
|
38
|
+
"get-class-api-matrix",
|
|
39
|
+
"get-registry-data"
|
|
40
|
+
]);
|
|
41
|
+
const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueue: 2 });
|
|
31
42
|
const nonEmptyString = z.string().trim().min(1);
|
|
32
43
|
const optionalNonEmptyString = z.string().trim().min(1).optional();
|
|
33
44
|
const optionalPositiveInt = z.number().int().positive().optional();
|
|
@@ -59,6 +70,9 @@ const sourceLookupTargetSchema = z.discriminatedUnion("type", [
|
|
|
59
70
|
value: nonEmptyString
|
|
60
71
|
})
|
|
61
72
|
]);
|
|
73
|
+
const RESOLVE_ARTIFACT_TARGET_DESCRIPTION = "Object with kind and value. Example: {\"kind\":\"version\",\"value\":\"1.21.10\"}. Must be an object, not a string.";
|
|
74
|
+
const SOURCE_LOOKUP_TARGET_DESCRIPTION = "Object: {\"type\":\"resolve\",\"kind\":\"version\",\"value\":\"1.21.10\"} or {\"type\":\"artifact\",\"artifactId\":\"...\"}. Must be an object, not a string.";
|
|
75
|
+
const SOURCE_SCOPE_DESCRIPTION = 'vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = currently behaves the same as "merged".';
|
|
62
76
|
const listVersionsShape = {
|
|
63
77
|
includeSnapshots: z.boolean().optional().describe("default false"),
|
|
64
78
|
limit: optionalPositiveInt.describe("default 20, max 200")
|
|
@@ -68,12 +82,12 @@ const resolveArtifactShape = {
|
|
|
68
82
|
target: z.object({
|
|
69
83
|
kind: targetKindSchema,
|
|
70
84
|
value: nonEmptyString
|
|
71
|
-
}).describe(
|
|
85
|
+
}).describe(RESOLVE_ARTIFACT_TARGET_DESCRIPTION),
|
|
72
86
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
|
|
73
87
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
74
88
|
allowDecompile: z.boolean().optional().describe("default true"),
|
|
75
89
|
projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
|
|
76
|
-
scope: artifactScopeSchema.optional().describe(
|
|
90
|
+
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
77
91
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
|
|
78
92
|
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
|
|
79
93
|
};
|
|
@@ -81,12 +95,12 @@ const resolveArtifactSchema = z.object(resolveArtifactShape);
|
|
|
81
95
|
const getClassSourceShape = {
|
|
82
96
|
className: nonEmptyString,
|
|
83
97
|
mode: sourceModeSchema.optional().describe("metadata (default) = symbol outline only; snippet = source with default maxLines=200; full = entire source"),
|
|
84
|
-
target: sourceLookupTargetSchema.describe(
|
|
98
|
+
target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
85
99
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
|
|
86
100
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
87
101
|
allowDecompile: z.boolean().optional().describe("default true"),
|
|
88
102
|
projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
|
|
89
|
-
scope: artifactScopeSchema.optional().describe(
|
|
103
|
+
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
90
104
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
|
|
91
105
|
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
|
|
92
106
|
startLine: optionalPositiveInt,
|
|
@@ -110,7 +124,7 @@ const getClassSourceSchema = z
|
|
|
110
124
|
});
|
|
111
125
|
const getClassMembersShape = {
|
|
112
126
|
className: nonEmptyString,
|
|
113
|
-
target: sourceLookupTargetSchema.describe(
|
|
127
|
+
target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
|
|
114
128
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
|
|
115
129
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
116
130
|
allowDecompile: z.boolean().optional().describe("default true"),
|
|
@@ -120,7 +134,7 @@ const getClassMembersShape = {
|
|
|
120
134
|
memberPattern: optionalNonEmptyString,
|
|
121
135
|
maxMembers: optionalPositiveInt.describe("default 500, max 5000"),
|
|
122
136
|
projectPath: optionalNonEmptyString,
|
|
123
|
-
scope: artifactScopeSchema.optional().describe(
|
|
137
|
+
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
124
138
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
|
|
125
139
|
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
|
|
126
140
|
};
|
|
@@ -176,7 +190,8 @@ const diffClassSignaturesShape = {
|
|
|
176
190
|
fromVersion: nonEmptyString,
|
|
177
191
|
toVersion: nonEmptyString,
|
|
178
192
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
|
|
179
|
-
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
|
|
193
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
194
|
+
includeFullDiff: z.boolean().optional().describe("When false, omit from/to snapshots from modified entries and keep only key+changed")
|
|
180
195
|
};
|
|
181
196
|
const diffClassSignaturesSchema = z.object(diffClassSignaturesShape);
|
|
182
197
|
const findMappingShape = {
|
|
@@ -194,7 +209,8 @@ const findMappingShape = {
|
|
|
194
209
|
descriptorHint: optionalNonEmptyString
|
|
195
210
|
})
|
|
196
211
|
.partial()
|
|
197
|
-
.optional()
|
|
212
|
+
.optional(),
|
|
213
|
+
maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
|
|
198
214
|
};
|
|
199
215
|
const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
|
|
200
216
|
if (value.kind === "class") {
|
|
@@ -260,7 +276,8 @@ const resolveMethodMappingExactShape = {
|
|
|
260
276
|
descriptor: nonEmptyString.describe("required JVM descriptor"),
|
|
261
277
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
262
278
|
targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
263
|
-
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
|
|
279
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
280
|
+
maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
|
|
264
281
|
};
|
|
265
282
|
const resolveMethodMappingExactSchema = z
|
|
266
283
|
.object(resolveMethodMappingExactShape)
|
|
@@ -298,7 +315,8 @@ const getClassApiMatrixShape = {
|
|
|
298
315
|
className: nonEmptyString,
|
|
299
316
|
classNameMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
300
317
|
includeKinds: classApiKindsSchema.optional().describe("comma-separated: class,field,method"),
|
|
301
|
-
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
|
|
318
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
319
|
+
maxRows: optionalPositiveInt.describe("Limit returned rows (max 5000)")
|
|
302
320
|
};
|
|
303
321
|
const getClassApiMatrixSchema = z.object(getClassApiMatrixShape);
|
|
304
322
|
const resolveWorkspaceSymbolShape = {
|
|
@@ -309,7 +327,8 @@ const resolveWorkspaceSymbolShape = {
|
|
|
309
327
|
owner: optionalNonEmptyString,
|
|
310
328
|
descriptor: optionalNonEmptyString,
|
|
311
329
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
312
|
-
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
|
|
330
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
331
|
+
maxCandidates: optionalPositiveInt.describe("Limit returned candidates for field/method lookups (default 200, max 200)")
|
|
313
332
|
};
|
|
314
333
|
const resolveWorkspaceSymbolSchema = z
|
|
315
334
|
.object(resolveWorkspaceSymbolShape)
|
|
@@ -380,7 +399,8 @@ const checkSymbolExistsShape = {
|
|
|
380
399
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
381
400
|
nameMode: classNameModeSchema.optional().describe("fqcn | auto (default fqcn)"),
|
|
382
401
|
signatureMode: z.enum(["exact", "name-only"]).optional()
|
|
383
|
-
.describe("exact (default): require descriptor for methods; name-only: match by owner+name only")
|
|
402
|
+
.describe("exact (default): require descriptor for methods; name-only: match by owner+name only"),
|
|
403
|
+
maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
|
|
384
404
|
};
|
|
385
405
|
const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
|
|
386
406
|
if (value.kind === "class") {
|
|
@@ -483,14 +503,18 @@ const validateMixinShape = {
|
|
|
483
503
|
z.object({
|
|
484
504
|
mode: z.literal("config"),
|
|
485
505
|
configPaths: z.array(nonEmptyString).min(1).describe("Path array to mixin config JSON files (e.g. modid.mixins.json)")
|
|
506
|
+
}),
|
|
507
|
+
z.object({
|
|
508
|
+
mode: z.literal("project"),
|
|
509
|
+
path: nonEmptyString.describe("Workspace root path used to discover *.mixins.json files automatically")
|
|
486
510
|
})
|
|
487
|
-
]),
|
|
511
|
+
]).describe("One of { mode: 'inline', source }, { mode: 'path', path }, { mode: 'paths', paths[] }, { mode: 'config', configPaths[] }, or { mode: 'project', path }."),
|
|
488
512
|
sourceRoots: z.array(z.string().min(1)).optional()
|
|
489
513
|
.describe("Array of source roots for multi-module projects (e.g. ['common/src/main/java', 'neoforge/src/main/java'])"),
|
|
490
514
|
version: nonEmptyString.describe("Minecraft version"),
|
|
491
515
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
|
|
492
516
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
493
|
-
scope: artifactScopeSchema.optional().describe(
|
|
517
|
+
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
494
518
|
projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
|
|
495
519
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
|
|
496
520
|
minSeverity: z.enum(["error", "warning", "all"]).optional()
|
|
@@ -503,12 +527,14 @@ const validateMixinShape = {
|
|
|
503
527
|
.describe("'full'=all warnings (default), 'aggregated'=group warnings by category with counts and samples"),
|
|
504
528
|
preferProjectMapping: z.boolean().optional()
|
|
505
529
|
.describe("When true, auto-detect mapping from project config even if mapping is explicitly provided"),
|
|
506
|
-
reportMode: z.enum(["compact", "full"]).optional()
|
|
507
|
-
.describe("'compact' omits
|
|
530
|
+
reportMode: z.enum(["compact", "full", "summary-first"]).optional()
|
|
531
|
+
.describe("'compact' omits heavy per-result detail, 'summary-first' hoists shared provenance/warnings/incomplete reasons, 'full'=everything (default)"),
|
|
508
532
|
warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional()
|
|
509
533
|
.describe("Only include warnings/issues matching these categories (default: all)"),
|
|
510
534
|
treatInfoAsWarning: z.boolean().optional()
|
|
511
|
-
.describe("When false, suppress info-severity structured warnings from output (default true)")
|
|
535
|
+
.describe("When false, suppress info-severity structured warnings from output (default true)"),
|
|
536
|
+
includeIssues: z.boolean().optional()
|
|
537
|
+
.describe("When false, keep summary fields but omit per-result issues[] payloads")
|
|
512
538
|
};
|
|
513
539
|
const validateMixinSchema = z.object(validateMixinShape);
|
|
514
540
|
const validateAccessWidenerShape = {
|
|
@@ -525,7 +551,9 @@ const analyzeModJarShape = {
|
|
|
525
551
|
const analyzeModJarSchema = z.object(analyzeModJarShape);
|
|
526
552
|
const getRegistryDataShape = {
|
|
527
553
|
version: nonEmptyString.describe("Minecraft version (e.g. 1.21)"),
|
|
528
|
-
registry: optionalNonEmptyString.describe('Optional registry name (e.g. "block", "item", "minecraft:biome"). Omit to list all registries.')
|
|
554
|
+
registry: optionalNonEmptyString.describe('Optional registry name (e.g. "block", "item", "minecraft:biome"). Omit to list all registries.'),
|
|
555
|
+
includeData: z.boolean().optional().describe("When false, return registry names/counts without full entry bodies"),
|
|
556
|
+
maxEntriesPerRegistry: optionalPositiveInt.describe("Limit returned entries per registry body")
|
|
529
557
|
};
|
|
530
558
|
const getRegistryDataSchema = z.object(getRegistryDataShape);
|
|
531
559
|
const COMPARE_VERSIONS_CATEGORIES = ["classes", "registry", "all"];
|
|
@@ -540,7 +568,9 @@ const compareVersionsShape = {
|
|
|
540
568
|
const compareVersionsSchema = z.object(compareVersionsShape);
|
|
541
569
|
const decompileModJarShape = {
|
|
542
570
|
jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
|
|
543
|
-
className: optionalNonEmptyString.describe("Optional fully-qualified class name to view source. Omit to list all classes.")
|
|
571
|
+
className: optionalNonEmptyString.describe("Optional fully-qualified class name to view source. Omit to list all classes."),
|
|
572
|
+
includeFiles: z.boolean().optional().describe("When false, omit the full class list and return counts only"),
|
|
573
|
+
maxFiles: optionalPositiveInt.describe("Limit returned class names when files are included")
|
|
544
574
|
};
|
|
545
575
|
const decompileModJarSchema = z.object(decompileModJarShape);
|
|
546
576
|
const getModClassSourceShape = {
|
|
@@ -588,6 +618,9 @@ const server = new McpServer({
|
|
|
588
618
|
name: "@adhisang/minecraft-modding-mcp",
|
|
589
619
|
version: SERVER_VERSION
|
|
590
620
|
});
|
|
621
|
+
// The SDK validates tool args before invoking handlers and returns generic InvalidParams text.
|
|
622
|
+
// Bypass that layer so runTool() remains the single source of truth for validation and error envelopes.
|
|
623
|
+
server.validateToolInput = async (_tool, args) => args;
|
|
591
624
|
const config = loadConfig();
|
|
592
625
|
const nbtLimits = {
|
|
593
626
|
maxInputBytes: config.maxNbtInputBytes,
|
|
@@ -768,8 +801,314 @@ function extractFieldErrorsFromDetails(details) {
|
|
|
768
801
|
.filter((entry) => entry != null);
|
|
769
802
|
return normalized.length > 0 ? normalized : undefined;
|
|
770
803
|
}
|
|
771
|
-
function
|
|
804
|
+
function asObjectRecord(value) {
|
|
805
|
+
return typeof value === "object" && value != null && !Array.isArray(value)
|
|
806
|
+
? value
|
|
807
|
+
: undefined;
|
|
808
|
+
}
|
|
809
|
+
function asNonEmptyString(value) {
|
|
810
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
811
|
+
}
|
|
812
|
+
function asStringArray(value) {
|
|
813
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim())
|
|
814
|
+
? value
|
|
815
|
+
: undefined;
|
|
816
|
+
}
|
|
817
|
+
function truncateSuggestionText(value, maxLength = 500) {
|
|
818
|
+
return value.length > maxLength
|
|
819
|
+
? `${value.slice(0, maxLength)}...`
|
|
820
|
+
: value;
|
|
821
|
+
}
|
|
822
|
+
function parseJsonObjectString(value) {
|
|
823
|
+
if (!value.trim().startsWith("{")) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const parsed = JSON.parse(value);
|
|
828
|
+
return asObjectRecord(parsed);
|
|
829
|
+
}
|
|
830
|
+
catch {
|
|
831
|
+
return undefined;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function inferTargetKindFromString(value) {
|
|
835
|
+
if (/[\\/]/.test(value) || /\.jar$/i.test(value)) {
|
|
836
|
+
return "jar";
|
|
837
|
+
}
|
|
838
|
+
if (value.split(":").length >= 3) {
|
|
839
|
+
return "coordinate";
|
|
840
|
+
}
|
|
841
|
+
return "version";
|
|
842
|
+
}
|
|
843
|
+
function copySourceLookupSuggestionFields(tool, source) {
|
|
844
|
+
const result = {};
|
|
845
|
+
const stringFields = tool === "get-class-source"
|
|
846
|
+
? ["className", "mode", "mapping", "sourcePriority", "projectPath", "scope", "outputFile"]
|
|
847
|
+
: ["className", "mapping", "sourcePriority", "projectPath", "scope", "access", "memberPattern"];
|
|
848
|
+
for (const field of stringFields) {
|
|
849
|
+
const value = source[field];
|
|
850
|
+
if (typeof value === "string" && value.trim()) {
|
|
851
|
+
result[field] = value;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const numericFields = tool === "get-class-source"
|
|
855
|
+
? ["startLine", "endLine", "maxLines", "maxChars"]
|
|
856
|
+
: ["maxMembers"];
|
|
857
|
+
for (const field of numericFields) {
|
|
858
|
+
const value = source[field];
|
|
859
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
860
|
+
result[field] = value;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const booleanFields = tool === "get-class-source"
|
|
864
|
+
? ["allowDecompile", "preferProjectVersion", "strictVersion"]
|
|
865
|
+
: ["allowDecompile", "preferProjectVersion", "strictVersion", "includeSynthetic", "includeInherited"];
|
|
866
|
+
for (const field of booleanFields) {
|
|
867
|
+
const value = source[field];
|
|
868
|
+
if (typeof value === "boolean") {
|
|
869
|
+
result[field] = value;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return result;
|
|
873
|
+
}
|
|
874
|
+
function copyValidateMixinSharedParams(source) {
|
|
875
|
+
const result = {};
|
|
876
|
+
const stringFields = [
|
|
877
|
+
"version",
|
|
878
|
+
"mapping",
|
|
879
|
+
"sourcePriority",
|
|
880
|
+
"scope",
|
|
881
|
+
"projectPath",
|
|
882
|
+
"minSeverity",
|
|
883
|
+
"warningMode",
|
|
884
|
+
"reportMode"
|
|
885
|
+
];
|
|
886
|
+
for (const field of stringFields) {
|
|
887
|
+
const value = source[field];
|
|
888
|
+
if (typeof value === "string" && value.trim()) {
|
|
889
|
+
result[field] = value;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const booleanFields = [
|
|
893
|
+
"preferProjectVersion",
|
|
894
|
+
"hideUncertain",
|
|
895
|
+
"explain",
|
|
896
|
+
"preferProjectMapping",
|
|
897
|
+
"treatInfoAsWarning",
|
|
898
|
+
"includeIssues"
|
|
899
|
+
];
|
|
900
|
+
for (const field of booleanFields) {
|
|
901
|
+
const value = source[field];
|
|
902
|
+
if (typeof value === "boolean") {
|
|
903
|
+
result[field] = value;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const sourceRoots = asStringArray(source.sourceRoots);
|
|
907
|
+
if (sourceRoots) {
|
|
908
|
+
result.sourceRoots = sourceRoots;
|
|
909
|
+
}
|
|
910
|
+
const warningCategoryFilter = asStringArray(source.warningCategoryFilter);
|
|
911
|
+
if (warningCategoryFilter) {
|
|
912
|
+
result.warningCategoryFilter = warningCategoryFilter;
|
|
913
|
+
}
|
|
914
|
+
return result;
|
|
915
|
+
}
|
|
916
|
+
function buildValidateMixinSuggestedParams(normalizedInput) {
|
|
917
|
+
const record = asObjectRecord(normalizedInput);
|
|
918
|
+
if (!record) {
|
|
919
|
+
return {
|
|
920
|
+
input: {
|
|
921
|
+
mode: "inline",
|
|
922
|
+
source: "<Mixin Java source>"
|
|
923
|
+
},
|
|
924
|
+
version: "<minecraft-version>"
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
const inputRecord = asObjectRecord(record.input);
|
|
928
|
+
const shared = copyValidateMixinSharedParams(record);
|
|
929
|
+
const version = asNonEmptyString(record.version) ?? "<minecraft-version>";
|
|
930
|
+
const inlineSource = asNonEmptyString(record.input) ??
|
|
931
|
+
asNonEmptyString(inputRecord?.source) ??
|
|
932
|
+
asNonEmptyString(record.source);
|
|
933
|
+
if (inlineSource) {
|
|
934
|
+
const parsedInlineObject = parseJsonObjectString(inlineSource);
|
|
935
|
+
if (parsedInlineObject && typeof parsedInlineObject.mode === "string") {
|
|
936
|
+
return {
|
|
937
|
+
...shared,
|
|
938
|
+
input: parsedInlineObject,
|
|
939
|
+
version
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
...shared,
|
|
944
|
+
input: {
|
|
945
|
+
mode: "inline",
|
|
946
|
+
source: truncateSuggestionText(inlineSource)
|
|
947
|
+
},
|
|
948
|
+
version
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
const path = asNonEmptyString(inputRecord?.path) ??
|
|
952
|
+
asNonEmptyString(record.sourcePath);
|
|
953
|
+
if (path) {
|
|
954
|
+
return {
|
|
955
|
+
...shared,
|
|
956
|
+
input: {
|
|
957
|
+
mode: "path",
|
|
958
|
+
path
|
|
959
|
+
},
|
|
960
|
+
version
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
const paths = asStringArray(inputRecord?.paths) ??
|
|
964
|
+
asStringArray(record.sourcePaths);
|
|
965
|
+
if (paths) {
|
|
966
|
+
return {
|
|
967
|
+
...shared,
|
|
968
|
+
input: {
|
|
969
|
+
mode: "paths",
|
|
970
|
+
paths
|
|
971
|
+
},
|
|
972
|
+
version
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const configPaths = asStringArray(inputRecord?.configPaths) ??
|
|
976
|
+
(asNonEmptyString(record.mixinConfigPath) ? [record.mixinConfigPath] : undefined);
|
|
977
|
+
if (configPaths) {
|
|
978
|
+
return {
|
|
979
|
+
...shared,
|
|
980
|
+
input: {
|
|
981
|
+
mode: "config",
|
|
982
|
+
configPaths
|
|
983
|
+
},
|
|
984
|
+
version
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
const projectPath = asNonEmptyString(record.projectPath) ??
|
|
988
|
+
(inputRecord?.mode === "project" ? asNonEmptyString(inputRecord.path) : undefined);
|
|
989
|
+
if (projectPath) {
|
|
990
|
+
return {
|
|
991
|
+
...shared,
|
|
992
|
+
input: {
|
|
993
|
+
mode: "project",
|
|
994
|
+
path: projectPath
|
|
995
|
+
},
|
|
996
|
+
version
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
...shared,
|
|
1001
|
+
input: {
|
|
1002
|
+
mode: "inline",
|
|
1003
|
+
source: "<Mixin Java source>"
|
|
1004
|
+
},
|
|
1005
|
+
version
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function buildResolveArtifactSuggestedParams(normalizedInput) {
|
|
1009
|
+
const record = asObjectRecord(normalizedInput);
|
|
1010
|
+
if (!record) {
|
|
1011
|
+
return {
|
|
1012
|
+
target: {
|
|
1013
|
+
kind: "version",
|
|
1014
|
+
value: "<minecraft-version>"
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
const targetValue = asNonEmptyString(record.target);
|
|
1019
|
+
const result = {
|
|
1020
|
+
target: targetValue
|
|
1021
|
+
? {
|
|
1022
|
+
kind: inferTargetKindFromString(targetValue),
|
|
1023
|
+
value: targetValue
|
|
1024
|
+
}
|
|
1025
|
+
: {
|
|
1026
|
+
kind: "version",
|
|
1027
|
+
value: "<minecraft-version>"
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const stringFields = ["mapping", "sourcePriority", "projectPath", "scope"];
|
|
1031
|
+
for (const field of stringFields) {
|
|
1032
|
+
const value = record[field];
|
|
1033
|
+
if (typeof value === "string" && value.trim()) {
|
|
1034
|
+
result[field] = value;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const booleanFields = ["allowDecompile", "preferProjectVersion", "strictVersion"];
|
|
1038
|
+
for (const field of booleanFields) {
|
|
1039
|
+
const value = record[field];
|
|
1040
|
+
if (typeof value === "boolean") {
|
|
1041
|
+
result[field] = value;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return result;
|
|
1045
|
+
}
|
|
1046
|
+
function buildSourceLookupSuggestedParams(tool, normalizedInput) {
|
|
1047
|
+
const record = asObjectRecord(normalizedInput);
|
|
1048
|
+
const result = record ? copySourceLookupSuggestionFields(tool, record) : {};
|
|
1049
|
+
const targetValue = asNonEmptyString(record?.target);
|
|
1050
|
+
result.target = targetValue
|
|
1051
|
+
? {
|
|
1052
|
+
type: "resolve",
|
|
1053
|
+
kind: inferTargetKindFromString(targetValue),
|
|
1054
|
+
value: targetValue
|
|
1055
|
+
}
|
|
1056
|
+
: {
|
|
1057
|
+
type: "resolve",
|
|
1058
|
+
kind: "version",
|
|
1059
|
+
value: "<minecraft-version>"
|
|
1060
|
+
};
|
|
1061
|
+
if (!asNonEmptyString(result.className)) {
|
|
1062
|
+
result.className = "<fully-qualified-class-name>";
|
|
1063
|
+
}
|
|
1064
|
+
return result;
|
|
1065
|
+
}
|
|
1066
|
+
function buildInvalidInputGuidance(tool, normalizedInput) {
|
|
1067
|
+
if (tool === "validate-mixin") {
|
|
1068
|
+
const hints = [
|
|
1069
|
+
"validate-mixin.input must be an object with input.mode = \"inline\" | \"path\" | \"paths\" | \"config\" | \"project\".",
|
|
1070
|
+
"Whole-project example: {\"input\":{\"mode\":\"project\",\"path\":\"/workspace\"},\"version\":\"1.21.10\",\"preferProjectVersion\":true,\"preferProjectMapping\":true}.",
|
|
1071
|
+
"Legacy top-level source/sourcePath/sourcePaths/mixinConfigPath fields are no longer accepted; wrap them under input.mode instead."
|
|
1072
|
+
];
|
|
1073
|
+
return {
|
|
1074
|
+
hints,
|
|
1075
|
+
suggestedCall: {
|
|
1076
|
+
tool,
|
|
1077
|
+
params: buildValidateMixinSuggestedParams(normalizedInput)
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
if (tool === "resolve-artifact") {
|
|
1082
|
+
return {
|
|
1083
|
+
hints: [
|
|
1084
|
+
"resolve-artifact.target must be an object: {\"kind\":\"version|jar|coordinate\",\"value\":\"...\"}.",
|
|
1085
|
+
"Bare string targets are not accepted; wrap the value under target.kind and target.value."
|
|
1086
|
+
],
|
|
1087
|
+
suggestedCall: {
|
|
1088
|
+
tool,
|
|
1089
|
+
params: buildResolveArtifactSuggestedParams(normalizedInput)
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
if (tool === "get-class-source" || tool === "get-class-members") {
|
|
1094
|
+
return {
|
|
1095
|
+
hints: [
|
|
1096
|
+
`${tool}.target must be an object: {"type":"resolve","kind":"version|jar|coordinate","value":"..."} or {"type":"artifact","artifactId":"..."}.`,
|
|
1097
|
+
"Bare string targets are not accepted; wrap the value under target.type/target.kind/target.value."
|
|
1098
|
+
],
|
|
1099
|
+
suggestedCall: {
|
|
1100
|
+
tool,
|
|
1101
|
+
params: buildSourceLookupSuggestedParams(tool, normalizedInput)
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
return undefined;
|
|
1106
|
+
}
|
|
1107
|
+
function mapErrorToProblem(caughtError, requestId, context) {
|
|
772
1108
|
if (caughtError instanceof ZodError) {
|
|
1109
|
+
const guidance = context?.tool
|
|
1110
|
+
? buildInvalidInputGuidance(context.tool, context.normalizedInput)
|
|
1111
|
+
: undefined;
|
|
773
1112
|
return {
|
|
774
1113
|
type: "https://minecraft-modding-mcp.dev/problems/invalid-input",
|
|
775
1114
|
title: "Invalid input",
|
|
@@ -778,7 +1117,8 @@ function mapErrorToProblem(caughtError, requestId) {
|
|
|
778
1117
|
code: ERROR_CODES.INVALID_INPUT,
|
|
779
1118
|
instance: requestId,
|
|
780
1119
|
fieldErrors: toFieldErrorsFromZod(caughtError),
|
|
781
|
-
hints: ["Check fieldErrors and submit a valid tool argument payload."]
|
|
1120
|
+
hints: guidance?.hints ?? ["Check fieldErrors and submit a valid tool argument payload."],
|
|
1121
|
+
...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {})
|
|
782
1122
|
};
|
|
783
1123
|
}
|
|
784
1124
|
if (isAppError(caughtError)) {
|
|
@@ -823,8 +1163,11 @@ function splitWarnings(data) {
|
|
|
823
1163
|
async function runTool(tool, rawInput, schema, action) {
|
|
824
1164
|
const requestId = buildRequestId();
|
|
825
1165
|
const startedAt = Date.now();
|
|
1166
|
+
let normalizedInput = rawInput;
|
|
826
1167
|
try {
|
|
827
|
-
const
|
|
1168
|
+
const preparedInput = prepareToolInput(rawInput);
|
|
1169
|
+
normalizedInput = preparedInput.normalizedInput;
|
|
1170
|
+
const { removedOfficialPaths, suggestedReplacementInput } = preparedInput;
|
|
828
1171
|
if (removedOfficialPaths.length > 0) {
|
|
829
1172
|
throw createError({
|
|
830
1173
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -846,7 +1189,9 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
846
1189
|
});
|
|
847
1190
|
}
|
|
848
1191
|
const parsedInput = schema.parse(normalizedInput);
|
|
849
|
-
const payload = await
|
|
1192
|
+
const payload = await (HEAVY_TOOL_NAMES.has(tool)
|
|
1193
|
+
? heavyToolExecutionGate.run(tool, () => action(parsedInput))
|
|
1194
|
+
: action(parsedInput));
|
|
850
1195
|
const { result, warnings } = splitWarnings(payload);
|
|
851
1196
|
return objectResult({
|
|
852
1197
|
result,
|
|
@@ -859,7 +1204,10 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
859
1204
|
});
|
|
860
1205
|
}
|
|
861
1206
|
catch (caughtError) {
|
|
862
|
-
const problem = mapErrorToProblem(caughtError, requestId
|
|
1207
|
+
const problem = mapErrorToProblem(caughtError, requestId, {
|
|
1208
|
+
tool,
|
|
1209
|
+
normalizedInput
|
|
1210
|
+
});
|
|
863
1211
|
if (isAppError(caughtError)) {
|
|
864
1212
|
const isSevere = caughtError.code === ERROR_CODES.DB_FAILURE ||
|
|
865
1213
|
caughtError.code === ERROR_CODES.REPO_FETCH_FAILED ||
|
|
@@ -1008,7 +1356,8 @@ server.tool("diff-class-signatures", "Compare one class signature between two Mi
|
|
|
1008
1356
|
fromVersion: input.fromVersion,
|
|
1009
1357
|
toVersion: input.toVersion,
|
|
1010
1358
|
mapping: input.mapping,
|
|
1011
|
-
sourcePriority: input.sourcePriority
|
|
1359
|
+
sourcePriority: input.sourcePriority,
|
|
1360
|
+
includeFullDiff: input.includeFullDiff
|
|
1012
1361
|
})));
|
|
1013
1362
|
server.tool("find-mapping", "Find symbol mapping candidates between namespaces using structured symbol inputs for a specific Minecraft version.", findMappingShape, { readOnlyHint: true }, async (args) => runTool("find-mapping", args, findMappingSchema, async (input) => sourceService.findMapping({
|
|
1014
1363
|
version: input.version,
|
|
@@ -1019,7 +1368,8 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
|
|
|
1019
1368
|
sourceMapping: input.sourceMapping,
|
|
1020
1369
|
targetMapping: input.targetMapping,
|
|
1021
1370
|
sourcePriority: input.sourcePriority,
|
|
1022
|
-
disambiguation: input.disambiguation
|
|
1371
|
+
disambiguation: input.disambiguation,
|
|
1372
|
+
maxCandidates: input.maxCandidates
|
|
1023
1373
|
})));
|
|
1024
1374
|
server.tool("resolve-method-mapping-exact", "Resolve one method mapping exactly by owner+name+descriptor between namespaces and report resolved/not_found/ambiguous.", resolveMethodMappingExactShape, { readOnlyHint: true }, async (args) => runTool("resolve-method-mapping-exact", args, resolveMethodMappingExactSchema, async (input) => sourceService.resolveMethodMappingExact({
|
|
1025
1375
|
version: input.version,
|
|
@@ -1028,14 +1378,16 @@ server.tool("resolve-method-mapping-exact", "Resolve one method mapping exactly
|
|
|
1028
1378
|
descriptor: input.descriptor,
|
|
1029
1379
|
sourceMapping: input.sourceMapping,
|
|
1030
1380
|
targetMapping: input.targetMapping,
|
|
1031
|
-
sourcePriority: input.sourcePriority
|
|
1381
|
+
sourcePriority: input.sourcePriority,
|
|
1382
|
+
maxCandidates: input.maxCandidates
|
|
1032
1383
|
})));
|
|
1033
1384
|
server.tool("get-class-api-matrix", "List class/member API rows across obfuscated/mojang/intermediary/yarn mappings for one class and Minecraft version.", getClassApiMatrixShape, { readOnlyHint: true }, async (args) => runTool("get-class-api-matrix", args, getClassApiMatrixSchema, async (input) => sourceService.getClassApiMatrix({
|
|
1034
1385
|
version: input.version,
|
|
1035
1386
|
className: input.className,
|
|
1036
1387
|
classNameMapping: input.classNameMapping,
|
|
1037
1388
|
includeKinds: parseClassApiKinds(input.includeKinds),
|
|
1038
|
-
sourcePriority: input.sourcePriority
|
|
1389
|
+
sourcePriority: input.sourcePriority,
|
|
1390
|
+
maxRows: input.maxRows
|
|
1039
1391
|
})));
|
|
1040
1392
|
server.tool("resolve-workspace-symbol", "Resolve class/field/method names as seen at compile time for a workspace by reading Gradle Loom mapping settings.", resolveWorkspaceSymbolShape, { readOnlyHint: true }, async (args) => runTool("resolve-workspace-symbol", args, resolveWorkspaceSymbolSchema, async (input) => sourceService.resolveWorkspaceSymbol({
|
|
1041
1393
|
projectPath: input.projectPath,
|
|
@@ -1045,7 +1397,8 @@ server.tool("resolve-workspace-symbol", "Resolve class/field/method names as see
|
|
|
1045
1397
|
owner: input.owner,
|
|
1046
1398
|
descriptor: input.descriptor,
|
|
1047
1399
|
sourceMapping: input.sourceMapping,
|
|
1048
|
-
sourcePriority: input.sourcePriority
|
|
1400
|
+
sourcePriority: input.sourcePriority,
|
|
1401
|
+
maxCandidates: input.maxCandidates
|
|
1049
1402
|
})));
|
|
1050
1403
|
server.tool("check-symbol-exists", "Check whether a class/field/method symbol exists in a specific mapping namespace for one Minecraft version.", checkSymbolExistsShape, { readOnlyHint: true }, async (args) => runTool("check-symbol-exists", args, checkSymbolExistsSchema, async (input) => sourceService.checkSymbolExists({
|
|
1051
1404
|
version: input.version,
|
|
@@ -1056,7 +1409,8 @@ server.tool("check-symbol-exists", "Check whether a class/field/method symbol ex
|
|
|
1056
1409
|
sourceMapping: input.sourceMapping,
|
|
1057
1410
|
sourcePriority: input.sourcePriority,
|
|
1058
1411
|
nameMode: input.nameMode,
|
|
1059
|
-
signatureMode: input.signatureMode
|
|
1412
|
+
signatureMode: input.signatureMode,
|
|
1413
|
+
maxCandidates: input.maxCandidates
|
|
1060
1414
|
})));
|
|
1061
1415
|
server.tool("nbt-to-json", "Decode Java Edition NBT binary payload (base64) into typed JSON.", nbtToJsonShape, { readOnlyHint: true }, async (args) => runTool("nbt-to-json", args, nbtToJsonSchema, async (input) => Promise.resolve(nbtBase64ToTypedJson({
|
|
1062
1416
|
nbtBase64: input.nbtBase64,
|
|
@@ -1091,7 +1445,8 @@ server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode
|
|
|
1091
1445
|
preferProjectMapping: input.preferProjectMapping,
|
|
1092
1446
|
reportMode: input.reportMode,
|
|
1093
1447
|
warningCategoryFilter: input.warningCategoryFilter,
|
|
1094
|
-
treatInfoAsWarning: input.treatInfoAsWarning
|
|
1448
|
+
treatInfoAsWarning: input.treatInfoAsWarning,
|
|
1449
|
+
includeIssues: input.includeIssues
|
|
1095
1450
|
})));
|
|
1096
1451
|
server.tool("validate-access-widener", "Validate Access Widener file entries against Minecraft bytecode signatures for a given version.", validateAccessWidenerShape, { readOnlyHint: true }, async (args) => runTool("validate-access-widener", args, validateAccessWidenerSchema, async (input) => sourceService.validateAccessWidener({
|
|
1097
1452
|
content: input.content,
|
|
@@ -1107,7 +1462,9 @@ server.tool("analyze-mod-jar", "Analyze a Minecraft mod JAR to extract loader ty
|
|
|
1107
1462
|
}));
|
|
1108
1463
|
server.tool("get-registry-data", "Get Minecraft registry data (blocks, items, biomes, etc.) for a specific version by running the server data generator.", getRegistryDataShape, { readOnlyHint: true }, async (args) => runTool("get-registry-data", args, getRegistryDataSchema, async (input) => sourceService.getRegistryData({
|
|
1109
1464
|
version: input.version,
|
|
1110
|
-
registry: input.registry
|
|
1465
|
+
registry: input.registry,
|
|
1466
|
+
includeData: input.includeData,
|
|
1467
|
+
maxEntriesPerRegistry: input.maxEntriesPerRegistry
|
|
1111
1468
|
})));
|
|
1112
1469
|
server.tool("compare-versions", "Compare two Minecraft versions to find added/removed classes and registry entry changes. Useful for understanding what changed between versions during mod migration.", compareVersionsShape, { readOnlyHint: true }, async (args) => runTool("compare-versions", args, compareVersionsSchema, async (input) => sourceService.compareVersions({
|
|
1113
1470
|
fromVersion: input.fromVersion,
|
|
@@ -1118,7 +1475,9 @@ server.tool("compare-versions", "Compare two Minecraft versions to find added/re
|
|
|
1118
1475
|
})));
|
|
1119
1476
|
server.tool("decompile-mod-jar", "Decompile a Minecraft mod JAR using Vineflower and list available classes, or view a specific class source. Builds on analyze-mod-jar by exposing the actual source code.", decompileModJarShape, { readOnlyHint: true }, async (args) => runTool("decompile-mod-jar", args, decompileModJarSchema, async (input) => sourceService.decompileModJar({
|
|
1120
1477
|
jarPath: input.jarPath,
|
|
1121
|
-
className: input.className
|
|
1478
|
+
className: input.className,
|
|
1479
|
+
includeFiles: input.includeFiles,
|
|
1480
|
+
maxFiles: input.maxFiles
|
|
1122
1481
|
})));
|
|
1123
1482
|
server.tool("get-mod-class-source", "Get decompiled source code for a specific class in a mod JAR. The mod JAR will be decompiled if not already cached.", getModClassSourceShape, { readOnlyHint: true }, async (args) => runTool("get-mod-class-source", args, getModClassSourceSchema, async (input) => sourceService.getModClassSource({
|
|
1124
1483
|
jarPath: input.jarPath,
|