@adhisang/minecraft-modding-mcp 1.1.0 → 1.2.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.
@@ -1,11 +1,16 @@
1
1
  import { access, constants } from "node:fs/promises";
2
- import { mkdirSync, readdirSync, statSync } from "node:fs";
2
+ import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
3
3
  import { createHash } from "node:crypto";
4
4
  import { basename, join, relative, sep } from "node:path";
5
5
  import { createError, ERROR_CODES, isAppError } from "../errors.js";
6
6
  import { assertJavaAvailable, runJavaProcess } from "../java-process.js";
7
7
  import { log } from "../logger.js";
8
8
  const DEFAULT_TIMEOUT_MS = 120_000;
9
+ const VINEFLOWER_FLAG_PROFILES = [
10
+ { label: "default", flags: ["-din=1", "-rbr=1", "-dgs=1"] },
11
+ { label: "relaxed", flags: ["-din=1", "-rbr=0", "-dgs=0"] },
12
+ { label: "safe", flags: ["-din=0", "-rbr=0", "-dgs=0"] },
13
+ ];
9
14
  function emitDecompileLog(event, details) {
10
15
  log(event === "decompile.error" ? "error" : "info", event, details);
11
16
  }
@@ -83,10 +88,14 @@ function decompileOutputDir(cacheDir, binaryJarPath, signature) {
83
88
  const digest = createHash("sha256").update(binaryJarPath).update(signature).digest("hex");
84
89
  return join(cacheDir, "decompiled", digest);
85
90
  }
86
- async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeoutMs) {
91
+ function clearOutputDir(dir) {
92
+ rmSync(dir, { recursive: true, force: true });
93
+ mkdirSync(dir, { recursive: true });
94
+ }
95
+ async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeoutMs, flags = VINEFLOWER_FLAG_PROFILES[0].flags) {
87
96
  const result = await runJavaProcess({
88
97
  jarPath: vineflowerJarPath,
89
- args: [binaryJarPath, outputDir, "-din=1", "-rbr=1", "-dgs=1"],
98
+ args: [...flags, binaryJarPath, outputDir],
90
99
  timeoutMs,
91
100
  normalizePathArgs: true
92
101
  });
@@ -97,8 +106,8 @@ async function runVineflower(vineflowerJarPath, binaryJarPath, outputDir, timeou
97
106
  details: {
98
107
  binaryJarPath,
99
108
  outputDir,
100
- code: result.exitCode,
101
- stdout: result.stdoutTail,
109
+ exitCode: result.exitCode,
110
+ stdoutTail: result.stdoutTail,
102
111
  stderrTail: result.stderrTail
103
112
  }
104
113
  });
@@ -145,31 +154,71 @@ export async function decompileBinaryJar(binaryJarPath, cacheDir, options) {
145
154
  };
146
155
  }
147
156
  }
148
- await runVineflower(options.vineflowerJarPath, normalizedBinaryJarPath, outputDir, timeoutMs);
149
- const javaFileNames = await collectJavaFiles(outputDir);
150
- if (javaFileNames.length === 0) {
151
- throw createError({
152
- code: ERROR_CODES.DECOMPILER_FAILED,
153
- message: "No Java files were produced by decompilation.",
154
- details: { binaryJarPath: normalizedBinaryJarPath, outputDir }
155
- });
157
+ const profilesAttempted = [];
158
+ let lastDecompileError;
159
+ for (const profile of VINEFLOWER_FLAG_PROFILES) {
160
+ profilesAttempted.push(profile.label);
161
+ try {
162
+ if (profilesAttempted.length > 1) {
163
+ clearOutputDir(outputDir);
164
+ emitDecompileLog("decompile.retry", {
165
+ binaryJarPath: normalizedBinaryJarPath,
166
+ profile: profile.label,
167
+ attempt: profilesAttempted.length
168
+ });
169
+ }
170
+ await runVineflower(options.vineflowerJarPath, normalizedBinaryJarPath, outputDir, timeoutMs, profile.flags);
171
+ const javaFileNames = await collectJavaFiles(outputDir);
172
+ if (javaFileNames.length === 0) {
173
+ throw createError({
174
+ code: ERROR_CODES.DECOMPILER_FAILED,
175
+ message: "No Java files were produced by decompilation.",
176
+ details: {
177
+ binaryJarPath: normalizedBinaryJarPath,
178
+ outputDir,
179
+ producedJavaCount: 0,
180
+ profile: profile.label
181
+ }
182
+ });
183
+ }
184
+ const javaFiles = await Promise.all(javaFileNames.map(async (candidate) => {
185
+ const abs = join(outputDir, candidate);
186
+ return {
187
+ filePath: normalizeOutputPath(outputDir, abs),
188
+ content: await readFileTreeText(abs)
189
+ };
190
+ }));
191
+ emitDecompileLog("decompile.done", {
192
+ durationMs: Date.now() - startedAt,
193
+ artifactIdCandidate: options.artifactIdCandidate,
194
+ javaFileCount: javaFiles.length,
195
+ profile: profile.label
196
+ });
197
+ return {
198
+ outputDir,
199
+ javaFiles,
200
+ decompileProfile: profile.label
201
+ };
202
+ }
203
+ catch (retryError) {
204
+ if (isAppError(retryError) && retryError.code !== ERROR_CODES.DECOMPILER_FAILED) {
205
+ throw retryError;
206
+ }
207
+ lastDecompileError = retryError;
208
+ }
156
209
  }
157
- const javaFiles = await Promise.all(javaFileNames.map(async (candidate) => {
158
- const abs = join(outputDir, candidate);
159
- return {
160
- filePath: normalizeOutputPath(outputDir, abs),
161
- content: await readFileTreeText(abs)
162
- };
163
- }));
164
- emitDecompileLog("decompile.done", {
165
- durationMs: Date.now() - startedAt,
166
- artifactIdCandidate: options.artifactIdCandidate,
167
- javaFileCount: javaFiles.length
168
- });
169
- return {
170
- outputDir,
171
- javaFiles
172
- };
210
+ throw isAppError(lastDecompileError)
211
+ ? createError({
212
+ code: ERROR_CODES.DECOMPILER_FAILED,
213
+ message: `Decompilation failed after trying all flag profiles.`,
214
+ details: {
215
+ binaryJarPath: normalizedBinaryJarPath,
216
+ outputDir,
217
+ profilesAttempted,
218
+ stderrTail: extractStderrTail(lastDecompileError)
219
+ }
220
+ })
221
+ : lastDecompileError;
173
222
  }
174
223
  catch (error) {
175
224
  emitDecompileLog("decompile.error", {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
3
  import { ZodError, z } from "zod";
4
+ import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
5
5
  import { objectResult } from "./mcp-helpers.js";
6
6
  import { loadConfig } from "./config.js";
7
7
  import { ERROR_CODES, isAppError } from "./errors.js";
@@ -22,6 +22,9 @@ const SEARCH_MATCHES = ["exact", "prefix", "contains", "regex"];
22
22
  const SEARCH_SYMBOL_KINDS = ["class", "interface", "enum", "record", "method", "field"];
23
23
  const MEMBER_ACCESS = ["public", "all"];
24
24
  const WORKSPACE_SYMBOL_KINDS = ["class", "field", "method"];
25
+ const CLASS_NAME_MODES = ["fqcn", "auto"];
26
+ const SOURCE_MODES = ["metadata", "snippet", "full"];
27
+ const ARTIFACT_SCOPES = ["vanilla", "merged", "loader"];
25
28
  const DECODE_COMPRESSIONS = ["none", "gzip", "auto"];
26
29
  const ENCODE_COMPRESSIONS = ["none", "gzip"];
27
30
  const nonEmptyString = z.string().trim().min(1);
@@ -35,6 +38,9 @@ const searchMatchSchema = z.enum(SEARCH_MATCHES);
35
38
  const searchSymbolKindSchema = z.enum(SEARCH_SYMBOL_KINDS);
36
39
  const memberAccessSchema = z.enum(MEMBER_ACCESS);
37
40
  const workspaceSymbolKindSchema = z.enum(WORKSPACE_SYMBOL_KINDS);
41
+ const classNameModeSchema = z.enum(CLASS_NAME_MODES);
42
+ const sourceModeSchema = z.enum(SOURCE_MODES);
43
+ const artifactScopeSchema = z.enum(ARTIFACT_SCOPES);
38
44
  const decodeCompressionSchema = z.enum(DECODE_COMPRESSIONS);
39
45
  const encodeCompressionSchema = z.enum(ENCODE_COMPRESSIONS);
40
46
  function validateTargetPair(value, ctx) {
@@ -75,20 +81,31 @@ const resolveArtifactShape = {
75
81
  targetValue: nonEmptyString,
76
82
  mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
77
83
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
78
- allowDecompile: z.boolean().optional().describe("default true")
84
+ allowDecompile: z.boolean().optional().describe("default true"),
85
+ projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
86
+ scope: artifactScopeSchema.optional().describe("vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = loader-specific"),
87
+ preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override targetValue"),
88
+ strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
79
89
  };
80
90
  const resolveArtifactSchema = z.object(resolveArtifactShape);
81
91
  const getClassSourceShape = {
82
92
  className: nonEmptyString,
93
+ mode: sourceModeSchema.optional().describe("metadata (default) = symbol outline only; snippet = source with default maxLines=200; full = entire source"),
83
94
  artifactId: optionalNonEmptyString,
84
95
  targetKind: targetKindSchema.optional().describe("version | jar | coordinate"),
85
96
  targetValue: optionalNonEmptyString,
86
97
  mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
87
98
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
88
99
  allowDecompile: z.boolean().optional().describe("default true"),
100
+ projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
101
+ scope: artifactScopeSchema.optional().describe("vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = loader-specific"),
102
+ preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override targetValue"),
103
+ strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
89
104
  startLine: optionalPositiveInt,
90
105
  endLine: optionalPositiveInt,
91
- maxLines: optionalPositiveInt
106
+ maxLines: optionalPositiveInt,
107
+ maxChars: optionalPositiveInt.describe("Hard character limit on sourceText; truncates if exceeded"),
108
+ outputFile: optionalNonEmptyString.describe("Write source to this file path and return metadata-only response")
92
109
  };
93
110
  const getClassSourceSchema = z
94
111
  .object(getClassSourceShape)
@@ -120,7 +137,11 @@ const getClassMembersShape = {
120
137
  includeSynthetic: z.boolean().optional().describe("default false"),
121
138
  includeInherited: z.boolean().optional().describe("default false"),
122
139
  memberPattern: optionalNonEmptyString,
123
- maxMembers: optionalPositiveInt.describe("default 500, max 5000")
140
+ maxMembers: optionalPositiveInt.describe("default 500, max 5000"),
141
+ projectPath: optionalNonEmptyString,
142
+ scope: artifactScopeSchema.optional().describe("vanilla | merged | loader"),
143
+ preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
144
+ strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
124
145
  };
125
146
  const getClassMembersSchema = z
126
147
  .object(getClassMembersShape)
@@ -142,6 +163,7 @@ const searchClassSourceShape = {
142
163
  snippetLines: optionalPositiveInt.describe("default 8, clamp 1..80"),
143
164
  includeDefinition: z.boolean().optional().describe("default false"),
144
165
  includeOneHop: z.boolean().optional().describe("default false"),
166
+ queryMode: z.enum(["auto", "token", "literal"]).optional().describe("auto (default): FTS5 with literal fallback for separator queries; token: FTS5 only; literal: substring scan only"),
145
167
  limit: optionalPositiveInt.describe("default 20"),
146
168
  cursor: optionalNonEmptyString
147
169
  };
@@ -187,7 +209,14 @@ const findMappingShape = {
187
209
  descriptor: optionalNonEmptyString,
188
210
  sourceMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
189
211
  targetMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
190
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
212
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
213
+ disambiguation: z
214
+ .object({
215
+ ownerHint: optionalNonEmptyString,
216
+ descriptorHint: optionalNonEmptyString
217
+ })
218
+ .partial()
219
+ .optional()
191
220
  };
192
221
  const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
193
222
  if (value.kind === "class") {
@@ -390,9 +419,12 @@ const checkSymbolExistsShape = {
390
419
  kind: workspaceSymbolKindSchema.describe("class | field | method"),
391
420
  owner: optionalNonEmptyString,
392
421
  name: nonEmptyString,
393
- descriptor: optionalNonEmptyString.describe("required for kind=method"),
422
+ descriptor: optionalNonEmptyString.describe("required for kind=method unless signatureMode=name-only"),
394
423
  sourceMapping: sourceMappingSchema.describe("official | mojang | intermediary | yarn"),
395
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
424
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
425
+ nameMode: classNameModeSchema.optional().describe("fqcn | auto (default fqcn)"),
426
+ signatureMode: z.enum(["exact", "name-only"]).optional()
427
+ .describe("exact (default): require descriptor for methods; name-only: match by owner+name only")
396
428
  };
397
429
  const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
398
430
  if (value.kind === "class") {
@@ -410,7 +442,7 @@ const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((va
410
442
  path: ["descriptor"]
411
443
  });
412
444
  }
413
- if (!value.name.includes(".")) {
445
+ if (value.nameMode !== "auto" && !value.name.includes(".")) {
414
446
  ctx.addIssue({
415
447
  code: z.ZodIssueCode.custom,
416
448
  message: "name must be fully-qualified class name when kind=class.",
@@ -443,10 +475,10 @@ const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((va
443
475
  }
444
476
  return;
445
477
  }
446
- if (!value.descriptor) {
478
+ if (!value.descriptor && value.signatureMode !== "name-only") {
447
479
  ctx.addIssue({
448
480
  code: z.ZodIssueCode.custom,
449
- message: "descriptor is required when kind=method.",
481
+ message: "descriptor is required when kind=method (use signatureMode='name-only' to match by name only).",
450
482
  path: ["descriptor"]
451
483
  });
452
484
  }
@@ -479,12 +511,44 @@ const indexArtifactShape = {
479
511
  };
480
512
  const indexArtifactSchema = z.object(indexArtifactShape);
481
513
  const validateMixinShape = {
482
- source: nonEmptyString.describe("Mixin Java source text"),
514
+ source: optionalNonEmptyString.describe("Mixin Java source text (mutually exclusive with sourcePath/sourcePaths/mixinConfigPath)"),
515
+ sourcePath: optionalNonEmptyString.describe("Path to Mixin .java file (alternative to source/sourcePaths/mixinConfigPath)"),
516
+ sourcePaths: z.array(z.string().min(1)).optional().describe("Array of Mixin .java file paths for batch validation"),
517
+ mixinConfigPath: z.union([nonEmptyString, z.array(nonEmptyString).min(1)]).optional().describe("Path (or array of paths) to mixin config JSON (e.g. modid.mixins.json); auto-discovers and batch-validates all listed classes"),
518
+ sourceRoot: optionalNonEmptyString.describe("Source root relative to projectPath (default 'src/main/java')"),
519
+ sourceRoots: z.array(z.string().min(1)).optional()
520
+ .describe("Array of source roots for multi-module projects (e.g. ['common/src/main/java', 'neoforge/src/main/java'])"),
483
521
  version: nonEmptyString.describe("Minecraft version"),
484
522
  mapping: sourceMappingSchema.optional().describe("official | mojang | intermediary | yarn"),
485
- sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
523
+ sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
524
+ scope: artifactScopeSchema.optional().describe("vanilla | merged | loader"),
525
+ projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
526
+ preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
527
+ minSeverity: z.enum(["error", "warning", "all"]).optional()
528
+ .describe("'error'=errors only, 'warning'=errors+warnings, 'all'=everything (default 'all')"),
529
+ hideUncertain: z.boolean().optional()
530
+ .describe("Omit issues with confidence='uncertain' (default false)"),
531
+ explain: z.boolean().optional()
532
+ .describe("When true, enrich each issue with explanation and suggestedCall for agent recovery (default false)"),
533
+ warningMode: z.enum(["full", "aggregated"]).optional()
534
+ .describe("'full'=all warnings (default), 'aggregated'=group warnings by category with counts and samples"),
535
+ preferProjectMapping: z.boolean().optional()
536
+ .describe("When true, auto-detect mapping from project config even if mapping is explicitly provided"),
537
+ reportMode: z.enum(["compact", "full"]).optional()
538
+ .describe("'compact' omits resolvedMembers/structuredWarnings/toolHealth details, 'full'=everything (default)"),
539
+ warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional()
540
+ .describe("Only include warnings/issues matching these categories (default: all)"),
541
+ treatInfoAsWarning: z.boolean().optional()
542
+ .describe("When false, suppress info-severity structured warnings from output (default true)")
486
543
  };
487
- const validateMixinSchema = z.object(validateMixinShape);
544
+ const validateMixinSchema = z.object(validateMixinShape).refine((d) => {
545
+ const hasSource = d.source != null;
546
+ const hasSourcePath = d.sourcePath != null;
547
+ const hasSourcePaths = d.sourcePaths != null && d.sourcePaths.length > 0;
548
+ const hasMixinConfig = d.mixinConfigPath != null && (typeof d.mixinConfigPath === "string" || (Array.isArray(d.mixinConfigPath) && d.mixinConfigPath.length > 0));
549
+ // Exactly one of the four must be provided
550
+ return [hasSource, hasSourcePath, hasSourcePaths, hasMixinConfig].filter(Boolean).length === 1;
551
+ }, { message: "Exactly one of 'source', 'sourcePath', 'sourcePaths', or 'mixinConfigPath' must be provided." });
488
552
  const validateAccessWidenerShape = {
489
553
  content: nonEmptyString.describe("Access Widener file content"),
490
554
  version: nonEmptyString.describe("Minecraft version"),
@@ -519,7 +583,10 @@ const decompileModJarShape = {
519
583
  const decompileModJarSchema = z.object(decompileModJarShape);
520
584
  const getModClassSourceShape = {
521
585
  jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
522
- className: nonEmptyString.describe("Fully-qualified class name (e.g. com.example.MyMixin)")
586
+ className: nonEmptyString.describe("Fully-qualified class name (e.g. com.example.MyMixin)"),
587
+ maxLines: optionalPositiveInt.describe("Max lines to return"),
588
+ maxChars: optionalPositiveInt.describe("Hard character limit; truncates if exceeded"),
589
+ outputFile: optionalNonEmptyString.describe("Write full source to file, return placeholder in content")
523
590
  };
524
591
  const getModClassSourceSchema = z.object(getModClassSourceShape);
525
592
  const MOD_SEARCH_TYPES = ["class", "method", "field", "content", "all"];
@@ -565,7 +632,18 @@ const nbtLimits = {
565
632
  maxInflatedBytes: config.maxNbtInflatedBytes,
566
633
  maxResponseBytes: config.maxNbtResponseBytes
567
634
  };
568
- const sourceService = new SourceService(config);
635
+ let sourceServiceInstance;
636
+ function getSourceService() {
637
+ sourceServiceInstance ??= new SourceService(config);
638
+ return sourceServiceInstance;
639
+ }
640
+ const sourceService = new Proxy({}, {
641
+ get(_target, property, _receiver) {
642
+ const service = getSourceService();
643
+ const value = Reflect.get(service, property, service);
644
+ return typeof value === "function" ? value.bind(service) : value;
645
+ }
646
+ });
569
647
  registerResources(server, sourceService);
570
648
  let processHandlersAttached = false;
571
649
  let serverStarted = false;
@@ -634,6 +712,20 @@ function toHints(details) {
634
712
  }
635
713
  return hints;
636
714
  }
715
+ function toSuggestedCall(details) {
716
+ if (typeof details !== "object" || details == null) {
717
+ return undefined;
718
+ }
719
+ const maybe = details.suggestedCall;
720
+ if (typeof maybe !== "object" || maybe == null) {
721
+ return undefined;
722
+ }
723
+ const call = maybe;
724
+ if (typeof call.tool !== "string" || typeof call.params !== "object" || call.params == null) {
725
+ return undefined;
726
+ }
727
+ return { tool: call.tool, params: call.params };
728
+ }
637
729
  function statusForErrorCode(code) {
638
730
  if (code === ERROR_CODES.INVALID_INPUT ||
639
731
  code === ERROR_CODES.COORDINATE_PARSE_FAILED ||
@@ -723,6 +815,7 @@ function mapErrorToProblem(caughtError, requestId) {
723
815
  };
724
816
  }
725
817
  if (isAppError(caughtError)) {
818
+ const suggestedCall = toSuggestedCall(caughtError.details);
726
819
  return {
727
820
  type: `https://minecraft-modding-mcp.dev/problems/${caughtError.code.toLowerCase()}`,
728
821
  title: "Tool execution error",
@@ -731,7 +824,8 @@ function mapErrorToProblem(caughtError, requestId) {
731
824
  code: caughtError.code,
732
825
  instance: requestId,
733
826
  fieldErrors: extractFieldErrorsFromDetails(caughtError.details),
734
- hints: toHints(caughtError.details)
827
+ hints: toHints(caughtError.details),
828
+ ...(suggestedCall ? { suggestedCall } : {})
735
829
  };
736
830
  }
737
831
  return {
@@ -830,18 +924,40 @@ server.tool("resolve-artifact", "Resolve source artifact from version, jar path,
830
924
  },
831
925
  mapping: input.mapping,
832
926
  sourcePriority: input.sourcePriority,
833
- allowDecompile: input.allowDecompile
927
+ allowDecompile: input.allowDecompile,
928
+ projectPath: input.projectPath,
929
+ scope: input.scope,
930
+ preferProjectVersion: input.preferProjectVersion,
931
+ strictVersion: input.strictVersion
834
932
  })));
835
- server.tool("get-class-source", "Get Java source for a class by artifactId or by resolving target (version/jar/coordinate), with optional line-range filtering.", getClassSourceShape, { readOnlyHint: true }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => sourceService.getClassSource({
933
+ const findClassShape = {
934
+ className: nonEmptyString.describe("Simple name (e.g. Blocks) or fully-qualified name (e.g. net.minecraft.world.level.block.Blocks)"),
935
+ artifactId: nonEmptyString,
936
+ limit: optionalPositiveInt.describe("default 20, max 200")
937
+ };
938
+ const findClassSchema = z.object(findClassShape);
939
+ 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({
836
940
  className: input.className,
837
941
  artifactId: input.artifactId,
942
+ limit: input.limit
943
+ })));
944
+ server.tool("get-class-source", "Get Java source for a class by artifactId or by resolving target (version/jar/coordinate). Default mode=metadata returns symbol outline only; use mode=snippet for bounded excerpts or mode=full for entire source.", getClassSourceShape, { readOnlyHint: true }, async (args) => runTool("get-class-source", args, getClassSourceSchema, async (input) => sourceService.getClassSource({
945
+ className: input.className,
946
+ mode: input.mode,
947
+ artifactId: input.artifactId,
838
948
  target: buildTarget(input.targetKind, input.targetValue),
839
949
  mapping: input.mapping,
840
950
  sourcePriority: input.sourcePriority,
841
951
  allowDecompile: input.allowDecompile,
952
+ projectPath: input.projectPath,
953
+ scope: input.scope,
954
+ preferProjectVersion: input.preferProjectVersion,
955
+ strictVersion: input.strictVersion,
842
956
  startLine: input.startLine,
843
957
  endLine: input.endLine,
844
- maxLines: input.maxLines
958
+ maxLines: input.maxLines,
959
+ maxChars: input.maxChars,
960
+ outputFile: input.outputFile
845
961
  })));
846
962
  server.tool("get-class-members", "Get fields/methods/constructors for one class from binary bytecode by artifactId or by resolving target (version/jar/coordinate).", getClassMembersShape, { readOnlyHint: true }, async (args) => runTool("get-class-members", args, getClassMembersSchema, async (input) => sourceService.getClassMembers({
847
963
  className: input.className,
@@ -854,7 +970,11 @@ server.tool("get-class-members", "Get fields/methods/constructors for one class
854
970
  includeSynthetic: input.includeSynthetic,
855
971
  includeInherited: input.includeInherited,
856
972
  memberPattern: input.memberPattern,
857
- maxMembers: input.maxMembers
973
+ maxMembers: input.maxMembers,
974
+ projectPath: input.projectPath,
975
+ scope: input.scope,
976
+ preferProjectVersion: input.preferProjectVersion,
977
+ strictVersion: input.strictVersion
858
978
  })));
859
979
  server.tool("search-class-source", "Search indexed class source files for one artifact with symbol/text/path intent and optional one-hop relation expansion.", searchClassSourceShape, { readOnlyHint: true }, async (args) => runTool("search-class-source", args, searchClassSourceSchema, async (input) => {
860
980
  const scope = input.packagePrefix || input.fileGlob || input.symbolKind
@@ -880,6 +1000,7 @@ server.tool("search-class-source", "Search indexed class source files for one ar
880
1000
  match: input.match,
881
1001
  scope: scope,
882
1002
  include,
1003
+ queryMode: input.queryMode,
883
1004
  limit: input.limit,
884
1005
  cursor: input.cursor
885
1006
  });
@@ -916,7 +1037,8 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
916
1037
  descriptor: input.descriptor,
917
1038
  sourceMapping: input.sourceMapping,
918
1039
  targetMapping: input.targetMapping,
919
- sourcePriority: input.sourcePriority
1040
+ sourcePriority: input.sourcePriority,
1041
+ disambiguation: input.disambiguation
920
1042
  })));
921
1043
  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({
922
1044
  version: input.version,
@@ -952,7 +1074,9 @@ server.tool("check-symbol-exists", "Check whether a class/field/method symbol ex
952
1074
  name: input.name,
953
1075
  descriptor: input.descriptor,
954
1076
  sourceMapping: input.sourceMapping,
955
- sourcePriority: input.sourcePriority
1077
+ sourcePriority: input.sourcePriority,
1078
+ nameMode: input.nameMode,
1079
+ signatureMode: input.signatureMode
956
1080
  })));
957
1081
  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({
958
1082
  nbtBase64: input.nbtBase64,
@@ -973,9 +1097,25 @@ server.tool("index-artifact", "Rebuild indexed files/symbols metadata for an exi
973
1097
  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())));
974
1098
  server.tool("validate-mixin", "Validate Mixin source against Minecraft bytecode signatures for a given version.", validateMixinShape, { readOnlyHint: true }, async (args) => runTool("validate-mixin", args, validateMixinSchema, async (input) => sourceService.validateMixin({
975
1099
  source: input.source,
1100
+ sourcePath: input.sourcePath,
1101
+ sourcePaths: input.sourcePaths,
1102
+ mixinConfigPath: input.mixinConfigPath,
1103
+ sourceRoot: input.sourceRoot,
1104
+ sourceRoots: input.sourceRoots,
976
1105
  version: input.version,
977
1106
  mapping: input.mapping,
978
- sourcePriority: input.sourcePriority
1107
+ sourcePriority: input.sourcePriority,
1108
+ scope: input.scope,
1109
+ projectPath: input.projectPath,
1110
+ preferProjectVersion: input.preferProjectVersion,
1111
+ minSeverity: input.minSeverity,
1112
+ hideUncertain: input.hideUncertain,
1113
+ explain: input.explain,
1114
+ warningMode: input.warningMode,
1115
+ preferProjectMapping: input.preferProjectMapping,
1116
+ reportMode: input.reportMode,
1117
+ warningCategoryFilter: input.warningCategoryFilter,
1118
+ treatInfoAsWarning: input.treatInfoAsWarning
979
1119
  })));
980
1120
  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({
981
1121
  content: input.content,
@@ -1006,7 +1146,10 @@ server.tool("decompile-mod-jar", "Decompile a Minecraft mod JAR using Vineflower
1006
1146
  })));
1007
1147
  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({
1008
1148
  jarPath: input.jarPath,
1009
- className: input.className
1149
+ className: input.className,
1150
+ maxLines: input.maxLines,
1151
+ maxChars: input.maxChars,
1152
+ outputFile: input.outputFile
1010
1153
  })));
1011
1154
  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({
1012
1155
  jarPath: input.jarPath,
@@ -1034,11 +1177,12 @@ export async function startServer() {
1034
1177
  sqlitePath: config.sqlitePath,
1035
1178
  sourceRepos: config.sourceRepos.length
1036
1179
  });
1037
- const transport = new StdioServerTransport();
1180
+ const transport = new CompatStdioServerTransport();
1038
1181
  await server.connect(transport);
1039
1182
  // In stdio mode, explicitly resume stdin so JSON-RPC lines are consumed.
1040
1183
  process.stdin.resume();
1041
1184
  serverStarted = true;
1185
+ setImmediate(getSourceService); // Eagerly init SourceService during MCP handshake
1042
1186
  }
1043
1187
  export { server, sourceService, config, SERVER_VERSION };
1044
1188
  //# sourceMappingURL=index.js.map
@@ -48,6 +48,7 @@ export type SymbolResolutionOutput = {
48
48
  candidates: Array<SymbolReference & Pick<MappingLookupCandidate, "matchKind" | "confidence">>;
49
49
  warnings: string[];
50
50
  provenance?: MappingLookupProvenance;
51
+ ambiguityReasons?: string[];
51
52
  };
52
53
  export type FindMappingInput = {
53
54
  version: string;
@@ -58,6 +59,10 @@ export type FindMappingInput = {
58
59
  sourceMapping: SourceMapping;
59
60
  targetMapping: SourceMapping;
60
61
  sourcePriority?: MappingSourcePriority;
62
+ disambiguation?: {
63
+ ownerHint?: string;
64
+ descriptorHint?: string;
65
+ };
61
66
  };
62
67
  export type FindMappingOutput = SymbolResolutionOutput;
63
68
  export type EnsureMappingAvailableInput = {
@@ -112,6 +117,7 @@ export type ClassApiMatrixOutput = {
112
117
  classIdentity: Partial<Record<SourceMapping, string>>;
113
118
  rows: ClassApiMatrixRow[];
114
119
  warnings: string[];
120
+ ambiguousRowCount?: number;
115
121
  };
116
122
  export type SymbolExistenceInput = {
117
123
  version: string;
@@ -121,6 +127,8 @@ export type SymbolExistenceInput = {
121
127
  descriptor?: string;
122
128
  sourceMapping: SourceMapping;
123
129
  sourcePriority?: MappingSourcePriority;
130
+ nameMode?: "fqcn" | "auto";
131
+ signatureMode?: "exact" | "name-only";
124
132
  };
125
133
  export type SymbolExistenceOutput = SymbolResolutionOutput;
126
134
  export declare class MappingService {
@@ -138,6 +146,20 @@ export declare class MappingService {
138
146
  private mapRecordBetweenMappings;
139
147
  private mapCandidatesAlongPath;
140
148
  private provenanceForPath;
149
+ /**
150
+ * Probe the mapping graph health for a given version.
151
+ * Returns availability of mojang mappings, tiny mappings, and member remap paths.
152
+ */
153
+ checkMappingHealth(input: {
154
+ version: string;
155
+ requestedMapping: SourceMapping;
156
+ sourcePriority?: MappingSourcePriority;
157
+ }): Promise<{
158
+ mojangMappingsAvailable: boolean;
159
+ tinyMappingsAvailable: boolean;
160
+ memberRemapAvailable: boolean;
161
+ degradations: string[];
162
+ }>;
141
163
  private loadGraph;
142
164
  private buildGraph;
143
165
  private mergePairs;