@adhisang/minecraft-modding-mcp 3.1.1 → 4.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 (61) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +37 -18
  3. package/dist/access-transformer-parser.d.ts +17 -0
  4. package/dist/access-transformer-parser.js +97 -0
  5. package/dist/cache-registry.d.ts +1 -1
  6. package/dist/cache-registry.js +10 -2
  7. package/dist/concurrency.d.ts +1 -0
  8. package/dist/concurrency.js +24 -0
  9. package/dist/config.d.ts +10 -1
  10. package/dist/config.js +52 -1
  11. package/dist/decompiler/vineflower.js +22 -21
  12. package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
  13. package/dist/entry-tools/analyze-symbol-service.d.ts +22 -22
  14. package/dist/entry-tools/analyze-symbol-service.js +13 -2
  15. package/dist/entry-tools/inspect-minecraft-service.d.ts +168 -168
  16. package/dist/entry-tools/inspect-minecraft-service.js +8 -2
  17. package/dist/entry-tools/manage-cache-service.d.ts +4 -4
  18. package/dist/entry-tools/validate-project-service.d.ts +153 -16
  19. package/dist/entry-tools/validate-project-service.js +442 -25
  20. package/dist/gradle-paths.d.ts +4 -0
  21. package/dist/gradle-paths.js +57 -0
  22. package/dist/index.js +148 -30
  23. package/dist/lru-list.d.ts +31 -0
  24. package/dist/lru-list.js +102 -0
  25. package/dist/mapping-pipeline-service.d.ts +12 -1
  26. package/dist/mapping-pipeline-service.js +28 -1
  27. package/dist/mapping-service.d.ts +16 -0
  28. package/dist/mapping-service.js +405 -68
  29. package/dist/minecraft-explorer-service.d.ts +13 -0
  30. package/dist/minecraft-explorer-service.js +8 -4
  31. package/dist/mixin-validator.d.ts +33 -2
  32. package/dist/mixin-validator.js +218 -17
  33. package/dist/mod-analyzer.d.ts +1 -0
  34. package/dist/mod-analyzer.js +17 -1
  35. package/dist/mod-decompile-service.js +4 -4
  36. package/dist/mod-remap-service.js +1 -54
  37. package/dist/mod-search-service.d.ts +1 -0
  38. package/dist/mod-search-service.js +84 -51
  39. package/dist/observability.d.ts +18 -1
  40. package/dist/observability.js +44 -1
  41. package/dist/response-utils.d.ts +69 -0
  42. package/dist/response-utils.js +227 -0
  43. package/dist/source-jar-reader.d.ts +16 -0
  44. package/dist/source-jar-reader.js +103 -1
  45. package/dist/source-resolver.d.ts +9 -1
  46. package/dist/source-resolver.js +23 -16
  47. package/dist/source-service.d.ts +119 -3
  48. package/dist/source-service.js +1836 -218
  49. package/dist/storage/artifacts-repo.d.ts +4 -1
  50. package/dist/storage/artifacts-repo.js +33 -5
  51. package/dist/storage/files-repo.d.ts +0 -2
  52. package/dist/storage/files-repo.js +0 -11
  53. package/dist/storage/migrations.d.ts +1 -1
  54. package/dist/storage/migrations.js +10 -2
  55. package/dist/storage/schema.d.ts +2 -0
  56. package/dist/storage/schema.js +25 -0
  57. package/dist/tool-contract-manifest.js +8 -6
  58. package/dist/types.d.ts +20 -0
  59. package/dist/workspace-mapping-service.d.ts +13 -0
  60. package/dist/workspace-mapping-service.js +146 -14
  61. package/package.json +3 -1
@@ -0,0 +1,57 @@
1
+ import { homedir } from "node:os";
2
+ import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
3
+ import { normalizePathForHost } from "./path-converter.js";
4
+ export function normalizeOptionalProjectPath(projectPath) {
5
+ if (!projectPath) {
6
+ return undefined;
7
+ }
8
+ const trimmed = projectPath.trim();
9
+ if (!trimmed) {
10
+ return undefined;
11
+ }
12
+ const normalized = normalizePathForHost(trimmed, undefined, "projectPath");
13
+ return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
14
+ }
15
+ export function resolveGradleUserHomePath() {
16
+ const configured = process.env.GRADLE_USER_HOME?.trim();
17
+ if (!configured) {
18
+ return resolvePath(homedir(), ".gradle");
19
+ }
20
+ const normalized = normalizePathForHost(configured, undefined, "GRADLE_USER_HOME");
21
+ return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
22
+ }
23
+ export function buildVersionSourceSearchRoots(projectPath) {
24
+ const roots = new Set();
25
+ if (projectPath) {
26
+ roots.add(resolvePath(projectPath, ".gradle", "loom-cache"));
27
+ roots.add(resolvePath(projectPath, ".gradle-user", "caches", "fabric-loom"));
28
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "fabric-loom"));
29
+ const projectParent = dirname(projectPath);
30
+ roots.add(resolvePath(projectParent, ".gradle-user-home", "loom-cache"));
31
+ roots.add(resolvePath(projectParent, ".gradle-user-home", "caches", "fabric-loom"));
32
+ }
33
+ const homeGradle = resolveGradleUserHomePath();
34
+ roots.add(resolvePath(homeGradle, "loom-cache"));
35
+ roots.add(resolvePath(homeGradle, "caches", "fabric-loom"));
36
+ return [...roots];
37
+ }
38
+ export function buildLoaderRuntimeSearchRoots(projectPath) {
39
+ const roots = new Set();
40
+ if (projectPath) {
41
+ roots.add(resolvePath(projectPath, "build"));
42
+ roots.add(resolvePath(projectPath, ".gradle"));
43
+ roots.add(resolvePath(projectPath, ".gradle", "forge-userdev"));
44
+ roots.add(resolvePath(projectPath, ".gradle", "neogradle"));
45
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "forge_gradle"));
46
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "neogradle"));
47
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "neoformruntime"));
48
+ roots.add(resolvePath(projectPath, ".gradle", "caches", "moddev"));
49
+ }
50
+ const homeGradle = resolveGradleUserHomePath();
51
+ roots.add(resolvePath(homeGradle, "caches", "forge_gradle"));
52
+ roots.add(resolvePath(homeGradle, "caches", "neogradle"));
53
+ roots.add(resolvePath(homeGradle, "caches", "neoformruntime"));
54
+ roots.add(resolvePath(homeGradle, "caches", "moddev"));
55
+ return [...roots];
56
+ }
57
+ //# sourceMappingURL=gradle-paths.js.map
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { ZodError, z } from "zod";
4
4
  import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
5
5
  import { objectResult } from "./mcp-helpers.js";
6
6
  import { prepareToolInput } from "./tool-input.js";
7
+ import { isCompactEnabled, COMPACT_MAPPING_TOOL_NAMES, COMPACT_SOURCE_TOOL_NAMES, COMPACT_MEMBERS_TOOL_NAMES, COMPACT_LIGHT_TOOL_NAMES, TOOL_PRESERVE_PAYLOAD_KEYS, compactResponse, compactArtifactResponse, compactMappingResponse, compactSourceResponse, compactMembersResponse, compactLightResponse } from "./response-utils.js";
7
8
  import { loadConfig } from "./config.js";
8
9
  import { createError, ERROR_CODES, isAppError } from "./errors.js";
9
10
  import { log } from "./logger.js";
@@ -18,7 +19,7 @@ import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape
18
19
  import { AnalyzeSymbolService, analyzeSymbolSchema, analyzeSymbolShape } from "./entry-tools/analyze-symbol-service.js";
19
20
  import { CompareMinecraftService, compareMinecraftSchema, compareMinecraftShape } from "./entry-tools/compare-minecraft-service.js";
20
21
  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 { ValidateProjectService, validateProjectSchema, validateProjectShape, discoverWorkspaceAccessTransformers, discoverWorkspaceAccessWideners, discoverWorkspaceMixins } from "./entry-tools/validate-project-service.js";
22
23
  import { ManageCacheService, manageCacheSchema, manageCacheShape } from "./entry-tools/manage-cache-service.js";
23
24
  import { createCacheRegistry } from "./cache-registry.js";
24
25
  import { buildEntryToolMeta } from "./entry-tools/response-contract.js";
@@ -59,6 +60,19 @@ const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueu
59
60
  const nonEmptyString = z.string().trim().min(1);
60
61
  const optionalNonEmptyString = z.string().trim().min(1).optional();
61
62
  const optionalPositiveInt = z.number().int().positive().optional();
63
+ // Optional descriptor: "" and whitespace-only strings are normalized to undefined so that
64
+ // tools with signatureMode="name-only" can accept "caller omitted descriptor" inputs whether
65
+ // the caller passed an empty string or omitted the field entirely. Malformed descriptors are
66
+ // still rejected downstream by normalizeMethodDescriptor in mapping-service.
67
+ const optionalDescriptorString = z
68
+ .string()
69
+ .optional()
70
+ .transform((value) => {
71
+ if (value === undefined)
72
+ return undefined;
73
+ const trimmed = value.trim();
74
+ return trimmed.length === 0 ? undefined : trimmed;
75
+ });
62
76
  const sourceMappingSchema = z.enum(SOURCE_MAPPINGS);
63
77
  const mappingSourcePrioritySchema = z.enum(SOURCE_PRIORITIES);
64
78
  const targetKindSchema = z.enum(TARGET_KINDS);
@@ -89,7 +103,7 @@ const sourceLookupTargetSchema = z.discriminatedUnion("type", [
89
103
  ]);
90
104
  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
105
  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".';
106
+ const SOURCE_SCOPE_DESCRIPTION = "vanilla = Mojang client jar only; merged = source-oriented merged runtime discovery; loader = loader/runtime artifact discovery when the workspace exposes transformed runtime jars.";
93
107
  const SUGGESTED_CALL_DEFAULTS = {
94
108
  allowDecompile: true,
95
109
  preferProjectVersion: false,
@@ -129,7 +143,10 @@ const resolveArtifactShape = {
129
143
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
130
144
  scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
131
145
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
132
- strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
146
+ strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
147
+ compact: z.boolean().default(true).describe("Return minimal fields (artifactId, origin, isDecompiled, version, requestedMapping, mappingApplied, qualityFlags). "
148
+ + "Omit provenance, artifactContents, sampleEntries, adjacentSourceCandidates, binaryJarPath, coordinate, repoUrl, resolvedSourceJarPath. "
149
+ + "Enabled by default; set to false for full output.")
133
150
  };
134
151
  const resolveArtifactSchema = z.object(resolveArtifactShape);
135
152
  const getClassSourceShape = {
@@ -147,7 +164,8 @@ const getClassSourceShape = {
147
164
  endLine: optionalPositiveInt,
148
165
  maxLines: optionalPositiveInt,
149
166
  maxChars: optionalPositiveInt.describe("Hard character limit on sourceText; truncates if exceeded"),
150
- outputFile: optionalNonEmptyString.describe("Write source to this file path and return metadata-only response")
167
+ outputFile: optionalNonEmptyString.describe("Write source to this file path and return metadata-only response"),
168
+ compact: z.boolean().default(false).describe("When true, strip debug metadata (provenance, artifactContents, qualityFlags) and empty fields from the response. Default false.")
151
169
  };
152
170
  const getClassSourceSchema = z
153
171
  .object(getClassSourceShape)
@@ -176,7 +194,8 @@ const getClassMembersShape = {
176
194
  projectPath: optionalNonEmptyString,
177
195
  scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
178
196
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
179
- strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
197
+ strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
198
+ compact: z.boolean().default(false).describe("When true, strip debug metadata (provenance, artifactContents, qualityFlags, context) and empty fields from the response. Default false.")
180
199
  };
181
200
  const getClassMembersSchema = z.object(getClassMembersShape);
182
201
  const searchClassSourceShape = {
@@ -189,7 +208,10 @@ const searchClassSourceShape = {
189
208
  symbolKind: searchSymbolKindSchema.optional().describe("class | interface | enum | record | method | field"),
190
209
  queryMode: z.enum(["auto", "token", "literal"]).default("auto").describe("auto: indexed search, including separator queries like foo.bar; token: indexed-only; literal: explicit substring scan only"),
191
210
  limit: optionalPositiveInt.default(20),
192
- cursor: optionalNonEmptyString
211
+ cursor: optionalNonEmptyString,
212
+ queryNamespace: sourceMappingSchema.optional().describe("Namespace of the query. When set and intent='symbol' with a fully-qualified class name, the query is translated through find-mapping before searching the artifact namespace. Ignored for text/path intents (warning surfaced)."),
213
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first. Used only when queryNamespace triggers translation."),
214
+ compact: z.boolean().default(false).describe("When true, strip the artifactContents summary and empty fields from the response. Default false.")
193
215
  };
194
216
  const searchClassSourceSchema = z.object(searchClassSourceShape).superRefine((value, ctx) => {
195
217
  if (value.symbolKind && value.intent && value.intent !== "symbol") {
@@ -210,12 +232,13 @@ const listArtifactFilesShape = {
210
232
  artifactId: nonEmptyString,
211
233
  prefix: optionalNonEmptyString,
212
234
  limit: optionalPositiveInt,
213
- cursor: optionalNonEmptyString
235
+ cursor: optionalNonEmptyString,
236
+ compact: z.boolean().default(false).describe("When true, strip the artifactContents summary and empty fields from the response. Default false.")
214
237
  };
215
238
  const listArtifactFilesSchema = z.object(listArtifactFilesShape);
216
239
  const traceSymbolLifecycleShape = {
217
240
  symbol: nonEmptyString.describe("fully.qualified.Class.method"),
218
- descriptor: optionalNonEmptyString.describe('optional JVM descriptor, e.g. "(I)V"'),
241
+ descriptor: optionalDescriptorString.describe('optional JVM descriptor, e.g. "(I)V". Empty strings are treated as omitted.'),
219
242
  fromVersion: optionalNonEmptyString,
220
243
  toVersion: optionalNonEmptyString,
221
244
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
@@ -239,10 +262,12 @@ const findMappingShape = {
239
262
  kind: workspaceSymbolKindSchema.describe("class | field | method"),
240
263
  name: nonEmptyString,
241
264
  owner: optionalNonEmptyString,
242
- descriptor: optionalNonEmptyString,
265
+ descriptor: optionalDescriptorString.describe("JVM descriptor. Optional when signatureMode='name-only' (default). Empty strings are treated as omitted."),
243
266
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
244
267
  targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
245
268
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
269
+ signatureMode: z.enum(["exact", "name-only"]).default("name-only")
270
+ .describe("exact: descriptor required for kind=method; name-only (default): match by owner+name only"),
246
271
  disambiguation: z
247
272
  .object({
248
273
  ownerHint: optionalNonEmptyString,
@@ -250,7 +275,10 @@ const findMappingShape = {
250
275
  })
251
276
  .partial()
252
277
  .optional(),
253
- maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
278
+ maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates (default 5, max 200). Raise when you need the full candidate list."),
279
+ compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
280
+ + "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
281
+ + "Enabled by default; set to false for full output.")
254
282
  };
255
283
  const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
256
284
  if (value.kind === "class") {
@@ -301,10 +329,10 @@ const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) =>
301
329
  }
302
330
  return;
303
331
  }
304
- if (!value.descriptor) {
332
+ if (!value.descriptor && value.signatureMode !== "name-only") {
305
333
  ctx.addIssue({
306
334
  code: z.ZodIssueCode.custom,
307
- message: "descriptor is required when kind=method.",
335
+ message: "descriptor is required when kind=method (use signatureMode='name-only' to match by name only).",
308
336
  path: ["descriptor"]
309
337
  });
310
338
  }
@@ -317,7 +345,10 @@ const resolveMethodMappingExactShape = {
317
345
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
318
346
  targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
319
347
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
320
- maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
348
+ maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates (default 5, max 200). Raise when you need the full candidate list."),
349
+ compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
350
+ + "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
351
+ + "Enabled by default; set to false for full output.")
321
352
  };
322
353
  const resolveMethodMappingExactSchema = z
323
354
  .object(resolveMethodMappingExactShape)
@@ -365,10 +396,13 @@ const resolveWorkspaceSymbolShape = {
365
396
  kind: workspaceSymbolKindSchema.describe("class | field | method"),
366
397
  name: nonEmptyString,
367
398
  owner: optionalNonEmptyString,
368
- descriptor: optionalNonEmptyString,
399
+ descriptor: optionalDescriptorString.describe("JVM descriptor. Empty strings are treated as omitted."),
369
400
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
370
401
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
371
- maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates for field/method lookups (max 200)")
402
+ maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates for field/method lookups (default 5, max 200). Raise when you need the full candidate list."),
403
+ compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
404
+ + "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
405
+ + "Enabled by default; set to false for full output.")
372
406
  };
373
407
  const resolveWorkspaceSymbolSchema = z
374
408
  .object(resolveWorkspaceSymbolShape)
@@ -434,13 +468,16 @@ const checkSymbolExistsShape = {
434
468
  kind: workspaceSymbolKindSchema.describe("class | field | method"),
435
469
  owner: optionalNonEmptyString,
436
470
  name: nonEmptyString,
437
- descriptor: optionalNonEmptyString.describe("required for kind=method unless signatureMode=name-only"),
471
+ descriptor: optionalDescriptorString.describe("required for kind=method unless signatureMode=name-only. Empty strings are treated as omitted."),
438
472
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
439
473
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
440
474
  nameMode: classNameModeSchema.default("fqcn").describe("fqcn | auto"),
441
475
  signatureMode: z.enum(["exact", "name-only"]).default("exact")
442
476
  .describe("exact: require descriptor for methods; name-only: match by owner+name only"),
443
- maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
477
+ maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates (default 5, max 200). Raise when you need the full candidate list."),
478
+ compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
479
+ + "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
480
+ + "Enabled by default; set to false for full output.")
444
481
  };
445
482
  const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
446
483
  if (value.kind === "class") {
@@ -581,9 +618,24 @@ const validateAccessWidenerShape = {
581
618
  content: nonEmptyString.describe("Access Widener file content"),
582
619
  version: nonEmptyString.describe("Minecraft version"),
583
620
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
584
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
621
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
622
+ projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted runtime validation"),
623
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
624
+ preferProjectVersion: z.boolean().default(false)
625
+ .describe("When true, detect MC version from gradle.properties and override version")
585
626
  };
586
627
  const validateAccessWidenerSchema = z.object(validateAccessWidenerShape);
628
+ const validateAccessTransformerShape = {
629
+ content: nonEmptyString.describe("Access Transformer file content"),
630
+ version: nonEmptyString.describe("Minecraft version"),
631
+ atNamespace: z.enum(["srg", "mojang", "obfuscated"]).optional().describe("srg | mojang | obfuscated"),
632
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
633
+ projectPath: optionalNonEmptyString.describe("Optional workspace root path for Forge/NeoForge runtime validation"),
634
+ scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
635
+ preferProjectVersion: z.boolean().default(false)
636
+ .describe("When true, detect MC version from gradle.properties and override version")
637
+ };
638
+ const validateAccessTransformerSchema = z.object(validateAccessTransformerShape);
587
639
  const analyzeModJarShape = {
588
640
  jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
589
641
  includeClasses: z.boolean().default(false).describe("Include full class listing")
@@ -715,8 +767,11 @@ const analyzeModService = new AnalyzeModService({
715
767
  const validateProjectService = new ValidateProjectService({
716
768
  validateMixin: (input) => sourceService.validateMixin(input),
717
769
  validateAccessWidener: (input) => sourceService.validateAccessWidener(input),
770
+ validateAccessTransformer: (input) => sourceService.validateAccessTransformer(input),
718
771
  discoverMixins: discoverWorkspaceMixins,
719
- discoverAccessWideners: discoverWorkspaceAccessWideners
772
+ discoverAccessWideners: discoverWorkspaceAccessWideners,
773
+ discoverAccessTransformers: discoverWorkspaceAccessTransformers,
774
+ detectProjectMinecraftVersion: (projectPath) => workspaceMappingService.detectProjectMinecraftVersion(projectPath)
720
775
  });
721
776
  const manageCacheService = new ManageCacheService({
722
777
  registry: createCacheRegistry({
@@ -857,6 +912,16 @@ function statusForErrorCode(code) {
857
912
  }
858
913
  return 500;
859
914
  }
915
+ function extractFailedStageFromDetails(details) {
916
+ if (typeof details !== "object" || details == null) {
917
+ return undefined;
918
+ }
919
+ const maybeStage = details.failedStage;
920
+ if (typeof maybeStage === "string" && maybeStage.trim().length > 0) {
921
+ return maybeStage;
922
+ }
923
+ return undefined;
924
+ }
860
925
  function extractFieldErrorsFromDetails(details) {
861
926
  if (typeof details !== "object" || details == null) {
862
927
  return undefined;
@@ -1379,7 +1444,7 @@ function buildInvalidInputGuidance(tool, normalizedInput) {
1379
1444
  if (tool === "validate-project") {
1380
1445
  return {
1381
1446
  hints: [
1382
- "validate-project.subject must be an object with subject.kind=workspace|mixin|access-widener.",
1447
+ "validate-project.subject must be an object with subject.kind=workspace|mixin|access-widener|access-transformer.",
1383
1448
  "task=\"project-summary\" uses {\"subject\":{\"kind\":\"workspace\",\"projectPath\":\"/workspace\"}}.",
1384
1449
  "Legacy include names like projectSummary/detectedConfig/validationSummary are not accepted; use include:[\"workspace\"] only when you need discovery details."
1385
1450
  ],
@@ -1418,11 +1483,18 @@ function mapErrorToProblem(caughtError, requestId, context) {
1418
1483
  instance: requestId,
1419
1484
  fieldErrors: toFieldErrorsFromZod(caughtError),
1420
1485
  hints: guidance?.hints ?? ["Check fieldErrors and submit a valid tool argument payload."],
1421
- ...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {})
1486
+ ...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {}),
1487
+ ...(context?.tool === "validate-mixin" ? { failedStage: "input-validation" } : {})
1422
1488
  };
1423
1489
  }
1424
1490
  if (isAppError(caughtError)) {
1425
1491
  const suggestedCall = toSuggestedCall(caughtError.details);
1492
+ let failedStage = extractFailedStageFromDetails(caughtError.details);
1493
+ if (!failedStage
1494
+ && context?.tool === "validate-mixin"
1495
+ && caughtError.code === ERROR_CODES.INVALID_INPUT) {
1496
+ failedStage = "input-validation";
1497
+ }
1426
1498
  return {
1427
1499
  type: `https://minecraft-modding-mcp.dev/problems/${caughtError.code.toLowerCase()}`,
1428
1500
  title: "Tool execution error",
@@ -1432,7 +1504,8 @@ function mapErrorToProblem(caughtError, requestId, context) {
1432
1504
  instance: requestId,
1433
1505
  fieldErrors: extractFieldErrorsFromDetails(caughtError.details),
1434
1506
  hints: toHints(caughtError.details),
1435
- ...(suggestedCall ? { suggestedCall } : {})
1507
+ ...(suggestedCall ? { suggestedCall } : {}),
1508
+ ...(failedStage ? { failedStage } : {})
1436
1509
  };
1437
1510
  }
1438
1511
  return {
@@ -1502,6 +1575,26 @@ async function runTool(tool, rawInput, schema, action) {
1502
1575
  ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
1503
1576
  : action(parsedInput));
1504
1577
  const { result, warnings, meta: resultMeta } = splitWarnings(payload);
1578
+ const isCompact = isCompactEnabled(tool, parsedInput);
1579
+ let projectedResult = result;
1580
+ if (isCompact) {
1581
+ if (tool === "resolve-artifact") {
1582
+ projectedResult = compactArtifactResponse(projectedResult);
1583
+ }
1584
+ if (COMPACT_MAPPING_TOOL_NAMES.has(tool)) {
1585
+ projectedResult = compactMappingResponse(projectedResult);
1586
+ }
1587
+ if (COMPACT_SOURCE_TOOL_NAMES.has(tool)) {
1588
+ projectedResult = compactSourceResponse(projectedResult);
1589
+ }
1590
+ if (COMPACT_MEMBERS_TOOL_NAMES.has(tool)) {
1591
+ projectedResult = compactMembersResponse(projectedResult);
1592
+ }
1593
+ if (COMPACT_LIGHT_TOOL_NAMES.has(tool)) {
1594
+ projectedResult = compactLightResponse(projectedResult);
1595
+ }
1596
+ projectedResult = compactResponse(projectedResult, TOOL_PRESERVE_PAYLOAD_KEYS[tool]);
1597
+ }
1505
1598
  const entryMeta = ENTRY_TOOL_NAMES.has(tool)
1506
1599
  ? buildEntryToolMeta({
1507
1600
  detail: normalizedInput &&
@@ -1518,14 +1611,16 @@ async function runTool(tool, rawInput, schema, action) {
1518
1611
  : undefined
1519
1612
  })
1520
1613
  : undefined;
1614
+ const durationMs = Date.now() - startedAt;
1615
+ sourceService.recordToolCall(tool, durationMs);
1521
1616
  return objectResult({
1522
- result,
1617
+ result: projectedResult,
1523
1618
  meta: {
1524
1619
  ...(entryMeta ?? {}),
1525
1620
  ...resultMeta,
1526
1621
  requestId,
1527
1622
  tool,
1528
- durationMs: Date.now() - startedAt,
1623
+ durationMs,
1529
1624
  warnings
1530
1625
  }
1531
1626
  });
@@ -1565,12 +1660,14 @@ async function runTool(tool, rawInput, schema, action) {
1565
1660
  reason: caughtError instanceof Error ? caughtError.message : String(caughtError)
1566
1661
  });
1567
1662
  }
1663
+ const errorDurationMs = Date.now() - startedAt;
1664
+ sourceService.recordToolCall(tool, errorDurationMs);
1568
1665
  return objectResult({
1569
1666
  error: problem,
1570
1667
  meta: {
1571
1668
  requestId,
1572
1669
  tool,
1573
- durationMs: Date.now() - startedAt,
1670
+ durationMs: errorDurationMs,
1574
1671
  warnings: []
1575
1672
  }
1576
1673
  }, { isError: true });
@@ -1584,7 +1681,7 @@ server.tool("inspect-minecraft", "High-level v3 entry tool for version discovery
1584
1681
  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)));
1585
1682
  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)));
1586
1683
  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)));
1587
- 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)));
1684
+ server.tool("validate-project", "High-level v3 entry tool for project summary, direct mixin validation, and access widener/access transformer validation.", validateProjectShape, { readOnlyHint: true }, async (args) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input)));
1588
1685
  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)));
1589
1686
  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({
1590
1687
  target: input.target,
@@ -1594,7 +1691,8 @@ server.tool("resolve-artifact", "Resolve source artifact from a target object ({
1594
1691
  projectPath: input.projectPath,
1595
1692
  scope: input.scope,
1596
1693
  preferProjectVersion: input.preferProjectVersion,
1597
- strictVersion: input.strictVersion
1694
+ strictVersion: input.strictVersion,
1695
+ compact: input.compact
1598
1696
  })));
1599
1697
  const findClassShape = {
1600
1698
  className: nonEmptyString.describe("Simple name (e.g. Blocks) or fully-qualified name (e.g. net.minecraft.world.level.block.Blocks)"),
@@ -1664,7 +1762,9 @@ server.tool("search-class-source", "Search indexed class source files for one ar
1664
1762
  scope: scope,
1665
1763
  queryMode: input.queryMode,
1666
1764
  limit: input.limit,
1667
- cursor: input.cursor
1765
+ cursor: input.cursor,
1766
+ queryNamespace: input.queryNamespace,
1767
+ sourcePriority: input.sourcePriority
1668
1768
  });
1669
1769
  }));
1670
1770
  server.tool("get-artifact-file", "Get full source file content by artifactId and file path.", getArtifactFileShape, { readOnlyHint: true }, async (args) => runTool("get-artifact-file", args, getArtifactFileSchema, async (input) => sourceService.getArtifactFile({
@@ -1672,7 +1772,12 @@ server.tool("get-artifact-file", "Get full source file content by artifactId and
1672
1772
  filePath: input.filePath,
1673
1773
  maxBytes: input.maxBytes
1674
1774
  })));
1675
- server.tool("list-artifact-files", "List source file paths in an artifact with optional prefix filter and cursor-based pagination.", listArtifactFilesShape, { readOnlyHint: true }, async (args) => runTool("list-artifact-files", args, listArtifactFilesSchema, async (input) => sourceService.listArtifactFiles(input)));
1775
+ server.tool("list-artifact-files", "List source file paths in an artifact with optional prefix filter and cursor-based pagination.", listArtifactFilesShape, { readOnlyHint: true }, async (args) => runTool("list-artifact-files", args, listArtifactFilesSchema, async (input) => sourceService.listArtifactFiles({
1776
+ artifactId: input.artifactId,
1777
+ prefix: input.prefix,
1778
+ limit: input.limit,
1779
+ cursor: input.cursor
1780
+ })));
1676
1781
  server.tool("trace-symbol-lifecycle", "Trace which Minecraft versions contain a specific class method and report first/last seen versions.", traceSymbolLifecycleShape, { readOnlyHint: true }, async (args) => runTool("trace-symbol-lifecycle", args, traceSymbolLifecycleSchema, async (input) => sourceService.traceSymbolLifecycle({
1677
1782
  symbol: input.symbol,
1678
1783
  descriptor: input.descriptor,
@@ -1701,6 +1806,7 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
1701
1806
  sourceMapping: input.sourceMapping,
1702
1807
  targetMapping: input.targetMapping,
1703
1808
  sourcePriority: input.sourcePriority,
1809
+ signatureMode: input.signatureMode,
1704
1810
  disambiguation: input.disambiguation,
1705
1811
  maxCandidates: input.maxCandidates
1706
1812
  })));
@@ -1785,7 +1891,19 @@ server.tool("validate-access-widener", "Validate Access Widener file entries aga
1785
1891
  content: input.content,
1786
1892
  version: input.version,
1787
1893
  mapping: input.mapping,
1788
- sourcePriority: input.sourcePriority
1894
+ sourcePriority: input.sourcePriority,
1895
+ projectPath: input.projectPath,
1896
+ scope: input.scope,
1897
+ preferProjectVersion: input.preferProjectVersion
1898
+ })));
1899
+ server.tool("validate-access-transformer", "Validate Access Transformer file entries against Minecraft bytecode signatures for a given version.", validateAccessTransformerShape, { readOnlyHint: true }, async (args) => runTool("validate-access-transformer", args, validateAccessTransformerSchema, async (input) => sourceService.validateAccessTransformer({
1900
+ content: input.content,
1901
+ version: input.version,
1902
+ atNamespace: input.atNamespace,
1903
+ sourcePriority: input.sourcePriority,
1904
+ projectPath: input.projectPath,
1905
+ scope: input.scope,
1906
+ preferProjectVersion: input.preferProjectVersion
1789
1907
  })));
1790
1908
  server.tool("analyze-mod-jar", "Analyze a Minecraft mod JAR to extract loader type, metadata, entrypoints, mixins, and dependencies.", analyzeModJarShape, { readOnlyHint: true }, async (args) => runTool("analyze-mod-jar", args, analyzeModJarSchema, async (input) => {
1791
1909
  const result = await analyzeModJar(input.jarPath, {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * O(1) doubly-linked list + Map LRU.
3
+ * head = oldest, tail = newest.
4
+ */
5
+ export declare class LruList<T> {
6
+ private map;
7
+ private head;
8
+ private tail;
9
+ get size(): number;
10
+ /** O(1) — move to tail (most recent), return value reference. */
11
+ touch(key: string): T | undefined;
12
+ /** O(1) — insert or update + move to tail. Returns previous value if existed. */
13
+ upsert(key: string, value: T): T | undefined;
14
+ /** O(1) — remove by key, return removed value. */
15
+ remove(key: string): T | undefined;
16
+ /** O(1) — peek at oldest (head) without removing. */
17
+ peekOldest(): {
18
+ key: string;
19
+ value: T;
20
+ } | undefined;
21
+ /** O(1) — clear all entries. */
22
+ clear(): void;
23
+ /** O(n) — iterate oldest → newest. */
24
+ toArray(): Array<{
25
+ key: string;
26
+ value: T;
27
+ }>;
28
+ private unlink;
29
+ private appendToTail;
30
+ private moveToTail;
31
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * O(1) doubly-linked list + Map LRU.
3
+ * head = oldest, tail = newest.
4
+ */
5
+ export class LruList {
6
+ map = new Map();
7
+ head = null;
8
+ tail = null;
9
+ get size() {
10
+ return this.map.size;
11
+ }
12
+ /** O(1) — move to tail (most recent), return value reference. */
13
+ touch(key) {
14
+ const node = this.map.get(key);
15
+ if (!node) {
16
+ return undefined;
17
+ }
18
+ this.moveToTail(node);
19
+ return node.value;
20
+ }
21
+ /** O(1) — insert or update + move to tail. Returns previous value if existed. */
22
+ upsert(key, value) {
23
+ const existing = this.map.get(key);
24
+ if (existing) {
25
+ const prev = existing.value;
26
+ existing.value = value;
27
+ this.moveToTail(existing);
28
+ return prev;
29
+ }
30
+ const node = { key, value, prev: null, next: null };
31
+ this.map.set(key, node);
32
+ this.appendToTail(node);
33
+ return undefined;
34
+ }
35
+ /** O(1) — remove by key, return removed value. */
36
+ remove(key) {
37
+ const node = this.map.get(key);
38
+ if (!node) {
39
+ return undefined;
40
+ }
41
+ this.unlink(node);
42
+ this.map.delete(key);
43
+ return node.value;
44
+ }
45
+ /** O(1) — peek at oldest (head) without removing. */
46
+ peekOldest() {
47
+ if (!this.head) {
48
+ return undefined;
49
+ }
50
+ return { key: this.head.key, value: this.head.value };
51
+ }
52
+ /** O(1) — clear all entries. */
53
+ clear() {
54
+ this.map.clear();
55
+ this.head = null;
56
+ this.tail = null;
57
+ }
58
+ /** O(n) — iterate oldest → newest. */
59
+ toArray() {
60
+ const result = [];
61
+ let current = this.head;
62
+ while (current) {
63
+ result.push({ key: current.key, value: current.value });
64
+ current = current.next;
65
+ }
66
+ return result;
67
+ }
68
+ unlink(node) {
69
+ if (node.prev) {
70
+ node.prev.next = node.next;
71
+ }
72
+ else {
73
+ this.head = node.next;
74
+ }
75
+ if (node.next) {
76
+ node.next.prev = node.prev;
77
+ }
78
+ else {
79
+ this.tail = node.prev;
80
+ }
81
+ node.prev = null;
82
+ node.next = null;
83
+ }
84
+ appendToTail(node) {
85
+ if (!this.tail) {
86
+ this.head = node;
87
+ this.tail = node;
88
+ return;
89
+ }
90
+ node.prev = this.tail;
91
+ this.tail.next = node;
92
+ this.tail = node;
93
+ }
94
+ moveToTail(node) {
95
+ if (node === this.tail) {
96
+ return;
97
+ }
98
+ this.unlink(node);
99
+ this.appendToTail(node);
100
+ }
101
+ }
102
+ //# sourceMappingURL=lru-list.js.map
@@ -3,6 +3,15 @@ export interface MappingPipelineInput {
3
3
  requestedMapping: SourceMapping;
4
4
  target: SourceTargetInput;
5
5
  resolved: ResolvedSourceArtifact;
6
+ runtimeNamesUnobfuscated?: boolean;
7
+ /**
8
+ * When true and the mojang request lands on a binary-only artifact, the pipeline
9
+ * authorizes a downstream tiny-remap (obfuscated -> mojang) followed by decompile
10
+ * instead of throwing MAPPING_NOT_APPLIED. The caller is responsible for
11
+ * pre-resolving tiny-remapper jar, mojang tiny mappings, and verifying mapping
12
+ * health before setting this flag.
13
+ */
14
+ allowBinaryRemap?: boolean;
6
15
  }
7
16
  export interface MappingPipelineResult {
8
17
  mappingApplied: SourceMapping;
@@ -13,6 +22,8 @@ export interface MappingPipelineResult {
13
22
  * Mapping pipeline for v0.3.
14
23
  * Current implementation enforces explicit guarantees:
15
24
  * - obfuscated: always pass-through
16
- * - mojang: requires source-backed artifact; decompile-only artifacts are rejected
25
+ * - mojang: requires source-backed artifacts on legacy obfuscated versions,
26
+ * but unobfuscated runtime jars can pass through directly,
27
+ * or binary-only artifacts may be remapped + decompiled when allowBinaryRemap=true
17
28
  */
18
29
  export declare function applyMappingPipeline(input: MappingPipelineInput): MappingPipelineResult;