@adhisang/minecraft-modding-mcp 2.1.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +224 -802
  3. package/dist/cache-registry.d.ts +95 -0
  4. package/dist/cache-registry.js +541 -0
  5. package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
  6. package/dist/entry-tools/analyze-mod-service.js +309 -0
  7. package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
  8. package/dist/entry-tools/analyze-symbol-service.js +359 -0
  9. package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
  10. package/dist/entry-tools/compare-minecraft-service.js +429 -0
  11. package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
  12. package/dist/entry-tools/entry-tool-schema.js +10 -0
  13. package/dist/entry-tools/inspect-minecraft-service.d.ts +1954 -0
  14. package/dist/entry-tools/inspect-minecraft-service.js +1030 -0
  15. package/dist/entry-tools/manage-cache-service.d.ts +130 -0
  16. package/dist/entry-tools/manage-cache-service.js +264 -0
  17. package/dist/entry-tools/request-normalizers.d.ts +10 -0
  18. package/dist/entry-tools/request-normalizers.js +36 -0
  19. package/dist/entry-tools/response-contract.d.ts +45 -0
  20. package/dist/entry-tools/response-contract.js +99 -0
  21. package/dist/entry-tools/validate-project-service.d.ts +543 -0
  22. package/dist/entry-tools/validate-project-service.js +414 -0
  23. package/dist/index.js +183 -59
  24. package/dist/observability.d.ts +18 -2
  25. package/dist/observability.js +47 -10
  26. package/dist/source-service.d.ts +0 -1
  27. package/dist/source-service.js +44 -54
  28. package/dist/storage/files-repo.d.ts +1 -0
  29. package/dist/storage/files-repo.js +29 -5
  30. package/dist/tool-contract-manifest.d.ts +4 -0
  31. package/dist/tool-contract-manifest.js +139 -0
  32. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13,6 +13,15 @@ import { remapModJar } from "./mod-remap-service.js";
13
13
  import { registerResources } from "./resources.js";
14
14
  import { SourceService } from "./source-service.js";
15
15
  import { ToolExecutionGate } from "./tool-execution-gate.js";
16
+ import { WorkspaceMappingService } from "./workspace-mapping-service.js";
17
+ import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape } from "./entry-tools/inspect-minecraft-service.js";
18
+ import { AnalyzeSymbolService, analyzeSymbolSchema, analyzeSymbolShape } from "./entry-tools/analyze-symbol-service.js";
19
+ import { CompareMinecraftService, compareMinecraftSchema, compareMinecraftShape } from "./entry-tools/compare-minecraft-service.js";
20
+ import { AnalyzeModService, analyzeModSchema, analyzeModShape } from "./entry-tools/analyze-mod-service.js";
21
+ import { ValidateProjectService, validateProjectSchema, validateProjectShape, discoverWorkspaceAccessWideners, discoverWorkspaceMixins } from "./entry-tools/validate-project-service.js";
22
+ import { ManageCacheService, manageCacheSchema, manageCacheShape } from "./entry-tools/manage-cache-service.js";
23
+ import { createCacheRegistry } from "./cache-registry.js";
24
+ import { buildEntryToolMeta } from "./entry-tools/response-contract.js";
16
25
  if (!process.env.NODE_ENV) {
17
26
  process.env.NODE_ENV = "production";
18
27
  }
@@ -38,6 +47,14 @@ const HEAVY_TOOL_NAMES = new Set([
38
47
  "get-class-api-matrix",
39
48
  "get-registry-data"
40
49
  ]);
50
+ const ENTRY_TOOL_NAMES = new Set([
51
+ "inspect-minecraft",
52
+ "analyze-symbol",
53
+ "compare-minecraft",
54
+ "analyze-mod",
55
+ "validate-project",
56
+ "manage-cache"
57
+ ]);
41
58
  const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueue: 2 });
42
59
  const nonEmptyString = z.string().trim().min(1);
43
60
  const optionalNonEmptyString = z.string().trim().min(1).optional();
@@ -73,9 +90,28 @@ const sourceLookupTargetSchema = z.discriminatedUnion("type", [
73
90
  const RESOLVE_ARTIFACT_TARGET_DESCRIPTION = "Object with kind and value. Example: {\"kind\":\"version\",\"value\":\"1.21.10\"}. Must be an object, not a string.";
74
91
  const SOURCE_LOOKUP_TARGET_DESCRIPTION = "Object: {\"type\":\"resolve\",\"kind\":\"version\",\"value\":\"1.21.10\"} or {\"type\":\"artifact\",\"artifactId\":\"...\"}. Must be an object, not a string.";
75
92
  const SOURCE_SCOPE_DESCRIPTION = 'vanilla = Mojang client jar only; merged = Loom cache discovery (default); loader = currently behaves the same as "merged".';
93
+ const SUGGESTED_CALL_DEFAULTS = {
94
+ allowDecompile: true,
95
+ preferProjectVersion: false,
96
+ strictVersion: false,
97
+ mode: "metadata",
98
+ access: "public",
99
+ includeSynthetic: false,
100
+ includeInherited: false,
101
+ hideUncertain: false,
102
+ explain: false,
103
+ preferProjectMapping: false,
104
+ minSeverity: "all",
105
+ reportMode: "full",
106
+ treatInfoAsWarning: true,
107
+ includeIssues: true
108
+ };
109
+ function isSuggestedCallDefault(field, value) {
110
+ return value === SUGGESTED_CALL_DEFAULTS[field];
111
+ }
76
112
  const listVersionsShape = {
77
- includeSnapshots: z.boolean().optional().describe("default false"),
78
- limit: optionalPositiveInt.describe("default 20, max 200")
113
+ includeSnapshots: z.boolean().default(false),
114
+ limit: optionalPositiveInt.default(20).describe("max 200")
79
115
  };
80
116
  const listVersionsSchema = z.object(listVersionsShape);
81
117
  const resolveArtifactShape = {
@@ -85,7 +121,7 @@ const resolveArtifactShape = {
85
121
  }).describe(RESOLVE_ARTIFACT_TARGET_DESCRIPTION),
86
122
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
87
123
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
88
- allowDecompile: z.boolean().optional().describe("default true"),
124
+ allowDecompile: z.boolean().default(true),
89
125
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
90
126
  scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
91
127
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
@@ -94,11 +130,11 @@ const resolveArtifactShape = {
94
130
  const resolveArtifactSchema = z.object(resolveArtifactShape);
95
131
  const getClassSourceShape = {
96
132
  className: nonEmptyString,
97
- mode: sourceModeSchema.optional().describe("metadata (default) = symbol outline only; snippet = source with default maxLines=200; full = entire source"),
133
+ mode: sourceModeSchema.default("metadata").describe("metadata = symbol outline only; snippet = source with default maxLines=200; full = entire source"),
98
134
  target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
99
135
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
100
136
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
101
- allowDecompile: z.boolean().optional().describe("default true"),
137
+ allowDecompile: z.boolean().default(true),
102
138
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
103
139
  scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
104
140
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
@@ -127,10 +163,10 @@ const getClassMembersShape = {
127
163
  target: sourceLookupTargetSchema.describe(SOURCE_LOOKUP_TARGET_DESCRIPTION),
128
164
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
129
165
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
130
- allowDecompile: z.boolean().optional().describe("default true"),
131
- access: memberAccessSchema.optional().describe("public | all (default public)"),
132
- includeSynthetic: z.boolean().optional().describe("default false"),
133
- includeInherited: z.boolean().optional().describe("default false"),
166
+ allowDecompile: z.boolean().default(true),
167
+ access: memberAccessSchema.default("public").describe("public | all"),
168
+ includeSynthetic: z.boolean().default(false),
169
+ includeInherited: z.boolean().default(false),
134
170
  memberPattern: optionalNonEmptyString,
135
171
  maxMembers: optionalPositiveInt.describe("default 500, max 5000"),
136
172
  projectPath: optionalNonEmptyString,
@@ -147,8 +183,8 @@ const searchClassSourceShape = {
147
183
  packagePrefix: optionalNonEmptyString,
148
184
  fileGlob: optionalNonEmptyString,
149
185
  symbolKind: searchSymbolKindSchema.optional().describe("class | interface | enum | record | method | field"),
150
- queryMode: z.enum(["auto", "token", "literal"]).optional().describe("auto (default): FTS5 with literal fallback for separator queries; token: FTS5 only; literal: substring scan only"),
151
- limit: optionalPositiveInt.describe("default 20"),
186
+ queryMode: z.enum(["auto", "token", "literal"]).default("auto").describe("auto: indexed search, including separator queries like foo.bar; token: indexed-only; literal: explicit substring scan only"),
187
+ limit: optionalPositiveInt.default(20),
152
188
  cursor: optionalNonEmptyString
153
189
  };
154
190
  const searchClassSourceSchema = z.object(searchClassSourceShape).superRefine((value, ctx) => {
@@ -180,9 +216,9 @@ const traceSymbolLifecycleShape = {
180
216
  toVersion: optionalNonEmptyString,
181
217
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
182
218
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
183
- includeSnapshots: z.boolean().optional().describe("default false"),
184
- maxVersions: optionalPositiveInt.describe("default 120, max 400"),
185
- includeTimeline: z.boolean().optional().describe("default false")
219
+ includeSnapshots: z.boolean().default(false),
220
+ maxVersions: optionalPositiveInt.default(120).describe("max 400"),
221
+ includeTimeline: z.boolean().default(false)
186
222
  };
187
223
  const traceSymbolLifecycleSchema = z.object(traceSymbolLifecycleShape);
188
224
  const diffClassSignaturesShape = {
@@ -191,7 +227,7 @@ const diffClassSignaturesShape = {
191
227
  toVersion: nonEmptyString,
192
228
  mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
193
229
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
194
- includeFullDiff: z.boolean().optional().describe("When false, omit from/to snapshots from modified entries and keep only key+changed")
230
+ includeFullDiff: z.boolean().default(true).describe("When false, omit from/to snapshots from modified entries and keep only key+changed")
195
231
  };
196
232
  const diffClassSignaturesSchema = z.object(diffClassSignaturesShape);
197
233
  const findMappingShape = {
@@ -210,7 +246,7 @@ const findMappingShape = {
210
246
  })
211
247
  .partial()
212
248
  .optional(),
213
- maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
249
+ maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
214
250
  };
215
251
  const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
216
252
  if (value.kind === "class") {
@@ -277,7 +313,7 @@ const resolveMethodMappingExactShape = {
277
313
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
278
314
  targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
279
315
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
280
- maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
316
+ maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
281
317
  };
282
318
  const resolveMethodMappingExactSchema = z
283
319
  .object(resolveMethodMappingExactShape)
@@ -328,7 +364,7 @@ const resolveWorkspaceSymbolShape = {
328
364
  descriptor: optionalNonEmptyString,
329
365
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
330
366
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
331
- maxCandidates: optionalPositiveInt.describe("Limit returned candidates for field/method lookups (default 200, max 200)")
367
+ maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates for field/method lookups (max 200)")
332
368
  };
333
369
  const resolveWorkspaceSymbolSchema = z
334
370
  .object(resolveWorkspaceSymbolShape)
@@ -397,10 +433,10 @@ const checkSymbolExistsShape = {
397
433
  descriptor: optionalNonEmptyString.describe("required for kind=method unless signatureMode=name-only"),
398
434
  sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
399
435
  sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
400
- nameMode: classNameModeSchema.optional().describe("fqcn | auto (default fqcn)"),
401
- signatureMode: z.enum(["exact", "name-only"]).optional()
402
- .describe("exact (default): require descriptor for methods; name-only: match by owner+name only"),
403
- maxCandidates: optionalPositiveInt.describe("Limit returned candidates (default 200, max 200)")
436
+ nameMode: classNameModeSchema.default("fqcn").describe("fqcn | auto"),
437
+ signatureMode: z.enum(["exact", "name-only"]).default("exact")
438
+ .describe("exact: require descriptor for methods; name-only: match by owner+name only"),
439
+ maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
404
440
  };
405
441
  const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
406
442
  if (value.kind === "class") {
@@ -461,7 +497,7 @@ const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((va
461
497
  });
462
498
  const nbtToJsonShape = {
463
499
  nbtBase64: nonEmptyString,
464
- compression: decodeCompressionSchema.optional().describe("none | gzip | auto (default auto)")
500
+ compression: decodeCompressionSchema.default("auto").describe("none | gzip | auto")
465
501
  };
466
502
  const nbtToJsonSchema = z.object(nbtToJsonShape);
467
503
  const nbtPatchOperationSchema = z
@@ -478,12 +514,12 @@ const nbtApplyJsonPatchShape = {
478
514
  const nbtApplyJsonPatchSchema = z.object(nbtApplyJsonPatchShape);
479
515
  const jsonToNbtShape = {
480
516
  typedJson: z.unknown(),
481
- compression: encodeCompressionSchema.optional().describe("none | gzip (default none)")
517
+ compression: encodeCompressionSchema.default("none").describe("none | gzip")
482
518
  };
483
519
  const jsonToNbtSchema = z.object(jsonToNbtShape);
484
520
  const indexArtifactShape = {
485
521
  artifactId: nonEmptyString,
486
- force: z.boolean().optional().describe("default false")
522
+ force: z.boolean().default(false)
487
523
  };
488
524
  const indexArtifactSchema = z.object(indexArtifactShape);
489
525
  const validateMixinShape = {
@@ -517,23 +553,23 @@ const validateMixinShape = {
517
553
  scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
518
554
  projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
519
555
  preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
520
- minSeverity: z.enum(["error", "warning", "all"]).optional()
521
- .describe("'error'=errors only, 'warning'=errors+warnings, 'all'=everything (default 'all')"),
522
- hideUncertain: z.boolean().optional()
523
- .describe("Omit issues with confidence='uncertain' (default false)"),
524
- explain: z.boolean().optional()
525
- .describe("When true, enrich each issue with explanation and suggestedCall for agent recovery (default false)"),
556
+ minSeverity: z.enum(["error", "warning", "all"]).default("all")
557
+ .describe("'error'=errors only, 'warning'=errors+warnings, 'all'=everything"),
558
+ hideUncertain: z.boolean().default(false)
559
+ .describe("Omit issues with confidence='uncertain'"),
560
+ explain: z.boolean().default(false)
561
+ .describe("When true, enrich each issue with explanation and suggestedCall for agent recovery"),
526
562
  warningMode: z.enum(["full", "aggregated"]).optional()
527
- .describe("'full'=all warnings (default), 'aggregated'=group warnings by category with counts and samples"),
528
- preferProjectMapping: z.boolean().optional()
563
+ .describe("'full'=all warnings; 'aggregated'=group warnings by category with counts and samples. Single validation uses the provided value as-is; batch validation defaults to 'aggregated'"),
564
+ preferProjectMapping: z.boolean().default(false)
529
565
  .describe("When true, auto-detect mapping from project config even if mapping is explicitly provided"),
530
- reportMode: z.enum(["compact", "full", "summary-first"]).optional()
531
- .describe("'compact' omits heavy per-result detail, 'summary-first' hoists shared provenance/warnings/incomplete reasons, 'full'=everything (default)"),
566
+ reportMode: z.enum(["compact", "full", "summary-first"]).default("full")
567
+ .describe("'compact' omits heavy per-result detail, 'summary-first' hoists shared provenance/warnings/incomplete reasons, 'full'=everything"),
532
568
  warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional()
533
569
  .describe("Only include warnings/issues matching these categories (default: all)"),
534
- treatInfoAsWarning: z.boolean().optional()
535
- .describe("When false, suppress info-severity structured warnings from output (default true)"),
536
- includeIssues: z.boolean().optional()
570
+ treatInfoAsWarning: z.boolean().default(true)
571
+ .describe("When false, suppress info-severity structured warnings from output"),
572
+ includeIssues: z.boolean().default(true)
537
573
  .describe("When false, keep summary fields but omit per-result issues[] payloads")
538
574
  };
539
575
  const validateMixinSchema = z.object(validateMixinShape);
@@ -546,13 +582,13 @@ const validateAccessWidenerShape = {
546
582
  const validateAccessWidenerSchema = z.object(validateAccessWidenerShape);
547
583
  const analyzeModJarShape = {
548
584
  jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
549
- includeClasses: z.boolean().optional().describe("Include full class listing (default false)")
585
+ includeClasses: z.boolean().default(false).describe("Include full class listing")
550
586
  };
551
587
  const analyzeModJarSchema = z.object(analyzeModJarShape);
552
588
  const getRegistryDataShape = {
553
589
  version: nonEmptyString.describe("Minecraft version (e.g. 1.21)"),
554
590
  registry: optionalNonEmptyString.describe('Optional registry name (e.g. "block", "item", "minecraft:biome"). Omit to list all registries.'),
555
- includeData: z.boolean().optional().describe("When false, return registry names/counts without full entry bodies"),
591
+ includeData: z.boolean().default(true).describe("When false, return registry names/counts without full entry bodies"),
556
592
  maxEntriesPerRegistry: optionalPositiveInt.describe("Limit returned entries per registry body")
557
593
  };
558
594
  const getRegistryDataSchema = z.object(getRegistryDataShape);
@@ -561,15 +597,15 @@ const compareVersionsCategorySchema = z.enum(COMPARE_VERSIONS_CATEGORIES);
561
597
  const compareVersionsShape = {
562
598
  fromVersion: nonEmptyString.describe("Older Minecraft version (e.g. 1.20.4)"),
563
599
  toVersion: nonEmptyString.describe("Newer Minecraft version (e.g. 1.21)"),
564
- category: compareVersionsCategorySchema.optional().describe("classes | registry | all (default all)"),
600
+ category: compareVersionsCategorySchema.default("all").describe("classes | registry | all"),
565
601
  packageFilter: optionalNonEmptyString.describe("Filter classes to a package prefix (e.g. net.minecraft.world.item)"),
566
- maxClassResults: optionalPositiveInt.describe("Max class results per direction (default 500, max 5000)")
602
+ maxClassResults: optionalPositiveInt.default(500).describe("Max class results per direction (max 5000)")
567
603
  };
568
604
  const compareVersionsSchema = z.object(compareVersionsShape);
569
605
  const decompileModJarShape = {
570
606
  jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
571
607
  className: optionalNonEmptyString.describe("Optional fully-qualified class name to view source. Omit to list all classes."),
572
- includeFiles: z.boolean().optional().describe("When false, omit the full class list and return counts only"),
608
+ includeFiles: z.boolean().default(true).describe("When false, omit the full class list and return counts only"),
573
609
  maxFiles: optionalPositiveInt.describe("Limit returned class names when files are included")
574
610
  };
575
611
  const decompileModJarSchema = z.object(decompileModJarShape);
@@ -586,8 +622,8 @@ const modSearchTypeSchema = z.enum(MOD_SEARCH_TYPES);
586
622
  const searchModSourceShape = {
587
623
  jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
588
624
  query: nonEmptyString.describe("Search pattern (regex or literal string)"),
589
- searchType: modSearchTypeSchema.optional().describe("class | method | field | content | all (default all)"),
590
- limit: optionalPositiveInt.describe("Max results (default 50, max 200)")
625
+ searchType: modSearchTypeSchema.default("all").describe("class | method | field | content | all"),
626
+ limit: optionalPositiveInt.default(50).describe("Max results (max 200)")
591
627
  };
592
628
  const searchModSourceSchema = z.object(searchModSourceShape);
593
629
  const REMAP_TARGETS = ["yarn", "mojang"];
@@ -639,6 +675,50 @@ const sourceService = new Proxy({}, {
639
675
  return typeof value === "function" ? value.bind(service) : value;
640
676
  }
641
677
  });
678
+ const workspaceMappingService = new WorkspaceMappingService();
679
+ const inspectMinecraftService = new InspectMinecraftService({
680
+ listVersions: (input) => sourceService.listVersions(input),
681
+ resolveArtifact: (input) => sourceService.resolveArtifact(input),
682
+ findClass: (input) => Promise.resolve(sourceService.findClass(input)),
683
+ getClassSource: (input) => sourceService.getClassSource(input),
684
+ getClassMembers: (input) => sourceService.getClassMembers(input),
685
+ searchClassSource: (input) => sourceService.searchClassSource(input),
686
+ getArtifactFile: (input) => sourceService.getArtifactFile(input),
687
+ listArtifactFiles: (input) => sourceService.listArtifactFiles(input),
688
+ detectProjectMinecraftVersion: (projectPath) => workspaceMappingService.detectProjectMinecraftVersion(projectPath)
689
+ });
690
+ const analyzeSymbolService = new AnalyzeSymbolService({
691
+ checkSymbolExists: (input) => sourceService.checkSymbolExists(input),
692
+ findMapping: (input) => sourceService.findMapping(input),
693
+ resolveMethodMappingExact: (input) => sourceService.resolveMethodMappingExact(input),
694
+ traceSymbolLifecycle: (input) => sourceService.traceSymbolLifecycle(input),
695
+ resolveWorkspaceSymbol: (input) => sourceService.resolveWorkspaceSymbol(input),
696
+ getClassApiMatrix: (input) => sourceService.getClassApiMatrix(input)
697
+ });
698
+ const compareMinecraftService = new CompareMinecraftService({
699
+ compareVersions: (input) => sourceService.compareVersions(input),
700
+ diffClassSignatures: (input) => sourceService.diffClassSignatures(input),
701
+ getRegistryData: (input) => sourceService.getRegistryData(input)
702
+ });
703
+ const analyzeModService = new AnalyzeModService({
704
+ analyzeModJar: (jarPath, options) => analyzeModJar(jarPath, options),
705
+ decompileModJar: (input) => sourceService.decompileModJar(input),
706
+ getModClassSource: (input) => sourceService.getModClassSource(input),
707
+ searchModSource: (input) => sourceService.searchModSource(input),
708
+ remapModJar: (input) => remapModJar(input, config)
709
+ });
710
+ const validateProjectService = new ValidateProjectService({
711
+ validateMixin: (input) => sourceService.validateMixin(input),
712
+ validateAccessWidener: (input) => sourceService.validateAccessWidener(input),
713
+ discoverMixins: discoverWorkspaceMixins,
714
+ discoverAccessWideners: discoverWorkspaceAccessWideners
715
+ });
716
+ const manageCacheService = new ManageCacheService({
717
+ registry: createCacheRegistry({
718
+ cacheDir: config.cacheDir,
719
+ sqlitePath: config.sqlitePath
720
+ })
721
+ });
642
722
  registerResources(server, sourceService);
643
723
  let processHandlersAttached = false;
644
724
  let serverStarted = false;
@@ -847,7 +927,10 @@ function copySourceLookupSuggestionFields(tool, source) {
847
927
  : ["className", "mapping", "sourcePriority", "projectPath", "scope", "access", "memberPattern"];
848
928
  for (const field of stringFields) {
849
929
  const value = source[field];
850
- if (typeof value === "string" && value.trim()) {
930
+ if (typeof value === "string" &&
931
+ value.trim() &&
932
+ (!Object.prototype.hasOwnProperty.call(SUGGESTED_CALL_DEFAULTS, field) ||
933
+ !isSuggestedCallDefault(field, value))) {
851
934
  result[field] = value;
852
935
  }
853
936
  }
@@ -865,7 +948,9 @@ function copySourceLookupSuggestionFields(tool, source) {
865
948
  : ["allowDecompile", "preferProjectVersion", "strictVersion", "includeSynthetic", "includeInherited"];
866
949
  for (const field of booleanFields) {
867
950
  const value = source[field];
868
- if (typeof value === "boolean") {
951
+ if (typeof value === "boolean" &&
952
+ (!Object.prototype.hasOwnProperty.call(SUGGESTED_CALL_DEFAULTS, field) ||
953
+ !isSuggestedCallDefault(field, value))) {
869
954
  result[field] = value;
870
955
  }
871
956
  }
@@ -885,7 +970,10 @@ function copyValidateMixinSharedParams(source) {
885
970
  ];
886
971
  for (const field of stringFields) {
887
972
  const value = source[field];
888
- if (typeof value === "string" && value.trim()) {
973
+ if (typeof value === "string" &&
974
+ value.trim() &&
975
+ (!Object.prototype.hasOwnProperty.call(SUGGESTED_CALL_DEFAULTS, field) ||
976
+ !isSuggestedCallDefault(field, value))) {
889
977
  result[field] = value;
890
978
  }
891
979
  }
@@ -899,7 +987,9 @@ function copyValidateMixinSharedParams(source) {
899
987
  ];
900
988
  for (const field of booleanFields) {
901
989
  const value = source[field];
902
- if (typeof value === "boolean") {
990
+ if (typeof value === "boolean" &&
991
+ (!Object.prototype.hasOwnProperty.call(SUGGESTED_CALL_DEFAULTS, field) ||
992
+ !isSuggestedCallDefault(field, value))) {
903
993
  result[field] = value;
904
994
  }
905
995
  }
@@ -1037,7 +1127,8 @@ function buildResolveArtifactSuggestedParams(normalizedInput) {
1037
1127
  const booleanFields = ["allowDecompile", "preferProjectVersion", "strictVersion"];
1038
1128
  for (const field of booleanFields) {
1039
1129
  const value = record[field];
1040
- if (typeof value === "boolean") {
1130
+ if (typeof value === "boolean" &&
1131
+ !isSuggestedCallDefault(field, value)) {
1041
1132
  result[field] = value;
1042
1133
  }
1043
1134
  }
@@ -1146,18 +1237,27 @@ function mapErrorToProblem(caughtError, requestId, context) {
1146
1237
  }
1147
1238
  function splitWarnings(data) {
1148
1239
  const result = { ...data };
1240
+ const warnings = [];
1149
1241
  const maybeWarnings = result.warnings;
1150
- if (!Array.isArray(maybeWarnings)) {
1151
- return {
1152
- result,
1153
- warnings: []
1154
- };
1242
+ if (Array.isArray(maybeWarnings)) {
1243
+ warnings.push(...maybeWarnings.filter((entry) => typeof entry === "string"));
1244
+ delete result.warnings;
1245
+ }
1246
+ let meta = {};
1247
+ const maybeMeta = result.meta;
1248
+ if (maybeMeta && typeof maybeMeta === "object" && !Array.isArray(maybeMeta)) {
1249
+ meta = { ...maybeMeta };
1250
+ delete result.meta;
1251
+ const metaWarnings = meta.warnings;
1252
+ if (Array.isArray(metaWarnings)) {
1253
+ warnings.push(...metaWarnings.filter((entry) => typeof entry === "string"));
1254
+ delete meta.warnings;
1255
+ }
1155
1256
  }
1156
- const warnings = maybeWarnings.filter((entry) => typeof entry === "string");
1157
- delete result.warnings;
1158
1257
  return {
1159
1258
  result,
1160
- warnings
1259
+ warnings: [...new Set(warnings)],
1260
+ meta
1161
1261
  };
1162
1262
  }
1163
1263
  async function runTool(tool, rawInput, schema, action) {
@@ -1192,10 +1292,28 @@ async function runTool(tool, rawInput, schema, action) {
1192
1292
  const payload = await (HEAVY_TOOL_NAMES.has(tool)
1193
1293
  ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
1194
1294
  : action(parsedInput));
1195
- const { result, warnings } = splitWarnings(payload);
1295
+ const { result, warnings, meta: resultMeta } = splitWarnings(payload);
1296
+ const entryMeta = ENTRY_TOOL_NAMES.has(tool)
1297
+ ? buildEntryToolMeta({
1298
+ detail: normalizedInput &&
1299
+ typeof normalizedInput === "object" &&
1300
+ !Array.isArray(normalizedInput) &&
1301
+ typeof normalizedInput.detail === "string"
1302
+ ? normalizedInput.detail ?? "summary"
1303
+ : "summary",
1304
+ include: normalizedInput &&
1305
+ typeof normalizedInput === "object" &&
1306
+ !Array.isArray(normalizedInput) &&
1307
+ Array.isArray(normalizedInput.include)
1308
+ ? normalizedInput.include
1309
+ : undefined
1310
+ })
1311
+ : undefined;
1196
1312
  return objectResult({
1197
1313
  result,
1198
1314
  meta: {
1315
+ ...(entryMeta ?? {}),
1316
+ ...resultMeta,
1199
1317
  requestId,
1200
1318
  tool,
1201
1319
  durationMs: Date.now() - startedAt,
@@ -1253,6 +1371,12 @@ server.tool("list-versions", "List available Minecraft versions from Mojang mani
1253
1371
  includeSnapshots: input.includeSnapshots,
1254
1372
  limit: input.limit
1255
1373
  })));
1374
+ server.tool("inspect-minecraft", "High-level v3 entry 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)));
1375
+ server.tool("analyze-symbol", "High-level v3 entry tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
1376
+ server.tool("compare-minecraft", "High-level v3 entry tool for version comparisons, class diffs, registry diffs, and migration overviews.", compareMinecraftShape, { readOnlyHint: true }, async (args) => runTool("compare-minecraft", args, compareMinecraftSchema, async (input) => compareMinecraftService.execute(input)));
1377
+ server.tool("analyze-mod", "High-level v3 entry tool for mod metadata inspection, decompile/search flows, class source, and safe remap previews/applies.", analyzeModShape, { readOnlyHint: false }, async (args) => runTool("analyze-mod", args, analyzeModSchema, async (input) => analyzeModService.execute(input)));
1378
+ server.tool("validate-project", "High-level v3 entry tool for project summary, direct mixin validation, and access widener validation.", validateProjectShape, { readOnlyHint: true }, async (args) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input)));
1379
+ server.tool("manage-cache", "High-level v3 entry tool for cache summaries, listing, verification, previewed mutation, and explicit apply operations.", manageCacheShape, { readOnlyHint: false }, async (args) => runTool("manage-cache", args, manageCacheSchema, async (input) => manageCacheService.execute(input)));
1256
1380
  server.tool("resolve-artifact", "Resolve source artifact from a target object ({ kind, value }) and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted; other adjacent *-sources.jar files are informational.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
1257
1381
  target: input.target,
1258
1382
  mapping: input.mapping,
@@ -3,12 +3,19 @@ export interface MetricTimingSnapshot {
3
3
  totalMs: number;
4
4
  avgMs: number;
5
5
  lastMs: number;
6
+ p95Ms: number;
7
+ p99Ms: number;
6
8
  }
7
9
  export interface CacheArtifactByteAccountingRow {
8
10
  artifact_id: string;
9
11
  content_bytes: number;
10
12
  updated_at: string;
11
13
  }
14
+ type CacheArtifactByteAccountingRefRow = {
15
+ artifactId: string;
16
+ totalContentBytes: number;
17
+ updatedAt: string;
18
+ };
12
19
  export interface RuntimeMetricSnapshot {
13
20
  resolve_duration_ms: MetricTimingSnapshot;
14
21
  search_duration_ms: MetricTimingSnapshot;
@@ -18,6 +25,10 @@ export interface RuntimeMetricSnapshot {
18
25
  search_intent_symbol_duration_ms: MetricTimingSnapshot;
19
26
  search_intent_text_duration_ms: MetricTimingSnapshot;
20
27
  search_intent_path_duration_ms: MetricTimingSnapshot;
28
+ search_query_mode_auto_count: number;
29
+ search_query_mode_token_count: number;
30
+ search_query_mode_literal_count: number;
31
+ search_literal_explicit_count: number;
21
32
  search_regex_fallback_count: number;
22
33
  search_token_bytes_returned: number;
23
34
  onehop_expand_count: number;
@@ -43,6 +54,10 @@ export declare class RuntimeMetrics {
43
54
  private cacheHits;
44
55
  private cacheMisses;
45
56
  private repoFailoverCount;
57
+ private searchQueryModeAutoCount;
58
+ private searchQueryModeTokenCount;
59
+ private searchQueryModeLiteralCount;
60
+ private searchLiteralExplicitCount;
46
61
  private searchRegexFallbackCount;
47
62
  private searchTokenBytesReturned;
48
63
  private oneHopExpandCount;
@@ -58,12 +73,13 @@ export declare class RuntimeMetrics {
58
73
  private cacheEvictions;
59
74
  private cacheEntries;
60
75
  private cacheTotalContentBytes;
61
- private cacheArtifactBytesLru;
76
+ private cacheArtifactBytesLruRef;
62
77
  constructor();
63
78
  recordDuration(name: DurationMetricName, durationMs: number): void;
64
79
  recordArtifactCacheHit(): void;
65
80
  recordArtifactCacheMiss(): void;
66
81
  recordRepoFailover(): void;
82
+ recordSearchQueryMode(mode: "auto" | "token" | "literal"): void;
67
83
  recordSearchIntentDuration(intent: "symbol" | "text" | "path", durationMs: number): void;
68
84
  recordSearchRegexFallback(): void;
69
85
  recordSearchTokenBytesReturned(tokenBytes: number): void;
@@ -80,7 +96,7 @@ export declare class RuntimeMetrics {
80
96
  recordCacheEviction(count?: number): void;
81
97
  setCacheEntries(entries: number): void;
82
98
  setCacheTotalContentBytes(totalBytes: number): void;
83
- setCacheArtifactByteAccounting(entries: CacheArtifactByteAccountingRow[]): void;
99
+ setCacheArtifactByteAccountingRef(entries: ReadonlyArray<CacheArtifactByteAccountingRefRow>): void;
84
100
  snapshot(): RuntimeMetricSnapshot;
85
101
  private toSnapshot;
86
102
  private resolveCacheHitRate;
@@ -1,8 +1,21 @@
1
+ const MAX_TIMING_SAMPLES = 512;
2
+ function percentile(samples, p) {
3
+ if (samples.length === 0) {
4
+ return 0;
5
+ }
6
+ const sorted = [...samples].sort((left, right) => left - right);
7
+ const index = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);
8
+ return sorted[index] ?? 0;
9
+ }
1
10
  export class RuntimeMetrics {
2
11
  timings = new Map();
3
12
  cacheHits = 0;
4
13
  cacheMisses = 0;
5
14
  repoFailoverCount = 0;
15
+ searchQueryModeAutoCount = 0;
16
+ searchQueryModeTokenCount = 0;
17
+ searchQueryModeLiteralCount = 0;
18
+ searchLiteralExplicitCount = 0;
6
19
  searchRegexFallbackCount = 0;
7
20
  searchTokenBytesReturned = 0;
8
21
  oneHopExpandCount = 0;
@@ -18,7 +31,7 @@ export class RuntimeMetrics {
18
31
  cacheEvictions = 0;
19
32
  cacheEntries = 0;
20
33
  cacheTotalContentBytes = 0;
21
- cacheArtifactBytesLru = [];
34
+ cacheArtifactBytesLruRef = [];
22
35
  constructor() {
23
36
  const names = [
24
37
  "resolve_duration_ms",
@@ -31,7 +44,7 @@ export class RuntimeMetrics {
31
44
  "search_intent_path_duration_ms"
32
45
  ];
33
46
  for (const name of names) {
34
- this.timings.set(name, { count: 0, totalMs: 0, lastMs: 0 });
47
+ this.timings.set(name, { count: 0, totalMs: 0, lastMs: 0, samples: [] });
35
48
  }
36
49
  }
37
50
  recordDuration(name, durationMs) {
@@ -43,6 +56,10 @@ export class RuntimeMetrics {
43
56
  timing.count += 1;
44
57
  timing.totalMs += normalizedDuration;
45
58
  timing.lastMs = normalizedDuration;
59
+ timing.samples.push(normalizedDuration);
60
+ if (timing.samples.length > MAX_TIMING_SAMPLES) {
61
+ timing.samples.shift();
62
+ }
46
63
  }
47
64
  recordArtifactCacheHit() {
48
65
  this.cacheHits += 1;
@@ -53,6 +70,20 @@ export class RuntimeMetrics {
53
70
  recordRepoFailover() {
54
71
  this.repoFailoverCount += 1;
55
72
  }
73
+ recordSearchQueryMode(mode) {
74
+ switch (mode) {
75
+ case "auto":
76
+ this.searchQueryModeAutoCount += 1;
77
+ break;
78
+ case "token":
79
+ this.searchQueryModeTokenCount += 1;
80
+ break;
81
+ case "literal":
82
+ this.searchQueryModeLiteralCount += 1;
83
+ this.searchLiteralExplicitCount += 1;
84
+ break;
85
+ }
86
+ }
56
87
  recordSearchIntentDuration(intent, durationMs) {
57
88
  const metricName = intent === "symbol"
58
89
  ? "search_intent_symbol_duration_ms"
@@ -106,12 +137,8 @@ export class RuntimeMetrics {
106
137
  setCacheTotalContentBytes(totalBytes) {
107
138
  this.cacheTotalContentBytes = Math.max(0, Math.trunc(totalBytes));
108
139
  }
109
- setCacheArtifactByteAccounting(entries) {
110
- this.cacheArtifactBytesLru = entries.map((entry) => ({
111
- artifact_id: entry.artifact_id,
112
- content_bytes: Math.max(0, Math.trunc(entry.content_bytes)),
113
- updated_at: entry.updated_at
114
- }));
140
+ setCacheArtifactByteAccountingRef(entries) {
141
+ this.cacheArtifactBytesLruRef = entries;
115
142
  }
116
143
  snapshot() {
117
144
  return {
@@ -123,6 +150,10 @@ export class RuntimeMetrics {
123
150
  search_intent_symbol_duration_ms: this.toSnapshot("search_intent_symbol_duration_ms"),
124
151
  search_intent_text_duration_ms: this.toSnapshot("search_intent_text_duration_ms"),
125
152
  search_intent_path_duration_ms: this.toSnapshot("search_intent_path_duration_ms"),
153
+ search_query_mode_auto_count: this.searchQueryModeAutoCount,
154
+ search_query_mode_token_count: this.searchQueryModeTokenCount,
155
+ search_query_mode_literal_count: this.searchQueryModeLiteralCount,
156
+ search_literal_explicit_count: this.searchLiteralExplicitCount,
126
157
  search_regex_fallback_count: this.searchRegexFallbackCount,
127
158
  search_token_bytes_returned: this.searchTokenBytesReturned,
128
159
  onehop_expand_count: this.oneHopExpandCount,
@@ -138,7 +169,11 @@ export class RuntimeMetrics {
138
169
  cache_evictions: this.cacheEvictions,
139
170
  cache_entries: this.cacheEntries,
140
171
  cache_total_content_bytes: this.cacheTotalContentBytes,
141
- cache_artifact_bytes_lru: this.cacheArtifactBytesLru,
172
+ cache_artifact_bytes_lru: this.cacheArtifactBytesLruRef.map((entry) => ({
173
+ artifact_id: entry.artifactId,
174
+ content_bytes: Math.max(0, Math.trunc(entry.totalContentBytes)),
175
+ updated_at: entry.updatedAt
176
+ })),
142
177
  cache_hit_rate: this.resolveCacheHitRate(),
143
178
  repo_failover_count: this.repoFailoverCount
144
179
  };
@@ -151,7 +186,9 @@ export class RuntimeMetrics {
151
186
  count,
152
187
  totalMs,
153
188
  avgMs: count > 0 ? totalMs / count : 0,
154
- lastMs: timing?.lastMs ?? 0
189
+ lastMs: timing?.lastMs ?? 0,
190
+ p95Ms: percentile(timing?.samples ?? [], 95),
191
+ p99Ms: percentile(timing?.samples ?? [], 99)
155
192
  };
156
193
  }
157
194
  resolveCacheHitRate() {
@@ -488,7 +488,6 @@ export declare class SourceService {
488
488
  private searchTextIntent;
489
489
  private searchPathIntent;
490
490
  private findSymbolHits;
491
- private loadScopedFilePaths;
492
491
  private indexedCandidateLimit;
493
492
  private indexedCandidateLimitForMatch;
494
493
  private extractClassMetadata;