@adhisang/minecraft-modding-mcp 2.0.0 → 3.0.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +139 -30
  3. package/dist/cache-registry.d.ts +95 -0
  4. package/dist/cache-registry.js +541 -0
  5. package/dist/cli.js +31 -4
  6. package/dist/compat-stdio-transport.d.ts +2 -7
  7. package/dist/compat-stdio-transport.js +12 -154
  8. package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
  9. package/dist/entry-tools/analyze-mod-service.js +253 -0
  10. package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
  11. package/dist/entry-tools/analyze-symbol-service.js +304 -0
  12. package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
  13. package/dist/entry-tools/compare-minecraft-service.js +397 -0
  14. package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
  15. package/dist/entry-tools/entry-tool-schema.js +10 -0
  16. package/dist/entry-tools/inspect-minecraft-service.d.ts +1953 -0
  17. package/dist/entry-tools/inspect-minecraft-service.js +876 -0
  18. package/dist/entry-tools/manage-cache-service.d.ts +130 -0
  19. package/dist/entry-tools/manage-cache-service.js +229 -0
  20. package/dist/entry-tools/request-normalizers.d.ts +10 -0
  21. package/dist/entry-tools/request-normalizers.js +36 -0
  22. package/dist/entry-tools/response-contract.d.ts +44 -0
  23. package/dist/entry-tools/response-contract.js +96 -0
  24. package/dist/entry-tools/validate-project-service.d.ts +543 -0
  25. package/dist/entry-tools/validate-project-service.js +381 -0
  26. package/dist/index.js +495 -42
  27. package/dist/json-rpc-framing.d.ts +22 -0
  28. package/dist/json-rpc-framing.js +168 -0
  29. package/dist/mapping-pipeline-service.js +9 -1
  30. package/dist/mapping-service.d.ts +9 -0
  31. package/dist/mapping-service.js +183 -60
  32. package/dist/minecraft-explorer-service.d.ts +0 -1
  33. package/dist/minecraft-explorer-service.js +119 -23
  34. package/dist/mixin-validator.d.ts +24 -2
  35. package/dist/mixin-validator.js +223 -98
  36. package/dist/mod-decompile-service.d.ts +5 -0
  37. package/dist/mod-decompile-service.js +40 -5
  38. package/dist/mod-remap-service.js +142 -30
  39. package/dist/path-resolver.js +41 -4
  40. package/dist/registry-service.d.ts +10 -1
  41. package/dist/registry-service.js +154 -22
  42. package/dist/search-hit-accumulator.js +23 -2
  43. package/dist/source-jar-reader.js +16 -2
  44. package/dist/source-resolver.js +6 -7
  45. package/dist/source-service.d.ts +42 -4
  46. package/dist/source-service.js +781 -127
  47. package/dist/stdio-supervisor.d.ts +46 -0
  48. package/dist/stdio-supervisor.js +349 -0
  49. package/dist/storage/files-repo.d.ts +3 -9
  50. package/dist/storage/files-repo.js +66 -43
  51. package/dist/symbols/symbol-extractor.js +6 -4
  52. package/dist/tool-execution-gate.d.ts +15 -0
  53. package/dist/tool-execution-gate.js +58 -0
  54. package/dist/version-diff-service.js +10 -5
  55. package/dist/version-service.js +7 -2
  56. package/dist/workspace-mapping-service.js +12 -0
  57. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12,6 +12,16 @@ 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";
16
+ import { WorkspaceMappingService } from "./workspace-mapping-service.js";
17
+ import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape } from "./entry-tools/inspect-minecraft-service.js";
18
+ import { AnalyzeSymbolService, analyzeSymbolSchema, analyzeSymbolShape } from "./entry-tools/analyze-symbol-service.js";
19
+ import { CompareMinecraftService, compareMinecraftSchema, compareMinecraftShape } from "./entry-tools/compare-minecraft-service.js";
20
+ import { AnalyzeModService, analyzeModSchema, analyzeModShape } from "./entry-tools/analyze-mod-service.js";
21
+ import { ValidateProjectService, validateProjectSchema, validateProjectShape, discoverWorkspaceAccessWideners, discoverWorkspaceMixins } from "./entry-tools/validate-project-service.js";
22
+ import { ManageCacheService, manageCacheSchema, manageCacheShape } from "./entry-tools/manage-cache-service.js";
23
+ import { createCacheRegistry } from "./cache-registry.js";
24
+ import { buildEntryToolMeta } from "./entry-tools/response-contract.js";
15
25
  if (!process.env.NODE_ENV) {
16
26
  process.env.NODE_ENV = "production";
17
27
  }
@@ -28,6 +38,24 @@ const SOURCE_MODES = ["metadata", "snippet", "full"];
28
38
  const ARTIFACT_SCOPES = ["vanilla", "merged", "loader"];
29
39
  const DECODE_COMPRESSIONS = ["none", "gzip", "auto"];
30
40
  const ENCODE_COMPRESSIONS = ["none", "gzip"];
41
+ const HEAVY_TOOL_NAMES = new Set([
42
+ "trace-symbol-lifecycle",
43
+ "diff-class-signatures",
44
+ "compare-versions",
45
+ "find-mapping",
46
+ "resolve-method-mapping-exact",
47
+ "get-class-api-matrix",
48
+ "get-registry-data"
49
+ ]);
50
+ const ENTRY_TOOL_NAMES = new Set([
51
+ "inspect-minecraft",
52
+ "analyze-symbol",
53
+ "compare-minecraft",
54
+ "analyze-mod",
55
+ "validate-project",
56
+ "manage-cache"
57
+ ]);
58
+ const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueue: 2 });
31
59
  const nonEmptyString = z.string().trim().min(1);
32
60
  const optionalNonEmptyString = z.string().trim().min(1).optional();
33
61
  const optionalPositiveInt = z.number().int().positive().optional();
@@ -59,6 +87,9 @@ const sourceLookupTargetSchema = z.discriminatedUnion("type", [
59
87
  value: nonEmptyString
60
88
  })
61
89
  ]);
90
+ const RESOLVE_ARTIFACT_TARGET_DESCRIPTION = "Object with kind and value. Example: {\"kind\":\"version\",\"value\":\"1.21.10\"}. Must be an object, not a string.";
91
+ 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.";
92
+ const SOURCE_SCOPE_DESCRIPTION = 'vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = currently behaves the same as "merged".';
62
93
  const listVersionsShape = {
63
94
  includeSnapshots: z.boolean().optional().describe("default false"),
64
95
  limit: optionalPositiveInt.describe("default 20, max 200")
@@ -68,12 +99,12 @@ const resolveArtifactShape = {
68
99
  target: z.object({
69
100
  kind: targetKindSchema,
70
101
  value: nonEmptyString
71
- }).describe("Resolve target: { kind: version|jar|coordinate, value }"),
102
+ }).describe(RESOLVE_ARTIFACT_TARGET_DESCRIPTION),
72
103
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
73
104
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
74
105
  allowDecompile: z.boolean().optional().describe("default true"),
75
106
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
76
- scope: artifactScopeSchema.optional().describe("vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = loader-specific"),
107
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
77
108
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
78
109
  strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
79
110
  };
@@ -81,12 +112,12 @@ const resolveArtifactSchema = z.object(resolveArtifactShape);
81
112
  const getClassSourceShape = {
82
113
  className: nonEmptyString,
83
114
  mode: sourceModeSchema.optional().describe("metadata (default) = symbol outline only; snippet = source with default maxLines=200; full = entire source"),
84
- target: sourceLookupTargetSchema.describe("Either { type: 'artifact', artifactId } or { type: 'resolve', kind, value }"),
115
+ target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
85
116
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
86
117
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
87
118
  allowDecompile: z.boolean().optional().describe("default true"),
88
119
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
89
- scope: artifactScopeSchema.optional().describe("vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = loader-specific"),
120
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
90
121
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
91
122
  strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
92
123
  startLine: optionalPositiveInt,
@@ -110,7 +141,7 @@ const getClassSourceSchema = z
110
141
  });
111
142
  const getClassMembersShape = {
112
143
  className: nonEmptyString,
113
- target: sourceLookupTargetSchema.describe("Either { type: 'artifact', artifactId } or { type: 'resolve', kind, value }"),
144
+ target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
114
145
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
115
146
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
116
147
  allowDecompile: z.boolean().optional().describe("default true"),
@@ -120,7 +151,7 @@ const getClassMembersShape = {
120
151
  memberPattern: optionalNonEmptyString,
121
152
  maxMembers: optionalPositiveInt.describe("default 500, max 5000"),
122
153
  projectPath: optionalNonEmptyString,
123
- scope: artifactScopeSchema.optional().describe("vanilla | merged | loader"),
154
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
124
155
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
125
156
  strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
126
157
  };
@@ -176,7 +207,8 @@ const diffClassSignaturesShape = {
176
207
  fromVersion: nonEmptyString,
177
208
  toVersion: nonEmptyString,
178
209
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
179
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
210
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
211
+ includeFullDiff: z.boolean().optional().describe("When false, omit from/to snapshots from modified entries and keep only key+changed")
180
212
  };
181
213
  const diffClassSignaturesSchema = z.object(diffClassSignaturesShape);
182
214
  const findMappingShape = {
@@ -194,7 +226,8 @@ const findMappingShape = {
194
226
  descriptorHint: optionalNonEmptyString
195
227
  })
196
228
  .partial()
197
- .optional()
229
+ .optional(),
230
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
198
231
  };
199
232
  const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
200
233
  if (value.kind === "class") {
@@ -260,7 +293,8 @@ const resolveMethodMappingExactShape = {
260
293
  descriptor: nonEmptyString.describe("required JVM descriptor"),
261
294
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
262
295
  targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
263
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
296
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
297
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
264
298
  };
265
299
  const resolveMethodMappingExactSchema = z
266
300
  .object(resolveMethodMappingExactShape)
@@ -298,7 +332,8 @@ const getClassApiMatrixShape = {
298
332
  className: nonEmptyString,
299
333
  classNameMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
300
334
  includeKinds: classApiKindsSchema.optional().describe("comma-separated: class,field,method"),
301
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
335
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
336
+ maxRows: optionalPositiveInt.describe("Limit returned rows (max 5000)")
302
337
  };
303
338
  const getClassApiMatrixSchema = z.object(getClassApiMatrixShape);
304
339
  const resolveWorkspaceSymbolShape = {
@@ -309,7 +344,8 @@ const resolveWorkspaceSymbolShape = {
309
344
  owner: optionalNonEmptyString,
310
345
  descriptor: optionalNonEmptyString,
311
346
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
312
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
347
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
348
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates for field/method lookups (default 200, max 200)")
313
349
  };
314
350
  const resolveWorkspaceSymbolSchema = z
315
351
  .object(resolveWorkspaceSymbolShape)
@@ -380,7 +416,8 @@ const checkSymbolExistsShape = {
380
416
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
381
417
  nameMode: classNameModeSchema.optional().describe("fqcn | auto (default fqcn)"),
382
418
  signatureMode: z.enum(["exact", "name-only"]).optional()
383
- .describe("exact (default): require descriptor for methods; name-only: match by owner+name only")
419
+ .describe("exact (default): require descriptor for methods; name-only: match by owner+name only"),
420
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
384
421
  };
385
422
  const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
386
423
  if (value.kind === "class") {
@@ -483,14 +520,18 @@ const validateMixinShape = {
483
520
  z.object({
484
521
  mode: z.literal("config"),
485
522
  configPaths: z.array(nonEmptyString).min(1).describe("Path array to mixin config JSON files (e.g. modid.mixins.json)")
523
+ }),
524
+ z.object({
525
+ mode: z.literal("project"),
526
+ path: nonEmptyString.describe("Workspace root path used to discover *.mixins.json files automatically")
486
527
  })
487
- ]),
528
+ ]).describe("One of { mode: 'inline', source }, { mode: 'path', path }, { mode: 'paths', paths[] }, { mode: 'config', configPaths[] }, or { mode: 'project', path }."),
488
529
  sourceRoots: z.array(z.string().min(1)).optional()
489
530
  .describe("Array of source roots for multi-module projects (e.g. ['common/src/main/java', 'neoforge/src/main/java'])"),
490
531
  version: nonEmptyString.describe("Minecraft version"),
491
532
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
492
533
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
493
- scope: artifactScopeSchema.optional().describe("vanilla | merged | loader"),
534
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
494
535
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
495
536
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
496
537
  minSeverity: z.enum(["error", "warning", "all"]).optional()
@@ -503,12 +544,14 @@ const validateMixinShape = {
503
544
  .describe("'full'=all warnings (default), 'aggregated'=group warnings by category with counts and samples"),
504
545
  preferProjectMapping: z.boolean().optional()
505
546
  .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 resolvedMembers/structuredWarnings/toolHealth details, 'full'=everything (default)"),
547
+ reportMode: z.enum(["compact", "full", "summary-first"]).optional()
548
+ .describe("'compact' omits heavy per-result detail, 'summary-first' hoists shared provenance/warnings/incomplete reasons, 'full'=everything (default)"),
508
549
  warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional()
509
550
  .describe("Only include warnings/issues matching these categories (default: all)"),
510
551
  treatInfoAsWarning: z.boolean().optional()
511
- .describe("When false, suppress info-severity structured warnings from output (default true)")
552
+ .describe("When false, suppress info-severity structured warnings from output (default true)"),
553
+ includeIssues: z.boolean().optional()
554
+ .describe("When false, keep summary fields but omit per-result issues[] payloads")
512
555
  };
513
556
  const validateMixinSchema = z.object(validateMixinShape);
514
557
  const validateAccessWidenerShape = {
@@ -525,7 +568,9 @@ const analyzeModJarShape = {
525
568
  const analyzeModJarSchema = z.object(analyzeModJarShape);
526
569
  const getRegistryDataShape = {
527
570
  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.')
571
+ registry: optionalNonEmptyString.describe('Optional registry name (e.g. "block", "item", "minecraft:biome"). Omit to list all registries.'),
572
+ includeData: z.boolean().optional().describe("When false, return registry names/counts without full entry bodies"),
573
+ maxEntriesPerRegistry: optionalPositiveInt.describe("Limit returned entries per registry body")
529
574
  };
530
575
  const getRegistryDataSchema = z.object(getRegistryDataShape);
531
576
  const COMPARE_VERSIONS_CATEGORIES = ["classes", "registry", "all"];
@@ -540,7 +585,9 @@ const compareVersionsShape = {
540
585
  const compareVersionsSchema = z.object(compareVersionsShape);
541
586
  const decompileModJarShape = {
542
587
  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.")
588
+ className: optionalNonEmptyString.describe("Optional fully-qualified class name to view source. Omit to list all classes."),
589
+ includeFiles: z.boolean().optional().describe("When false, omit the full class list and return counts only"),
590
+ maxFiles: optionalPositiveInt.describe("Limit returned class names when files are included")
544
591
  };
545
592
  const decompileModJarSchema = z.object(decompileModJarShape);
546
593
  const getModClassSourceShape = {
@@ -588,6 +635,9 @@ const server = new McpServer({
588
635
  name: "@adhisang/minecraft-modding-mcp",
589
636
  version: SERVER_VERSION
590
637
  });
638
+ // The SDK validates tool args before invoking handlers and returns generic InvalidParams text.
639
+ // Bypass that layer so runTool() remains the single source of truth for validation and error envelopes.
640
+ server.validateToolInput = async (_tool, args) => args;
591
641
  const config = loadConfig();
592
642
  const nbtLimits = {
593
643
  maxInputBytes: config.maxNbtInputBytes,
@@ -606,6 +656,50 @@ const sourceService = new Proxy({}, {
606
656
  return typeof value === "function" ? value.bind(service) : value;
607
657
  }
608
658
  });
659
+ const workspaceMappingService = new WorkspaceMappingService();
660
+ const inspectMinecraftService = new InspectMinecraftService({
661
+ listVersions: (input) => sourceService.listVersions(input),
662
+ resolveArtifact: (input) => sourceService.resolveArtifact(input),
663
+ findClass: (input) => Promise.resolve(sourceService.findClass(input)),
664
+ getClassSource: (input) => sourceService.getClassSource(input),
665
+ getClassMembers: (input) => sourceService.getClassMembers(input),
666
+ searchClassSource: (input) => sourceService.searchClassSource(input),
667
+ getArtifactFile: (input) => sourceService.getArtifactFile(input),
668
+ listArtifactFiles: (input) => sourceService.listArtifactFiles(input),
669
+ detectProjectMinecraftVersion: (projectPath) => workspaceMappingService.detectProjectMinecraftVersion(projectPath)
670
+ });
671
+ const analyzeSymbolService = new AnalyzeSymbolService({
672
+ checkSymbolExists: (input) => sourceService.checkSymbolExists(input),
673
+ findMapping: (input) => sourceService.findMapping(input),
674
+ resolveMethodMappingExact: (input) => sourceService.resolveMethodMappingExact(input),
675
+ traceSymbolLifecycle: (input) => sourceService.traceSymbolLifecycle(input),
676
+ resolveWorkspaceSymbol: (input) => sourceService.resolveWorkspaceSymbol(input),
677
+ getClassApiMatrix: (input) => sourceService.getClassApiMatrix(input)
678
+ });
679
+ const compareMinecraftService = new CompareMinecraftService({
680
+ compareVersions: (input) => sourceService.compareVersions(input),
681
+ diffClassSignatures: (input) => sourceService.diffClassSignatures(input),
682
+ getRegistryData: (input) => sourceService.getRegistryData(input)
683
+ });
684
+ const analyzeModService = new AnalyzeModService({
685
+ analyzeModJar: (jarPath, options) => analyzeModJar(jarPath, options),
686
+ decompileModJar: (input) => sourceService.decompileModJar(input),
687
+ getModClassSource: (input) => sourceService.getModClassSource(input),
688
+ searchModSource: (input) => sourceService.searchModSource(input),
689
+ remapModJar: (input) => remapModJar(input, config)
690
+ });
691
+ const validateProjectService = new ValidateProjectService({
692
+ validateMixin: (input) => sourceService.validateMixin(input),
693
+ validateAccessWidener: (input) => sourceService.validateAccessWidener(input),
694
+ discoverMixins: discoverWorkspaceMixins,
695
+ discoverAccessWideners: discoverWorkspaceAccessWideners
696
+ });
697
+ const manageCacheService = new ManageCacheService({
698
+ registry: createCacheRegistry({
699
+ cacheDir: config.cacheDir,
700
+ sqlitePath: config.sqlitePath
701
+ })
702
+ });
609
703
  registerResources(server, sourceService);
610
704
  let processHandlersAttached = false;
611
705
  let serverStarted = false;
@@ -768,8 +862,314 @@ function extractFieldErrorsFromDetails(details) {
768
862
  .filter((entry) => entry != null);
769
863
  return normalized.length > 0 ? normalized : undefined;
770
864
  }
771
- function mapErrorToProblem(caughtError, requestId) {
865
+ function asObjectRecord(value) {
866
+ return typeof value === "object" && value != null && !Array.isArray(value)
867
+ ? value
868
+ : undefined;
869
+ }
870
+ function asNonEmptyString(value) {
871
+ return typeof value === "string" && value.trim() ? value : undefined;
872
+ }
873
+ function asStringArray(value) {
874
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string" && entry.trim())
875
+ ? value
876
+ : undefined;
877
+ }
878
+ function truncateSuggestionText(value, maxLength = 500) {
879
+ return value.length > maxLength
880
+ ? `${value.slice(0, maxLength)}...`
881
+ : value;
882
+ }
883
+ function parseJsonObjectString(value) {
884
+ if (!value.trim().startsWith("{")) {
885
+ return undefined;
886
+ }
887
+ try {
888
+ const parsed = JSON.parse(value);
889
+ return asObjectRecord(parsed);
890
+ }
891
+ catch {
892
+ return undefined;
893
+ }
894
+ }
895
+ function inferTargetKindFromString(value) {
896
+ if (/[\\/]/.test(value) || /\.jar$/i.test(value)) {
897
+ return "jar";
898
+ }
899
+ if (value.split(":").length >= 3) {
900
+ return "coordinate";
901
+ }
902
+ return "version";
903
+ }
904
+ function copySourceLookupSuggestionFields(tool, source) {
905
+ const result = {};
906
+ const stringFields = tool === "get-class-source"
907
+ ? ["className", "mode", "mapping", "sourcePriority", "projectPath", "scope", "outputFile"]
908
+ : ["className", "mapping", "sourcePriority", "projectPath", "scope", "access", "memberPattern"];
909
+ for (const field of stringFields) {
910
+ const value = source[field];
911
+ if (typeof value === "string" && value.trim()) {
912
+ result[field] = value;
913
+ }
914
+ }
915
+ const numericFields = tool === "get-class-source"
916
+ ? ["startLine", "endLine", "maxLines", "maxChars"]
917
+ : ["maxMembers"];
918
+ for (const field of numericFields) {
919
+ const value = source[field];
920
+ if (typeof value === "number" && Number.isFinite(value)) {
921
+ result[field] = value;
922
+ }
923
+ }
924
+ const booleanFields = tool === "get-class-source"
925
+ ? ["allowDecompile", "preferProjectVersion", "strictVersion"]
926
+ : ["allowDecompile", "preferProjectVersion", "strictVersion", "includeSynthetic", "includeInherited"];
927
+ for (const field of booleanFields) {
928
+ const value = source[field];
929
+ if (typeof value === "boolean") {
930
+ result[field] = value;
931
+ }
932
+ }
933
+ return result;
934
+ }
935
+ function copyValidateMixinSharedParams(source) {
936
+ const result = {};
937
+ const stringFields = [
938
+ "version",
939
+ "mapping",
940
+ "sourcePriority",
941
+ "scope",
942
+ "projectPath",
943
+ "minSeverity",
944
+ "warningMode",
945
+ "reportMode"
946
+ ];
947
+ for (const field of stringFields) {
948
+ const value = source[field];
949
+ if (typeof value === "string" && value.trim()) {
950
+ result[field] = value;
951
+ }
952
+ }
953
+ const booleanFields = [
954
+ "preferProjectVersion",
955
+ "hideUncertain",
956
+ "explain",
957
+ "preferProjectMapping",
958
+ "treatInfoAsWarning",
959
+ "includeIssues"
960
+ ];
961
+ for (const field of booleanFields) {
962
+ const value = source[field];
963
+ if (typeof value === "boolean") {
964
+ result[field] = value;
965
+ }
966
+ }
967
+ const sourceRoots = asStringArray(source.sourceRoots);
968
+ if (sourceRoots) {
969
+ result.sourceRoots = sourceRoots;
970
+ }
971
+ const warningCategoryFilter = asStringArray(source.warningCategoryFilter);
972
+ if (warningCategoryFilter) {
973
+ result.warningCategoryFilter = warningCategoryFilter;
974
+ }
975
+ return result;
976
+ }
977
+ function buildValidateMixinSuggestedParams(normalizedInput) {
978
+ const record = asObjectRecord(normalizedInput);
979
+ if (!record) {
980
+ return {
981
+ input: {
982
+ mode: "inline",
983
+ source: "<Mixin Java source>"
984
+ },
985
+ version: "<minecraft-version>"
986
+ };
987
+ }
988
+ const inputRecord = asObjectRecord(record.input);
989
+ const shared = copyValidateMixinSharedParams(record);
990
+ const version = asNonEmptyString(record.version) ?? "<minecraft-version>";
991
+ const inlineSource = asNonEmptyString(record.input) ??
992
+ asNonEmptyString(inputRecord?.source) ??
993
+ asNonEmptyString(record.source);
994
+ if (inlineSource) {
995
+ const parsedInlineObject = parseJsonObjectString(inlineSource);
996
+ if (parsedInlineObject && typeof parsedInlineObject.mode === "string") {
997
+ return {
998
+ ...shared,
999
+ input: parsedInlineObject,
1000
+ version
1001
+ };
1002
+ }
1003
+ return {
1004
+ ...shared,
1005
+ input: {
1006
+ mode: "inline",
1007
+ source: truncateSuggestionText(inlineSource)
1008
+ },
1009
+ version
1010
+ };
1011
+ }
1012
+ const path = asNonEmptyString(inputRecord?.path) ??
1013
+ asNonEmptyString(record.sourcePath);
1014
+ if (path) {
1015
+ return {
1016
+ ...shared,
1017
+ input: {
1018
+ mode: "path",
1019
+ path
1020
+ },
1021
+ version
1022
+ };
1023
+ }
1024
+ const paths = asStringArray(inputRecord?.paths) ??
1025
+ asStringArray(record.sourcePaths);
1026
+ if (paths) {
1027
+ return {
1028
+ ...shared,
1029
+ input: {
1030
+ mode: "paths",
1031
+ paths
1032
+ },
1033
+ version
1034
+ };
1035
+ }
1036
+ const configPaths = asStringArray(inputRecord?.configPaths) ??
1037
+ (asNonEmptyString(record.mixinConfigPath) ? [record.mixinConfigPath] : undefined);
1038
+ if (configPaths) {
1039
+ return {
1040
+ ...shared,
1041
+ input: {
1042
+ mode: "config",
1043
+ configPaths
1044
+ },
1045
+ version
1046
+ };
1047
+ }
1048
+ const projectPath = asNonEmptyString(record.projectPath) ??
1049
+ (inputRecord?.mode === "project" ? asNonEmptyString(inputRecord.path) : undefined);
1050
+ if (projectPath) {
1051
+ return {
1052
+ ...shared,
1053
+ input: {
1054
+ mode: "project",
1055
+ path: projectPath
1056
+ },
1057
+ version
1058
+ };
1059
+ }
1060
+ return {
1061
+ ...shared,
1062
+ input: {
1063
+ mode: "inline",
1064
+ source: "<Mixin Java source>"
1065
+ },
1066
+ version
1067
+ };
1068
+ }
1069
+ function buildResolveArtifactSuggestedParams(normalizedInput) {
1070
+ const record = asObjectRecord(normalizedInput);
1071
+ if (!record) {
1072
+ return {
1073
+ target: {
1074
+ kind: "version",
1075
+ value: "<minecraft-version>"
1076
+ }
1077
+ };
1078
+ }
1079
+ const targetValue = asNonEmptyString(record.target);
1080
+ const result = {
1081
+ target: targetValue
1082
+ ? {
1083
+ kind: inferTargetKindFromString(targetValue),
1084
+ value: targetValue
1085
+ }
1086
+ : {
1087
+ kind: "version",
1088
+ value: "<minecraft-version>"
1089
+ }
1090
+ };
1091
+ const stringFields = ["mapping", "sourcePriority", "projectPath", "scope"];
1092
+ for (const field of stringFields) {
1093
+ const value = record[field];
1094
+ if (typeof value === "string" && value.trim()) {
1095
+ result[field] = value;
1096
+ }
1097
+ }
1098
+ const booleanFields = ["allowDecompile", "preferProjectVersion", "strictVersion"];
1099
+ for (const field of booleanFields) {
1100
+ const value = record[field];
1101
+ if (typeof value === "boolean") {
1102
+ result[field] = value;
1103
+ }
1104
+ }
1105
+ return result;
1106
+ }
1107
+ function buildSourceLookupSuggestedParams(tool, normalizedInput) {
1108
+ const record = asObjectRecord(normalizedInput);
1109
+ const result = record ? copySourceLookupSuggestionFields(tool, record) : {};
1110
+ const targetValue = asNonEmptyString(record?.target);
1111
+ result.target = targetValue
1112
+ ? {
1113
+ type: "resolve",
1114
+ kind: inferTargetKindFromString(targetValue),
1115
+ value: targetValue
1116
+ }
1117
+ : {
1118
+ type: "resolve",
1119
+ kind: "version",
1120
+ value: "<minecraft-version>"
1121
+ };
1122
+ if (!asNonEmptyString(result.className)) {
1123
+ result.className = "<fully-qualified-class-name>";
1124
+ }
1125
+ return result;
1126
+ }
1127
+ function buildInvalidInputGuidance(tool, normalizedInput) {
1128
+ if (tool === "validate-mixin") {
1129
+ const hints = [
1130
+ "validate-mixin.input must be an object with input.mode = \"inline\" | \"path\" | \"paths\" | \"config\" | \"project\".",
1131
+ "Whole-project example: {\"input\":{\"mode\":\"project\",\"path\":\"/workspace\"},\"version\":\"1.21.10\",\"preferProjectVersion\":true,\"preferProjectMapping\":true}.",
1132
+ "Legacy top-level source/sourcePath/sourcePaths/mixinConfigPath fields are no longer accepted; wrap them under input.mode instead."
1133
+ ];
1134
+ return {
1135
+ hints,
1136
+ suggestedCall: {
1137
+ tool,
1138
+ params: buildValidateMixinSuggestedParams(normalizedInput)
1139
+ }
1140
+ };
1141
+ }
1142
+ if (tool === "resolve-artifact") {
1143
+ return {
1144
+ hints: [
1145
+ "resolve-artifact.target must be an object: {\"kind\":\"version|jar|coordinate\",\"value\":\"...\"}.",
1146
+ "Bare string targets are not accepted; wrap the value under target.kind and target.value."
1147
+ ],
1148
+ suggestedCall: {
1149
+ tool,
1150
+ params: buildResolveArtifactSuggestedParams(normalizedInput)
1151
+ }
1152
+ };
1153
+ }
1154
+ if (tool === "get-class-source" || tool === "get-class-members") {
1155
+ return {
1156
+ hints: [
1157
+ `${tool}.target must be an object: {"type":"resolve","kind":"version|jar|coordinate","value":"..."} or {"type":"artifact","artifactId":"..."}.`,
1158
+ "Bare string targets are not accepted; wrap the value under target.type/target.kind/target.value."
1159
+ ],
1160
+ suggestedCall: {
1161
+ tool,
1162
+ params: buildSourceLookupSuggestedParams(tool, normalizedInput)
1163
+ }
1164
+ };
1165
+ }
1166
+ return undefined;
1167
+ }
1168
+ function mapErrorToProblem(caughtError, requestId, context) {
772
1169
  if (caughtError instanceof ZodError) {
1170
+ const guidance = context?.tool
1171
+ ? buildInvalidInputGuidance(context.tool, context.normalizedInput)
1172
+ : undefined;
773
1173
  return {
774
1174
  type: "https://minecraft-modding-mcp.dev/problems/invalid-input",
775
1175
  title: "Invalid input",
@@ -778,7 +1178,8 @@ function mapErrorToProblem(caughtError, requestId) {
778
1178
  code: ERROR_CODES.INVALID_INPUT,
779
1179
  instance: requestId,
780
1180
  fieldErrors: toFieldErrorsFromZod(caughtError),
781
- hints: ["Check fieldErrors and submit a valid tool argument payload."]
1181
+ hints: guidance?.hints ?? ["Check fieldErrors and submit a valid tool argument payload."],
1182
+ ...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {})
782
1183
  };
783
1184
  }
784
1185
  if (isAppError(caughtError)) {
@@ -806,25 +1207,37 @@ function mapErrorToProblem(caughtError, requestId) {
806
1207
  }
807
1208
  function splitWarnings(data) {
808
1209
  const result = { ...data };
1210
+ const warnings = [];
809
1211
  const maybeWarnings = result.warnings;
810
- if (!Array.isArray(maybeWarnings)) {
811
- return {
812
- result,
813
- warnings: []
814
- };
1212
+ if (Array.isArray(maybeWarnings)) {
1213
+ warnings.push(...maybeWarnings.filter((entry) => typeof entry === "string"));
1214
+ delete result.warnings;
1215
+ }
1216
+ let meta = {};
1217
+ const maybeMeta = result.meta;
1218
+ if (maybeMeta && typeof maybeMeta === "object" && !Array.isArray(maybeMeta)) {
1219
+ meta = { ...maybeMeta };
1220
+ delete result.meta;
1221
+ const metaWarnings = meta.warnings;
1222
+ if (Array.isArray(metaWarnings)) {
1223
+ warnings.push(...metaWarnings.filter((entry) => typeof entry === "string"));
1224
+ delete meta.warnings;
1225
+ }
815
1226
  }
816
- const warnings = maybeWarnings.filter((entry) => typeof entry === "string");
817
- delete result.warnings;
818
1227
  return {
819
1228
  result,
820
- warnings
1229
+ warnings: [...new Set(warnings)],
1230
+ meta
821
1231
  };
822
1232
  }
823
1233
  async function runTool(tool, rawInput, schema, action) {
824
1234
  const requestId = buildRequestId();
825
1235
  const startedAt = Date.now();
1236
+ let normalizedInput = rawInput;
826
1237
  try {
827
- const { normalizedInput, removedOfficialPaths, suggestedReplacementInput } = prepareToolInput(rawInput);
1238
+ const preparedInput = prepareToolInput(rawInput);
1239
+ normalizedInput = preparedInput.normalizedInput;
1240
+ const { removedOfficialPaths, suggestedReplacementInput } = preparedInput;
828
1241
  if (removedOfficialPaths.length > 0) {
829
1242
  throw createError({
830
1243
  code: ERROR_CODES.INVALID_INPUT,
@@ -846,11 +1259,31 @@ async function runTool(tool, rawInput, schema, action) {
846
1259
  });
847
1260
  }
848
1261
  const parsedInput = schema.parse(normalizedInput);
849
- const payload = await action(parsedInput);
850
- const { result, warnings } = splitWarnings(payload);
1262
+ const payload = await (HEAVY_TOOL_NAMES.has(tool)
1263
+ ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
1264
+ : action(parsedInput));
1265
+ const { result, warnings, meta: resultMeta } = splitWarnings(payload);
1266
+ const entryMeta = ENTRY_TOOL_NAMES.has(tool)
1267
+ ? buildEntryToolMeta({
1268
+ detail: normalizedInput &&
1269
+ typeof normalizedInput === "object" &&
1270
+ !Array.isArray(normalizedInput) &&
1271
+ typeof normalizedInput.detail === "string"
1272
+ ? normalizedInput.detail ?? "summary"
1273
+ : "summary",
1274
+ include: normalizedInput &&
1275
+ typeof normalizedInput === "object" &&
1276
+ !Array.isArray(normalizedInput) &&
1277
+ Array.isArray(normalizedInput.include)
1278
+ ? normalizedInput.include
1279
+ : undefined
1280
+ })
1281
+ : undefined;
851
1282
  return objectResult({
852
1283
  result,
853
1284
  meta: {
1285
+ ...(entryMeta ?? {}),
1286
+ ...resultMeta,
854
1287
  requestId,
855
1288
  tool,
856
1289
  durationMs: Date.now() - startedAt,
@@ -859,7 +1292,10 @@ async function runTool(tool, rawInput, schema, action) {
859
1292
  });
860
1293
  }
861
1294
  catch (caughtError) {
862
- const problem = mapErrorToProblem(caughtError, requestId);
1295
+ const problem = mapErrorToProblem(caughtError, requestId, {
1296
+ tool,
1297
+ normalizedInput
1298
+ });
863
1299
  if (isAppError(caughtError)) {
864
1300
  const isSevere = caughtError.code === ERROR_CODES.DB_FAILURE ||
865
1301
  caughtError.code === ERROR_CODES.REPO_FETCH_FAILED ||
@@ -905,6 +1341,12 @@ server.tool("list-versions", "List available Minecraft versions from Mojang mani
905
1341
  includeSnapshots: input.includeSnapshots,
906
1342
  limit: input.limit
907
1343
  })));
1344
+ server.tool("inspect-minecraft", "High-level v3 entry 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)));
1345
+ server.tool("analyze-symbol", "High-level v3 entry tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
1346
+ server.tool("compare-minecraft", "High-level v3 entry tool for version comparisons, class diffs, registry diffs, and migration overviews.", compareMinecraftShape, { readOnlyHint: true }, async (args) => runTool("compare-minecraft", args, compareMinecraftSchema, async (input) => compareMinecraftService.execute(input)));
1347
+ server.tool("analyze-mod", "High-level v3 entry tool for mod metadata inspection, decompile/search flows, class source, and safe remap previews/applies.", analyzeModShape, { readOnlyHint: false }, async (args) => runTool("analyze-mod", args, analyzeModSchema, async (input) => analyzeModService.execute(input)));
1348
+ server.tool("validate-project", "High-level v3 entry tool for project summary, direct mixin validation, and access widener validation.", validateProjectShape, { readOnlyHint: true }, async (args) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input)));
1349
+ server.tool("manage-cache", "High-level v3 entry tool for cache summaries, listing, verification, previewed mutation, and explicit apply operations.", manageCacheShape, { readOnlyHint: false }, async (args) => runTool("manage-cache", args, manageCacheSchema, async (input) => manageCacheService.execute(input)));
908
1350
  server.tool("resolve-artifact", "Resolve source artifact from a target object ({ kind, value }) and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted; other adjacent *-sources.jar files are informational.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
909
1351
  target: input.target,
910
1352
  mapping: input.mapping,
@@ -1008,7 +1450,8 @@ server.tool("diff-class-signatures", "Compare one class signature between two Mi
1008
1450
  fromVersion: input.fromVersion,
1009
1451
  toVersion: input.toVersion,
1010
1452
  mapping: input.mapping,
1011
- sourcePriority: input.sourcePriority
1453
+ sourcePriority: input.sourcePriority,
1454
+ includeFullDiff: input.includeFullDiff
1012
1455
  })));
1013
1456
  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
1457
  version: input.version,
@@ -1019,7 +1462,8 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
1019
1462
  sourceMapping: input.sourceMapping,
1020
1463
  targetMapping: input.targetMapping,
1021
1464
  sourcePriority: input.sourcePriority,
1022
- disambiguation: input.disambiguation
1465
+ disambiguation: input.disambiguation,
1466
+ maxCandidates: input.maxCandidates
1023
1467
  })));
1024
1468
  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
1469
  version: input.version,
@@ -1028,14 +1472,16 @@ server.tool("resolve-method-mapping-exact", "Resolve one method mapping exactly
1028
1472
  descriptor: input.descriptor,
1029
1473
  sourceMapping: input.sourceMapping,
1030
1474
  targetMapping: input.targetMapping,
1031
- sourcePriority: input.sourcePriority
1475
+ sourcePriority: input.sourcePriority,
1476
+ maxCandidates: input.maxCandidates
1032
1477
  })));
1033
1478
  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
1479
  version: input.version,
1035
1480
  className: input.className,
1036
1481
  classNameMapping: input.classNameMapping,
1037
1482
  includeKinds: parseClassApiKinds(input.includeKinds),
1038
- sourcePriority: input.sourcePriority
1483
+ sourcePriority: input.sourcePriority,
1484
+ maxRows: input.maxRows
1039
1485
  })));
1040
1486
  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
1487
  projectPath: input.projectPath,
@@ -1045,7 +1491,8 @@ server.tool("resolve-workspace-symbol", "Resolve class/field/method names as see
1045
1491
  owner: input.owner,
1046
1492
  descriptor: input.descriptor,
1047
1493
  sourceMapping: input.sourceMapping,
1048
- sourcePriority: input.sourcePriority
1494
+ sourcePriority: input.sourcePriority,
1495
+ maxCandidates: input.maxCandidates
1049
1496
  })));
1050
1497
  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
1498
  version: input.version,
@@ -1056,7 +1503,8 @@ server.tool("check-symbol-exists", "Check whether a class/field/method symbol ex
1056
1503
  sourceMapping: input.sourceMapping,
1057
1504
  sourcePriority: input.sourcePriority,
1058
1505
  nameMode: input.nameMode,
1059
- signatureMode: input.signatureMode
1506
+ signatureMode: input.signatureMode,
1507
+ maxCandidates: input.maxCandidates
1060
1508
  })));
1061
1509
  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
1510
  nbtBase64: input.nbtBase64,
@@ -1091,7 +1539,8 @@ server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode
1091
1539
  preferProjectMapping: input.preferProjectMapping,
1092
1540
  reportMode: input.reportMode,
1093
1541
  warningCategoryFilter: input.warningCategoryFilter,
1094
- treatInfoAsWarning: input.treatInfoAsWarning
1542
+ treatInfoAsWarning: input.treatInfoAsWarning,
1543
+ includeIssues: input.includeIssues
1095
1544
  })));
1096
1545
  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
1546
  content: input.content,
@@ -1107,7 +1556,9 @@ server.tool("analyze-mod-jar", "Analyze a Minecraft mod JAR to extract loader ty
1107
1556
  }));
1108
1557
  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
1558
  version: input.version,
1110
- registry: input.registry
1559
+ registry: input.registry,
1560
+ includeData: input.includeData,
1561
+ maxEntriesPerRegistry: input.maxEntriesPerRegistry
1111
1562
  })));
1112
1563
  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
1564
  fromVersion: input.fromVersion,
@@ -1118,7 +1569,9 @@ server.tool("compare-versions", "Compare two Minecraft versions to find added/re
1118
1569
  })));
1119
1570
  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
1571
  jarPath: input.jarPath,
1121
- className: input.className
1572
+ className: input.className,
1573
+ includeFiles: input.includeFiles,
1574
+ maxFiles: input.maxFiles
1122
1575
  })));
1123
1576
  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
1577
  jarPath: input.jarPath,