@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.
- package/CHANGELOG.md +18 -0
- package/README.md +33 -4
- package/dist/cache-registry.d.ts +95 -0
- package/dist/cache-registry.js +541 -0
- package/dist/entry-tools/analyze-mod-service.d.ts +207 -0
- package/dist/entry-tools/analyze-mod-service.js +253 -0
- package/dist/entry-tools/analyze-symbol-service.d.ts +209 -0
- package/dist/entry-tools/analyze-symbol-service.js +304 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +210 -0
- package/dist/entry-tools/compare-minecraft-service.js +397 -0
- package/dist/entry-tools/entry-tool-schema.d.ts +6 -0
- package/dist/entry-tools/entry-tool-schema.js +10 -0
- package/dist/entry-tools/inspect-minecraft-service.d.ts +1953 -0
- package/dist/entry-tools/inspect-minecraft-service.js +876 -0
- package/dist/entry-tools/manage-cache-service.d.ts +130 -0
- package/dist/entry-tools/manage-cache-service.js +229 -0
- package/dist/entry-tools/request-normalizers.d.ts +10 -0
- package/dist/entry-tools/request-normalizers.js +36 -0
- package/dist/entry-tools/response-contract.d.ts +44 -0
- package/dist/entry-tools/response-contract.js +96 -0
- package/dist/entry-tools/validate-project-service.d.ts +543 -0
- package/dist/entry-tools/validate-project-service.js +381 -0
- package/dist/index.js +103 -9
- package/package.json +1 -1
|
@@ -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 (
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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,
|