@adhisang/minecraft-modding-mcp 1.2.1 → 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.
Files changed (51) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +184 -64
  3. package/dist/cli.js +31 -4
  4. package/dist/compat-stdio-transport.d.ts +2 -7
  5. package/dist/compat-stdio-transport.js +12 -154
  6. package/dist/index.js +537 -202
  7. package/dist/json-rpc-framing.d.ts +22 -0
  8. package/dist/json-rpc-framing.js +168 -0
  9. package/dist/mapping-pipeline-service.d.ts +1 -1
  10. package/dist/mapping-pipeline-service.js +13 -5
  11. package/dist/mapping-service.d.ts +12 -4
  12. package/dist/mapping-service.js +222 -105
  13. package/dist/mcp-helpers.d.ts +10 -2
  14. package/dist/mcp-helpers.js +59 -5
  15. package/dist/minecraft-explorer-service.d.ts +1 -2
  16. package/dist/minecraft-explorer-service.js +120 -24
  17. package/dist/mixin-validator.d.ts +24 -2
  18. package/dist/mixin-validator.js +228 -103
  19. package/dist/mod-decompile-service.d.ts +5 -0
  20. package/dist/mod-decompile-service.js +40 -5
  21. package/dist/mod-remap-service.js +142 -30
  22. package/dist/mojang-tiny-mapping-service.js +26 -26
  23. package/dist/path-resolver.js +41 -4
  24. package/dist/registry-service.d.ts +10 -1
  25. package/dist/registry-service.js +154 -22
  26. package/dist/resources.js +7 -7
  27. package/dist/search-hit-accumulator.d.ts +0 -3
  28. package/dist/search-hit-accumulator.js +27 -6
  29. package/dist/source-jar-reader.js +16 -2
  30. package/dist/source-resolver.d.ts +1 -0
  31. package/dist/source-resolver.js +93 -2
  32. package/dist/source-service.d.ts +76 -47
  33. package/dist/source-service.js +1344 -763
  34. package/dist/stdio-supervisor.d.ts +46 -0
  35. package/dist/stdio-supervisor.js +349 -0
  36. package/dist/storage/files-repo.d.ts +3 -0
  37. package/dist/storage/files-repo.js +66 -1
  38. package/dist/storage/migrations.d.ts +1 -1
  39. package/dist/storage/migrations.js +6 -2
  40. package/dist/storage/schema.d.ts +1 -0
  41. package/dist/storage/schema.js +7 -0
  42. package/dist/symbols/symbol-extractor.js +6 -4
  43. package/dist/tool-execution-gate.d.ts +15 -0
  44. package/dist/tool-execution-gate.js +58 -0
  45. package/dist/tool-input.d.ts +6 -0
  46. package/dist/tool-input.js +64 -0
  47. package/dist/types.d.ts +1 -1
  48. package/dist/version-diff-service.js +10 -5
  49. package/dist/version-service.js +7 -2
  50. package/dist/workspace-mapping-service.js +12 -0
  51. package/package.json +4 -1
package/dist/index.js CHANGED
@@ -3,18 +3,20 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { ZodError, z } from "zod";
4
4
  import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
5
5
  import { objectResult } from "./mcp-helpers.js";
6
+ import { prepareToolInput } from "./tool-input.js";
6
7
  import { loadConfig } from "./config.js";
7
- import { ERROR_CODES, isAppError } from "./errors.js";
8
+ import { createError, ERROR_CODES, isAppError } from "./errors.js";
8
9
  import { log } from "./logger.js";
9
10
  import { applyNbtJsonPatch, nbtBase64ToTypedJson, typedJsonToNbtBase64 } from "./nbt/pipeline.js";
10
11
  import { analyzeModJar } from "./mod-analyzer.js";
11
12
  import { remapModJar } from "./mod-remap-service.js";
12
13
  import { registerResources } from "./resources.js";
13
14
  import { SourceService } from "./source-service.js";
15
+ import { ToolExecutionGate } from "./tool-execution-gate.js";
14
16
  if (!process.env.NODE_ENV) {
15
17
  process.env.NODE_ENV = "production";
16
18
  }
17
- const SOURCE_MAPPINGS = ["official", "mojang", "intermediary", "yarn"];
19
+ const SOURCE_MAPPINGS = ["obfuscated", "mojang", "intermediary", "yarn"];
18
20
  const SOURCE_PRIORITIES = ["loom-first", "maven-first"];
19
21
  const TARGET_KINDS = ["version", "jar", "coordinate"];
20
22
  const SEARCH_INTENTS = ["symbol", "text", "path"];
@@ -27,6 +29,16 @@ const SOURCE_MODES = ["metadata", "snippet", "full"];
27
29
  const ARTIFACT_SCOPES = ["vanilla", "merged", "loader"];
28
30
  const DECODE_COMPRESSIONS = ["none", "gzip", "auto"];
29
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 });
30
42
  const nonEmptyString = z.string().trim().min(1);
31
43
  const optionalNonEmptyString = z.string().trim().min(1).optional();
32
44
  const optionalPositiveInt = z.number().int().positive().optional();
@@ -43,63 +55,53 @@ const sourceModeSchema = z.enum(SOURCE_MODES);
43
55
  const artifactScopeSchema = z.enum(ARTIFACT_SCOPES);
44
56
  const decodeCompressionSchema = z.enum(DECODE_COMPRESSIONS);
45
57
  const encodeCompressionSchema = z.enum(ENCODE_COMPRESSIONS);
46
- function validateTargetPair(value, ctx) {
47
- const hasArtifactId = Boolean(value.artifactId);
48
- const hasTargetKind = value.targetKind !== undefined;
49
- const hasTargetValue = value.targetValue !== undefined;
50
- if (hasArtifactId && (hasTargetKind || hasTargetValue)) {
51
- ctx.addIssue({
52
- code: z.ZodIssueCode.custom,
53
- message: "artifactId and targetKind/targetValue are mutually exclusive.",
54
- path: ["artifactId"]
55
- });
56
- return;
57
- }
58
- if (hasTargetKind !== hasTargetValue) {
59
- ctx.addIssue({
60
- code: z.ZodIssueCode.custom,
61
- message: "targetKind and targetValue must be provided together.",
62
- path: [hasTargetKind ? "targetValue" : "targetKind"]
63
- });
64
- return;
65
- }
66
- if (!hasArtifactId && !hasTargetKind) {
67
- ctx.addIssue({
68
- code: z.ZodIssueCode.custom,
69
- message: "Either artifactId or targetKind+targetValue must be provided.",
70
- path: ["artifactId"]
71
- });
72
- }
73
- }
58
+ const resolveArtifactTargetSchema = z.object({
59
+ kind: targetKindSchema,
60
+ value: nonEmptyString
61
+ });
62
+ const sourceLookupTargetSchema = z.discriminatedUnion("type", [
63
+ z.object({
64
+ type: z.literal("artifact"),
65
+ artifactId: nonEmptyString
66
+ }),
67
+ z.object({
68
+ type: z.literal("resolve"),
69
+ kind: targetKindSchema,
70
+ value: nonEmptyString
71
+ })
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".';
74
76
  const listVersionsShape = {
75
77
  includeSnapshots: z.boolean().optional().describe("default false"),
76
78
  limit: optionalPositiveInt.describe("default 20, max 200")
77
79
  };
78
80
  const listVersionsSchema = z.object(listVersionsShape);
79
81
  const resolveArtifactShape = {
80
- targetKind: targetKindSchema.describe("version | jar | coordinate"),
81
- targetValue: nonEmptyString,
82
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
82
+ target: z.object({
83
+ kind: targetKindSchema,
84
+ value: nonEmptyString
85
+ }).describe(RESOLVE_ARTIFACT_TARGET_DESCRIPTION),
86
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
83
87
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
84
88
  allowDecompile: z.boolean().optional().describe("default true"),
85
89
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
86
- scope: artifactScopeSchema.optional().describe("vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = loader-specific"),
87
- preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override targetValue"),
90
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
91
+ preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
88
92
  strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
89
93
  };
90
94
  const resolveArtifactSchema = z.object(resolveArtifactShape);
91
95
  const getClassSourceShape = {
92
96
  className: nonEmptyString,
93
97
  mode: sourceModeSchema.optional().describe("metadata (default) = symbol outline only; snippet = source with default maxLines=200; full = entire source"),
94
- artifactId: optionalNonEmptyString,
95
- targetKind: targetKindSchema.optional().describe("version | jar | coordinate"),
96
- targetValue: optionalNonEmptyString,
97
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
98
+ target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
99
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
98
100
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
99
101
  allowDecompile: z.boolean().optional().describe("default true"),
100
102
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
101
- scope: artifactScopeSchema.optional().describe("vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = loader-specific"),
102
- preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override targetValue"),
103
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
104
+ preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
103
105
  strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
104
106
  startLine: optionalPositiveInt,
105
107
  endLine: optionalPositiveInt,
@@ -110,11 +112,6 @@ const getClassSourceShape = {
110
112
  const getClassSourceSchema = z
111
113
  .object(getClassSourceShape)
112
114
  .superRefine((value, ctx) => {
113
- validateTargetPair({
114
- artifactId: value.artifactId,
115
- targetKind: value.targetKind,
116
- targetValue: value.targetValue
117
- }, ctx);
118
115
  if (value.startLine !== undefined &&
119
116
  value.endLine !== undefined &&
120
117
  value.startLine > value.endLine) {
@@ -127,10 +124,8 @@ const getClassSourceSchema = z
127
124
  });
128
125
  const getClassMembersShape = {
129
126
  className: nonEmptyString,
130
- artifactId: optionalNonEmptyString,
131
- targetKind: targetKindSchema.optional().describe("version | jar | coordinate"),
132
- targetValue: optionalNonEmptyString,
133
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn (default official)"),
127
+ target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
128
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
134
129
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
135
130
  allowDecompile: z.boolean().optional().describe("default true"),
136
131
  access: memberAccessSchema.optional().describe("public | all (default public)"),
@@ -139,19 +134,11 @@ const getClassMembersShape = {
139
134
  memberPattern: optionalNonEmptyString,
140
135
  maxMembers: optionalPositiveInt.describe("default 500, max 5000"),
141
136
  projectPath: optionalNonEmptyString,
142
- scope: artifactScopeSchema.optional().describe("vanilla | merged | loader"),
137
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
143
138
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
144
139
  strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
145
140
  };
146
- const getClassMembersSchema = z
147
- .object(getClassMembersShape)
148
- .superRefine((value, ctx) => {
149
- validateTargetPair({
150
- artifactId: value.artifactId,
151
- targetKind: value.targetKind,
152
- targetValue: value.targetValue
153
- }, ctx);
154
- });
141
+ const getClassMembersSchema = z.object(getClassMembersShape);
155
142
  const searchClassSourceShape = {
156
143
  artifactId: nonEmptyString,
157
144
  query: nonEmptyString,
@@ -160,14 +147,19 @@ const searchClassSourceShape = {
160
147
  packagePrefix: optionalNonEmptyString,
161
148
  fileGlob: optionalNonEmptyString,
162
149
  symbolKind: searchSymbolKindSchema.optional().describe("class | interface | enum | record | method | field"),
163
- snippetLines: optionalPositiveInt.describe("default 8, clamp 1..80"),
164
- includeDefinition: z.boolean().optional().describe("default false"),
165
- includeOneHop: z.boolean().optional().describe("default false"),
166
150
  queryMode: z.enum(["auto", "token", "literal"]).optional().describe("auto (default): FTS5 with literal fallback for separator queries; token: FTS5 only; literal: substring scan only"),
167
151
  limit: optionalPositiveInt.describe("default 20"),
168
152
  cursor: optionalNonEmptyString
169
153
  };
170
- const searchClassSourceSchema = z.object(searchClassSourceShape);
154
+ const searchClassSourceSchema = z.object(searchClassSourceShape).superRefine((value, ctx) => {
155
+ if (value.symbolKind && value.intent && value.intent !== "symbol") {
156
+ ctx.addIssue({
157
+ code: z.ZodIssueCode.custom,
158
+ path: ["symbolKind"],
159
+ message: 'symbolKind filter is only supported when intent="symbol".'
160
+ });
161
+ }
162
+ });
171
163
  const getArtifactFileShape = {
172
164
  artifactId: nonEmptyString,
173
165
  filePath: nonEmptyString,
@@ -186,7 +178,7 @@ const traceSymbolLifecycleShape = {
186
178
  descriptor: optionalNonEmptyString.describe('optional JVM descriptor, e.g. "(I)V"'),
187
179
  fromVersion: optionalNonEmptyString,
188
180
  toVersion: optionalNonEmptyString,
189
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn (default official)"),
181
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
190
182
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
191
183
  includeSnapshots: z.boolean().optional().describe("default false"),
192
184
  maxVersions: optionalPositiveInt.describe("default 120, max 400"),
@@ -197,8 +189,9 @@ const diffClassSignaturesShape = {
197
189
  className: nonEmptyString,
198
190
  fromVersion: nonEmptyString,
199
191
  toVersion: nonEmptyString,
200
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn (default official)"),
201
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
192
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
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")
202
195
  };
203
196
  const diffClassSignaturesSchema = z.object(diffClassSignaturesShape);
204
197
  const findMappingShape = {
@@ -207,8 +200,8 @@ const findMappingShape = {
207
200
  name: nonEmptyString,
208
201
  owner: optionalNonEmptyString,
209
202
  descriptor: optionalNonEmptyString,
210
- sourceMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
211
- targetMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
203
+ sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
204
+ targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
212
205
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
213
206
  disambiguation: z
214
207
  .object({
@@ -216,7 +209,8 @@ const findMappingShape = {
216
209
  descriptorHint: optionalNonEmptyString
217
210
  })
218
211
  .partial()
219
- .optional()
212
+ .optional(),
213
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
220
214
  };
221
215
  const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
222
216
  if (value.kind === "class") {
@@ -277,42 +271,21 @@ const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) =>
277
271
  });
278
272
  const resolveMethodMappingExactShape = {
279
273
  version: nonEmptyString,
280
- kind: workspaceSymbolKindSchema.describe("class | field | method"),
281
274
  name: nonEmptyString,
282
- owner: optionalNonEmptyString,
283
- descriptor: optionalNonEmptyString.describe("required for kind=method"),
284
- sourceMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
285
- targetMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
286
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
275
+ owner: nonEmptyString,
276
+ descriptor: nonEmptyString.describe("required JVM descriptor"),
277
+ sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
278
+ targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
279
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
280
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
287
281
  };
288
282
  const resolveMethodMappingExactSchema = z
289
283
  .object(resolveMethodMappingExactShape)
290
284
  .superRefine((value, ctx) => {
291
- if (value.kind !== "method") {
292
- ctx.addIssue({
293
- code: z.ZodIssueCode.custom,
294
- message: "resolve-method-mapping-exact requires kind=method.",
295
- path: ["kind"]
296
- });
297
- }
298
- if (!value.owner) {
299
- ctx.addIssue({
300
- code: z.ZodIssueCode.custom,
301
- message: "owner is required when kind=method.",
302
- path: ["owner"]
303
- });
304
- }
305
- if (!value.descriptor) {
306
- ctx.addIssue({
307
- code: z.ZodIssueCode.custom,
308
- message: "descriptor is required when kind=method.",
309
- path: ["descriptor"]
310
- });
311
- }
312
285
  if (/[\s./()]/.test(value.name)) {
313
286
  ctx.addIssue({
314
287
  code: z.ZodIssueCode.custom,
315
- message: "name must be a simple method name when kind=method.",
288
+ message: "name must be a simple method name.",
316
289
  path: ["name"]
317
290
  });
318
291
  }
@@ -340,9 +313,10 @@ const classApiKindsSchema = z.string().superRefine((value, ctx) => {
340
313
  const getClassApiMatrixShape = {
341
314
  version: nonEmptyString,
342
315
  className: nonEmptyString,
343
- classNameMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
316
+ classNameMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
344
317
  includeKinds: classApiKindsSchema.optional().describe("comma-separated: class,field,method"),
345
- 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)")
346
320
  };
347
321
  const getClassApiMatrixSchema = z.object(getClassApiMatrixShape);
348
322
  const resolveWorkspaceSymbolShape = {
@@ -352,8 +326,9 @@ const resolveWorkspaceSymbolShape = {
352
326
  name: nonEmptyString,
353
327
  owner: optionalNonEmptyString,
354
328
  descriptor: optionalNonEmptyString,
355
- sourceMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
356
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
329
+ sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
330
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
331
+ maxCandidates: optionalPositiveInt.describe("Limit returned candidates for field/method lookups (default 200, max 200)")
357
332
  };
358
333
  const resolveWorkspaceSymbolSchema = z
359
334
  .object(resolveWorkspaceSymbolShape)
@@ -420,11 +395,12 @@ const checkSymbolExistsShape = {
420
395
  owner: optionalNonEmptyString,
421
396
  name: nonEmptyString,
422
397
  descriptor: optionalNonEmptyString.describe("required for kind=method unless signatureMode=name-only"),
423
- sourceMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
398
+ sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
424
399
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
425
400
  nameMode: classNameModeSchema.optional().describe("fqcn | auto (default fqcn)"),
426
401
  signatureMode: z.enum(["exact", "name-only"]).optional()
427
- .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)")
428
404
  };
429
405
  const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
430
406
  if (value.kind === "class") {
@@ -511,17 +487,34 @@ const indexArtifactShape = {
511
487
  };
512
488
  const indexArtifactSchema = z.object(indexArtifactShape);
513
489
  const validateMixinShape = {
514
- source: optionalNonEmptyString.describe("Mixin Java source text (mutually exclusive with sourcePath/sourcePaths/mixinConfigPath)"),
515
- sourcePath: optionalNonEmptyString.describe("Path to Mixin .java file (alternative to source/sourcePaths/mixinConfigPath)"),
516
- sourcePaths: z.array(z.string().min(1)).optional().describe("Array of Mixin .java file paths for batch validation"),
517
- mixinConfigPath: z.union([nonEmptyString, z.array(nonEmptyString).min(1)]).optional().describe("Path (or array of paths) to mixin config JSON (e.g. modid.mixins.json); auto-discovers and batch-validates all listed classes"),
518
- sourceRoot: optionalNonEmptyString.describe("Source root relative to projectPath (default 'src/main/java')"),
490
+ input: z.discriminatedUnion("mode", [
491
+ z.object({
492
+ mode: z.literal("inline"),
493
+ source: nonEmptyString.describe("Mixin Java source text")
494
+ }),
495
+ z.object({
496
+ mode: z.literal("path"),
497
+ path: nonEmptyString.describe("Path to a Mixin .java file")
498
+ }),
499
+ z.object({
500
+ mode: z.literal("paths"),
501
+ paths: z.array(nonEmptyString).min(1).describe("Array of Mixin .java file paths for batch validation")
502
+ }),
503
+ z.object({
504
+ mode: z.literal("config"),
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")
510
+ })
511
+ ]).describe("One of { mode: 'inline', source }, { mode: 'path', path }, { mode: 'paths', paths[] }, { mode: 'config', configPaths[] }, or { mode: 'project', path }."),
519
512
  sourceRoots: z.array(z.string().min(1)).optional()
520
513
  .describe("Array of source roots for multi-module projects (e.g. ['common/src/main/java', 'neoforge/src/main/java'])"),
521
514
  version: nonEmptyString.describe("Minecraft version"),
522
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
515
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
523
516
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
524
- scope: artifactScopeSchema.optional().describe("vanilla | merged | loader"),
517
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
525
518
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
526
519
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
527
520
  minSeverity: z.enum(["error", "warning", "all"]).optional()
@@ -534,25 +527,20 @@ const validateMixinShape = {
534
527
  .describe("'full'=all warnings (default), 'aggregated'=group warnings by category with counts and samples"),
535
528
  preferProjectMapping: z.boolean().optional()
536
529
  .describe("When true, auto-detect mapping from project config even if mapping is explicitly provided"),
537
- reportMode: z.enum(["compact", "full"]).optional()
538
- .describe("'compact' omits resolvedMembers/structuredWarnings/toolHealth details, 'full'=everything (default)"),
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)"),
539
532
  warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional()
540
533
  .describe("Only include warnings/issues matching these categories (default: all)"),
541
534
  treatInfoAsWarning: z.boolean().optional()
542
- .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")
543
538
  };
544
- const validateMixinSchema = z.object(validateMixinShape).refine((d) => {
545
- const hasSource = d.source != null;
546
- const hasSourcePath = d.sourcePath != null;
547
- const hasSourcePaths = d.sourcePaths != null && d.sourcePaths.length > 0;
548
- const hasMixinConfig = d.mixinConfigPath != null && (typeof d.mixinConfigPath === "string" || (Array.isArray(d.mixinConfigPath) && d.mixinConfigPath.length > 0));
549
- // Exactly one of the four must be provided
550
- return [hasSource, hasSourcePath, hasSourcePaths, hasMixinConfig].filter(Boolean).length === 1;
551
- }, { message: "Exactly one of 'source', 'sourcePath', 'sourcePaths', or 'mixinConfigPath' must be provided." });
539
+ const validateMixinSchema = z.object(validateMixinShape);
552
540
  const validateAccessWidenerShape = {
553
541
  content: nonEmptyString.describe("Access Widener file content"),
554
542
  version: nonEmptyString.describe("Minecraft version"),
555
- mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
543
+ mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
556
544
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
557
545
  };
558
546
  const validateAccessWidenerSchema = z.object(validateAccessWidenerShape);
@@ -563,7 +551,9 @@ const analyzeModJarShape = {
563
551
  const analyzeModJarSchema = z.object(analyzeModJarShape);
564
552
  const getRegistryDataShape = {
565
553
  version: nonEmptyString.describe("Minecraft version (e.g. 1.21)"),
566
- 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")
567
557
  };
568
558
  const getRegistryDataSchema = z.object(getRegistryDataShape);
569
559
  const COMPARE_VERSIONS_CATEGORIES = ["classes", "registry", "all"];
@@ -578,7 +568,9 @@ const compareVersionsShape = {
578
568
  const compareVersionsSchema = z.object(compareVersionsShape);
579
569
  const decompileModJarShape = {
580
570
  jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
581
- 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")
582
574
  };
583
575
  const decompileModJarSchema = z.object(decompileModJarShape);
584
576
  const getModClassSourceShape = {
@@ -626,6 +618,9 @@ const server = new McpServer({
626
618
  name: "@adhisang/minecraft-modding-mcp",
627
619
  version: SERVER_VERSION
628
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;
629
624
  const config = loadConfig();
630
625
  const nbtLimits = {
631
626
  maxInputBytes: config.maxNbtInputBytes,
@@ -672,11 +667,16 @@ function attachProcessErrorHandlers() {
672
667
  function buildRequestId() {
673
668
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
674
669
  }
675
- function buildTarget(kind, value) {
676
- if (!kind || !value) {
677
- return undefined;
670
+ function normalizeSourceLookupTarget(target) {
671
+ if (target.type === "artifact") {
672
+ return { artifactId: target.artifactId };
678
673
  }
679
- return { kind, value };
674
+ return {
675
+ target: {
676
+ kind: target.kind,
677
+ value: target.value
678
+ }
679
+ };
680
680
  }
681
681
  function parseClassApiKinds(value) {
682
682
  if (value == null) {
@@ -801,8 +801,314 @@ function extractFieldErrorsFromDetails(details) {
801
801
  .filter((entry) => entry != null);
802
802
  return normalized.length > 0 ? normalized : undefined;
803
803
  }
804
- function mapErrorToProblem(caughtError, requestId) {
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) {
805
1108
  if (caughtError instanceof ZodError) {
1109
+ const guidance = context?.tool
1110
+ ? buildInvalidInputGuidance(context.tool, context.normalizedInput)
1111
+ : undefined;
806
1112
  return {
807
1113
  type: "https://minecraft-modding-mcp.dev/problems/invalid-input",
808
1114
  title: "Invalid input",
@@ -811,7 +1117,8 @@ function mapErrorToProblem(caughtError, requestId) {
811
1117
  code: ERROR_CODES.INVALID_INPUT,
812
1118
  instance: requestId,
813
1119
  fieldErrors: toFieldErrorsFromZod(caughtError),
814
- 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 } : {})
815
1122
  };
816
1123
  }
817
1124
  if (isAppError(caughtError)) {
@@ -856,9 +1163,35 @@ function splitWarnings(data) {
856
1163
  async function runTool(tool, rawInput, schema, action) {
857
1164
  const requestId = buildRequestId();
858
1165
  const startedAt = Date.now();
1166
+ let normalizedInput = rawInput;
859
1167
  try {
860
- const parsedInput = schema.parse(rawInput);
861
- const payload = await action(parsedInput);
1168
+ const preparedInput = prepareToolInput(rawInput);
1169
+ normalizedInput = preparedInput.normalizedInput;
1170
+ const { removedOfficialPaths, suggestedReplacementInput } = preparedInput;
1171
+ if (removedOfficialPaths.length > 0) {
1172
+ throw createError({
1173
+ code: ERROR_CODES.INVALID_INPUT,
1174
+ message: `The "official" mapping namespace was removed. Use "obfuscated" instead.`,
1175
+ details: {
1176
+ fieldErrors: removedOfficialPaths.map((path) => ({
1177
+ path,
1178
+ message: `"official" is no longer supported for this field. Use "obfuscated".`,
1179
+ code: "invalid_enum_value"
1180
+ })),
1181
+ nextAction: `Replace "official" with "obfuscated" in mapping-related fields and retry.`,
1182
+ suggestedCall: suggestedReplacementInput
1183
+ ? {
1184
+ tool,
1185
+ params: suggestedReplacementInput
1186
+ }
1187
+ : undefined
1188
+ }
1189
+ });
1190
+ }
1191
+ const parsedInput = schema.parse(normalizedInput);
1192
+ const payload = await (HEAVY_TOOL_NAMES.has(tool)
1193
+ ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
1194
+ : action(parsedInput));
862
1195
  const { result, warnings } = splitWarnings(payload);
863
1196
  return objectResult({
864
1197
  result,
@@ -871,7 +1204,10 @@ async function runTool(tool, rawInput, schema, action) {
871
1204
  });
872
1205
  }
873
1206
  catch (caughtError) {
874
- const problem = mapErrorToProblem(caughtError, requestId);
1207
+ const problem = mapErrorToProblem(caughtError, requestId, {
1208
+ tool,
1209
+ normalizedInput
1210
+ });
875
1211
  if (isAppError(caughtError)) {
876
1212
  const isSevere = caughtError.code === ERROR_CODES.DB_FAILURE ||
877
1213
  caughtError.code === ERROR_CODES.REPO_FETCH_FAILED ||
@@ -910,18 +1246,15 @@ async function runTool(tool, rawInput, schema, action) {
910
1246
  durationMs: Date.now() - startedAt,
911
1247
  warnings: []
912
1248
  }
913
- });
1249
+ }, { isError: true });
914
1250
  }
915
1251
  }
916
1252
  server.tool("list-versions", "List available Minecraft versions from Mojang manifest and locally cached version jars.", listVersionsShape, { readOnlyHint: true }, async (args) => runTool("list-versions", args, listVersionsSchema, async (input) => sourceService.listVersions({
917
1253
  includeSnapshots: input.includeSnapshots,
918
1254
  limit: input.limit
919
1255
  })));
920
- server.tool("resolve-artifact", "Resolve source artifact from version, jar path, or Maven coordinate and return artifact metadata. For targetKind=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({
921
- target: {
922
- kind: input.targetKind,
923
- value: input.targetValue
924
- },
1256
+ 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({
1257
+ target: input.target,
925
1258
  mapping: input.mapping,
926
1259
  sourcePriority: input.sourcePriority,
927
1260
  allowDecompile: input.allowDecompile,
@@ -941,42 +1274,48 @@ server.tool("find-class", "Resolve a simple or qualified class name to fully-qua
941
1274
  artifactId: input.artifactId,
942
1275
  limit: input.limit
943
1276
  })));
944
- server.tool("get-class-source", "Get Java source for a class by artifactId or by resolving target (version/jar/coordinate). Default mode=metadata returns symbol outline only; use mode=snippet for bounded excerpts or mode=full for entire source.", getClassSourceShape, { readOnlyHint: true }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => sourceService.getClassSource({
945
- className: input.className,
946
- mode: input.mode,
947
- artifactId: input.artifactId,
948
- target: buildTarget(input.targetKind, input.targetValue),
949
- mapping: input.mapping,
950
- sourcePriority: input.sourcePriority,
951
- allowDecompile: input.allowDecompile,
952
- projectPath: input.projectPath,
953
- scope: input.scope,
954
- preferProjectVersion: input.preferProjectVersion,
955
- strictVersion: input.strictVersion,
956
- startLine: input.startLine,
957
- endLine: input.endLine,
958
- maxLines: input.maxLines,
959
- maxChars: input.maxChars,
960
- outputFile: input.outputFile
961
- })));
962
- server.tool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode by artifactId or by resolving target (version/jar/coordinate).", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => sourceService.getClassMembers({
963
- className: input.className,
964
- artifactId: input.artifactId,
965
- target: buildTarget(input.targetKind, input.targetValue),
966
- mapping: input.mapping,
967
- sourcePriority: input.sourcePriority,
968
- allowDecompile: input.allowDecompile,
969
- access: input.access,
970
- includeSynthetic: input.includeSynthetic,
971
- includeInherited: input.includeInherited,
972
- memberPattern: input.memberPattern,
973
- maxMembers: input.maxMembers,
974
- projectPath: input.projectPath,
975
- scope: input.scope,
976
- preferProjectVersion: input.preferProjectVersion,
977
- strictVersion: input.strictVersion
978
- })));
979
- server.tool("search-class-source", "Search indexed class source files for one artifact with symbol/text/path intent and optional one-hop relation expansion.", searchClassSourceShape, { readOnlyHint: true }, async (args) => runTool("search-class-source", args, searchClassSourceSchema, async (input) => {
1277
+ server.tool("get-class-source", "Get Java source for a class by target ({ type: 'artifact', artifactId } or { type: 'resolve', kind, value }). Default mode=metadata returns symbol outline only; use mode=snippet for bounded excerpts or mode=full for entire source.", getClassSourceShape, { readOnlyHint: true }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => {
1278
+ const normalizedTarget = normalizeSourceLookupTarget(input.target);
1279
+ return sourceService.getClassSource({
1280
+ className: input.className,
1281
+ mode: input.mode,
1282
+ artifactId: normalizedTarget.artifactId,
1283
+ target: normalizedTarget.target,
1284
+ mapping: input.mapping,
1285
+ sourcePriority: input.sourcePriority,
1286
+ allowDecompile: input.allowDecompile,
1287
+ projectPath: input.projectPath,
1288
+ scope: input.scope,
1289
+ preferProjectVersion: input.preferProjectVersion,
1290
+ strictVersion: input.strictVersion,
1291
+ startLine: input.startLine,
1292
+ endLine: input.endLine,
1293
+ maxLines: input.maxLines,
1294
+ maxChars: input.maxChars,
1295
+ outputFile: input.outputFile
1296
+ });
1297
+ }));
1298
+ server.tool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode by target ({ type: 'artifact', artifactId } or { type: 'resolve', kind, value }).", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => {
1299
+ const normalizedTarget = normalizeSourceLookupTarget(input.target);
1300
+ return sourceService.getClassMembers({
1301
+ className: input.className,
1302
+ artifactId: normalizedTarget.artifactId,
1303
+ target: normalizedTarget.target,
1304
+ mapping: input.mapping,
1305
+ sourcePriority: input.sourcePriority,
1306
+ allowDecompile: input.allowDecompile,
1307
+ access: input.access,
1308
+ includeSynthetic: input.includeSynthetic,
1309
+ includeInherited: input.includeInherited,
1310
+ memberPattern: input.memberPattern,
1311
+ maxMembers: input.maxMembers,
1312
+ projectPath: input.projectPath,
1313
+ scope: input.scope,
1314
+ preferProjectVersion: input.preferProjectVersion,
1315
+ strictVersion: input.strictVersion
1316
+ });
1317
+ }));
1318
+ server.tool("search-class-source", "Search indexed class source files for one artifact with symbol/text/path intent and compact hit output.", searchClassSourceShape, { readOnlyHint: true }, async (args) => runTool("search-class-source", args, searchClassSourceSchema, async (input) => {
980
1319
  const scope = input.packagePrefix || input.fileGlob || input.symbolKind
981
1320
  ? {
982
1321
  packagePrefix: input.packagePrefix,
@@ -984,22 +1323,12 @@ server.tool("search-class-source", "Search indexed class source files for one ar
984
1323
  symbolKind: input.symbolKind
985
1324
  }
986
1325
  : undefined;
987
- const include = input.snippetLines !== undefined ||
988
- input.includeDefinition !== undefined ||
989
- input.includeOneHop !== undefined
990
- ? {
991
- snippetLines: input.snippetLines,
992
- includeDefinition: input.includeDefinition,
993
- includeOneHop: input.includeOneHop
994
- }
995
- : undefined;
996
1326
  return sourceService.searchClassSource({
997
1327
  artifactId: input.artifactId,
998
1328
  query: input.query,
999
1329
  intent: input.intent,
1000
1330
  match: input.match,
1001
1331
  scope: scope,
1002
- include,
1003
1332
  queryMode: input.queryMode,
1004
1333
  limit: input.limit,
1005
1334
  cursor: input.cursor
@@ -1027,7 +1356,8 @@ server.tool("diff-class-signatures", "Compare one class signature between two Mi
1027
1356
  fromVersion: input.fromVersion,
1028
1357
  toVersion: input.toVersion,
1029
1358
  mapping: input.mapping,
1030
- sourcePriority: input.sourcePriority
1359
+ sourcePriority: input.sourcePriority,
1360
+ includeFullDiff: input.includeFullDiff
1031
1361
  })));
1032
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({
1033
1363
  version: input.version,
@@ -1038,24 +1368,26 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
1038
1368
  sourceMapping: input.sourceMapping,
1039
1369
  targetMapping: input.targetMapping,
1040
1370
  sourcePriority: input.sourcePriority,
1041
- disambiguation: input.disambiguation
1371
+ disambiguation: input.disambiguation,
1372
+ maxCandidates: input.maxCandidates
1042
1373
  })));
1043
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({
1044
1375
  version: input.version,
1045
- kind: input.kind,
1046
1376
  name: input.name,
1047
1377
  owner: input.owner,
1048
1378
  descriptor: input.descriptor,
1049
1379
  sourceMapping: input.sourceMapping,
1050
1380
  targetMapping: input.targetMapping,
1051
- sourcePriority: input.sourcePriority
1381
+ sourcePriority: input.sourcePriority,
1382
+ maxCandidates: input.maxCandidates
1052
1383
  })));
1053
- server.tool("get-class-api-matrix", "List class/member API rows across official/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({
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({
1054
1385
  version: input.version,
1055
1386
  className: input.className,
1056
1387
  classNameMapping: input.classNameMapping,
1057
1388
  includeKinds: parseClassApiKinds(input.includeKinds),
1058
- sourcePriority: input.sourcePriority
1389
+ sourcePriority: input.sourcePriority,
1390
+ maxRows: input.maxRows
1059
1391
  })));
1060
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({
1061
1393
  projectPath: input.projectPath,
@@ -1065,7 +1397,8 @@ server.tool("resolve-workspace-symbol", "Resolve class/field/method names as see
1065
1397
  owner: input.owner,
1066
1398
  descriptor: input.descriptor,
1067
1399
  sourceMapping: input.sourceMapping,
1068
- sourcePriority: input.sourcePriority
1400
+ sourcePriority: input.sourcePriority,
1401
+ maxCandidates: input.maxCandidates
1069
1402
  })));
1070
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({
1071
1404
  version: input.version,
@@ -1076,7 +1409,8 @@ server.tool("check-symbol-exists", "Check whether a class/field/method symbol ex
1076
1409
  sourceMapping: input.sourceMapping,
1077
1410
  sourcePriority: input.sourcePriority,
1078
1411
  nameMode: input.nameMode,
1079
- signatureMode: input.signatureMode
1412
+ signatureMode: input.signatureMode,
1413
+ maxCandidates: input.maxCandidates
1080
1414
  })));
1081
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({
1082
1416
  nbtBase64: input.nbtBase64,
@@ -1096,11 +1430,7 @@ server.tool("index-artifact", "Rebuild indexed files/symbols metadata for an exi
1096
1430
  })));
1097
1431
  server.tool("get-runtime-metrics", "Get runtime service counters and latency snapshots for cache/search/index diagnostics.", { readOnlyHint: true }, async (args) => runTool("get-runtime-metrics", args, emptySchema, async () => Promise.resolve(sourceService.getRuntimeMetrics())));
1098
1432
  server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode signatures for a given version.", validateMixinShape, { readOnlyHint: true }, async (args) => runTool("validate-mixin", args, validateMixinSchema, async (input) => sourceService.validateMixin({
1099
- source: input.source,
1100
- sourcePath: input.sourcePath,
1101
- sourcePaths: input.sourcePaths,
1102
- mixinConfigPath: input.mixinConfigPath,
1103
- sourceRoot: input.sourceRoot,
1433
+ input: input.input,
1104
1434
  sourceRoots: input.sourceRoots,
1105
1435
  version: input.version,
1106
1436
  mapping: input.mapping,
@@ -1115,7 +1445,8 @@ server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode
1115
1445
  preferProjectMapping: input.preferProjectMapping,
1116
1446
  reportMode: input.reportMode,
1117
1447
  warningCategoryFilter: input.warningCategoryFilter,
1118
- treatInfoAsWarning: input.treatInfoAsWarning
1448
+ treatInfoAsWarning: input.treatInfoAsWarning,
1449
+ includeIssues: input.includeIssues
1119
1450
  })));
1120
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({
1121
1452
  content: input.content,
@@ -1131,7 +1462,9 @@ server.tool("analyze-mod-jar", "Analyze a Minecraft mod JAR to extract loader ty
1131
1462
  }));
1132
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({
1133
1464
  version: input.version,
1134
- registry: input.registry
1465
+ registry: input.registry,
1466
+ includeData: input.includeData,
1467
+ maxEntriesPerRegistry: input.maxEntriesPerRegistry
1135
1468
  })));
1136
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({
1137
1470
  fromVersion: input.fromVersion,
@@ -1142,7 +1475,9 @@ server.tool("compare-versions", "Compare two Minecraft versions to find added/re
1142
1475
  })));
1143
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({
1144
1477
  jarPath: input.jarPath,
1145
- className: input.className
1478
+ className: input.className,
1479
+ includeFiles: input.includeFiles,
1480
+ maxFiles: input.maxFiles
1146
1481
  })));
1147
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({
1148
1483
  jarPath: input.jarPath,