@adhisang/minecraft-modding-mcp 4.2.0 → 5.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 (120) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +6 -4
  3. package/dist/access-transformer-parser.d.ts +2 -1
  4. package/dist/access-transformer-parser.js +10 -1
  5. package/dist/build-suggested-call.js +31 -10
  6. package/dist/cache-registry.d.ts +8 -0
  7. package/dist/cache-registry.js +99 -6
  8. package/dist/config.d.ts +1 -0
  9. package/dist/config.js +4 -0
  10. package/dist/entry-tools/analyze-mod-service.d.ts +24 -24
  11. package/dist/entry-tools/analyze-symbol-service.d.ts +57 -22
  12. package/dist/entry-tools/analyze-symbol-service.js +124 -28
  13. package/dist/entry-tools/batch-class-members-service.d.ts +3 -1
  14. package/dist/entry-tools/batch-class-members-service.js +6 -6
  15. package/dist/entry-tools/batch-class-source-service.d.ts +3 -1
  16. package/dist/entry-tools/batch-class-source-service.js +5 -6
  17. package/dist/entry-tools/batch-mappings-service.d.ts +3 -1
  18. package/dist/entry-tools/batch-mappings-service.js +4 -5
  19. package/dist/entry-tools/batch-symbol-exists-service.d.ts +3 -1
  20. package/dist/entry-tools/batch-symbol-exists-service.js +4 -5
  21. package/dist/entry-tools/compare-minecraft-service.d.ts +10 -10
  22. package/dist/entry-tools/entry-tool-schema.d.ts +1 -1
  23. package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +1 -3
  24. package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -2
  25. package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +0 -5
  26. package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +0 -1
  27. package/dist/entry-tools/inspect-minecraft/handlers/file.js +0 -1
  28. package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +0 -1
  29. package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +8 -0
  30. package/dist/entry-tools/inspect-minecraft/handlers/search.js +13 -2
  31. package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +1 -1
  32. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +1 -1
  33. package/dist/entry-tools/inspect-minecraft/internal.d.ts +90 -89
  34. package/dist/entry-tools/inspect-minecraft/internal.js +30 -14
  35. package/dist/entry-tools/inspect-minecraft-service.d.ts +218 -218
  36. package/dist/entry-tools/inspect-minecraft-service.js +1 -1
  37. package/dist/entry-tools/manage-cache-service.d.ts +4 -4
  38. package/dist/entry-tools/response-contract.d.ts +1 -1
  39. package/dist/entry-tools/response-contract.js +1 -0
  40. package/dist/entry-tools/validate-project/cases/mixin.js +2 -1
  41. package/dist/entry-tools/validate-project/cases/project-summary.js +5 -3
  42. package/dist/entry-tools/validate-project-service.d.ts +42 -36
  43. package/dist/entry-tools/validate-project-service.js +3 -1
  44. package/dist/entry-tools/verify-mixin-target-service.d.ts +30 -0
  45. package/dist/entry-tools/verify-mixin-target-service.js +113 -22
  46. package/dist/error-mapping.d.ts +51 -0
  47. package/dist/error-mapping.js +157 -2
  48. package/dist/errors.js +4 -1
  49. package/dist/index.js +138 -71
  50. package/dist/java-process.d.ts +9 -0
  51. package/dist/java-process.js +11 -5
  52. package/dist/mapping/internal-types.d.ts +7 -0
  53. package/dist/mapping/lookup.d.ts +1 -1
  54. package/dist/mapping/lookup.js +7 -1
  55. package/dist/mapping/types.d.ts +6 -0
  56. package/dist/mapping-service.d.ts +26 -2
  57. package/dist/mapping-service.js +178 -36
  58. package/dist/mcp-helpers.d.ts +1 -0
  59. package/dist/mcp-helpers.js +25 -3
  60. package/dist/minecraft-explorer-service.js +24 -11
  61. package/dist/mixin/access-validators.js +33 -1
  62. package/dist/mixin/annotation-validators.js +53 -7
  63. package/dist/mixin/parsed-validator.js +2 -2
  64. package/dist/mixin-parser.js +48 -4
  65. package/dist/mod-decompile-service.js +2 -1
  66. package/dist/mod-remap-service.d.ts +20 -1
  67. package/dist/mod-remap-service.js +95 -40
  68. package/dist/nbt/java-nbt-codec.js +87 -2
  69. package/dist/observability.d.ts +6 -0
  70. package/dist/observability.js +10 -0
  71. package/dist/page-cursor.d.ts +17 -0
  72. package/dist/page-cursor.js +0 -0
  73. package/dist/registry-service.js +25 -1
  74. package/dist/resources.js +45 -9
  75. package/dist/response-utils.d.ts +29 -4
  76. package/dist/response-utils.js +95 -5
  77. package/dist/source/access-validate.js +12 -2
  78. package/dist/source/artifact-resolver.js +1 -1
  79. package/dist/source/cache-metrics.js +2 -2
  80. package/dist/source/class-source/members-builder.d.ts +44 -1
  81. package/dist/source/class-source/members-builder.js +51 -4
  82. package/dist/source/class-source/snippet-builder.d.ts +13 -0
  83. package/dist/source/class-source/snippet-builder.js +48 -4
  84. package/dist/source/class-source-helpers.js +67 -11
  85. package/dist/source/class-source.js +97 -20
  86. package/dist/source/indexer.d.ts +31 -6
  87. package/dist/source/indexer.js +95 -25
  88. package/dist/source/search.d.ts +1 -1
  89. package/dist/source/search.js +94 -22
  90. package/dist/source/validate-mixin.d.ts +2 -1
  91. package/dist/source/validate-mixin.js +45 -4
  92. package/dist/source/workspace-target.js +15 -9
  93. package/dist/source-jar-reader.d.ts +54 -1
  94. package/dist/source-jar-reader.js +121 -10
  95. package/dist/source-service.d.ts +28 -7
  96. package/dist/storage/files-repo.d.ts +12 -0
  97. package/dist/storage/files-repo.js +24 -0
  98. package/dist/storage/symbols-repo.d.ts +6 -0
  99. package/dist/storage/symbols-repo.js +6 -0
  100. package/dist/symbols/symbol-extractor.js +35 -6
  101. package/dist/text-truncate.d.ts +7 -0
  102. package/dist/text-truncate.js +21 -0
  103. package/dist/tiny-remapper-resolver.js +10 -0
  104. package/dist/tiny-remapper-service.js +3 -3
  105. package/dist/tool-contract-manifest.js +2 -2
  106. package/dist/tool-execution-gate.js +12 -1
  107. package/dist/tool-guidance.d.ts +4 -1
  108. package/dist/tool-guidance.js +37 -12
  109. package/dist/tool-schemas.d.ts +559 -494
  110. package/dist/tool-schemas.js +99 -63
  111. package/dist/types.d.ts +1 -0
  112. package/dist/version-diff-service.js +5 -0
  113. package/dist/version-service.d.ts +3 -0
  114. package/dist/version-service.js +18 -2
  115. package/dist/warning-details.d.ts +18 -0
  116. package/dist/warning-details.js +63 -0
  117. package/docs/README-ja.md +2 -2
  118. package/docs/examples.md +26 -2
  119. package/docs/tool-reference.md +23 -17
  120. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { ZodError, z } from "zod";
5
5
  import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
6
6
  import { objectResult } from "./mcp-helpers.js";
7
7
  import { prepareToolInput } from "./tool-input.js";
8
- 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";
8
+ import { DETAIL_ENABLED_TOOL_NAMES, DEFAULT_DETAIL_BY_TOOL, projectByDetail } from "./response-utils.js";
9
9
  import { loadConfig } from "./config.js";
10
10
  import { createError, ERROR_CODES, isAppError } from "./errors.js";
11
11
  import { log } from "./logger.js";
@@ -15,6 +15,7 @@ import { remapModJar } from "./mod-remap-service.js";
15
15
  import { registerResources } from "./resources.js";
16
16
  import { SourceService } from "./source-service.js";
17
17
  import { ToolExecutionGate } from "./tool-execution-gate.js";
18
+ import { classifyWarnings } from "./warning-details.js";
18
19
  import { WorkspaceMappingService } from "./workspace-mapping-service.js";
19
20
  import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape } from "./entry-tools/inspect-minecraft-service.js";
20
21
  import { AnalyzeSymbolService, analyzeSymbolSchema, analyzeSymbolShape } from "./entry-tools/analyze-symbol-service.js";
@@ -55,6 +56,13 @@ const ENTRY_TOOL_NAMES = new Set([
55
56
  "validate-project",
56
57
  "manage-cache"
57
58
  ]);
59
+ // Batch tools self-project per entry but report the applied detail at the top level.
60
+ const BATCH_DETAIL_TOOL_NAMES = new Set([
61
+ "batch-class-source",
62
+ "batch-class-members",
63
+ "batch-symbol-exists",
64
+ "batch-mappings"
65
+ ]);
58
66
  const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueue: 2 });
59
67
  function getServerVersionFromPackageJson() {
60
68
  try {
@@ -77,6 +85,13 @@ const server = new McpServer({
77
85
  // The SDK validates tool args before invoking handlers and returns generic InvalidParams text.
78
86
  // Bypass that layer so runTool() remains the single source of truth for validation and error envelopes.
79
87
  server.validateToolInput = async (_tool, args) => args;
88
+ // Low-level/expert tools duplicate capability that the six entry tools expose
89
+ // in a single resolve+fetch call. expertTool() registers them exactly like
90
+ // server.tool() but appends a note steering agents to the entry tools first.
91
+ // Entry tools, batch tools, and the NBT/runtime utilities (which have no entry
92
+ // equivalent) keep their plain descriptions via server.tool().
93
+ const EXPERT_TOOL_NOTE = " Expert/follow-up tool: prefer the entry tools (inspect-minecraft, analyze-symbol, compare-minecraft, analyze-mod, validate-project) first; reach for this only for the narrow operation it names.";
94
+ const expertTool = ((name, description, ...rest) => server.tool(name, description + EXPERT_TOOL_NOTE, ...rest));
80
95
  const config = loadConfig();
81
96
  const nbtLimits = {
82
97
  maxInputBytes: config.maxNbtInputBytes,
@@ -176,10 +191,32 @@ const verifyMixinTargetService = new VerifyMixinTargetService({
176
191
  artifactId: output.artifactId,
177
192
  mappingApplied: output.mappingApplied,
178
193
  binaryJarPath: output.binaryJarPath,
194
+ version: output.version,
179
195
  provenance: output.provenance,
180
196
  warnings: output.warnings
181
197
  };
182
198
  },
199
+ findMapping: async (input) => {
200
+ const output = await sourceService.findMapping({
201
+ version: input.version,
202
+ kind: input.kind,
203
+ name: input.name,
204
+ owner: input.owner,
205
+ descriptor: input.descriptor,
206
+ sourceMapping: input.sourceMapping,
207
+ targetMapping: input.targetMapping
208
+ });
209
+ return {
210
+ resolved: output.resolved,
211
+ resolvedSymbol: output.resolvedSymbol
212
+ ? {
213
+ name: output.resolvedSymbol.name,
214
+ owner: output.resolvedSymbol.owner,
215
+ descriptor: output.resolvedSymbol.descriptor
216
+ }
217
+ : undefined
218
+ };
219
+ },
183
220
  getSignature: (input) => sourceService.explorerService.getSignature(input)
184
221
  });
185
222
  const batchClassSourceService = new BatchClassSourceService({
@@ -226,11 +263,10 @@ function buildRequestId() {
226
263
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
227
264
  }
228
265
  function normalizeSourceLookupTarget(target) {
229
- if (target.type === "artifact") {
266
+ if (target.kind === "artifact") {
230
267
  return { artifactId: target.artifactId };
231
268
  }
232
- const { type: _type, ...rest } = target;
233
- return { target: rest };
269
+ return { target: target };
234
270
  }
235
271
  function parseClassApiKinds(value) {
236
272
  if (value == null) {
@@ -270,6 +306,33 @@ function splitWarnings(data) {
270
306
  meta
271
307
  };
272
308
  }
309
+ /**
310
+ * Read the detail/include response-shape controls from a parsed tool input.
311
+ * Folds the legacy includeProvenance/includeDescriptors flags into the include
312
+ * set as aliases so the migration off `compact` preserves Phase-4 behavior.
313
+ */
314
+ function readResponseShapeInput(parsedInput) {
315
+ const include = new Set();
316
+ if (parsedInput === null || typeof parsedInput !== "object" || Array.isArray(parsedInput)) {
317
+ return { include };
318
+ }
319
+ const record = parsedInput;
320
+ if (Array.isArray(record.include)) {
321
+ for (const group of record.include) {
322
+ if (typeof group === "string")
323
+ include.add(group);
324
+ }
325
+ }
326
+ if (record.includeProvenance === true)
327
+ include.add("provenance");
328
+ if (record.includeDescriptors === true)
329
+ include.add("descriptors");
330
+ const detail = typeof record.detail === "string" &&
331
+ (record.detail === "summary" || record.detail === "standard" || record.detail === "full")
332
+ ? record.detail
333
+ : undefined;
334
+ return { detail, include };
335
+ }
273
336
  async function runTool(tool, rawInput, schema, action) {
274
337
  const requestId = buildRequestId();
275
338
  const startedAt = Date.now();
@@ -289,6 +352,11 @@ async function runTool(tool, rawInput, schema, action) {
289
352
  code: "invalid_enum_value"
290
353
  })),
291
354
  nextAction: `Replace "official" with "obfuscated" in mapping-related fields and retry.`,
355
+ // Route construction through buildSuggestedCall to satisfy the D12
356
+ // invariant (tests/suggested-call-invariant.test.ts): every emitted
357
+ // suggestedCall must be built by the helper. The downstream
358
+ // mapErrorToProblem gate re-validates on emission; that second pass is
359
+ // the intentional, cheap cost of the two complementary safety gates.
292
360
  ...(suggestedReplacementInput
293
361
  ? buildSuggestedCall({
294
362
  tool,
@@ -303,44 +371,32 @@ async function runTool(tool, rawInput, schema, action) {
303
371
  ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
304
372
  : action(parsedInput));
305
373
  const { result, warnings, meta: resultMeta } = splitWarnings(payload);
306
- const isCompact = isCompactEnabled(tool, parsedInput);
374
+ // Expert tools share the entry-tool detail/include response contract (the old
375
+ // per-tool `compact` boolean is gone). projectByDetail reproduces the previous
376
+ // compact defaults byte-identically: resolution tools default detail=summary
377
+ // (old compact:true), source/file tools default detail=standard (old compact:false).
378
+ // The legacy includeProvenance/includeDescriptors flags are folded into the
379
+ // include set as aliases so Phase-4 behavior is preserved.
380
+ const shapeInput = readResponseShapeInput(parsedInput);
381
+ const effectiveDetail = ENTRY_TOOL_NAMES.has(tool)
382
+ ? (shapeInput.detail ?? "summary")
383
+ : (shapeInput.detail ?? DEFAULT_DETAIL_BY_TOOL[tool] ?? "summary");
307
384
  let projectedResult = result;
308
- if (isCompact) {
309
- if (tool === "resolve-artifact") {
310
- projectedResult = compactArtifactResponse(projectedResult);
311
- }
312
- if (COMPACT_MAPPING_TOOL_NAMES.has(tool)) {
313
- projectedResult = compactMappingResponse(projectedResult);
314
- }
315
- if (COMPACT_SOURCE_TOOL_NAMES.has(tool)) {
316
- projectedResult = compactSourceResponse(projectedResult);
317
- }
318
- if (COMPACT_MEMBERS_TOOL_NAMES.has(tool)) {
319
- projectedResult = compactMembersResponse(projectedResult);
320
- }
321
- if (COMPACT_LIGHT_TOOL_NAMES.has(tool)) {
322
- projectedResult = compactLightResponse(projectedResult);
323
- }
324
- projectedResult = compactResponse(projectedResult, TOOL_PRESERVE_PAYLOAD_KEYS[tool]);
385
+ if (DETAIL_ENABLED_TOOL_NAMES.has(tool)) {
386
+ projectedResult = projectByDetail(tool, projectedResult, effectiveDetail, shapeInput.include);
325
387
  }
326
- const entryMeta = ENTRY_TOOL_NAMES.has(tool)
388
+ // Entry, expert, and batch tools all report the applied detail/include shape in meta.
389
+ const entryMeta = ENTRY_TOOL_NAMES.has(tool) ||
390
+ DETAIL_ENABLED_TOOL_NAMES.has(tool) ||
391
+ BATCH_DETAIL_TOOL_NAMES.has(tool)
327
392
  ? buildEntryToolMeta({
328
- detail: normalizedInput &&
329
- typeof normalizedInput === "object" &&
330
- !Array.isArray(normalizedInput) &&
331
- typeof normalizedInput.detail === "string"
332
- ? normalizedInput.detail ?? "summary"
333
- : "summary",
334
- include: normalizedInput &&
335
- typeof normalizedInput === "object" &&
336
- !Array.isArray(normalizedInput) &&
337
- Array.isArray(normalizedInput.include)
338
- ? normalizedInput.include
339
- : undefined
393
+ detail: effectiveDetail,
394
+ include: shapeInput.include.size > 0 ? [...shapeInput.include] : undefined
340
395
  })
341
396
  : undefined;
342
397
  const durationMs = Date.now() - startedAt;
343
398
  sourceService.recordToolCall(tool, durationMs);
399
+ const warningDetails = classifyWarnings(warnings);
344
400
  return objectResult({
345
401
  result: projectedResult,
346
402
  meta: {
@@ -349,7 +405,8 @@ async function runTool(tool, rawInput, schema, action) {
349
405
  requestId,
350
406
  tool,
351
407
  durationMs,
352
- warnings
408
+ warnings,
409
+ ...(warningDetails.length > 0 ? { warningDetails } : {})
353
410
  }
354
411
  });
355
412
  }
@@ -403,14 +460,14 @@ async function runTool(tool, rawInput, schema, action) {
403
460
  }, { isError: true });
404
461
  }
405
462
  }
406
- 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({
463
+ expertTool("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({
407
464
  includeSnapshots: input.includeSnapshots,
408
465
  limit: input.limit
409
466
  })));
410
467
  registerToolSchema("list-versions", listVersionsSchema);
411
468
  server.tool("inspect-minecraft", "Top-level workflow tool for version discovery, artifact resolution, class inspection, source search, file reads, and file listings.", inspectMinecraftShape, { readOnlyHint: true }, async (args) => runTool("inspect-minecraft", args, inspectMinecraftSchema, async (input) => inspectMinecraftService.execute(input)));
412
469
  registerToolSchema("inspect-minecraft", inspectMinecraftSchema);
413
- server.tool("analyze-symbol", "Top-level workflow 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)));
470
+ server.tool("analyze-symbol", "Top-level workflow tool for symbol existence, mapping, lifecycle (with fromVersion/toVersion/maxVersions/includeTimeline range controls), workspace analysis, and API overview. subject.kind accepts 'symbol' to auto-detect class/field/method from the selector (inferred kind is returned as a warning).", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
414
471
  registerToolSchema("analyze-symbol", analyzeSymbolSchema);
415
472
  server.tool("compare-minecraft", "Top-level workflow 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)));
416
473
  registerToolSchema("compare-minecraft", compareMinecraftSchema);
@@ -428,6 +485,7 @@ if (!VERIFY_MIXIN_TARGET_OFF) {
428
485
  member: input.member,
429
486
  mixinMemberName: input.mixinMemberName,
430
487
  mapping: input.mapping,
488
+ autoRemap: input.autoRemap,
431
489
  sourcePriority: input.sourcePriority,
432
490
  projectPath: input.projectPath,
433
491
  gradleUserHome: input.gradleUserHome,
@@ -439,7 +497,7 @@ if (!VERIFY_MIXIN_TARGET_OFF) {
439
497
  registerToolSchema("verify-mixin-target", verifyMixinTargetSchema);
440
498
  }
441
499
  if (!BATCH_TOOLS_OFF) {
442
- server.tool("batch-class-source", "Batch lookup: read source for many classes in one call, sharing a single resolved artifact. Returns per-entry { status, result?, error? } plus aggregate summary. Per-entry retry suggestions point at get-class-source.", batchClassSourceShape, { readOnlyHint: true }, async (args) => runTool("batch-class-source", args, batchClassSourceSchema, async (input) => batchClassSourceService.execute(input)));
500
+ server.tool("batch-class-source", "Batch lookup: read source for many classes in one call, sharing a single resolved artifact. Returns per-entry { status, result?, error? } plus aggregate summary. Per-entry retry suggestions point at get-class-source. Not read-only: per-entry outputFile writes source files to disk.", batchClassSourceShape, { readOnlyHint: false }, async (args) => runTool("batch-class-source", args, batchClassSourceSchema, async (input) => batchClassSourceService.execute(input)));
443
501
  registerToolSchema("batch-class-source", batchClassSourceSchema);
444
502
  server.tool("batch-class-members", "Batch lookup: list members for many classes in one call, sharing a single resolved artifact. Returns per-entry { status, result?, error? } plus aggregate summary. Per-entry retry suggestions point at get-class-members.", batchClassMembersShape, { readOnlyHint: true }, async (args) => runTool("batch-class-members", args, batchClassMembersSchema, async (input) => batchClassMembersService.execute(input)));
445
503
  registerToolSchema("batch-class-members", batchClassMembersSchema);
@@ -448,7 +506,7 @@ if (!BATCH_TOOLS_OFF) {
448
506
  server.tool("batch-mappings", "Batch mapping translation: resolve many symbols across mapping namespaces with one shared Minecraft version. Per-entry retry suggestions point at find-mapping.", batchMappingsShape, { readOnlyHint: true }, async (args) => runTool("batch-mappings", args, batchMappingsSchema, async (input) => batchMappingsService.execute(input)));
449
507
  registerToolSchema("batch-mappings", batchMappingsSchema);
450
508
  }
451
- 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({
509
+ expertTool("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({
452
510
  target: input.target,
453
511
  mapping: input.mapping,
454
512
  sourcePriority: input.sourcePriority,
@@ -458,7 +516,8 @@ server.tool("resolve-artifact", "Resolve source artifact from a target object ({
458
516
  scope: input.scope,
459
517
  preferProjectVersion: input.preferProjectVersion,
460
518
  strictVersion: input.strictVersion,
461
- compact: input.compact
519
+ includeSampleEntries: input.detail === "full" ||
520
+ (Array.isArray(input.include) && input.include.includes("samples"))
462
521
  })));
463
522
  registerToolSchema("resolve-artifact", resolveArtifactSchema);
464
523
  const findClassShape = {
@@ -467,13 +526,13 @@ const findClassShape = {
467
526
  limit: optionalPositiveInt.describe("default 20, max 200")
468
527
  };
469
528
  const findClassSchema = z.object(findClassShape);
470
- server.tool("find-class", "Resolve a simple or qualified class name to fully-qualified class names within an artifact. Use this before get-class-source when you only have a simple name.", findClassShape, { readOnlyHint: true }, async (args) => runTool("find-class", args, findClassSchema, async (input) => sourceService.findClass({
529
+ expertTool("find-class", "Resolve a simple or qualified class name to fully-qualified class names within an artifact. Use this before get-class-source when you only have a simple name.", findClassShape, { readOnlyHint: true }, async (args) => runTool("find-class", args, findClassSchema, async (input) => sourceService.findClass({
471
530
  className: input.className,
472
531
  artifactId: input.artifactId,
473
532
  limit: input.limit
474
533
  })));
475
534
  registerToolSchema("find-class", findClassSchema);
476
- 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) => {
535
+ expertTool("get-class-source", "Get Java source for a class by target ({ kind: 'artifact', artifactId } or { kind: 'version'|'jar'|'coordinate'|'workspace'|'dependency', ... } — same shape as resolve-artifact). To read source text, pass mode=snippet (bounded excerpt) or mode=full (entire source); the default mode=metadata returns a symbol outline only, not the body. Not read-only: outputFile writes the source to disk.", getClassSourceShape, { readOnlyHint: false }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => {
477
536
  const normalizedTarget = normalizeSourceLookupTarget(input.target);
478
537
  return sourceService.getClassSource({
479
538
  className: input.className,
@@ -496,7 +555,7 @@ server.tool("get-class-source", "Get Java source for a class by target ({ type:
496
555
  });
497
556
  }));
498
557
  registerToolSchema("get-class-source", getClassSourceSchema);
499
- 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) => {
558
+ expertTool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode by target ({ kind: 'artifact', artifactId } or { kind: 'version'|'jar'|'coordinate'|'workspace'|'dependency', ... } — same shape as resolve-artifact).", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => {
500
559
  const normalizedTarget = normalizeSourceLookupTarget(input.target);
501
560
  return sourceService.getClassMembers({
502
561
  className: input.className,
@@ -510,15 +569,20 @@ server.tool("get-class-members", "Get fields/methods/constructors for one class
510
569
  includeInherited: input.includeInherited,
511
570
  memberPattern: input.memberPattern,
512
571
  maxMembers: input.maxMembers,
572
+ cursor: input.cursor,
513
573
  projectPath: input.projectPath,
514
574
  gradleUserHome: input.gradleUserHome,
515
575
  scope: input.scope,
516
576
  preferProjectVersion: input.preferProjectVersion,
517
- strictVersion: input.strictVersion
577
+ strictVersion: input.strictVersion,
578
+ // includeDescriptors is the documented alias for include:["descriptors"]; honor both
579
+ // (mirror batch-class-members-service / inspect-minecraft class-members handler).
580
+ includeDescriptors: input.includeDescriptors === true ||
581
+ (Array.isArray(input.include) && input.include.includes("descriptors"))
518
582
  });
519
583
  }));
520
584
  registerToolSchema("get-class-members", getClassMembersSchema);
521
- 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) => {
585
+ expertTool("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) => {
522
586
  const scope = input.packagePrefix || input.fileGlob || input.symbolKind
523
587
  ? {
524
588
  packagePrefix: input.packagePrefix,
@@ -541,20 +605,20 @@ server.tool("search-class-source", "Search indexed class source files for one ar
541
605
  });
542
606
  }));
543
607
  registerToolSchema("search-class-source", searchClassSourceSchema);
544
- 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({
608
+ expertTool("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({
545
609
  artifactId: input.artifactId,
546
610
  filePath: input.filePath,
547
611
  maxBytes: input.maxBytes
548
612
  })));
549
613
  registerToolSchema("get-artifact-file", getArtifactFileSchema);
550
- 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({
614
+ expertTool("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({
551
615
  artifactId: input.artifactId,
552
616
  prefix: input.prefix,
553
617
  limit: input.limit,
554
618
  cursor: input.cursor
555
619
  })));
556
620
  registerToolSchema("list-artifact-files", listArtifactFilesSchema);
557
- 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({
621
+ expertTool("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({
558
622
  symbol: input.symbol,
559
623
  descriptor: input.descriptor,
560
624
  fromVersion: input.fromVersion,
@@ -567,7 +631,7 @@ server.tool("trace-symbol-lifecycle", "Trace which Minecraft versions contain a
567
631
  includeTimeline: input.includeTimeline
568
632
  })));
569
633
  registerToolSchema("trace-symbol-lifecycle", traceSymbolLifecycleSchema);
570
- server.tool("diff-class-signatures", "Compare one class signature between two Minecraft versions and report added/removed/modified constructors, methods, and fields.", diffClassSignaturesShape, { readOnlyHint: true }, async (args) => runTool("diff-class-signatures", args, diffClassSignaturesSchema, async (input) => sourceService.diffClassSignatures({
634
+ expertTool("diff-class-signatures", "Compare one class signature between two Minecraft versions and report added/removed/modified constructors, methods, and fields.", diffClassSignaturesShape, { readOnlyHint: true }, async (args) => runTool("diff-class-signatures", args, diffClassSignaturesSchema, async (input) => sourceService.diffClassSignatures({
571
635
  className: input.className,
572
636
  fromVersion: input.fromVersion,
573
637
  toVersion: input.toVersion,
@@ -577,7 +641,7 @@ server.tool("diff-class-signatures", "Compare one class signature between two Mi
577
641
  includeFullDiff: input.includeFullDiff
578
642
  })));
579
643
  registerToolSchema("diff-class-signatures", diffClassSignaturesSchema);
580
- 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({
644
+ expertTool("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({
581
645
  version: input.version,
582
646
  kind: input.kind,
583
647
  name: input.name,
@@ -587,12 +651,13 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
587
651
  targetMapping: input.targetMapping,
588
652
  sourcePriority: input.sourcePriority,
589
653
  gradleUserHome: input.gradleUserHome,
654
+ nameMode: input.nameMode,
590
655
  signatureMode: input.signatureMode,
591
656
  disambiguation: input.disambiguation,
592
657
  maxCandidates: input.maxCandidates
593
658
  })));
594
659
  registerToolSchema("find-mapping", findMappingSchema);
595
- 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({
660
+ expertTool("resolve-method-mapping-exact", "Strict shortcut for find-mapping(kind=method, signatureMode=exact): resolve one method mapping by owner+name+descriptor between namespaces and report resolved/not_found/ambiguous. Stricter than find-mapping's exact mode — it requires a COMPLETE descriptor projection and returns mapping_unavailable when the descriptor's class references cannot all be projected to the target namespace (find-mapping is more lenient there). Use find-mapping kind=method signatureMode=exact unless you specifically need that strict-completeness guarantee.", resolveMethodMappingExactShape, { readOnlyHint: true }, async (args) => runTool("resolve-method-mapping-exact", args, resolveMethodMappingExactSchema, async (input) => sourceService.resolveMethodMappingExact({
596
661
  version: input.version,
597
662
  name: input.name,
598
663
  owner: input.owner,
@@ -604,17 +669,18 @@ server.tool("resolve-method-mapping-exact", "Resolve one method mapping exactly
604
669
  maxCandidates: input.maxCandidates
605
670
  })));
606
671
  registerToolSchema("resolve-method-mapping-exact", resolveMethodMappingExactSchema);
607
- 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({
672
+ expertTool("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({
608
673
  version: input.version,
609
674
  className: input.className,
610
675
  classNameMapping: input.classNameMapping,
611
676
  includeKinds: parseClassApiKinds(input.includeKinds),
612
677
  sourcePriority: input.sourcePriority,
613
678
  gradleUserHome: input.gradleUserHome,
614
- maxRows: input.maxRows
679
+ maxRows: input.maxRows,
680
+ cursor: input.cursor
615
681
  })));
616
682
  registerToolSchema("get-class-api-matrix", getClassApiMatrixSchema);
617
- 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({
683
+ expertTool("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({
618
684
  projectPath: input.projectPath,
619
685
  version: input.version,
620
686
  kind: input.kind,
@@ -627,7 +693,7 @@ server.tool("resolve-workspace-symbol", "Resolve class/field/method names as see
627
693
  maxCandidates: input.maxCandidates
628
694
  })));
629
695
  registerToolSchema("resolve-workspace-symbol", resolveWorkspaceSymbolSchema);
630
- 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({
696
+ expertTool("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({
631
697
  version: input.version,
632
698
  kind: input.kind,
633
699
  owner: input.owner,
@@ -641,29 +707,29 @@ server.tool("check-symbol-exists", "Check whether a class/field/method symbol ex
641
707
  maxCandidates: input.maxCandidates
642
708
  })));
643
709
  registerToolSchema("check-symbol-exists", checkSymbolExistsSchema);
644
- 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({
710
+ server.tool("nbt-to-json", "Decode Java Edition NBT binary payload (base64) into typed JSON.", nbtToJsonShape, { readOnlyHint: true, openWorldHint: false }, async (args) => runTool("nbt-to-json", args, nbtToJsonSchema, async (input) => Promise.resolve(nbtBase64ToTypedJson({
645
711
  nbtBase64: input.nbtBase64,
646
712
  compression: input.compression
647
713
  }, nbtLimits))));
648
714
  registerToolSchema("nbt-to-json", nbtToJsonSchema);
649
- server.tool("nbt-apply-json-patch", "Apply RFC6902 add/remove/replace/test operations to typed NBT JSON.", nbtApplyJsonPatchShape, { readOnlyHint: true }, async (args) => runTool("nbt-apply-json-patch", args, nbtApplyJsonPatchSchema, async (input) => Promise.resolve(applyNbtJsonPatch({
715
+ server.tool("nbt-apply-json-patch", "Apply RFC6902 add/remove/replace/test operations to typed NBT JSON.", nbtApplyJsonPatchShape, { readOnlyHint: true, openWorldHint: false }, async (args) => runTool("nbt-apply-json-patch", args, nbtApplyJsonPatchSchema, async (input) => Promise.resolve(applyNbtJsonPatch({
650
716
  typedJson: input.typedJson,
651
717
  patch: input.patch
652
718
  }, nbtLimits))));
653
719
  registerToolSchema("nbt-apply-json-patch", nbtApplyJsonPatchSchema);
654
- server.tool("json-to-nbt", "Encode typed NBT JSON to Java Edition NBT binary payload (base64).", jsonToNbtShape, { readOnlyHint: true }, async (args) => runTool("json-to-nbt", args, jsonToNbtSchema, async (input) => Promise.resolve(typedJsonToNbtBase64({
720
+ server.tool("json-to-nbt", "Encode typed NBT JSON to Java Edition NBT binary payload (base64).", jsonToNbtShape, { readOnlyHint: true, openWorldHint: false }, async (args) => runTool("json-to-nbt", args, jsonToNbtSchema, async (input) => Promise.resolve(typedJsonToNbtBase64({
655
721
  typedJson: input.typedJson,
656
722
  compression: input.compression
657
723
  }, nbtLimits))));
658
724
  registerToolSchema("json-to-nbt", jsonToNbtSchema);
659
- server.tool("index-artifact", "Rebuild indexed files/symbols metadata for an existing artifactId. Does not resolve new artifacts.", indexArtifactShape, async (args) => runTool("index-artifact", args, indexArtifactSchema, async (input) => sourceService.indexArtifact({
725
+ expertTool("index-artifact", "Rebuild indexed files/symbols metadata for an existing artifactId. Does not resolve new artifacts.", indexArtifactShape, { readOnlyHint: false, idempotentHint: true }, async (args) => runTool("index-artifact", args, indexArtifactSchema, async (input) => sourceService.indexArtifact({
660
726
  artifactId: input.artifactId,
661
727
  force: input.force
662
728
  })));
663
729
  registerToolSchema("index-artifact", indexArtifactSchema);
664
- 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())));
730
+ server.tool("get-runtime-metrics", "Get runtime service counters and latency snapshots for cache/search/index diagnostics.", { readOnlyHint: true, openWorldHint: false }, async (args) => runTool("get-runtime-metrics", args, emptySchema, async () => Promise.resolve(sourceService.getRuntimeMetrics())));
665
731
  registerToolSchema("get-runtime-metrics", emptySchema);
666
- server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode signatures for a given version.", validateMixinShape, { readOnlyHint: true }, async (args, extra) => runTool("validate-mixin", args, validateMixinSchema, async (input) => sourceService.validateMixin({
732
+ expertTool("validate-mixin", "Validate Mixin source against Minecraft bytecode signatures for a given version.", validateMixinShape, { readOnlyHint: true }, async (args, extra) => runTool("validate-mixin", args, validateMixinSchema, async (input) => sourceService.validateMixin({
667
733
  input: input.input,
668
734
  sourceRoots: input.sourceRoots,
669
735
  version: input.version,
@@ -686,7 +752,7 @@ server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode
686
752
  stageEmitter: makeStageEmitter(extra)
687
753
  })));
688
754
  registerToolSchema("validate-mixin", validateMixinSchema);
689
- 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({
755
+ expertTool("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({
690
756
  content: input.content,
691
757
  version: input.version,
692
758
  mapping: input.mapping,
@@ -697,7 +763,7 @@ server.tool("validate-access-widener", "Validate Access Widener file entries aga
697
763
  preferProjectVersion: input.preferProjectVersion
698
764
  })));
699
765
  registerToolSchema("validate-access-widener", validateAccessWidenerSchema);
700
- 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({
766
+ expertTool("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({
701
767
  content: input.content,
702
768
  version: input.version,
703
769
  atNamespace: input.atNamespace,
@@ -708,7 +774,7 @@ server.tool("validate-access-transformer", "Validate Access Transformer file ent
708
774
  preferProjectVersion: input.preferProjectVersion
709
775
  })));
710
776
  registerToolSchema("validate-access-transformer", validateAccessTransformerSchema);
711
- 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) => {
777
+ expertTool("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) => {
712
778
  const result = await analyzeModJar(input.jarPath, {
713
779
  includeClasses: input.includeClasses ?? false
714
780
  });
@@ -722,7 +788,7 @@ server.tool("get-registry-data", "Get Minecraft registry data (blocks, items, bi
722
788
  maxEntriesPerRegistry: input.maxEntriesPerRegistry
723
789
  })));
724
790
  registerToolSchema("get-registry-data", getRegistryDataSchema);
725
- 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({
791
+ expertTool("compare-versions", "Compare two Minecraft versions to find added/removed classes and registry entry changes.", compareVersionsShape, { readOnlyHint: true }, async (args) => runTool("compare-versions", args, compareVersionsSchema, async (input) => sourceService.compareVersions({
726
792
  fromVersion: input.fromVersion,
727
793
  toVersion: input.toVersion,
728
794
  category: input.category,
@@ -730,14 +796,14 @@ server.tool("compare-versions", "Compare two Minecraft versions to find added/re
730
796
  maxClassResults: input.maxClassResults
731
797
  })));
732
798
  registerToolSchema("compare-versions", compareVersionsSchema);
733
- 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({
799
+ expertTool("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.", decompileModJarShape, { readOnlyHint: true }, async (args) => runTool("decompile-mod-jar", args, decompileModJarSchema, async (input) => sourceService.decompileModJar({
734
800
  jarPath: input.jarPath,
735
801
  className: input.className,
736
802
  includeFiles: input.includeFiles,
737
803
  maxFiles: input.maxFiles
738
804
  })));
739
805
  registerToolSchema("decompile-mod-jar", decompileModJarSchema);
740
- 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({
806
+ expertTool("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. Not read-only: outputFile writes the source to disk.", getModClassSourceShape, { readOnlyHint: false }, async (args) => runTool("get-mod-class-source", args, getModClassSourceSchema, async (input) => sourceService.getModClassSource({
741
807
  jarPath: input.jarPath,
742
808
  className: input.className,
743
809
  maxLines: input.maxLines,
@@ -745,19 +811,20 @@ server.tool("get-mod-class-source", "Get decompiled source code for a specific c
745
811
  outputFile: input.outputFile
746
812
  })));
747
813
  registerToolSchema("get-mod-class-source", getModClassSourceSchema);
748
- server.tool("search-mod-source", "Search through decompiled mod JAR source code by class name, method, field, or content pattern. The mod JAR will be decompiled automatically if not already cached.", searchModSourceShape, { readOnlyHint: true }, async (args) => runTool("search-mod-source", args, searchModSourceSchema, async (input) => sourceService.searchModSource({
814
+ expertTool("search-mod-source", "Search through decompiled mod JAR source code by class name, method, field, or content pattern. The mod JAR will be decompiled automatically if not already cached.", searchModSourceShape, { readOnlyHint: true }, async (args) => runTool("search-mod-source", args, searchModSourceSchema, async (input) => sourceService.searchModSource({
749
815
  jarPath: input.jarPath,
750
816
  query: input.query,
751
817
  searchType: input.searchType,
752
818
  limit: input.limit
753
819
  })));
754
820
  registerToolSchema("search-mod-source", searchModSourceSchema);
755
- server.tool("remap-mod-jar", "Remap a Fabric mod JAR from intermediary to yarn/mojang names. Requires Java to be installed.", remapModJarShape, { readOnlyHint: false }, async (args) => runTool("remap-mod-jar", args, remapModJarSchema, async (input) => {
821
+ expertTool("remap-mod-jar", "Remap a Fabric mod JAR from intermediary to yarn/mojang names. Requires Java to be installed.", remapModJarShape, { readOnlyHint: false }, async (args) => runTool("remap-mod-jar", args, remapModJarSchema, async (input) => {
756
822
  const result = await remapModJar({
757
823
  inputJar: input.inputJar,
758
824
  outputJar: input.outputJar,
759
825
  mcVersion: input.mcVersion,
760
- targetMapping: input.targetMapping
826
+ targetMapping: input.targetMapping,
827
+ forceRemap: input.forceRemap
761
828
  }, config);
762
829
  return result;
763
830
  }));
@@ -1,3 +1,4 @@
1
+ export declare const MAX_STDIO_SNAPSHOT = 6240;
1
2
  export interface JavaProcessOptions {
2
3
  jarPath: string;
3
4
  args: string[];
@@ -12,5 +13,13 @@ export interface JavaProcessResult {
12
13
  stdoutTail: string;
13
14
  stderrTail: string;
14
15
  }
16
+ export declare function limitStdio(text: string): string;
17
+ export declare function isAbsolutePath(value: string): boolean;
18
+ export declare function isOptionArg(value: string): boolean;
19
+ export declare function normalizeArgs(args: string[]): string[];
15
20
  export declare function assertJavaAvailable(): Promise<void>;
16
21
  export declare function runJavaProcess(options: JavaProcessOptions): Promise<JavaProcessResult>;
22
+ export declare const javaRunner: {
23
+ run: (options: JavaProcessOptions) => Promise<JavaProcessResult>;
24
+ assertAvailable: () => Promise<void>;
25
+ };
@@ -2,20 +2,20 @@ import { spawn } from "node:child_process";
2
2
  import { createError, ERROR_CODES } from "./errors.js";
3
3
  import { normalizePathForHost } from "./path-converter.js";
4
4
  const JAVA_CHECK_TIMEOUT_MS = 2_000;
5
- const MAX_STDIO_SNAPSHOT = 6_240;
6
- function limitStdio(text) {
5
+ export const MAX_STDIO_SNAPSHOT = 6_240;
6
+ export function limitStdio(text) {
7
7
  if (text.length <= MAX_STDIO_SNAPSHOT) {
8
8
  return text;
9
9
  }
10
10
  return text.slice(-MAX_STDIO_SNAPSHOT);
11
11
  }
12
- function isAbsolutePath(value) {
12
+ export function isAbsolutePath(value) {
13
13
  return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value);
14
14
  }
15
- function isOptionArg(value) {
15
+ export function isOptionArg(value) {
16
16
  return value.startsWith("-");
17
17
  }
18
- function normalizeArgs(args) {
18
+ export function normalizeArgs(args) {
19
19
  return args.map((arg) => {
20
20
  if (isOptionArg(arg)) {
21
21
  return arg;
@@ -117,4 +117,10 @@ export function runJavaProcess(options) {
117
117
  });
118
118
  });
119
119
  }
120
+ // Mutable container that lets tests stub the Java subprocess boundary via
121
+ // `mock.method(javaRunner, "run", ...)` without rewriting ESM named imports.
122
+ export const javaRunner = {
123
+ run: runJavaProcess,
124
+ assertAvailable: assertJavaAvailable
125
+ };
120
126
  //# sourceMappingURL=java-process.js.map
@@ -50,5 +50,12 @@ export type LoadedGraph = {
50
50
  adjacency: Map<import("../types.js").SourceMapping, import("../types.js").SourceMapping[]>;
51
51
  pathCache: Map<PairKey, import("../types.js").SourceMapping[] | undefined>;
52
52
  recordsByTarget: Map<import("../types.js").SourceMapping, MappingSymbolRecord[]>;
53
+ /**
54
+ * Graph-scoped cache of class-to-class descriptor projections, keyed by
55
+ * `path.join(">") + NUL + internalName`. Value is the projected internal name,
56
+ * or `null` to memoize an unmapped/ambiguous class. Shared across all member
57
+ * lookups on this graph; dies with graph eviction.
58
+ */
59
+ classProjectionCache: Map<string, string | null>;
53
60
  warnings: string[];
54
61
  };
@@ -37,7 +37,7 @@ export declare function normalizeDescriptorHint(descriptorHint: string | undefin
37
37
  export declare function applyDisambiguationHints(candidates: MappingLookupCandidate[], disambiguation: {
38
38
  ownerHint?: string;
39
39
  descriptorHint?: string;
40
- } | undefined): MappingLookupCandidate[];
40
+ } | undefined, warnings?: string[]): MappingLookupCandidate[];
41
41
  export declare function projectLookupCandidateDescriptor(candidate: MappingLookupCandidate, sourceDescriptor: string, targetDescriptor: string | undefined): MappingLookupCandidate;
42
42
  export declare function effectiveLoomSearchProjectPath(projectPath: string | undefined): string | undefined;
43
43
  export declare function collectTargetRecords(graph: LoadedGraph, targetMapping: SourceMapping): MappingSymbolRecord[];
@@ -376,7 +376,7 @@ export function normalizeDescriptorHint(descriptorHint) {
376
376
  const normalized = descriptorHint?.trim();
377
377
  return normalized || undefined;
378
378
  }
379
- export function applyDisambiguationHints(candidates, disambiguation) {
379
+ export function applyDisambiguationHints(candidates, disambiguation, warnings) {
380
380
  if (!disambiguation || candidates.length <= 1) {
381
381
  return candidates;
382
382
  }
@@ -400,6 +400,12 @@ export function applyDisambiguationHints(candidates, disambiguation) {
400
400
  if (descriptorMatched.length > 0) {
401
401
  filtered = descriptorMatched;
402
402
  }
403
+ else {
404
+ // Candidate descriptors are projected toward the target namespace, so a hint
405
+ // written in another namespace silently matches nothing. Surface that instead
406
+ // of leaving the ambiguity unexplained.
407
+ warnings?.push(`descriptorHint "${descriptorHint}" matched none of the ${filtered.length} candidate descriptor(s); it may be in a different mapping namespace. Hint ignored.`);
408
+ }
403
409
  }
404
410
  return filtered;
405
411
  }