@adhisang/minecraft-modding-mcp 6.2.0 → 7.0.0-rc.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 (98) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +21 -5
  3. package/dist/cache-policy.d.ts +71 -0
  4. package/dist/cache-policy.js +83 -0
  5. package/dist/cache-registry.js +6 -6
  6. package/dist/cli.js +74 -3
  7. package/dist/compat-stdio-transport.d.ts +1 -1
  8. package/dist/compat-stdio-transport.js +13 -1
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +8 -2
  11. package/dist/decompiler/vineflower.d.ts +1 -0
  12. package/dist/decompiler/vineflower.js +8 -5
  13. package/dist/entry-tools/analyze-mod-service.d.ts +70 -136
  14. package/dist/entry-tools/analyze-symbol-service.d.ts +112 -150
  15. package/dist/entry-tools/compare-minecraft-service.d.ts +59 -145
  16. package/dist/entry-tools/entry-tool-schema.d.ts +38 -4
  17. package/dist/entry-tools/entry-tool-schema.js +4 -1
  18. package/dist/entry-tools/inspect-minecraft/internal.d.ts +235 -799
  19. package/dist/entry-tools/inspect-minecraft/internal.js +65 -28
  20. package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
  21. package/dist/entry-tools/inspect-minecraft-service.js +1 -1
  22. package/dist/entry-tools/manage-cache-service.d.ts +81 -91
  23. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
  24. package/dist/entry-tools/validate-project-service.d.ts +164 -592
  25. package/dist/entry-tools/verify-mixin-target-service.d.ts +3 -19
  26. package/dist/era-classifier.d.ts +161 -0
  27. package/dist/era-classifier.js +292 -0
  28. package/dist/error-mapping.js +9 -2
  29. package/dist/index.d.ts +42 -4
  30. package/dist/index.js +637 -475
  31. package/dist/java-process.d.ts +2 -0
  32. package/dist/java-process.js +22 -2
  33. package/dist/json-rpc-framing.d.ts +77 -1
  34. package/dist/json-rpc-framing.js +249 -13
  35. package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
  36. package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
  37. package/dist/mapping/loaders/tiny-loom.js +45 -33
  38. package/dist/mapping/loaders/tiny-maven.js +6 -11
  39. package/dist/mapping/parsers/tiny.d.ts +57 -0
  40. package/dist/mapping/parsers/tiny.js +99 -22
  41. package/dist/mapping-service.d.ts +19 -0
  42. package/dist/mapping-service.js +93 -9
  43. package/dist/mcp-helpers.d.ts +19 -2
  44. package/dist/mcp-helpers.js +48 -6
  45. package/dist/minecraft-explorer-service.d.ts +1 -1
  46. package/dist/mixin/types.d.ts +8 -0
  47. package/dist/mod-analyzer.js +7 -7
  48. package/dist/mod-decompile-service.js +1 -0
  49. package/dist/nbt/java-nbt-codec.js +12 -2
  50. package/dist/nbt/json-patch.js +14 -3
  51. package/dist/nbt/pipeline.js +40 -3
  52. package/dist/nbt/typed-json.js +26 -1
  53. package/dist/registration-adapter.d.ts +32 -0
  54. package/dist/registration-adapter.js +52 -0
  55. package/dist/request-context.d.ts +7 -0
  56. package/dist/request-context.js +9 -0
  57. package/dist/resources.d.ts +1 -1
  58. package/dist/resources.js +25 -19
  59. package/dist/server-identity.d.ts +27 -0
  60. package/dist/server-identity.js +26 -0
  61. package/dist/source/access-validate.js +53 -0
  62. package/dist/source/artifact-resolver.d.ts +69 -1
  63. package/dist/source/artifact-resolver.js +215 -14
  64. package/dist/source/class-source.d.ts +22 -0
  65. package/dist/source/class-source.js +162 -29
  66. package/dist/source/did-you-mean.d.ts +12 -1
  67. package/dist/source/did-you-mean.js +6 -2
  68. package/dist/source/file-access.js +150 -46
  69. package/dist/source/indexer.js +1 -0
  70. package/dist/source/nested-jars.d.ts +19 -0
  71. package/dist/source/nested-jars.js +90 -21
  72. package/dist/source/shared-utils.d.ts +21 -0
  73. package/dist/source/shared-utils.js +23 -0
  74. package/dist/source-service.d.ts +12 -0
  75. package/dist/source-service.js +3 -0
  76. package/dist/stdio-supervisor.d.ts +357 -2
  77. package/dist/stdio-supervisor.js +1031 -80
  78. package/dist/storage/db.d.ts +2 -1
  79. package/dist/storage/db.js +15 -8
  80. package/dist/synthetic-decorator.d.ts +24 -0
  81. package/dist/synthetic-decorator.js +48 -0
  82. package/dist/tool-contract-manifest.js +1 -1
  83. package/dist/tool-guidance.d.ts +17 -1
  84. package/dist/tool-guidance.js +417 -13
  85. package/dist/tool-schema-registry.d.ts +2 -0
  86. package/dist/tool-schema-registry.js +4 -0
  87. package/dist/tool-schemas.d.ts +2214 -3915
  88. package/dist/tool-schemas.js +34 -7
  89. package/dist/types.d.ts +35 -0
  90. package/dist/v1-parity-schemas.d.ts +7 -0
  91. package/dist/v1-parity-schemas.js +5584 -0
  92. package/dist/version-diff-service.d.ts +33 -0
  93. package/dist/version-diff-service.js +148 -3
  94. package/dist/version-service.js +36 -14
  95. package/dist/warning-details.js +18 -1
  96. package/docs/README-ja.md +9 -3
  97. package/docs/tool-reference.md +267 -22
  98. package/package.json +12 -9
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
- import { readFileSync } from "node:fs";
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { McpServer } from "@modelcontextprotocol/server";
2
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
3
3
  import { makeStageEmitter } from "./stage-emitter.js";
4
4
  import { ZodError } from "zod";
5
+ import { RESOURCE_LISTS_CACHE_HINT } from "./cache-policy.js";
5
6
  import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
7
+ import { registerAppTool as registerAppToolDirect, registerExpertTool as registerExpertToolDirect } from "./registration-adapter.js";
6
8
  import { objectResult } from "./mcp-helpers.js";
9
+ import { runWithRequestContext } from "./request-context.js";
7
10
  import { prepareToolInput } from "./tool-input.js";
8
11
  import { DETAIL_ENABLED_TOOL_NAMES, DEFAULT_DETAIL_BY_TOOL, projectByDetail } from "./response-utils.js";
9
12
  import { loadConfig } from "./config.js";
@@ -15,6 +18,8 @@ import { remapModJar } from "./mod-remap-service.js";
15
18
  import { registerResources } from "./resources.js";
16
19
  import { SourceService } from "./source-service.js";
17
20
  import { ToolExecutionGate } from "./tool-execution-gate.js";
21
+ import { SERVER_IDENTITY, SERVER_VERSION } from "./server-identity.js";
22
+ import { STDIO_WORKER_MODE_ENV } from "./stdio-supervisor.js";
18
23
  import { capWarningDetailsForSummary, classifyWarnings } from "./warning-details.js";
19
24
  import { WorkspaceMappingService } from "./workspace-mapping-service.js";
20
25
  import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape } from "./entry-tools/inspect-minecraft-service.js";
@@ -31,7 +36,7 @@ import { BatchSymbolExistsService } from "./entry-tools/batch-symbol-exists-serv
31
36
  import { BatchMappingsService } from "./entry-tools/batch-mappings-service.js";
32
37
  import { createCacheRegistry } from "./cache-registry.js";
33
38
  import { buildEntryToolMeta } from "./entry-tools/response-contract.js";
34
- import { registerToolSchema } from "./tool-schema-registry.js";
39
+ import { getToolSchema, registerToolSchema as registerToolSchemaInRegistry } from "./tool-schema-registry.js";
35
40
  import { buildSuggestedCall } from "./build-suggested-call.js";
36
41
  import { applyErrorMetaExtensions, mapErrorToProblem } from "./tool-guidance.js";
37
42
  import { analyzeModJarSchema, analyzeModJarShape, batchClassMembersSchema, batchClassMembersShape, batchClassSourceSchema, batchClassSourceShape, batchMappingsSchema, batchMappingsShape, batchSymbolExistsSchema, batchSymbolExistsShape, checkSymbolExistsSchema, checkSymbolExistsShape, compareVersionsSchema, compareVersionsShape, decompileModJarSchema, decompileModJarShape, diffClassSignaturesSchema, diffClassSignaturesShape, emptySchema, findMappingSchema, findMappingShape, getArtifactFileSchema, getArtifactFileShape, getClassApiMatrixSchema, getClassApiMatrixShape, getClassMembersSchema, getClassMembersShape, getClassSourceSchema, getClassSourceShape, getModClassSourceSchema, getModClassSourceShape, getRegistryDataSchema, getRegistryDataShape, indexArtifactSchema, indexArtifactShape, jsonToNbtSchema, jsonToNbtShape, listArtifactFilesSchema, listArtifactFilesShape, listVersionsSchema, listVersionsShape, nbtApplyJsonPatchSchema, nbtApplyJsonPatchShape, nbtToJsonSchema, nbtToJsonShape, remapModJarSchema, remapModJarShape, resolveArtifactSchema, resolveArtifactShape, resolveMethodMappingExactSchema, resolveMethodMappingExactShape, resolveWorkspaceSymbolSchema, resolveWorkspaceSymbolShape, searchClassSourceSchema, searchClassSourceShape, searchModSourceSchema, searchModSourceShape, traceSymbolLifecycleSchema, traceSymbolLifecycleShape, validateAccessTransformerSchema, validateAccessTransformerShape, validateAccessWidenerSchema, validateAccessWidenerShape, validateMixinSchema, validateMixinShape, verifyMixinTargetSchema, verifyMixinTargetShape, findClassShape, findClassSchema } from "./tool-schemas.js";
@@ -64,34 +69,18 @@ const BATCH_DETAIL_TOOL_NAMES = new Set([
64
69
  "batch-mappings"
65
70
  ]);
66
71
  const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueue: 2 });
67
- function getServerVersionFromPackageJson() {
68
- try {
69
- const packageJsonUrl = new URL("../package.json", import.meta.url);
70
- const packageJson = JSON.parse(readFileSync(packageJsonUrl, "utf8"));
71
- if (typeof packageJson.version === "string" && packageJson.version.trim()) {
72
- return packageJson.version.trim();
73
- }
74
- }
75
- catch {
76
- // ignore and fallback
77
- }
78
- return "0.3.0";
79
- }
80
- const SERVER_VERSION = getServerVersionFromPackageJson();
81
- const server = new McpServer({
82
- name: "@adhisang/minecraft-modding-mcp",
83
- version: SERVER_VERSION
84
- });
85
- // The SDK validates tool args before invoking handlers and returns generic InvalidParams text.
86
- // Bypass that layer so runTool() remains the single source of truth for validation and error envelopes.
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 tool: prefer the entry tools (inspect-minecraft, analyze-symbol, compare-minecraft, analyze-mod, validate-project) first.";
94
- const expertTool = ((name, description, ...rest) => server.tool(name, description + EXPERT_TOOL_NOTE, ...rest));
72
+ // Server identity ({name, version}) comes from the canonical module shared
73
+ // with the supervisor's synthetic decorator (src/server-identity.ts): the SDK
74
+ // stamps the Implementation passed to McpServer verbatim as
75
+ // `_meta[SERVER_INFO_META_KEY]` on every modern result, so identity equality
76
+ // with supervisor-synthesized results holds by construction.
77
+ // The McpServer instance is constructed per serveStdio factory call inside
78
+ // buildServer() below; module scope keeps only the shared services/config.
79
+ // Tool registration goes through src/registration-adapter.ts: a
80
+ // validation-bypassing Standard Schema adapter keeps runTool() the single
81
+ // source of truth for validation and error envelopes, and serves the frozen
82
+ // pre-migration inputSchema bytes (src/v1-parity-schemas.ts) in tools/list.
83
+ // registerExpertTool() appends the expert-tool note to the description.
95
84
  const config = loadConfig();
96
85
  const nbtLimits = {
97
86
  maxInputBytes: config.maxNbtInputBytes,
@@ -110,11 +99,22 @@ const sourceService = new Proxy({}, {
110
99
  return typeof value === "function" ? value.bind(service) : value;
111
100
  }
112
101
  });
102
+ function recordToolCallBestEffort(tool, durationMs) {
103
+ try {
104
+ sourceService.recordToolCall(tool, durationMs);
105
+ }
106
+ catch (caughtError) {
107
+ log("warn", "tool.metrics.record.failed", {
108
+ tool,
109
+ reason: caughtError instanceof Error ? caughtError.message : String(caughtError)
110
+ });
111
+ }
112
+ }
113
113
  const workspaceMappingService = new WorkspaceMappingService();
114
114
  const inspectMinecraftService = new InspectMinecraftService({
115
115
  listVersions: (input) => sourceService.listVersions(input),
116
116
  resolveArtifact: (input) => sourceService.resolveArtifact(input),
117
- findClass: (input) => Promise.resolve(sourceService.findClass(input)),
117
+ findClass: (input) => sourceService.findClassIncludingNested(input),
118
118
  checkSymbolExists: (input) => sourceService.checkSymbolExists(input),
119
119
  getClassSource: (input) => sourceService.getClassSource(input),
120
120
  getClassMembers: (input) => sourceService.getClassMembers(input),
@@ -237,7 +237,6 @@ const batchSymbolExistsService = new BatchSymbolExistsService({
237
237
  const batchMappingsService = new BatchMappingsService({
238
238
  findMapping: (input) => sourceService.findMapping(input)
239
239
  });
240
- registerResources(server, sourceService);
241
240
  let processHandlersAttached = false;
242
241
  let serverStarted = false;
243
242
  function attachProcessErrorHandlers() {
@@ -251,6 +250,9 @@ function attachProcessErrorHandlers() {
251
250
  message: error.message,
252
251
  stack: error.stack
253
252
  });
253
+ if (process.env[STDIO_WORKER_MODE_ENV] === "1") {
254
+ process.exit(1);
255
+ }
254
256
  process.exitCode = 1;
255
257
  });
256
258
  process.on("unhandledRejection", (reason) => {
@@ -259,6 +261,9 @@ function attachProcessErrorHandlers() {
259
261
  message: error.message,
260
262
  stack: error.stack
261
263
  });
264
+ if (process.env[STDIO_WORKER_MODE_ENV] === "1") {
265
+ process.exit(1);
266
+ }
262
267
  process.exitCode = 1;
263
268
  });
264
269
  }
@@ -341,81 +346,83 @@ async function runTool(tool, rawInput, schema, action) {
341
346
  const startedAt = Date.now();
342
347
  let normalizedInput = rawInput;
343
348
  try {
344
- const preparedInput = prepareToolInput(rawInput);
345
- normalizedInput = preparedInput.normalizedInput;
346
- const { removedOfficialPaths, suggestedReplacementInput } = preparedInput;
347
- if (removedOfficialPaths.length > 0) {
348
- throw createError({
349
- code: ERROR_CODES.INVALID_INPUT,
350
- message: `The "official" mapping namespace was removed. Use "obfuscated" instead.`,
351
- details: {
352
- fieldErrors: removedOfficialPaths.map((path) => ({
353
- path,
354
- message: `"official" is no longer supported for this field. Use "obfuscated".`,
355
- code: "invalid_enum_value"
356
- })),
357
- nextAction: `Replace "official" with "obfuscated" in mapping-related fields and retry.`,
358
- // Route construction through buildSuggestedCall to satisfy the D12
359
- // invariant (tests/suggested-call-invariant.test.ts): every emitted
360
- // suggestedCall must be built by the helper. The downstream
361
- // mapErrorToProblem gate re-validates on emission; that second pass is
362
- // the intentional, cheap cost of the two complementary safety gates.
363
- ...(suggestedReplacementInput
364
- ? buildSuggestedCall({
365
- tool,
366
- params: suggestedReplacementInput
367
- })
368
- : {})
349
+ return await runWithRequestContext({ requestId }, async () => {
350
+ const preparedInput = prepareToolInput(rawInput);
351
+ normalizedInput = preparedInput.normalizedInput;
352
+ const { removedOfficialPaths, suggestedReplacementInput } = preparedInput;
353
+ if (removedOfficialPaths.length > 0) {
354
+ throw createError({
355
+ code: ERROR_CODES.INVALID_INPUT,
356
+ message: `The "official" mapping namespace was removed. Use "obfuscated" instead.`,
357
+ details: {
358
+ fieldErrors: removedOfficialPaths.map((path) => ({
359
+ path,
360
+ message: `"official" is no longer supported for this field. Use "obfuscated".`,
361
+ code: "invalid_enum_value"
362
+ })),
363
+ nextAction: `Replace "official" with "obfuscated" in mapping-related fields and retry.`,
364
+ // Route construction through buildSuggestedCall to satisfy the D12
365
+ // invariant (tests/suggested-call-invariant.test.ts): every emitted
366
+ // suggestedCall must be built by the helper. The downstream
367
+ // mapErrorToProblem gate re-validates on emission; that second pass is
368
+ // the intentional, cheap cost of the two complementary safety gates.
369
+ ...(suggestedReplacementInput
370
+ ? buildSuggestedCall({
371
+ tool,
372
+ params: suggestedReplacementInput
373
+ })
374
+ : {})
375
+ }
376
+ });
377
+ }
378
+ const parsedInput = schema.parse(normalizedInput);
379
+ const payload = await (HEAVY_TOOL_NAMES.has(tool)
380
+ ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
381
+ : action(parsedInput));
382
+ const { result, warnings, meta: resultMeta } = splitWarnings(payload);
383
+ // Expert tools share the entry-tool detail/include response contract (the old
384
+ // per-tool `compact` boolean is gone). projectByDetail reproduces the previous
385
+ // compact defaults byte-identically: resolution tools default detail=summary
386
+ // (old compact:true), source/file tools default detail=standard (old compact:false).
387
+ // The legacy includeProvenance/includeDescriptors flags are folded into the
388
+ // include set as aliases so Phase-4 behavior is preserved.
389
+ const shapeInput = readResponseShapeInput(parsedInput);
390
+ const effectiveDetail = ENTRY_TOOL_NAMES.has(tool)
391
+ ? (shapeInput.detail ?? "summary")
392
+ : (shapeInput.detail ?? DEFAULT_DETAIL_BY_TOOL[tool] ?? "summary");
393
+ let projectedResult = result;
394
+ if (DETAIL_ENABLED_TOOL_NAMES.has(tool)) {
395
+ projectedResult = projectByDetail(tool, projectedResult, effectiveDetail, shapeInput.include);
396
+ }
397
+ // Entry, expert, and batch tools report the applied detail/include shape in meta.
398
+ // detailApplied is omitted when it matches the tool's default to keep responses lean.
399
+ const defaultDetail = ENTRY_TOOL_NAMES.has(tool)
400
+ ? "summary"
401
+ : DEFAULT_DETAIL_BY_TOOL[tool] ?? "summary";
402
+ const entryMeta = ENTRY_TOOL_NAMES.has(tool) ||
403
+ DETAIL_ENABLED_TOOL_NAMES.has(tool) ||
404
+ BATCH_DETAIL_TOOL_NAMES.has(tool)
405
+ ? buildEntryToolMeta({
406
+ detail: effectiveDetail,
407
+ defaultDetail,
408
+ include: shapeInput.include.size > 0 ? [...shapeInput.include] : undefined
409
+ })
410
+ : undefined;
411
+ const durationMs = Date.now() - startedAt;
412
+ recordToolCallBestEffort(tool, durationMs);
413
+ const warningDetails = capWarningDetailsForSummary(classifyWarnings(warnings), effectiveDetail === "summary");
414
+ return objectResult({
415
+ result: projectedResult,
416
+ meta: {
417
+ ...(entryMeta ?? {}),
418
+ ...resultMeta,
419
+ requestId,
420
+ tool,
421
+ durationMs,
422
+ ...(warnings.length > 0 ? { warnings } : {}),
423
+ ...(warningDetails.length > 0 ? { warningDetails } : {})
369
424
  }
370
425
  });
371
- }
372
- const parsedInput = schema.parse(normalizedInput);
373
- const payload = await (HEAVY_TOOL_NAMES.has(tool)
374
- ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
375
- : action(parsedInput));
376
- const { result, warnings, meta: resultMeta } = splitWarnings(payload);
377
- // Expert tools share the entry-tool detail/include response contract (the old
378
- // per-tool `compact` boolean is gone). projectByDetail reproduces the previous
379
- // compact defaults byte-identically: resolution tools default detail=summary
380
- // (old compact:true), source/file tools default detail=standard (old compact:false).
381
- // The legacy includeProvenance/includeDescriptors flags are folded into the
382
- // include set as aliases so Phase-4 behavior is preserved.
383
- const shapeInput = readResponseShapeInput(parsedInput);
384
- const effectiveDetail = ENTRY_TOOL_NAMES.has(tool)
385
- ? (shapeInput.detail ?? "summary")
386
- : (shapeInput.detail ?? DEFAULT_DETAIL_BY_TOOL[tool] ?? "summary");
387
- let projectedResult = result;
388
- if (DETAIL_ENABLED_TOOL_NAMES.has(tool)) {
389
- projectedResult = projectByDetail(tool, projectedResult, effectiveDetail, shapeInput.include);
390
- }
391
- // Entry, expert, and batch tools report the applied detail/include shape in meta.
392
- // detailApplied is omitted when it matches the tool's default to keep responses lean.
393
- const defaultDetail = ENTRY_TOOL_NAMES.has(tool)
394
- ? "summary"
395
- : DEFAULT_DETAIL_BY_TOOL[tool] ?? "summary";
396
- const entryMeta = ENTRY_TOOL_NAMES.has(tool) ||
397
- DETAIL_ENABLED_TOOL_NAMES.has(tool) ||
398
- BATCH_DETAIL_TOOL_NAMES.has(tool)
399
- ? buildEntryToolMeta({
400
- detail: effectiveDetail,
401
- defaultDetail,
402
- include: shapeInput.include.size > 0 ? [...shapeInput.include] : undefined
403
- })
404
- : undefined;
405
- const durationMs = Date.now() - startedAt;
406
- sourceService.recordToolCall(tool, durationMs);
407
- const warningDetails = capWarningDetailsForSummary(classifyWarnings(warnings), effectiveDetail === "summary");
408
- return objectResult({
409
- result: projectedResult,
410
- meta: {
411
- ...(entryMeta ?? {}),
412
- ...resultMeta,
413
- requestId,
414
- tool,
415
- durationMs,
416
- ...(warnings.length > 0 ? { warnings } : {}),
417
- ...(warningDetails.length > 0 ? { warningDetails } : {})
418
- }
419
426
  });
420
427
  }
421
428
  catch (caughtError) {
@@ -454,7 +461,7 @@ async function runTool(tool, rawInput, schema, action) {
454
461
  });
455
462
  }
456
463
  const errorDurationMs = Date.now() - startedAt;
457
- sourceService.recordToolCall(tool, errorDurationMs);
464
+ recordToolCallBestEffort(tool, errorDurationMs);
458
465
  const errorMeta = {
459
466
  requestId,
460
467
  tool,
@@ -467,393 +474,530 @@ async function runTool(tool, rawInput, schema, action) {
467
474
  }, { isError: true });
468
475
  }
469
476
  }
470
- 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({
471
- includeSnapshots: input.includeSnapshots,
472
- limit: input.limit
473
- })));
474
- registerToolSchema("list-versions", listVersionsSchema);
475
- 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)));
476
- registerToolSchema("inspect-minecraft", inspectMinecraftSchema);
477
- server.tool("analyze-symbol", "Top-level workflow tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview. subject.kind='symbol' auto-detects class/field/method from the selector.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
478
- registerToolSchema("analyze-symbol", analyzeSymbolSchema);
479
- 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)));
480
- registerToolSchema("compare-minecraft", compareMinecraftSchema);
481
- server.tool("analyze-mod", "Top-level workflow tool for mod metadata inspection, decompile/search flows, class source, bytecode-only class member reads, and safe remap previews/applies.", analyzeModShape, { readOnlyHint: false }, async (args) => runTool("analyze-mod", args, analyzeModSchema, async (input) => analyzeModService.execute(input)));
482
- registerToolSchema("analyze-mod", analyzeModSchema);
483
- server.tool("validate-project", "Top-level workflow tool for project summary, direct mixin validation, and access widener/access transformer validation.", validateProjectShape, { readOnlyHint: true }, async (args, extra) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input, {
484
- stageEmitter: makeStageEmitter(extra)
485
- })));
486
- registerToolSchema("validate-project", validateProjectSchema);
487
- server.tool("manage-cache", "Top-level workflow 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)));
488
- registerToolSchema("manage-cache", manageCacheSchema);
489
- if (!VERIFY_MIXIN_TARGET_OFF) {
490
- server.tool("verify-mixin-target", "Single-call probe: does this target owner / member exist and which @Shadow / @Accessor / @Invoker should the mixin use? Reuses target.kind workspace/version/coordinate/dependency/jar.", verifyMixinTargetShape, { readOnlyHint: true }, async (args) => runTool("verify-mixin-target", args, verifyMixinTargetSchema, async (input) => verifyMixinTargetService.execute({
491
- owner: input.owner,
492
- member: input.member,
493
- mixinMemberName: input.mixinMemberName,
477
+ /**
478
+ * Builds a fully-registered McpServer instance. serveStdio calls its factory
479
+ * PER INSTANCE, not per connection: a modern `server/discover` opening builds
480
+ * a probe instance which a following legacy `initialize` DISCARDS
481
+ * (`product.close()`) before calling the factory again — and the probe path
482
+ * MUTATES the instance (installModernOnlyHandlers adds "2026-07-28" support
483
+ * and a server/discover handler). Every call therefore constructs a FRESH
484
+ * McpServer so probe-instance mutations can never leak onto the re-pinned
485
+ * legacy instance (frozen negotiate-down contract). Module-scope services,
486
+ * config, and helpers stay shared; only the McpServer and its registrations
487
+ * are per-instance.
488
+ *
489
+ * Tool registration ORDER: the CALL-SITE order inside this function is the
490
+ * frozen legacy tools/list order — do not reorder the call sites. The calls
491
+ * are captured as deferred thunks and executed at the bottom of the
492
+ * function: in call-site order for legacy/ctx-less instances (byte-frozen
493
+ * golden contract), and in raw tool-name-ascending order when serveStdio
494
+ * constructs a MODERN-era instance (`ctx.era === "modern"` — the SDK's
495
+ * documented era-parameterized factory seam; the v2 SDK itself emits
496
+ * registration order and never sorts). Each instance serves exactly one era,
497
+ * so the two orders can never mix on one connection. (Adopted ordering
498
+ * policy.)
499
+ *
500
+ * Cache hints (adopted policy, src/cache-policy.ts): the constructor options
501
+ * configure the non-zero resources/list + resources/templates/list rows;
502
+ * per-resource rows live in registerResources(); all hints ride the SDK's
503
+ * never-serialized carrier, so 2025-era responses are unaffected.
504
+ *
505
+ * NOTE: the function body below intentionally keeps the original module-scope
506
+ * indentation of the registration block to preserve a reviewable minimal diff.
507
+ */
508
+ // eslint-disable-next-line func-style
509
+ function buildServer(ctx) {
510
+ const server = new McpServer({
511
+ name: SERVER_IDENTITY.name,
512
+ version: SERVER_IDENTITY.version
513
+ }, {
514
+ // Advertised capabilities (adopted policy). The SDK defaults BOTH
515
+ // listChanged flags to `true` the first time a tool/resource is registered
516
+ // (`... ?? true`), so an explicit `false` passed here is the only public way
517
+ // to suppress them — the key cannot be omitted through any public API, and
518
+ // per the revision an explicit `false` and an absent flag are equivalent.
519
+ // The suppression is deliberate and is NOT era-gated: this server never
520
+ // emits `notifications/tools/list_changed` or
521
+ // `notifications/resources/list_changed` (the tool and resource surface is
522
+ // fixed at process start by environment flags), and `subscriptions/listen`
523
+ // answers -32601 in both eras, so advertising `true` would promise a stream
524
+ // no client can subscribe to or ever receive.
525
+ // KEY ORDER IS LOAD-BEARING: `resources` before `tools` reproduces the
526
+ // frozen wire order of the legacy `initialize` and modern `server/discover`
527
+ // capability payloads. Pinned by
528
+ // tests/stdio/stdio-dependency-method-inventory.test.ts (legacy) and
529
+ // tests/stdio/stdio-modern-discover-contents.test.ts (modern).
530
+ capabilities: {
531
+ resources: { listChanged: false },
532
+ tools: { listChanged: false }
533
+ },
534
+ cacheHints: {
535
+ "resources/list": RESOURCE_LISTS_CACHE_HINT,
536
+ "resources/templates/list": RESOURCE_LISTS_CACHE_HINT
537
+ }
538
+ });
539
+ registerResources(server, sourceService);
540
+ // Deferred tool-registration capture: the adapter imports are RENAMED to
541
+ // registerAppToolDirect/registerExpertToolDirect, and these local wrapper
542
+ // functions take the original names, so every call site below records
543
+ // {name, thunk} in call-site order; the executor at the bottom of this
544
+ // function replays them in the era-appropriate order.
545
+ const pendingToolRegistrations = [];
546
+ const registerAppTool = (srv, name, description, shape, annotations, handler) => {
547
+ pendingToolRegistrations.push({
548
+ name,
549
+ register: () => registerAppToolDirect(srv, name, description, shape, annotations, handler)
550
+ });
551
+ };
552
+ const registerExpertTool = (srv, name, description, shape, annotations, handler) => {
553
+ pendingToolRegistrations.push({
554
+ name,
555
+ register: () => registerExpertToolDirect(srv, name, description, shape, annotations, handler)
556
+ });
557
+ };
558
+ // The app-level schema registry (tool-schema-registry) is register-once and
559
+ // module-scoped: the eager `buildServer()` call at the bottom of this module
560
+ // populates it at load time; re-builds (one per serveStdio instance) skip
561
+ // re-registration so the registry never sees a duplicate.
562
+ const registerToolSchema = (name, schema) => {
563
+ if (getToolSchema(name) === undefined)
564
+ registerToolSchemaInRegistry(name, schema);
565
+ };
566
+ registerExpertTool(server, "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({
567
+ includeSnapshots: input.includeSnapshots,
568
+ limit: input.limit
569
+ })));
570
+ registerToolSchema("list-versions", listVersionsSchema);
571
+ registerAppTool(server, "inspect-minecraft", "Top-level workflow tool for version discovery, artifact resolution, class inspection, source search, file reads, and file listings. Workspace subject.focus is a structured class/file/search object, never a string; task=auto dispatches from subject.kind and focus.kind.", inspectMinecraftShape, { readOnlyHint: true }, async (args) => runTool("inspect-minecraft", args, inspectMinecraftSchema, async (input) => inspectMinecraftService.execute(input)));
572
+ registerToolSchema("inspect-minecraft", inspectMinecraftSchema);
573
+ registerAppTool(server, "analyze-symbol", "Top-level workflow tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview. subject.kind='symbol' auto-detects class/field/method from the selector.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
574
+ registerToolSchema("analyze-symbol", analyzeSymbolSchema);
575
+ registerAppTool(server, "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)));
576
+ registerToolSchema("compare-minecraft", compareMinecraftSchema);
577
+ registerAppTool(server, "analyze-mod", "Top-level workflow tool for mod metadata inspection, decompile/search flows, class source, bytecode-only class member reads, and safe remap previews/applies.", analyzeModShape, { readOnlyHint: false }, async (args) => runTool("analyze-mod", args, analyzeModSchema, async (input) => analyzeModService.execute(input)));
578
+ registerToolSchema("analyze-mod", analyzeModSchema);
579
+ registerAppTool(server, "validate-project", "Top-level workflow tool for project summary, direct mixin validation, and access widener/access transformer validation.", validateProjectShape, { readOnlyHint: true }, async (args, ctx) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input, {
580
+ stageEmitter: makeStageEmitter(stageEmitterExtraFromCtx(ctx))
581
+ })));
582
+ registerToolSchema("validate-project", validateProjectSchema);
583
+ registerAppTool(server, "manage-cache", "Top-level workflow 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)));
584
+ registerToolSchema("manage-cache", manageCacheSchema);
585
+ if (!VERIFY_MIXIN_TARGET_OFF) {
586
+ registerAppTool(server, "verify-mixin-target", "Single-call probe: does this target owner / member exist and which @Shadow / @Accessor / @Invoker should the mixin use? Reuses target.kind workspace/version/coordinate/dependency/jar.", verifyMixinTargetShape, { readOnlyHint: true }, async (args) => runTool("verify-mixin-target", args, verifyMixinTargetSchema, async (input) => verifyMixinTargetService.execute({
587
+ owner: input.owner,
588
+ member: input.member,
589
+ mixinMemberName: input.mixinMemberName,
590
+ mapping: input.mapping,
591
+ autoRemap: input.autoRemap,
592
+ sourcePriority: input.sourcePriority,
593
+ projectPath: input.projectPath,
594
+ gradleUserHome: input.gradleUserHome,
595
+ target: input.target,
596
+ scope: input.scope,
597
+ preferProjectVersion: input.preferProjectVersion,
598
+ strictVersion: input.strictVersion
599
+ })));
600
+ registerToolSchema("verify-mixin-target", verifyMixinTargetSchema);
601
+ }
602
+ if (!BATCH_TOOLS_OFF) {
603
+ registerAppTool(server, "batch-class-source", "Batch get-class-source for many classes sharing one resolved artifact; returns per-entry status plus aggregate summary. Not read-only: per-entry outputFile writes to disk.", batchClassSourceShape, { readOnlyHint: false }, async (args) => runTool("batch-class-source", args, batchClassSourceSchema, async (input) => batchClassSourceService.execute(input)));
604
+ registerToolSchema("batch-class-source", batchClassSourceSchema);
605
+ registerAppTool(server, "batch-class-members", "Batch get-class-members for many classes sharing one resolved artifact; returns per-entry status plus aggregate summary.", batchClassMembersShape, { readOnlyHint: true }, async (args) => runTool("batch-class-members", args, batchClassMembersSchema, async (input) => batchClassMembersService.execute(input)));
606
+ registerToolSchema("batch-class-members", batchClassMembersSchema);
607
+ registerAppTool(server, "batch-symbol-exists", "Batch check-symbol-exists for many symbols against one shared Minecraft version (target.kind=version or workspace only).", batchSymbolExistsShape, { readOnlyHint: true }, async (args) => runTool("batch-symbol-exists", args, batchSymbolExistsSchema, async (input) => batchSymbolExistsService.execute(input)));
608
+ registerToolSchema("batch-symbol-exists", batchSymbolExistsSchema);
609
+ registerAppTool(server, "batch-mappings", "Batch find-mapping: resolve many symbols across mapping namespaces with one shared Minecraft version.", batchMappingsShape, { readOnlyHint: true }, async (args) => runTool("batch-mappings", args, batchMappingsSchema, async (input) => batchMappingsService.execute(input)));
610
+ registerToolSchema("batch-mappings", batchMappingsSchema);
611
+ }
612
+ registerExpertTool(server, "resolve-artifact", "Resolve a source artifact and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
613
+ target: input.target,
494
614
  mapping: input.mapping,
495
- autoRemap: input.autoRemap,
496
615
  sourcePriority: input.sourcePriority,
616
+ allowDecompile: input.allowDecompile,
497
617
  projectPath: input.projectPath,
498
618
  gradleUserHome: input.gradleUserHome,
499
- target: input.target,
500
619
  scope: input.scope,
501
620
  preferProjectVersion: input.preferProjectVersion,
502
- strictVersion: input.strictVersion
621
+ strictVersion: input.strictVersion,
622
+ includeSampleEntries: input.detail === "full" ||
623
+ (Array.isArray(input.include) && input.include.includes("samples"))
503
624
  })));
504
- registerToolSchema("verify-mixin-target", verifyMixinTargetSchema);
505
- }
506
- if (!BATCH_TOOLS_OFF) {
507
- server.tool("batch-class-source", "Batch get-class-source for many classes sharing one resolved artifact; returns per-entry status plus aggregate summary. Not read-only: per-entry outputFile writes to disk.", batchClassSourceShape, { readOnlyHint: false }, async (args) => runTool("batch-class-source", args, batchClassSourceSchema, async (input) => batchClassSourceService.execute(input)));
508
- registerToolSchema("batch-class-source", batchClassSourceSchema);
509
- server.tool("batch-class-members", "Batch get-class-members for many classes sharing one resolved artifact; returns per-entry status plus aggregate summary.", batchClassMembersShape, { readOnlyHint: true }, async (args) => runTool("batch-class-members", args, batchClassMembersSchema, async (input) => batchClassMembersService.execute(input)));
510
- registerToolSchema("batch-class-members", batchClassMembersSchema);
511
- server.tool("batch-symbol-exists", "Batch check-symbol-exists for many symbols against one shared Minecraft version (target.kind=version or workspace only).", batchSymbolExistsShape, { readOnlyHint: true }, async (args) => runTool("batch-symbol-exists", args, batchSymbolExistsSchema, async (input) => batchSymbolExistsService.execute(input)));
512
- registerToolSchema("batch-symbol-exists", batchSymbolExistsSchema);
513
- server.tool("batch-mappings", "Batch find-mapping: resolve many symbols across mapping namespaces with one shared Minecraft version.", batchMappingsShape, { readOnlyHint: true }, async (args) => runTool("batch-mappings", args, batchMappingsSchema, async (input) => batchMappingsService.execute(input)));
514
- registerToolSchema("batch-mappings", batchMappingsSchema);
515
- }
516
- expertTool("resolve-artifact", "Resolve a source artifact and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
517
- target: input.target,
518
- mapping: input.mapping,
519
- sourcePriority: input.sourcePriority,
520
- allowDecompile: input.allowDecompile,
521
- projectPath: input.projectPath,
522
- gradleUserHome: input.gradleUserHome,
523
- scope: input.scope,
524
- preferProjectVersion: input.preferProjectVersion,
525
- strictVersion: input.strictVersion,
526
- includeSampleEntries: input.detail === "full" ||
527
- (Array.isArray(input.include) && input.include.includes("samples"))
528
- })));
529
- registerToolSchema("resolve-artifact", resolveArtifactSchema);
530
- // Flat tools accept `target` as an additive alternative to `artifactId`
531
- // (mutually exclusive, enforced by each schema). A target is resolved to its
532
- // artifactId here before the underlying artifactId-based service call.
533
- async function resolveFlatArtifactId(input) {
534
- if (input.artifactId) {
535
- return input.artifactId;
536
- }
537
- const target = input.target;
538
- // {kind:"artifact"} reuses an already-resolved artifact without a resolve
539
- // round-trip. Workspace-context kinds carry their own projectPath when the
540
- // target shape provides one; kinds that require workspace context beyond
541
- // that fail with the resolver's own projectPath guidance.
542
- if (target?.kind === "artifact" && target.artifactId) {
543
- return target.artifactId;
625
+ registerToolSchema("resolve-artifact", resolveArtifactSchema);
626
+ // Flat tools accept `target` as an additive alternative to `artifactId`
627
+ // (mutually exclusive, enforced by each schema). A target is resolved to its
628
+ // artifactId here before the underlying artifactId-based service call.
629
+ async function resolveFlatArtifactId(input) {
630
+ if (input.artifactId) {
631
+ return input.artifactId;
632
+ }
633
+ const target = input.target;
634
+ // {kind:"artifact"} reuses an already-resolved artifact without a resolve
635
+ // round-trip. Workspace-context kinds carry their own projectPath when the
636
+ // target shape provides one; kinds that require workspace context beyond
637
+ // that fail with the resolver's own projectPath guidance.
638
+ if (target?.kind === "artifact" && target.artifactId) {
639
+ return target.artifactId;
640
+ }
641
+ const projectPath = input.projectPath ?? target?.projectPath;
642
+ const resolved = await sourceService.resolveArtifact({
643
+ target: target,
644
+ ...(projectPath ? { projectPath } : {})
645
+ });
646
+ return resolved.artifactId;
544
647
  }
545
- const resolved = await sourceService.resolveArtifact({
546
- target: target,
547
- ...(target && "projectPath" in target && target.projectPath
548
- ? { projectPath: target.projectPath }
549
- : {})
550
- });
551
- return resolved.artifactId;
552
- }
553
- 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({
554
- className: input.className,
555
- artifactId: await resolveFlatArtifactId(input),
556
- limit: input.limit
557
- })));
558
- registerToolSchema("find-class", findClassSchema);
559
- expertTool("get-class-source", "Get Java source for a class. Default mode=metadata returns a symbol outline only; pass mode=snippet or mode=full to read source text. Not read-only: outputFile writes to disk.", getClassSourceShape, { readOnlyHint: false }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => {
560
- const normalizedTarget = normalizeSourceLookupTarget(input.target);
561
- return sourceService.getClassSource({
648
+ registerExpertTool(server, "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.findClassIncludingNested({
649
+ className: input.className,
650
+ artifactId: await resolveFlatArtifactId(input),
651
+ limit: input.limit
652
+ })));
653
+ registerToolSchema("find-class", findClassSchema);
654
+ registerExpertTool(server, "get-class-source", "Get Java source for a class. Default mode=metadata returns a symbol outline only; pass mode=snippet or mode=full to read source text. Not read-only: outputFile writes to disk.", getClassSourceShape, { readOnlyHint: false }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => {
655
+ const normalizedTarget = normalizeSourceLookupTarget(input.target);
656
+ return sourceService.getClassSource({
657
+ className: input.className,
658
+ mode: input.mode,
659
+ artifactId: normalizedTarget.artifactId,
660
+ target: normalizedTarget.target,
661
+ mapping: input.mapping,
662
+ sourcePriority: input.sourcePriority,
663
+ allowDecompile: input.allowDecompile,
664
+ projectPath: input.projectPath,
665
+ gradleUserHome: input.gradleUserHome,
666
+ scope: input.scope,
667
+ preferProjectVersion: input.preferProjectVersion,
668
+ strictVersion: input.strictVersion,
669
+ startLine: input.startLine,
670
+ endLine: input.endLine,
671
+ maxLines: input.maxLines,
672
+ maxChars: input.maxChars,
673
+ outputFile: input.outputFile
674
+ });
675
+ }));
676
+ registerToolSchema("get-class-source", getClassSourceSchema);
677
+ registerExpertTool(server, "get-class-members", "Get fields/methods/constructors for one class from binary bytecode.", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => {
678
+ const normalizedTarget = normalizeSourceLookupTarget(input.target);
679
+ return sourceService.getClassMembers({
680
+ className: input.className,
681
+ artifactId: normalizedTarget.artifactId,
682
+ target: normalizedTarget.target,
683
+ mapping: input.mapping,
684
+ sourcePriority: input.sourcePriority,
685
+ allowDecompile: input.allowDecompile,
686
+ access: input.access,
687
+ includeSynthetic: input.includeSynthetic,
688
+ includeInherited: input.includeInherited,
689
+ memberPattern: input.memberPattern,
690
+ maxMembers: input.maxMembers,
691
+ projection: input.projection,
692
+ cursor: input.cursor,
693
+ projectPath: input.projectPath,
694
+ gradleUserHome: input.gradleUserHome,
695
+ scope: input.scope,
696
+ preferProjectVersion: input.preferProjectVersion,
697
+ strictVersion: input.strictVersion,
698
+ // includeDescriptors is the documented alias for include:["descriptors"]; honor both
699
+ // (mirror batch-class-members-service / inspect-minecraft class-members handler).
700
+ includeDescriptors: input.includeDescriptors === true ||
701
+ (Array.isArray(input.include) && input.include.includes("descriptors"))
702
+ });
703
+ }));
704
+ registerToolSchema("get-class-members", getClassMembersSchema);
705
+ registerExpertTool(server, "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) => {
706
+ const scope = input.packagePrefix || input.fileGlob || input.symbolKind
707
+ ? {
708
+ packagePrefix: input.packagePrefix,
709
+ fileGlob: input.fileGlob,
710
+ symbolKind: input.symbolKind
711
+ }
712
+ : undefined;
713
+ return sourceService.searchClassSource({
714
+ artifactId: await resolveFlatArtifactId(input),
715
+ query: input.query,
716
+ intent: input.intent,
717
+ match: input.match,
718
+ scope: scope,
719
+ queryMode: input.queryMode,
720
+ limit: input.limit,
721
+ cursor: input.cursor,
722
+ queryNamespace: input.queryNamespace,
723
+ sourcePriority: input.sourcePriority,
724
+ gradleUserHome: input.gradleUserHome
725
+ });
726
+ }));
727
+ registerToolSchema("search-class-source", searchClassSourceSchema);
728
+ registerExpertTool(server, "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({
729
+ artifactId: await resolveFlatArtifactId(input),
730
+ filePath: input.filePath,
731
+ maxBytes: input.maxBytes
732
+ })));
733
+ registerToolSchema("get-artifact-file", getArtifactFileSchema);
734
+ registerExpertTool(server, "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({
735
+ artifactId: await resolveFlatArtifactId(input),
736
+ prefix: input.prefix,
737
+ limit: input.limit,
738
+ cursor: input.cursor
739
+ })));
740
+ registerToolSchema("list-artifact-files", listArtifactFilesSchema);
741
+ registerExpertTool(server, "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({
742
+ symbol: input.symbol,
743
+ descriptor: input.descriptor,
744
+ fromVersion: input.fromVersion,
745
+ toVersion: input.toVersion,
746
+ mapping: input.mapping,
747
+ sourcePriority: input.sourcePriority,
748
+ gradleUserHome: input.gradleUserHome,
749
+ includeSnapshots: input.includeSnapshots,
750
+ maxVersions: input.maxVersions,
751
+ includeTimeline: input.includeTimeline
752
+ })));
753
+ registerToolSchema("trace-symbol-lifecycle", traceSymbolLifecycleSchema);
754
+ registerExpertTool(server, "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({
562
755
  className: input.className,
563
- mode: input.mode,
564
- artifactId: normalizedTarget.artifactId,
565
- target: normalizedTarget.target,
756
+ fromVersion: input.fromVersion,
757
+ toVersion: input.toVersion,
566
758
  mapping: input.mapping,
567
759
  sourcePriority: input.sourcePriority,
568
- allowDecompile: input.allowDecompile,
760
+ gradleUserHome: input.gradleUserHome,
761
+ includeFullDiff: input.includeFullDiff
762
+ })));
763
+ registerToolSchema("diff-class-signatures", diffClassSignaturesSchema);
764
+ registerExpertTool(server, "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({
765
+ version: input.version,
766
+ kind: input.kind,
767
+ name: input.name,
768
+ owner: input.owner,
769
+ descriptor: input.descriptor,
770
+ sourceMapping: input.sourceMapping,
771
+ targetMapping: input.targetMapping,
772
+ sourcePriority: input.sourcePriority,
773
+ gradleUserHome: input.gradleUserHome,
774
+ nameMode: input.nameMode,
775
+ signatureMode: input.signatureMode,
776
+ disambiguation: input.disambiguation,
777
+ maxCandidates: input.maxCandidates
778
+ })));
779
+ registerToolSchema("find-mapping", findMappingSchema);
780
+ registerExpertTool(server, "resolve-method-mapping-exact", "Strict variant of find-mapping(kind=method, signatureMode=exact): requires a COMPLETE descriptor projection and returns mapping_unavailable when any descriptor class reference cannot be projected. Prefer find-mapping unless you need that guarantee.", resolveMethodMappingExactShape, { readOnlyHint: true }, async (args) => runTool("resolve-method-mapping-exact", args, resolveMethodMappingExactSchema, async (input) => sourceService.resolveMethodMappingExact({
781
+ version: input.version,
782
+ name: input.name,
783
+ owner: input.owner,
784
+ descriptor: input.descriptor,
785
+ sourceMapping: input.sourceMapping,
786
+ targetMapping: input.targetMapping,
787
+ sourcePriority: input.sourcePriority,
788
+ gradleUserHome: input.gradleUserHome,
789
+ maxCandidates: input.maxCandidates
790
+ })));
791
+ registerToolSchema("resolve-method-mapping-exact", resolveMethodMappingExactSchema);
792
+ registerExpertTool(server, "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({
793
+ version: input.version,
794
+ className: input.className,
795
+ classNameMapping: input.classNameMapping,
796
+ includeKinds: parseClassApiKinds(input.includeKinds),
797
+ sourcePriority: input.sourcePriority,
798
+ gradleUserHome: input.gradleUserHome,
799
+ maxRows: input.maxRows,
800
+ cursor: input.cursor
801
+ })));
802
+ registerToolSchema("get-class-api-matrix", getClassApiMatrixSchema);
803
+ registerExpertTool(server, "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({
569
804
  projectPath: input.projectPath,
805
+ version: input.version,
806
+ kind: input.kind,
807
+ name: input.name,
808
+ owner: input.owner,
809
+ descriptor: input.descriptor,
810
+ sourceMapping: input.sourceMapping,
811
+ sourcePriority: input.sourcePriority,
570
812
  gradleUserHome: input.gradleUserHome,
813
+ maxCandidates: input.maxCandidates
814
+ })));
815
+ registerToolSchema("resolve-workspace-symbol", resolveWorkspaceSymbolSchema);
816
+ registerExpertTool(server, "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({
817
+ version: input.version,
818
+ kind: input.kind,
819
+ owner: input.owner,
820
+ name: input.name,
821
+ descriptor: input.descriptor,
822
+ sourceMapping: input.sourceMapping,
823
+ sourcePriority: input.sourcePriority,
824
+ gradleUserHome: input.gradleUserHome,
825
+ nameMode: input.nameMode,
826
+ signatureMode: input.signatureMode,
827
+ maxCandidates: input.maxCandidates
828
+ })));
829
+ registerToolSchema("check-symbol-exists", checkSymbolExistsSchema);
830
+ registerAppTool(server, "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({
831
+ nbtBase64: input.nbtBase64,
832
+ compression: input.compression
833
+ }, nbtLimits))));
834
+ registerToolSchema("nbt-to-json", nbtToJsonSchema);
835
+ registerAppTool(server, "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({
836
+ typedJson: input.typedJson,
837
+ patch: input.patch
838
+ }, nbtLimits))));
839
+ registerToolSchema("nbt-apply-json-patch", nbtApplyJsonPatchSchema);
840
+ registerAppTool(server, "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({
841
+ typedJson: input.typedJson,
842
+ compression: input.compression
843
+ }, nbtLimits))));
844
+ registerToolSchema("json-to-nbt", jsonToNbtSchema);
845
+ registerExpertTool(server, "index-artifact", "Rebuild indexed files/symbols metadata for an artifact addressed by artifactId or target. A new target is resolved and ingested first; force rebuilds an existing index.", indexArtifactShape, { readOnlyHint: false, idempotentHint: true }, async (args) => runTool("index-artifact", args, indexArtifactSchema, async (input) => sourceService.indexArtifact({
846
+ artifactId: await resolveFlatArtifactId(input),
847
+ force: input.force
848
+ })));
849
+ registerToolSchema("index-artifact", indexArtifactSchema);
850
+ registerAppTool(server, "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())));
851
+ registerToolSchema("get-runtime-metrics", emptySchema);
852
+ registerExpertTool(server, "validate-mixin", "Validate Mixin source against Minecraft bytecode signatures for a given version.", validateMixinShape, { readOnlyHint: true }, async (args, ctx) => runTool("validate-mixin", args, validateMixinSchema, async (input) => sourceService.validateMixin({
853
+ input: input.input,
854
+ sourceRoots: input.sourceRoots,
855
+ version: input.version,
856
+ mapping: input.mapping,
857
+ sourcePriority: input.sourcePriority,
571
858
  scope: input.scope,
859
+ projectPath: input.projectPath,
860
+ gradleUserHome: input.gradleUserHome,
572
861
  preferProjectVersion: input.preferProjectVersion,
573
- strictVersion: input.strictVersion,
574
- startLine: input.startLine,
575
- endLine: input.endLine,
576
- maxLines: input.maxLines,
577
- maxChars: input.maxChars,
578
- outputFile: input.outputFile
579
- });
580
- }));
581
- registerToolSchema("get-class-source", getClassSourceSchema);
582
- expertTool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode.", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => {
583
- const normalizedTarget = normalizeSourceLookupTarget(input.target);
584
- return sourceService.getClassMembers({
585
- className: input.className,
586
- artifactId: normalizedTarget.artifactId,
587
- target: normalizedTarget.target,
862
+ minSeverity: input.minSeverity,
863
+ hideUncertain: input.hideUncertain,
864
+ explain: input.explain,
865
+ warningMode: input.warningMode,
866
+ preferProjectMapping: input.preferProjectMapping,
867
+ reportMode: input.reportMode,
868
+ warningCategoryFilter: input.warningCategoryFilter,
869
+ treatInfoAsWarning: input.treatInfoAsWarning,
870
+ includeIssues: input.includeIssues
871
+ }, {
872
+ stageEmitter: makeStageEmitter(stageEmitterExtraFromCtx(ctx))
873
+ })));
874
+ registerToolSchema("validate-mixin", validateMixinSchema);
875
+ registerExpertTool(server, "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({
876
+ content: input.content,
877
+ version: input.version,
588
878
  mapping: input.mapping,
589
879
  sourcePriority: input.sourcePriority,
590
- allowDecompile: input.allowDecompile,
591
- access: input.access,
592
- includeSynthetic: input.includeSynthetic,
593
- includeInherited: input.includeInherited,
594
- memberPattern: input.memberPattern,
595
- maxMembers: input.maxMembers,
596
- projection: input.projection,
597
- cursor: input.cursor,
598
880
  projectPath: input.projectPath,
599
881
  gradleUserHome: input.gradleUserHome,
600
882
  scope: input.scope,
601
- preferProjectVersion: input.preferProjectVersion,
602
- strictVersion: input.strictVersion,
603
- // includeDescriptors is the documented alias for include:["descriptors"]; honor both
604
- // (mirror batch-class-members-service / inspect-minecraft class-members handler).
605
- includeDescriptors: input.includeDescriptors === true ||
606
- (Array.isArray(input.include) && input.include.includes("descriptors"))
607
- });
608
- }));
609
- registerToolSchema("get-class-members", getClassMembersSchema);
610
- 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) => {
611
- const scope = input.packagePrefix || input.fileGlob || input.symbolKind
612
- ? {
613
- packagePrefix: input.packagePrefix,
614
- fileGlob: input.fileGlob,
615
- symbolKind: input.symbolKind
616
- }
617
- : undefined;
618
- return sourceService.searchClassSource({
619
- artifactId: await resolveFlatArtifactId(input),
620
- query: input.query,
621
- intent: input.intent,
622
- match: input.match,
623
- scope: scope,
624
- queryMode: input.queryMode,
625
- limit: input.limit,
626
- cursor: input.cursor,
627
- queryNamespace: input.queryNamespace,
883
+ preferProjectVersion: input.preferProjectVersion
884
+ })));
885
+ registerToolSchema("validate-access-widener", validateAccessWidenerSchema);
886
+ registerExpertTool(server, "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({
887
+ content: input.content,
888
+ version: input.version,
889
+ atNamespace: input.atNamespace,
628
890
  sourcePriority: input.sourcePriority,
629
- gradleUserHome: input.gradleUserHome
630
- });
631
- }));
632
- registerToolSchema("search-class-source", searchClassSourceSchema);
633
- 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({
634
- artifactId: await resolveFlatArtifactId(input),
635
- filePath: input.filePath,
636
- maxBytes: input.maxBytes
637
- })));
638
- registerToolSchema("get-artifact-file", getArtifactFileSchema);
639
- 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({
640
- artifactId: await resolveFlatArtifactId(input),
641
- prefix: input.prefix,
642
- limit: input.limit,
643
- cursor: input.cursor
644
- })));
645
- registerToolSchema("list-artifact-files", listArtifactFilesSchema);
646
- 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({
647
- symbol: input.symbol,
648
- descriptor: input.descriptor,
649
- fromVersion: input.fromVersion,
650
- toVersion: input.toVersion,
651
- mapping: input.mapping,
652
- sourcePriority: input.sourcePriority,
653
- gradleUserHome: input.gradleUserHome,
654
- includeSnapshots: input.includeSnapshots,
655
- maxVersions: input.maxVersions,
656
- includeTimeline: input.includeTimeline
657
- })));
658
- registerToolSchema("trace-symbol-lifecycle", traceSymbolLifecycleSchema);
659
- 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({
660
- className: input.className,
661
- fromVersion: input.fromVersion,
662
- toVersion: input.toVersion,
663
- mapping: input.mapping,
664
- sourcePriority: input.sourcePriority,
665
- gradleUserHome: input.gradleUserHome,
666
- includeFullDiff: input.includeFullDiff
667
- })));
668
- registerToolSchema("diff-class-signatures", diffClassSignaturesSchema);
669
- 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({
670
- version: input.version,
671
- kind: input.kind,
672
- name: input.name,
673
- owner: input.owner,
674
- descriptor: input.descriptor,
675
- sourceMapping: input.sourceMapping,
676
- targetMapping: input.targetMapping,
677
- sourcePriority: input.sourcePriority,
678
- gradleUserHome: input.gradleUserHome,
679
- nameMode: input.nameMode,
680
- signatureMode: input.signatureMode,
681
- disambiguation: input.disambiguation,
682
- maxCandidates: input.maxCandidates
683
- })));
684
- registerToolSchema("find-mapping", findMappingSchema);
685
- expertTool("resolve-method-mapping-exact", "Strict variant of find-mapping(kind=method, signatureMode=exact): requires a COMPLETE descriptor projection and returns mapping_unavailable when any descriptor class reference cannot be projected. Prefer find-mapping unless you need that guarantee.", resolveMethodMappingExactShape, { readOnlyHint: true }, async (args) => runTool("resolve-method-mapping-exact", args, resolveMethodMappingExactSchema, async (input) => sourceService.resolveMethodMappingExact({
686
- version: input.version,
687
- name: input.name,
688
- owner: input.owner,
689
- descriptor: input.descriptor,
690
- sourceMapping: input.sourceMapping,
691
- targetMapping: input.targetMapping,
692
- sourcePriority: input.sourcePriority,
693
- gradleUserHome: input.gradleUserHome,
694
- maxCandidates: input.maxCandidates
695
- })));
696
- registerToolSchema("resolve-method-mapping-exact", resolveMethodMappingExactSchema);
697
- 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({
698
- version: input.version,
699
- className: input.className,
700
- classNameMapping: input.classNameMapping,
701
- includeKinds: parseClassApiKinds(input.includeKinds),
702
- sourcePriority: input.sourcePriority,
703
- gradleUserHome: input.gradleUserHome,
704
- maxRows: input.maxRows,
705
- cursor: input.cursor
706
- })));
707
- registerToolSchema("get-class-api-matrix", getClassApiMatrixSchema);
708
- 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({
709
- projectPath: input.projectPath,
710
- version: input.version,
711
- kind: input.kind,
712
- name: input.name,
713
- owner: input.owner,
714
- descriptor: input.descriptor,
715
- sourceMapping: input.sourceMapping,
716
- sourcePriority: input.sourcePriority,
717
- gradleUserHome: input.gradleUserHome,
718
- maxCandidates: input.maxCandidates
719
- })));
720
- registerToolSchema("resolve-workspace-symbol", resolveWorkspaceSymbolSchema);
721
- 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({
722
- version: input.version,
723
- kind: input.kind,
724
- owner: input.owner,
725
- name: input.name,
726
- descriptor: input.descriptor,
727
- sourceMapping: input.sourceMapping,
728
- sourcePriority: input.sourcePriority,
729
- gradleUserHome: input.gradleUserHome,
730
- nameMode: input.nameMode,
731
- signatureMode: input.signatureMode,
732
- maxCandidates: input.maxCandidates
733
- })));
734
- registerToolSchema("check-symbol-exists", checkSymbolExistsSchema);
735
- 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({
736
- nbtBase64: input.nbtBase64,
737
- compression: input.compression
738
- }, nbtLimits))));
739
- registerToolSchema("nbt-to-json", nbtToJsonSchema);
740
- 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({
741
- typedJson: input.typedJson,
742
- patch: input.patch
743
- }, nbtLimits))));
744
- registerToolSchema("nbt-apply-json-patch", nbtApplyJsonPatchSchema);
745
- 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({
746
- typedJson: input.typedJson,
747
- compression: input.compression
748
- }, nbtLimits))));
749
- registerToolSchema("json-to-nbt", jsonToNbtSchema);
750
- expertTool("index-artifact", "Rebuild indexed files/symbols metadata for an artifact addressed by artifactId or target. A new target is resolved and ingested first; force rebuilds an existing index.", indexArtifactShape, { readOnlyHint: false, idempotentHint: true }, async (args) => runTool("index-artifact", args, indexArtifactSchema, async (input) => sourceService.indexArtifact({
751
- artifactId: await resolveFlatArtifactId(input),
752
- force: input.force
753
- })));
754
- registerToolSchema("index-artifact", indexArtifactSchema);
755
- 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())));
756
- registerToolSchema("get-runtime-metrics", emptySchema);
757
- 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({
758
- input: input.input,
759
- sourceRoots: input.sourceRoots,
760
- version: input.version,
761
- mapping: input.mapping,
762
- sourcePriority: input.sourcePriority,
763
- scope: input.scope,
764
- projectPath: input.projectPath,
765
- gradleUserHome: input.gradleUserHome,
766
- preferProjectVersion: input.preferProjectVersion,
767
- minSeverity: input.minSeverity,
768
- hideUncertain: input.hideUncertain,
769
- explain: input.explain,
770
- warningMode: input.warningMode,
771
- preferProjectMapping: input.preferProjectMapping,
772
- reportMode: input.reportMode,
773
- warningCategoryFilter: input.warningCategoryFilter,
774
- treatInfoAsWarning: input.treatInfoAsWarning,
775
- includeIssues: input.includeIssues
776
- }, {
777
- stageEmitter: makeStageEmitter(extra)
778
- })));
779
- registerToolSchema("validate-mixin", validateMixinSchema);
780
- 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({
781
- content: input.content,
782
- version: input.version,
783
- mapping: input.mapping,
784
- sourcePriority: input.sourcePriority,
785
- projectPath: input.projectPath,
786
- gradleUserHome: input.gradleUserHome,
787
- scope: input.scope,
788
- preferProjectVersion: input.preferProjectVersion
789
- })));
790
- registerToolSchema("validate-access-widener", validateAccessWidenerSchema);
791
- 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({
792
- content: input.content,
793
- version: input.version,
794
- atNamespace: input.atNamespace,
795
- sourcePriority: input.sourcePriority,
796
- projectPath: input.projectPath,
797
- gradleUserHome: input.gradleUserHome,
798
- scope: input.scope,
799
- preferProjectVersion: input.preferProjectVersion
800
- })));
801
- registerToolSchema("validate-access-transformer", validateAccessTransformerSchema);
802
- 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) => {
803
- const result = await analyzeModJar(input.jarPath, {
804
- includeClasses: input.includeClasses ?? false
805
- });
806
- return result;
807
- }));
808
- registerToolSchema("analyze-mod-jar", analyzeModJarSchema);
809
- server.tool("get-registry-data", "Get Minecraft registry data (blocks, items, biomes, etc.) for a specific version by running the server data generator.", getRegistryDataShape, { readOnlyHint: true }, async (args) => runTool("get-registry-data", args, getRegistryDataSchema, async (input) => sourceService.getRegistryData({
810
- version: input.version,
811
- registry: input.registry,
812
- includeData: input.includeData,
813
- maxEntriesPerRegistry: input.maxEntriesPerRegistry
814
- })));
815
- registerToolSchema("get-registry-data", getRegistryDataSchema);
816
- 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({
817
- fromVersion: input.fromVersion,
818
- toVersion: input.toVersion,
819
- category: input.category,
820
- packageFilter: input.packageFilter,
821
- maxClassResults: input.maxClassResults
822
- })));
823
- registerToolSchema("compare-versions", compareVersionsSchema);
824
- 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({
825
- jarPath: input.jarPath,
826
- className: input.className,
827
- includeFiles: input.includeFiles,
828
- maxFiles: input.maxFiles
829
- })));
830
- registerToolSchema("decompile-mod-jar", decompileModJarSchema);
831
- 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({
832
- jarPath: input.jarPath,
833
- className: input.className,
834
- maxLines: input.maxLines,
835
- maxChars: input.maxChars,
836
- outputFile: input.outputFile
837
- })));
838
- registerToolSchema("get-mod-class-source", getModClassSourceSchema);
839
- 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({
840
- jarPath: input.jarPath,
841
- query: input.query,
842
- searchType: input.searchType,
843
- limit: input.limit
844
- })));
845
- registerToolSchema("search-mod-source", searchModSourceSchema);
846
- 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) => {
847
- const result = await remapModJar({
848
- inputJar: input.inputJar,
849
- outputJar: input.outputJar,
850
- mcVersion: input.mcVersion,
851
- targetMapping: input.targetMapping,
852
- forceRemap: input.forceRemap
853
- }, config);
854
- return result;
855
- }));
856
- registerToolSchema("remap-mod-jar", remapModJarSchema);
891
+ projectPath: input.projectPath,
892
+ gradleUserHome: input.gradleUserHome,
893
+ scope: input.scope,
894
+ preferProjectVersion: input.preferProjectVersion
895
+ })));
896
+ registerToolSchema("validate-access-transformer", validateAccessTransformerSchema);
897
+ registerExpertTool(server, "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) => {
898
+ const result = await analyzeModJar(input.jarPath, {
899
+ includeClasses: input.includeClasses ?? false
900
+ });
901
+ return result;
902
+ }));
903
+ registerToolSchema("analyze-mod-jar", analyzeModJarSchema);
904
+ registerAppTool(server, "get-registry-data", "Get Minecraft registry data (blocks, items, biomes, etc.) for a specific version by running the server data generator.", getRegistryDataShape, { readOnlyHint: true }, async (args) => runTool("get-registry-data", args, getRegistryDataSchema, async (input) => sourceService.getRegistryData({
905
+ version: input.version,
906
+ registry: input.registry,
907
+ includeData: input.includeData,
908
+ maxEntriesPerRegistry: input.maxEntriesPerRegistry
909
+ })));
910
+ registerToolSchema("get-registry-data", getRegistryDataSchema);
911
+ registerExpertTool(server, "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({
912
+ fromVersion: input.fromVersion,
913
+ toVersion: input.toVersion,
914
+ category: input.category,
915
+ packageFilter: input.packageFilter,
916
+ maxClassResults: input.maxClassResults
917
+ })));
918
+ registerToolSchema("compare-versions", compareVersionsSchema);
919
+ registerExpertTool(server, "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({
920
+ jarPath: input.jarPath,
921
+ className: input.className,
922
+ includeFiles: input.includeFiles,
923
+ maxFiles: input.maxFiles
924
+ })));
925
+ registerToolSchema("decompile-mod-jar", decompileModJarSchema);
926
+ registerExpertTool(server, "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({
927
+ jarPath: input.jarPath,
928
+ className: input.className,
929
+ maxLines: input.maxLines,
930
+ maxChars: input.maxChars,
931
+ outputFile: input.outputFile
932
+ })));
933
+ registerToolSchema("get-mod-class-source", getModClassSourceSchema);
934
+ registerExpertTool(server, "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({
935
+ jarPath: input.jarPath,
936
+ query: input.query,
937
+ searchType: input.searchType,
938
+ limit: input.limit
939
+ })));
940
+ registerToolSchema("search-mod-source", searchModSourceSchema);
941
+ registerExpertTool(server, "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) => {
942
+ const result = await remapModJar({
943
+ inputJar: input.inputJar,
944
+ outputJar: input.outputJar,
945
+ mcVersion: input.mcVersion,
946
+ targetMapping: input.targetMapping,
947
+ forceRemap: input.forceRemap
948
+ }, config);
949
+ return result;
950
+ }));
951
+ registerToolSchema("remap-mod-jar", remapModJarSchema);
952
+ // Deferred-registration executor: legacy (and the ctx-less module singleton)
953
+ // keeps the frozen call-site order; a modern-era instance registers in raw
954
+ // tool-name-ascending order (plain code-unit comparison — tool names are
955
+ // ASCII, so this is byte order). The advertised tools/list order follows
956
+ // registration order in the SDK, which is exactly what the two golden/pinned
957
+ // contracts observe.
958
+ const toolRegistrationOrder = ctx?.era === "modern"
959
+ ? [...pendingToolRegistrations].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
960
+ : pendingToolRegistrations;
961
+ for (const pending of toolRegistrationOrder)
962
+ pending.register();
963
+ return server;
964
+ }
965
+ /**
966
+ * Module-scope singleton: populates the register-once tool-schema registry at
967
+ * load time and serves in-process consumers (tests drive its request handlers
968
+ * directly). The stdio wire path does NOT serve this instance — serveStdio's
969
+ * factory builds a fresh one per pinned/probe instance (see buildServer).
970
+ */
971
+ const server = buildServer();
972
+ /**
973
+ * The worker's wire transport, kept module-scoped so per-request stage
974
+ * emitters can write `$/stageUpdate` notifications directly onto the wire
975
+ * (the v2 ServerContext exposes no raw sendNotification). Set once by
976
+ * startServer(); undefined outside stdio worker mode, in which case
977
+ * makeStageEmitter() NOOPs exactly as before.
978
+ */
979
+ let workerTransport;
980
+ /**
981
+ * Builds the makeStageEmitter() argument from a v2 ServerContext: the
982
+ * JSON-RPC request id comes from ctx.mcpReq.id and delivery goes through the
983
+ * module-scoped worker transport. Returns undefined (NOOP emitter) when the
984
+ * transport is not up or the id is not a wire-correlatable string/number.
985
+ */
986
+ function stageEmitterExtraFromCtx(ctx) {
987
+ const transport = workerTransport;
988
+ const requestId = ctx.mcpReq?.id;
989
+ if (!transport || (typeof requestId !== "string" && typeof requestId !== "number")) {
990
+ return undefined;
991
+ }
992
+ return {
993
+ requestId,
994
+ sendNotification: (notification) => transport.send({
995
+ jsonrpc: "2.0",
996
+ method: notification.method,
997
+ ...(notification.params !== undefined ? { params: notification.params } : {})
998
+ })
999
+ };
1000
+ }
857
1001
  export async function startServer() {
858
1002
  if (serverStarted) {
859
1003
  return;
@@ -866,10 +1010,28 @@ export async function startServer() {
866
1010
  sourceRepos: config.sourceRepos.length
867
1011
  });
868
1012
  const transport = new CompatStdioServerTransport();
869
- await server.connect(transport);
1013
+ workerTransport = transport;
1014
+ // serveStdio owns the connection and calls the factory PER INSTANCE: a
1015
+ // modern server/discover opening builds a probe instance that a following
1016
+ // legacy initialize discards and replaces via a second factory call. The
1017
+ // probe path mutates its instance (modern-only handlers + protocol
1018
+ // versions), so the factory MUST build a fresh McpServer each time — never
1019
+ // the module-scope singleton — or probe mutations would leak onto the
1020
+ // re-pinned legacy instance and break the negotiate-down contract. The
1021
+ // transport is started synchronously inside serveStdio, so resolving here
1022
+ // keeps the READY marker contract: the worker is listening once
1023
+ // startServer() returns.
1024
+ // maxSubscriptions: 0 is defense in depth for the supervisor's admission
1025
+ // rejection of subscriptions/listen (docs/tool-reference.md, "Absent modern
1026
+ // surfaces"). The SDK stdio entry auto-provides that method with zero
1027
+ // registration and its default cap is 1024, so without this a frame that
1028
+ // somehow bypassed admission would be ACCEPTED — answered with an id-less
1029
+ // acknowledgment notification that never settles the request id — instead of
1030
+ // being refused.
1031
+ serveStdio(buildServer, { transport, maxSubscriptions: 0 });
870
1032
  // In stdio mode, explicitly resume stdin so JSON-RPC lines are consumed.
871
1033
  process.stdin.resume();
872
1034
  serverStarted = true;
873
1035
  }
874
- export { server, sourceService, config, SERVER_VERSION };
1036
+ export { server, sourceService, config, SERVER_VERSION, buildServer };
875
1037
  //# sourceMappingURL=index.js.map