@adhisang/minecraft-modding-mcp 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,381 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import fastGlob from "fast-glob";
4
+ import { z } from "zod";
5
+ import { createError, ERROR_CODES } from "../errors.js";
6
+ import { buildIncludeSchema, detailSchema } from "./entry-tool-schema.js";
7
+ import { buildEntryToolResult } from "./response-contract.js";
8
+ import { resolveDetail, resolveInclude } from "./request-normalizers.js";
9
+ const nonEmptyString = z.string().trim().min(1);
10
+ const INCLUDE_GROUPS = ["warnings", "issues", "workspace", "recovery"];
11
+ const mixinInputSchema = z.discriminatedUnion("mode", [
12
+ z.object({ mode: z.literal("inline"), source: nonEmptyString }),
13
+ z.object({ mode: z.literal("path"), path: nonEmptyString }),
14
+ z.object({ mode: z.literal("paths"), paths: z.array(nonEmptyString).min(1) }),
15
+ z.object({ mode: z.literal("config"), configPaths: z.array(nonEmptyString).min(1) }),
16
+ z.object({ mode: z.literal("project"), path: nonEmptyString })
17
+ ]);
18
+ const accessWidenerInputSchema = z.discriminatedUnion("mode", [
19
+ z.object({ mode: z.literal("inline"), content: nonEmptyString }),
20
+ z.object({ mode: z.literal("path"), path: nonEmptyString })
21
+ ]);
22
+ const subjectSchema = z.discriminatedUnion("kind", [
23
+ z.object({
24
+ kind: z.literal("workspace"),
25
+ projectPath: nonEmptyString,
26
+ discover: z.array(z.enum(["mixins", "access-wideners"])).optional()
27
+ }),
28
+ z.object({
29
+ kind: z.literal("mixin"),
30
+ input: mixinInputSchema
31
+ }),
32
+ z.object({
33
+ kind: z.literal("access-widener"),
34
+ input: accessWidenerInputSchema
35
+ })
36
+ ]);
37
+ export const validateProjectShape = {
38
+ task: z.enum(["project-summary", "mixin", "access-widener"]),
39
+ subject: subjectSchema,
40
+ version: nonEmptyString.optional(),
41
+ mapping: z.enum(["obfuscated", "mojang", "intermediary", "yarn"]).optional(),
42
+ sourcePriority: z.enum(["loom-first", "maven-first"]).optional(),
43
+ scope: z.enum(["vanilla", "merged", "loader"]).optional(),
44
+ preferProjectVersion: z.boolean().optional(),
45
+ preferProjectMapping: z.boolean().optional(),
46
+ detail: detailSchema.optional(),
47
+ include: buildIncludeSchema(INCLUDE_GROUPS),
48
+ sourceRoots: z.array(nonEmptyString).optional(),
49
+ configPaths: z.array(nonEmptyString).optional(),
50
+ minSeverity: z.enum(["error", "warning", "all"]).optional(),
51
+ hideUncertain: z.boolean().optional(),
52
+ explain: z.boolean().optional(),
53
+ warningMode: z.enum(["full", "aggregated"]).optional(),
54
+ warningCategoryFilter: z.array(z.enum(["mapping", "configuration", "validation", "resolution", "parse"])).optional(),
55
+ treatInfoAsWarning: z.boolean().optional(),
56
+ includeIssues: z.boolean().optional()
57
+ };
58
+ export const validateProjectSchema = z.object(validateProjectShape).superRefine((value, ctx) => {
59
+ if (value.task === "project-summary" && value.subject.kind !== "workspace") {
60
+ ctx.addIssue({
61
+ code: z.ZodIssueCode.custom,
62
+ path: ["subject", "kind"],
63
+ message: "task=project-summary requires subject.kind=workspace."
64
+ });
65
+ }
66
+ if (value.task === "mixin" && value.subject.kind !== "mixin") {
67
+ ctx.addIssue({
68
+ code: z.ZodIssueCode.custom,
69
+ path: ["subject", "kind"],
70
+ message: "task=mixin requires subject.kind=mixin."
71
+ });
72
+ }
73
+ if (value.task === "access-widener" && value.subject.kind !== "access-widener") {
74
+ ctx.addIssue({
75
+ code: z.ZodIssueCode.custom,
76
+ path: ["subject", "kind"],
77
+ message: "task=access-widener requires subject.kind=access-widener."
78
+ });
79
+ }
80
+ if (value.configPaths?.length && value.task !== "project-summary") {
81
+ ctx.addIssue({
82
+ code: z.ZodIssueCode.custom,
83
+ path: ["configPaths"],
84
+ message: "configPaths is only supported for task=project-summary workspace discovery."
85
+ });
86
+ }
87
+ });
88
+ export async function discoverWorkspaceMixins(projectPath, configPaths) {
89
+ if (configPaths?.length) {
90
+ return [...configPaths];
91
+ }
92
+ return fastGlob.sync(["**/*.mixins.json"], {
93
+ cwd: projectPath,
94
+ absolute: true,
95
+ onlyFiles: true,
96
+ ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
97
+ });
98
+ }
99
+ export async function discoverWorkspaceAccessWideners(projectPath) {
100
+ const descriptorFiles = fastGlob.sync(["fabric.mod.json", "quilt.mod.json", "**/fabric.mod.json", "**/quilt.mod.json"], {
101
+ cwd: projectPath,
102
+ absolute: true,
103
+ onlyFiles: true,
104
+ ignore: ["**/.git/**", "**/build/**", "**/out/**", "**/node_modules/**"]
105
+ });
106
+ const discovered = new Set();
107
+ for (const descriptorPath of descriptorFiles) {
108
+ try {
109
+ const parsed = JSON.parse(await readFile(descriptorPath, "utf8"));
110
+ const relative = parsed.accessWidener ?? parsed.access_widener;
111
+ if (relative) {
112
+ discovered.add(resolve(descriptorPath, "..", relative));
113
+ }
114
+ }
115
+ catch {
116
+ // ignore malformed descriptors in discovery mode
117
+ }
118
+ }
119
+ return [...discovered].sort((left, right) => left.localeCompare(right));
120
+ }
121
+ export class ValidateProjectService {
122
+ deps;
123
+ constructor(deps) {
124
+ this.deps = deps;
125
+ }
126
+ async execute(input) {
127
+ const detail = resolveDetail(input.detail);
128
+ const include = resolveInclude(input.include);
129
+ switch (input.task) {
130
+ case "mixin": {
131
+ if (input.subject.kind !== "mixin") {
132
+ throw createError({
133
+ code: ERROR_CODES.INVALID_INPUT,
134
+ message: "task=mixin requires subject.kind=mixin."
135
+ });
136
+ }
137
+ const output = await this.deps.validateMixin({
138
+ input: input.subject.input,
139
+ version: input.version,
140
+ mapping: input.mapping,
141
+ sourcePriority: input.sourcePriority,
142
+ scope: input.scope,
143
+ preferProjectVersion: input.preferProjectVersion,
144
+ preferProjectMapping: input.preferProjectMapping,
145
+ sourceRoots: input.sourceRoots,
146
+ minSeverity: input.minSeverity,
147
+ hideUncertain: input.hideUncertain,
148
+ explain: input.explain,
149
+ warningMode: input.warningMode,
150
+ warningCategoryFilter: input.warningCategoryFilter,
151
+ treatInfoAsWarning: input.treatInfoAsWarning,
152
+ includeIssues: input.includeIssues
153
+ });
154
+ const summary = output.summary;
155
+ const invalidCount = summary?.invalid ?? 0;
156
+ const partialCount = summary?.partial ?? 0;
157
+ return {
158
+ ...buildEntryToolResult({
159
+ task: "mixin",
160
+ detail,
161
+ include,
162
+ summary: {
163
+ status: invalidCount > 0 ? "invalid" : partialCount > 0 ? "partial" : "ok",
164
+ headline: `Validated ${summary?.total ?? 0} mixin input(s).`,
165
+ counts: {
166
+ valid: summary?.valid ?? 0,
167
+ partial: partialCount,
168
+ invalid: invalidCount
169
+ }
170
+ },
171
+ blocks: {
172
+ project: {
173
+ summary
174
+ },
175
+ issues: include.includes("issues") || detail !== "summary" ? output.results : undefined
176
+ },
177
+ alwaysBlocks: ["project"]
178
+ }),
179
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
180
+ };
181
+ }
182
+ case "access-widener": {
183
+ if (input.subject.kind !== "access-widener") {
184
+ throw createError({
185
+ code: ERROR_CODES.INVALID_INPUT,
186
+ message: "task=access-widener requires subject.kind=access-widener."
187
+ });
188
+ }
189
+ const content = input.subject.input.mode === "inline"
190
+ ? input.subject.input.content
191
+ : await readFile(input.subject.input.path, "utf8");
192
+ const output = await this.deps.validateAccessWidener({
193
+ content,
194
+ version: input.version,
195
+ mapping: input.mapping,
196
+ sourcePriority: input.sourcePriority
197
+ });
198
+ return {
199
+ ...buildEntryToolResult({
200
+ task: "access-widener",
201
+ detail,
202
+ include,
203
+ summary: {
204
+ status: output.valid ? "ok" : "invalid",
205
+ headline: output.valid
206
+ ? "Access Widener is valid."
207
+ : "Access Widener contains validation issues.",
208
+ counts: {
209
+ valid: output.valid ? 1 : 0,
210
+ invalid: output.valid ? 0 : 1
211
+ }
212
+ },
213
+ blocks: {
214
+ project: {
215
+ summary: {
216
+ total: 1,
217
+ valid: output.valid ? 1 : 0,
218
+ invalid: output.valid ? 0 : 1
219
+ }
220
+ },
221
+ issues: include.includes("issues") || detail !== "summary" ? output.issues : undefined
222
+ },
223
+ alwaysBlocks: ["project"]
224
+ }),
225
+ warnings: Array.isArray(output.warnings) ? output.warnings : []
226
+ };
227
+ }
228
+ case "project-summary": {
229
+ if (input.subject.kind !== "workspace") {
230
+ throw createError({
231
+ code: ERROR_CODES.INVALID_INPUT,
232
+ message: "task=project-summary requires subject.kind=workspace."
233
+ });
234
+ }
235
+ if (!input.version && !input.preferProjectVersion) {
236
+ return {
237
+ ...buildEntryToolResult({
238
+ task: "project-summary",
239
+ detail,
240
+ include,
241
+ summary: {
242
+ status: "blocked",
243
+ headline: "project-summary requires version or preferProjectVersion=true.",
244
+ nextActions: [
245
+ {
246
+ tool: "validate-project",
247
+ params: {
248
+ task: "project-summary",
249
+ version: "1.21.10",
250
+ subject: input.subject
251
+ }
252
+ }
253
+ ]
254
+ },
255
+ blocks: {
256
+ workspace: {
257
+ projectPath: input.subject.projectPath
258
+ }
259
+ }
260
+ }),
261
+ warnings: []
262
+ };
263
+ }
264
+ const projectPath = input.subject.projectPath;
265
+ const discover = input.subject.discover ?? ["mixins", "access-wideners"];
266
+ const [mixinConfigs, accessWideners] = await Promise.all([
267
+ discover.includes("mixins")
268
+ ? this.deps.discoverMixins(projectPath, input.configPaths)
269
+ : Promise.resolve([]),
270
+ discover.includes("access-wideners")
271
+ ? this.deps.discoverAccessWideners(projectPath)
272
+ : Promise.resolve([])
273
+ ]);
274
+ const warnings = [];
275
+ let validMixins = 0;
276
+ let partialMixins = 0;
277
+ let invalidMixins = 0;
278
+ for (const configPath of mixinConfigs) {
279
+ try {
280
+ const mixinResult = await this.deps.validateMixin({
281
+ input: {
282
+ mode: "config",
283
+ configPaths: [configPath]
284
+ },
285
+ version: input.version,
286
+ mapping: input.mapping,
287
+ sourcePriority: input.sourcePriority,
288
+ scope: input.scope,
289
+ preferProjectVersion: input.preferProjectVersion,
290
+ preferProjectMapping: input.preferProjectMapping,
291
+ sourceRoots: input.sourceRoots,
292
+ minSeverity: input.minSeverity,
293
+ hideUncertain: input.hideUncertain,
294
+ explain: input.explain,
295
+ warningMode: input.warningMode,
296
+ warningCategoryFilter: input.warningCategoryFilter,
297
+ treatInfoAsWarning: input.treatInfoAsWarning,
298
+ includeIssues: input.includeIssues
299
+ });
300
+ const summary = mixinResult.summary;
301
+ validMixins += summary?.valid ?? 0;
302
+ partialMixins += summary?.partial ?? 0;
303
+ invalidMixins += summary?.invalid ?? 0;
304
+ if (Array.isArray(mixinResult.warnings)) {
305
+ warnings.push(...mixinResult.warnings);
306
+ }
307
+ }
308
+ catch (error) {
309
+ invalidMixins += 1;
310
+ if (error instanceof Error) {
311
+ warnings.push(`${configPath}: ${error.message}`);
312
+ }
313
+ }
314
+ }
315
+ let validAw = 0;
316
+ let invalidAw = 0;
317
+ for (const awPath of accessWideners) {
318
+ try {
319
+ const output = await this.deps.validateAccessWidener({
320
+ content: await readFile(awPath, "utf8"),
321
+ version: input.version,
322
+ mapping: input.mapping,
323
+ sourcePriority: input.sourcePriority
324
+ });
325
+ if (output.valid) {
326
+ validAw += 1;
327
+ }
328
+ else {
329
+ invalidAw += 1;
330
+ }
331
+ if (Array.isArray(output.warnings)) {
332
+ warnings.push(...output.warnings);
333
+ }
334
+ }
335
+ catch (error) {
336
+ invalidAw += 1;
337
+ if (error instanceof Error) {
338
+ warnings.push(error.message);
339
+ }
340
+ }
341
+ }
342
+ const invalidCount = invalidMixins + invalidAw;
343
+ const partialCount = partialMixins;
344
+ const status = invalidCount > 0 ? "invalid" : partialCount > 0 ? "partial" : "ok";
345
+ return {
346
+ ...buildEntryToolResult({
347
+ task: "project-summary",
348
+ detail,
349
+ include,
350
+ summary: {
351
+ status,
352
+ headline: `Validated ${mixinConfigs.length} mixin config(s) and ${accessWideners.length} access widener(s).`,
353
+ counts: {
354
+ valid: validMixins + validAw,
355
+ partial: partialCount,
356
+ invalid: invalidCount
357
+ }
358
+ },
359
+ blocks: {
360
+ project: {
361
+ summary: {
362
+ valid: validMixins + validAw,
363
+ partial: partialCount,
364
+ invalid: invalidCount
365
+ }
366
+ },
367
+ workspace: {
368
+ projectPath,
369
+ mixinConfigs,
370
+ accessWideners
371
+ }
372
+ },
373
+ alwaysBlocks: ["project"]
374
+ }),
375
+ warnings
376
+ };
377
+ }
378
+ }
379
+ }
380
+ }
381
+ //# sourceMappingURL=validate-project-service.js.map
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();
@@ -639,6 +656,50 @@ const sourceService = new Proxy({}, {
639
656
  return typeof value === "function" ? value.bind(service) : value;
640
657
  }
641
658
  });
659
+ const workspaceMappingService = new WorkspaceMappingService();
660
+ const inspectMinecraftService = new InspectMinecraftService({
661
+ listVersions: (input) => sourceService.listVersions(input),
662
+ resolveArtifact: (input) => sourceService.resolveArtifact(input),
663
+ findClass: (input) => Promise.resolve(sourceService.findClass(input)),
664
+ getClassSource: (input) => sourceService.getClassSource(input),
665
+ getClassMembers: (input) => sourceService.getClassMembers(input),
666
+ searchClassSource: (input) => sourceService.searchClassSource(input),
667
+ getArtifactFile: (input) => sourceService.getArtifactFile(input),
668
+ listArtifactFiles: (input) => sourceService.listArtifactFiles(input),
669
+ detectProjectMinecraftVersion: (projectPath) => workspaceMappingService.detectProjectMinecraftVersion(projectPath)
670
+ });
671
+ const analyzeSymbolService = new AnalyzeSymbolService({
672
+ checkSymbolExists: (input) => sourceService.checkSymbolExists(input),
673
+ findMapping: (input) => sourceService.findMapping(input),
674
+ resolveMethodMappingExact: (input) => sourceService.resolveMethodMappingExact(input),
675
+ traceSymbolLifecycle: (input) => sourceService.traceSymbolLifecycle(input),
676
+ resolveWorkspaceSymbol: (input) => sourceService.resolveWorkspaceSymbol(input),
677
+ getClassApiMatrix: (input) => sourceService.getClassApiMatrix(input)
678
+ });
679
+ const compareMinecraftService = new CompareMinecraftService({
680
+ compareVersions: (input) => sourceService.compareVersions(input),
681
+ diffClassSignatures: (input) => sourceService.diffClassSignatures(input),
682
+ getRegistryData: (input) => sourceService.getRegistryData(input)
683
+ });
684
+ const analyzeModService = new AnalyzeModService({
685
+ analyzeModJar: (jarPath, options) => analyzeModJar(jarPath, options),
686
+ decompileModJar: (input) => sourceService.decompileModJar(input),
687
+ getModClassSource: (input) => sourceService.getModClassSource(input),
688
+ searchModSource: (input) => sourceService.searchModSource(input),
689
+ remapModJar: (input) => remapModJar(input, config)
690
+ });
691
+ const validateProjectService = new ValidateProjectService({
692
+ validateMixin: (input) => sourceService.validateMixin(input),
693
+ validateAccessWidener: (input) => sourceService.validateAccessWidener(input),
694
+ discoverMixins: discoverWorkspaceMixins,
695
+ discoverAccessWideners: discoverWorkspaceAccessWideners
696
+ });
697
+ const manageCacheService = new ManageCacheService({
698
+ registry: createCacheRegistry({
699
+ cacheDir: config.cacheDir,
700
+ sqlitePath: config.sqlitePath
701
+ })
702
+ });
642
703
  registerResources(server, sourceService);
643
704
  let processHandlersAttached = false;
644
705
  let serverStarted = false;
@@ -1146,18 +1207,27 @@ function mapErrorToProblem(caughtError, requestId, context) {
1146
1207
  }
1147
1208
  function splitWarnings(data) {
1148
1209
  const result = { ...data };
1210
+ const warnings = [];
1149
1211
  const maybeWarnings = result.warnings;
1150
- if (!Array.isArray(maybeWarnings)) {
1151
- return {
1152
- result,
1153
- warnings: []
1154
- };
1212
+ if (Array.isArray(maybeWarnings)) {
1213
+ warnings.push(...maybeWarnings.filter((entry) => typeof entry === "string"));
1214
+ delete result.warnings;
1215
+ }
1216
+ let meta = {};
1217
+ const maybeMeta = result.meta;
1218
+ if (maybeMeta && typeof maybeMeta === "object" && !Array.isArray(maybeMeta)) {
1219
+ meta = { ...maybeMeta };
1220
+ delete result.meta;
1221
+ const metaWarnings = meta.warnings;
1222
+ if (Array.isArray(metaWarnings)) {
1223
+ warnings.push(...metaWarnings.filter((entry) => typeof entry === "string"));
1224
+ delete meta.warnings;
1225
+ }
1155
1226
  }
1156
- const warnings = maybeWarnings.filter((entry) => typeof entry === "string");
1157
- delete result.warnings;
1158
1227
  return {
1159
1228
  result,
1160
- warnings
1229
+ warnings: [...new Set(warnings)],
1230
+ meta
1161
1231
  };
1162
1232
  }
1163
1233
  async function runTool(tool, rawInput, schema, action) {
@@ -1192,10 +1262,28 @@ async function runTool(tool, rawInput, schema, action) {
1192
1262
  const payload = await (HEAVY_TOOL_NAMES.has(tool)
1193
1263
  ? heavyToolExecutionGate.run(tool, () => action(parsedInput))
1194
1264
  : action(parsedInput));
1195
- const { result, warnings } = splitWarnings(payload);
1265
+ const { result, warnings, meta: resultMeta } = splitWarnings(payload);
1266
+ const entryMeta = ENTRY_TOOL_NAMES.has(tool)
1267
+ ? buildEntryToolMeta({
1268
+ detail: normalizedInput &&
1269
+ typeof normalizedInput === "object" &&
1270
+ !Array.isArray(normalizedInput) &&
1271
+ typeof normalizedInput.detail === "string"
1272
+ ? normalizedInput.detail ?? "summary"
1273
+ : "summary",
1274
+ include: normalizedInput &&
1275
+ typeof normalizedInput === "object" &&
1276
+ !Array.isArray(normalizedInput) &&
1277
+ Array.isArray(normalizedInput.include)
1278
+ ? normalizedInput.include
1279
+ : undefined
1280
+ })
1281
+ : undefined;
1196
1282
  return objectResult({
1197
1283
  result,
1198
1284
  meta: {
1285
+ ...(entryMeta ?? {}),
1286
+ ...resultMeta,
1199
1287
  requestId,
1200
1288
  tool,
1201
1289
  durationMs: Date.now() - startedAt,
@@ -1253,6 +1341,12 @@ server.tool("list-versions", "List available Minecraft versions from Mojang mani
1253
1341
  includeSnapshots: input.includeSnapshots,
1254
1342
  limit: input.limit
1255
1343
  })));
1344
+ 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)));
1345
+ 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)));
1346
+ 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)));
1347
+ 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)));
1348
+ 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)));
1349
+ 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
1350
  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
1351
  target: input.target,
1258
1352
  mapping: input.mapping,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhisang/minecraft-modding-mcp",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "MCP server with utilities for Minecraft modding workflows",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",