@adhisang/minecraft-modding-mcp 3.2.0 → 4.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 +25 -0
- package/README.md +25 -18
- package/dist/cache-registry.d.ts +1 -1
- package/dist/cache-registry.js +10 -2
- package/dist/config.d.ts +10 -1
- package/dist/config.js +52 -1
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/analyze-symbol-service.js +13 -2
- package/dist/entry-tools/inspect-minecraft-service.d.ts +20 -20
- package/dist/entry-tools/inspect-minecraft-service.js +8 -2
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/validate-project-service.js +84 -4
- package/dist/index.js +99 -33
- package/dist/lru-list.d.ts +31 -0
- package/dist/lru-list.js +102 -0
- package/dist/mapping-pipeline-service.d.ts +10 -1
- package/dist/mapping-pipeline-service.js +13 -1
- package/dist/mapping-service.d.ts +12 -0
- package/dist/mapping-service.js +252 -10
- package/dist/mixin-validator.js +22 -7
- package/dist/observability.d.ts +18 -1
- package/dist/observability.js +44 -1
- package/dist/response-utils.d.ts +44 -10
- package/dist/response-utils.js +131 -17
- package/dist/source-resolver.d.ts +9 -1
- package/dist/source-resolver.js +14 -6
- package/dist/source-service.d.ts +97 -1
- package/dist/source-service.js +922 -113
- package/dist/storage/artifacts-repo.d.ts +4 -1
- package/dist/storage/artifacts-repo.js +33 -5
- package/dist/storage/files-repo.d.ts +0 -2
- package/dist/storage/files-repo.js +0 -11
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +10 -2
- package/dist/storage/schema.d.ts +2 -0
- package/dist/storage/schema.js +25 -0
- package/dist/types.d.ts +3 -0
- package/package.json +3 -1
|
@@ -606,6 +606,7 @@ export class InspectMinecraftService {
|
|
|
606
606
|
artifact: resolved.artifact
|
|
607
607
|
? {
|
|
608
608
|
artifactId: resolved.artifact.artifactId,
|
|
609
|
+
artifactAlias: resolved.artifact.artifactAlias,
|
|
609
610
|
origin: resolved.artifact.origin,
|
|
610
611
|
mappingApplied: resolved.artifact.mappingApplied,
|
|
611
612
|
version: resolved.artifact.version,
|
|
@@ -961,9 +962,14 @@ export class InspectMinecraftService {
|
|
|
961
962
|
}
|
|
962
963
|
},
|
|
963
964
|
members: include.includes("members") || detail !== "summary"
|
|
964
|
-
?
|
|
965
|
+
? {
|
|
966
|
+
...members.members,
|
|
967
|
+
...(members.decompiledFallback ? { decompiledFallback: members.decompiledFallback } : {}),
|
|
968
|
+
...(members.decompiledMemberCounts ? { decompiledMemberCounts: members.decompiledMemberCounts } : {})
|
|
969
|
+
}
|
|
965
970
|
: {
|
|
966
|
-
counts: members.counts
|
|
971
|
+
counts: members.counts,
|
|
972
|
+
...(members.decompiledMemberCounts ? { decompiledMemberCounts: members.decompiledMemberCounts } : {})
|
|
967
973
|
}
|
|
968
974
|
},
|
|
969
975
|
alwaysBlocks: ["subject"]
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { type CacheRegistry } from "../cache-registry.js";
|
|
3
3
|
export declare const manageCacheShape: {
|
|
4
4
|
action: z.ZodEnum<["summary", "list", "inspect", "delete", "prune", "rebuild", "verify"]>;
|
|
5
|
-
cacheKinds: z.ZodOptional<z.ZodArray<z.ZodEnum<["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap"]>, "many">>;
|
|
5
|
+
cacheKinds: z.ZodOptional<z.ZodArray<z.ZodEnum<["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap", "binary-remap"]>, "many">>;
|
|
6
6
|
selector: z.ZodOptional<z.ZodObject<{
|
|
7
7
|
artifactId: z.ZodOptional<z.ZodString>;
|
|
8
8
|
version: z.ZodOptional<z.ZodString>;
|
|
@@ -42,7 +42,7 @@ export declare const manageCacheShape: {
|
|
|
42
42
|
};
|
|
43
43
|
export declare const manageCacheSchema: z.ZodObject<{
|
|
44
44
|
action: z.ZodEnum<["summary", "list", "inspect", "delete", "prune", "rebuild", "verify"]>;
|
|
45
|
-
cacheKinds: z.ZodOptional<z.ZodArray<z.ZodEnum<["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap"]>, "many">>;
|
|
45
|
+
cacheKinds: z.ZodOptional<z.ZodArray<z.ZodEnum<["artifact-index", "downloads", "mapping", "registry", "decompiled-source", "mod-remap", "binary-remap"]>, "many">>;
|
|
46
46
|
selector: z.ZodOptional<z.ZodObject<{
|
|
47
47
|
artifactId: z.ZodOptional<z.ZodString>;
|
|
48
48
|
version: z.ZodOptional<z.ZodString>;
|
|
@@ -86,7 +86,7 @@ export declare const manageCacheSchema: z.ZodObject<{
|
|
|
86
86
|
cursor?: string | undefined;
|
|
87
87
|
detail?: "full" | "summary" | "standard" | undefined;
|
|
88
88
|
include?: string[] | undefined;
|
|
89
|
-
cacheKinds?: ("artifact-index" | "downloads" | "mapping" | "registry" | "decompiled-source" | "mod-remap")[] | undefined;
|
|
89
|
+
cacheKinds?: ("artifact-index" | "downloads" | "mapping" | "registry" | "decompiled-source" | "mod-remap" | "binary-remap")[] | undefined;
|
|
90
90
|
selector?: {
|
|
91
91
|
mapping?: string | undefined;
|
|
92
92
|
jarPath?: string | undefined;
|
|
@@ -105,7 +105,7 @@ export declare const manageCacheSchema: z.ZodObject<{
|
|
|
105
105
|
detail?: "full" | "summary" | "standard" | undefined;
|
|
106
106
|
include?: string[] | undefined;
|
|
107
107
|
executionMode?: "preview" | "apply" | undefined;
|
|
108
|
-
cacheKinds?: ("artifact-index" | "downloads" | "mapping" | "registry" | "decompiled-source" | "mod-remap")[] | undefined;
|
|
108
|
+
cacheKinds?: ("artifact-index" | "downloads" | "mapping" | "registry" | "decompiled-source" | "mod-remap" | "binary-remap")[] | undefined;
|
|
109
109
|
selector?: {
|
|
110
110
|
mapping?: string | undefined;
|
|
111
111
|
jarPath?: string | undefined;
|
|
@@ -269,7 +269,32 @@ export class ValidateProjectService {
|
|
|
269
269
|
if (input.subject.kind !== "mixin") {
|
|
270
270
|
throw createError({
|
|
271
271
|
code: ERROR_CODES.INVALID_INPUT,
|
|
272
|
-
message: "task=mixin requires subject.kind=mixin."
|
|
272
|
+
message: "task=mixin requires subject.kind=mixin.",
|
|
273
|
+
details: {
|
|
274
|
+
task: input.task,
|
|
275
|
+
subjectKind: input.subject.kind,
|
|
276
|
+
failedStage: "input-validation",
|
|
277
|
+
nextAction: "Set subject.kind to \"mixin\" for task=\"mixin\"."
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (!input.version) {
|
|
282
|
+
throw createError({
|
|
283
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
284
|
+
message: "task=mixin requires version.",
|
|
285
|
+
details: {
|
|
286
|
+
task: "mixin",
|
|
287
|
+
failedStage: "input-validation",
|
|
288
|
+
nextAction: "Pass version explicitly (e.g. \"1.21.10\"). task=\"project-summary\" supports preferProjectVersion for auto-detection from gradle.properties, but direct task=\"mixin\" requires an explicit version.",
|
|
289
|
+
suggestedCall: {
|
|
290
|
+
tool: "validate-project",
|
|
291
|
+
params: {
|
|
292
|
+
task: "mixin",
|
|
293
|
+
subject: input.subject,
|
|
294
|
+
version: "1.21.10"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
273
298
|
});
|
|
274
299
|
}
|
|
275
300
|
const output = await this.deps.validateMixin({
|
|
@@ -330,7 +355,32 @@ export class ValidateProjectService {
|
|
|
330
355
|
if (input.subject.kind !== "access-widener") {
|
|
331
356
|
throw createError({
|
|
332
357
|
code: ERROR_CODES.INVALID_INPUT,
|
|
333
|
-
message: "task=access-widener requires subject.kind=access-widener."
|
|
358
|
+
message: "task=access-widener requires subject.kind=access-widener.",
|
|
359
|
+
details: {
|
|
360
|
+
task: input.task,
|
|
361
|
+
subjectKind: input.subject.kind,
|
|
362
|
+
failedStage: "input-validation",
|
|
363
|
+
nextAction: "Set subject.kind to \"access-widener\" for task=\"access-widener\"."
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (!input.version) {
|
|
368
|
+
throw createError({
|
|
369
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
370
|
+
message: "task=access-widener requires version.",
|
|
371
|
+
details: {
|
|
372
|
+
task: "access-widener",
|
|
373
|
+
failedStage: "input-validation",
|
|
374
|
+
nextAction: "Pass version explicitly (e.g. \"1.21.10\"). Access Widener validation resolves class names against a specific Minecraft version.",
|
|
375
|
+
suggestedCall: {
|
|
376
|
+
tool: "validate-project",
|
|
377
|
+
params: {
|
|
378
|
+
task: "access-widener",
|
|
379
|
+
subject: input.subject,
|
|
380
|
+
version: "1.21.10"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
334
384
|
});
|
|
335
385
|
}
|
|
336
386
|
const content = input.subject.input.mode === "inline"
|
|
@@ -386,7 +436,32 @@ export class ValidateProjectService {
|
|
|
386
436
|
if (input.subject.kind !== "access-transformer") {
|
|
387
437
|
throw createError({
|
|
388
438
|
code: ERROR_CODES.INVALID_INPUT,
|
|
389
|
-
message: "task=access-transformer requires subject.kind=access-transformer."
|
|
439
|
+
message: "task=access-transformer requires subject.kind=access-transformer.",
|
|
440
|
+
details: {
|
|
441
|
+
task: input.task,
|
|
442
|
+
subjectKind: input.subject.kind,
|
|
443
|
+
failedStage: "input-validation",
|
|
444
|
+
nextAction: "Set subject.kind to \"access-transformer\" for task=\"access-transformer\"."
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (!input.version) {
|
|
449
|
+
throw createError({
|
|
450
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
451
|
+
message: "task=access-transformer requires version.",
|
|
452
|
+
details: {
|
|
453
|
+
task: "access-transformer",
|
|
454
|
+
failedStage: "input-validation",
|
|
455
|
+
nextAction: "Pass version explicitly (e.g. \"1.21.10\"). Access Transformer validation resolves class names against a specific Minecraft version.",
|
|
456
|
+
suggestedCall: {
|
|
457
|
+
tool: "validate-project",
|
|
458
|
+
params: {
|
|
459
|
+
task: "access-transformer",
|
|
460
|
+
subject: input.subject,
|
|
461
|
+
version: "1.21.10"
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
390
465
|
});
|
|
391
466
|
}
|
|
392
467
|
const content = input.subject.input.mode === "inline"
|
|
@@ -395,7 +470,12 @@ export class ValidateProjectService {
|
|
|
395
470
|
if (!this.deps.validateAccessTransformer) {
|
|
396
471
|
throw createError({
|
|
397
472
|
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
398
|
-
message: "Access Transformer validation is not configured."
|
|
473
|
+
message: "Access Transformer validation is not configured.",
|
|
474
|
+
details: {
|
|
475
|
+
task: "access-transformer",
|
|
476
|
+
failedStage: "dependency-resolution",
|
|
477
|
+
nextAction: "The current runtime was built without an Access Transformer validator. Rebuild the MCP server with validateAccessTransformer configured, or use task=\"access-widener\" if the workspace uses Fabric AccessWideners."
|
|
478
|
+
}
|
|
399
479
|
});
|
|
400
480
|
}
|
|
401
481
|
const output = await this.deps.validateAccessTransformer({
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ZodError, z } from "zod";
|
|
|
4
4
|
import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
|
|
5
5
|
import { objectResult } from "./mcp-helpers.js";
|
|
6
6
|
import { prepareToolInput } from "./tool-input.js";
|
|
7
|
-
import { isCompactEnabled, COMPACT_MAPPING_TOOL_NAMES, compactResponse, compactArtifactResponse, compactMappingResponse } from "./response-utils.js";
|
|
7
|
+
import { isCompactEnabled, COMPACT_MAPPING_TOOL_NAMES, COMPACT_SOURCE_TOOL_NAMES, COMPACT_MEMBERS_TOOL_NAMES, COMPACT_LIGHT_TOOL_NAMES, TOOL_PRESERVE_PAYLOAD_KEYS, compactResponse, compactArtifactResponse, compactMappingResponse, compactSourceResponse, compactMembersResponse, compactLightResponse } from "./response-utils.js";
|
|
8
8
|
import { loadConfig } from "./config.js";
|
|
9
9
|
import { createError, ERROR_CODES, isAppError } from "./errors.js";
|
|
10
10
|
import { log } from "./logger.js";
|
|
@@ -60,6 +60,19 @@ const heavyToolExecutionGate = new ToolExecutionGate({ maxConcurrent: 1, maxQueu
|
|
|
60
60
|
const nonEmptyString = z.string().trim().min(1);
|
|
61
61
|
const optionalNonEmptyString = z.string().trim().min(1).optional();
|
|
62
62
|
const optionalPositiveInt = z.number().int().positive().optional();
|
|
63
|
+
// Optional descriptor: "" and whitespace-only strings are normalized to undefined so that
|
|
64
|
+
// tools with signatureMode="name-only" can accept "caller omitted descriptor" inputs whether
|
|
65
|
+
// the caller passed an empty string or omitted the field entirely. Malformed descriptors are
|
|
66
|
+
// still rejected downstream by normalizeMethodDescriptor in mapping-service.
|
|
67
|
+
const optionalDescriptorString = z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.transform((value) => {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
75
|
+
});
|
|
63
76
|
const sourceMappingSchema = z.enum(SOURCE_MAPPINGS);
|
|
64
77
|
const mappingSourcePrioritySchema = z.enum(SOURCE_PRIORITIES);
|
|
65
78
|
const targetKindSchema = z.enum(TARGET_KINDS);
|
|
@@ -131,8 +144,9 @@ const resolveArtifactShape = {
|
|
|
131
144
|
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
132
145
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
|
|
133
146
|
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
|
|
134
|
-
compact: z.boolean().default(
|
|
135
|
-
+ "Omit provenance, artifactContents, sampleEntries, adjacentSourceCandidates, binaryJarPath, coordinate, repoUrl, resolvedSourceJarPath."
|
|
147
|
+
compact: z.boolean().default(true).describe("Return minimal fields (artifactId, origin, isDecompiled, version, requestedMapping, mappingApplied, qualityFlags). "
|
|
148
|
+
+ "Omit provenance, artifactContents, sampleEntries, adjacentSourceCandidates, binaryJarPath, coordinate, repoUrl, resolvedSourceJarPath. "
|
|
149
|
+
+ "Enabled by default; set to false for full output.")
|
|
136
150
|
};
|
|
137
151
|
const resolveArtifactSchema = z.object(resolveArtifactShape);
|
|
138
152
|
const getClassSourceShape = {
|
|
@@ -150,7 +164,8 @@ const getClassSourceShape = {
|
|
|
150
164
|
endLine: optionalPositiveInt,
|
|
151
165
|
maxLines: optionalPositiveInt,
|
|
152
166
|
maxChars: optionalPositiveInt.describe("Hard character limit on sourceText; truncates if exceeded"),
|
|
153
|
-
outputFile: optionalNonEmptyString.describe("Write source to this file path and return metadata-only response")
|
|
167
|
+
outputFile: optionalNonEmptyString.describe("Write source to this file path and return metadata-only response"),
|
|
168
|
+
compact: z.boolean().default(false).describe("When true, strip debug metadata (provenance, artifactContents, qualityFlags) and empty fields from the response. Default false.")
|
|
154
169
|
};
|
|
155
170
|
const getClassSourceSchema = z
|
|
156
171
|
.object(getClassSourceShape)
|
|
@@ -179,7 +194,8 @@ const getClassMembersShape = {
|
|
|
179
194
|
projectPath: optionalNonEmptyString,
|
|
180
195
|
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
181
196
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override version"),
|
|
182
|
-
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
|
|
197
|
+
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
|
|
198
|
+
compact: z.boolean().default(false).describe("When true, strip debug metadata (provenance, artifactContents, qualityFlags, context) and empty fields from the response. Default false.")
|
|
183
199
|
};
|
|
184
200
|
const getClassMembersSchema = z.object(getClassMembersShape);
|
|
185
201
|
const searchClassSourceShape = {
|
|
@@ -192,7 +208,10 @@ const searchClassSourceShape = {
|
|
|
192
208
|
symbolKind: searchSymbolKindSchema.optional().describe("class | interface | enum | record | method | field"),
|
|
193
209
|
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"),
|
|
194
210
|
limit: optionalPositiveInt.default(20),
|
|
195
|
-
cursor: optionalNonEmptyString
|
|
211
|
+
cursor: optionalNonEmptyString,
|
|
212
|
+
queryNamespace: sourceMappingSchema.optional().describe("Namespace of the query. When set and intent='symbol' with a fully-qualified class name, the query is translated through find-mapping before searching the artifact namespace. Ignored for text/path intents (warning surfaced)."),
|
|
213
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first. Used only when queryNamespace triggers translation."),
|
|
214
|
+
compact: z.boolean().default(false).describe("When true, strip the artifactContents summary and empty fields from the response. Default false.")
|
|
196
215
|
};
|
|
197
216
|
const searchClassSourceSchema = z.object(searchClassSourceShape).superRefine((value, ctx) => {
|
|
198
217
|
if (value.symbolKind && value.intent && value.intent !== "symbol") {
|
|
@@ -213,12 +232,13 @@ const listArtifactFilesShape = {
|
|
|
213
232
|
artifactId: nonEmptyString,
|
|
214
233
|
prefix: optionalNonEmptyString,
|
|
215
234
|
limit: optionalPositiveInt,
|
|
216
|
-
cursor: optionalNonEmptyString
|
|
235
|
+
cursor: optionalNonEmptyString,
|
|
236
|
+
compact: z.boolean().default(false).describe("When true, strip the artifactContents summary and empty fields from the response. Default false.")
|
|
217
237
|
};
|
|
218
238
|
const listArtifactFilesSchema = z.object(listArtifactFilesShape);
|
|
219
239
|
const traceSymbolLifecycleShape = {
|
|
220
240
|
symbol: nonEmptyString.describe("fully.qualified.Class.method"),
|
|
221
|
-
descriptor:
|
|
241
|
+
descriptor: optionalDescriptorString.describe('optional JVM descriptor, e.g. "(I)V". Empty strings are treated as omitted.'),
|
|
222
242
|
fromVersion: optionalNonEmptyString,
|
|
223
243
|
toVersion: optionalNonEmptyString,
|
|
224
244
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn (default obfuscated)"),
|
|
@@ -242,10 +262,12 @@ const findMappingShape = {
|
|
|
242
262
|
kind: workspaceSymbolKindSchema.describe("class | field | method"),
|
|
243
263
|
name: nonEmptyString,
|
|
244
264
|
owner: optionalNonEmptyString,
|
|
245
|
-
descriptor:
|
|
265
|
+
descriptor: optionalDescriptorString.describe("JVM descriptor. Optional when signatureMode='name-only' (default). Empty strings are treated as omitted."),
|
|
246
266
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
247
267
|
targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
248
268
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
269
|
+
signatureMode: z.enum(["exact", "name-only"]).default("name-only")
|
|
270
|
+
.describe("exact: descriptor required for kind=method; name-only (default): match by owner+name only"),
|
|
249
271
|
disambiguation: z
|
|
250
272
|
.object({
|
|
251
273
|
ownerHint: optionalNonEmptyString,
|
|
@@ -253,9 +275,10 @@ const findMappingShape = {
|
|
|
253
275
|
})
|
|
254
276
|
.partial()
|
|
255
277
|
.optional(),
|
|
256
|
-
maxCandidates: optionalPositiveInt.default(
|
|
257
|
-
compact: z.boolean().default(
|
|
258
|
-
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions."
|
|
278
|
+
maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates (default 5, max 200). Raise when you need the full candidate list."),
|
|
279
|
+
compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
280
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
|
|
281
|
+
+ "Enabled by default; set to false for full output.")
|
|
259
282
|
};
|
|
260
283
|
const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
|
|
261
284
|
if (value.kind === "class") {
|
|
@@ -306,10 +329,10 @@ const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) =>
|
|
|
306
329
|
}
|
|
307
330
|
return;
|
|
308
331
|
}
|
|
309
|
-
if (!value.descriptor) {
|
|
332
|
+
if (!value.descriptor && value.signatureMode !== "name-only") {
|
|
310
333
|
ctx.addIssue({
|
|
311
334
|
code: z.ZodIssueCode.custom,
|
|
312
|
-
message: "descriptor is required when kind=method.",
|
|
335
|
+
message: "descriptor is required when kind=method (use signatureMode='name-only' to match by name only).",
|
|
313
336
|
path: ["descriptor"]
|
|
314
337
|
});
|
|
315
338
|
}
|
|
@@ -322,9 +345,10 @@ const resolveMethodMappingExactShape = {
|
|
|
322
345
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
323
346
|
targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
324
347
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
325
|
-
maxCandidates: optionalPositiveInt.default(
|
|
326
|
-
compact: z.boolean().default(
|
|
327
|
-
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions."
|
|
348
|
+
maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates (default 5, max 200). Raise when you need the full candidate list."),
|
|
349
|
+
compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
350
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
|
|
351
|
+
+ "Enabled by default; set to false for full output.")
|
|
328
352
|
};
|
|
329
353
|
const resolveMethodMappingExactSchema = z
|
|
330
354
|
.object(resolveMethodMappingExactShape)
|
|
@@ -372,12 +396,13 @@ const resolveWorkspaceSymbolShape = {
|
|
|
372
396
|
kind: workspaceSymbolKindSchema.describe("class | field | method"),
|
|
373
397
|
name: nonEmptyString,
|
|
374
398
|
owner: optionalNonEmptyString,
|
|
375
|
-
descriptor:
|
|
399
|
+
descriptor: optionalDescriptorString.describe("JVM descriptor. Empty strings are treated as omitted."),
|
|
376
400
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
377
401
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
378
|
-
maxCandidates: optionalPositiveInt.default(
|
|
379
|
-
compact: z.boolean().default(
|
|
380
|
-
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions."
|
|
402
|
+
maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates for field/method lookups (default 5, max 200). Raise when you need the full candidate list."),
|
|
403
|
+
compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
404
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
|
|
405
|
+
+ "Enabled by default; set to false for full output.")
|
|
381
406
|
};
|
|
382
407
|
const resolveWorkspaceSymbolSchema = z
|
|
383
408
|
.object(resolveWorkspaceSymbolShape)
|
|
@@ -443,15 +468,16 @@ const checkSymbolExistsShape = {
|
|
|
443
468
|
kind: workspaceSymbolKindSchema.describe("class | field | method"),
|
|
444
469
|
owner: optionalNonEmptyString,
|
|
445
470
|
name: nonEmptyString,
|
|
446
|
-
descriptor:
|
|
471
|
+
descriptor: optionalDescriptorString.describe("required for kind=method unless signatureMode=name-only. Empty strings are treated as omitted."),
|
|
447
472
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
448
473
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
449
474
|
nameMode: classNameModeSchema.default("fqcn").describe("fqcn | auto"),
|
|
450
475
|
signatureMode: z.enum(["exact", "name-only"]).default("exact")
|
|
451
476
|
.describe("exact: require descriptor for methods; name-only: match by owner+name only"),
|
|
452
|
-
maxCandidates: optionalPositiveInt.default(
|
|
453
|
-
compact: z.boolean().default(
|
|
454
|
-
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions."
|
|
477
|
+
maxCandidates: optionalPositiveInt.default(5).describe("Limit returned candidates (default 5, max 200). Raise when you need the full candidate list."),
|
|
478
|
+
compact: z.boolean().default(true).describe("Omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
479
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions. "
|
|
480
|
+
+ "Enabled by default; set to false for full output.")
|
|
455
481
|
};
|
|
456
482
|
const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
|
|
457
483
|
if (value.kind === "class") {
|
|
@@ -886,6 +912,16 @@ function statusForErrorCode(code) {
|
|
|
886
912
|
}
|
|
887
913
|
return 500;
|
|
888
914
|
}
|
|
915
|
+
function extractFailedStageFromDetails(details) {
|
|
916
|
+
if (typeof details !== "object" || details == null) {
|
|
917
|
+
return undefined;
|
|
918
|
+
}
|
|
919
|
+
const maybeStage = details.failedStage;
|
|
920
|
+
if (typeof maybeStage === "string" && maybeStage.trim().length > 0) {
|
|
921
|
+
return maybeStage;
|
|
922
|
+
}
|
|
923
|
+
return undefined;
|
|
924
|
+
}
|
|
889
925
|
function extractFieldErrorsFromDetails(details) {
|
|
890
926
|
if (typeof details !== "object" || details == null) {
|
|
891
927
|
return undefined;
|
|
@@ -1447,11 +1483,18 @@ function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
1447
1483
|
instance: requestId,
|
|
1448
1484
|
fieldErrors: toFieldErrorsFromZod(caughtError),
|
|
1449
1485
|
hints: guidance?.hints ?? ["Check fieldErrors and submit a valid tool argument payload."],
|
|
1450
|
-
...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {})
|
|
1486
|
+
...(guidance?.suggestedCall ? { suggestedCall: guidance.suggestedCall } : {}),
|
|
1487
|
+
...(context?.tool === "validate-mixin" ? { failedStage: "input-validation" } : {})
|
|
1451
1488
|
};
|
|
1452
1489
|
}
|
|
1453
1490
|
if (isAppError(caughtError)) {
|
|
1454
1491
|
const suggestedCall = toSuggestedCall(caughtError.details);
|
|
1492
|
+
let failedStage = extractFailedStageFromDetails(caughtError.details);
|
|
1493
|
+
if (!failedStage
|
|
1494
|
+
&& context?.tool === "validate-mixin"
|
|
1495
|
+
&& caughtError.code === ERROR_CODES.INVALID_INPUT) {
|
|
1496
|
+
failedStage = "input-validation";
|
|
1497
|
+
}
|
|
1455
1498
|
return {
|
|
1456
1499
|
type: `https://minecraft-modding-mcp.dev/problems/${caughtError.code.toLowerCase()}`,
|
|
1457
1500
|
title: "Tool execution error",
|
|
@@ -1461,7 +1504,8 @@ function mapErrorToProblem(caughtError, requestId, context) {
|
|
|
1461
1504
|
instance: requestId,
|
|
1462
1505
|
fieldErrors: extractFieldErrorsFromDetails(caughtError.details),
|
|
1463
1506
|
hints: toHints(caughtError.details),
|
|
1464
|
-
...(suggestedCall ? { suggestedCall } : {})
|
|
1507
|
+
...(suggestedCall ? { suggestedCall } : {}),
|
|
1508
|
+
...(failedStage ? { failedStage } : {})
|
|
1465
1509
|
};
|
|
1466
1510
|
}
|
|
1467
1511
|
return {
|
|
@@ -1540,7 +1584,16 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
1540
1584
|
if (COMPACT_MAPPING_TOOL_NAMES.has(tool)) {
|
|
1541
1585
|
projectedResult = compactMappingResponse(projectedResult);
|
|
1542
1586
|
}
|
|
1543
|
-
|
|
1587
|
+
if (COMPACT_SOURCE_TOOL_NAMES.has(tool)) {
|
|
1588
|
+
projectedResult = compactSourceResponse(projectedResult);
|
|
1589
|
+
}
|
|
1590
|
+
if (COMPACT_MEMBERS_TOOL_NAMES.has(tool)) {
|
|
1591
|
+
projectedResult = compactMembersResponse(projectedResult);
|
|
1592
|
+
}
|
|
1593
|
+
if (COMPACT_LIGHT_TOOL_NAMES.has(tool)) {
|
|
1594
|
+
projectedResult = compactLightResponse(projectedResult);
|
|
1595
|
+
}
|
|
1596
|
+
projectedResult = compactResponse(projectedResult, TOOL_PRESERVE_PAYLOAD_KEYS[tool]);
|
|
1544
1597
|
}
|
|
1545
1598
|
const entryMeta = ENTRY_TOOL_NAMES.has(tool)
|
|
1546
1599
|
? buildEntryToolMeta({
|
|
@@ -1558,6 +1611,8 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
1558
1611
|
: undefined
|
|
1559
1612
|
})
|
|
1560
1613
|
: undefined;
|
|
1614
|
+
const durationMs = Date.now() - startedAt;
|
|
1615
|
+
sourceService.recordToolCall(tool, durationMs);
|
|
1561
1616
|
return objectResult({
|
|
1562
1617
|
result: projectedResult,
|
|
1563
1618
|
meta: {
|
|
@@ -1565,7 +1620,7 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
1565
1620
|
...resultMeta,
|
|
1566
1621
|
requestId,
|
|
1567
1622
|
tool,
|
|
1568
|
-
durationMs
|
|
1623
|
+
durationMs,
|
|
1569
1624
|
warnings
|
|
1570
1625
|
}
|
|
1571
1626
|
});
|
|
@@ -1605,12 +1660,14 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
1605
1660
|
reason: caughtError instanceof Error ? caughtError.message : String(caughtError)
|
|
1606
1661
|
});
|
|
1607
1662
|
}
|
|
1663
|
+
const errorDurationMs = Date.now() - startedAt;
|
|
1664
|
+
sourceService.recordToolCall(tool, errorDurationMs);
|
|
1608
1665
|
return objectResult({
|
|
1609
1666
|
error: problem,
|
|
1610
1667
|
meta: {
|
|
1611
1668
|
requestId,
|
|
1612
1669
|
tool,
|
|
1613
|
-
durationMs:
|
|
1670
|
+
durationMs: errorDurationMs,
|
|
1614
1671
|
warnings: []
|
|
1615
1672
|
}
|
|
1616
1673
|
}, { isError: true });
|
|
@@ -1634,7 +1691,8 @@ server.tool("resolve-artifact", "Resolve source artifact from a target object ({
|
|
|
1634
1691
|
projectPath: input.projectPath,
|
|
1635
1692
|
scope: input.scope,
|
|
1636
1693
|
preferProjectVersion: input.preferProjectVersion,
|
|
1637
|
-
strictVersion: input.strictVersion
|
|
1694
|
+
strictVersion: input.strictVersion,
|
|
1695
|
+
compact: input.compact
|
|
1638
1696
|
})));
|
|
1639
1697
|
const findClassShape = {
|
|
1640
1698
|
className: nonEmptyString.describe("Simple name (e.g. Blocks) or fully-qualified name (e.g. net.minecraft.world.level.block.Blocks)"),
|
|
@@ -1704,7 +1762,9 @@ server.tool("search-class-source", "Search indexed class source files for one ar
|
|
|
1704
1762
|
scope: scope,
|
|
1705
1763
|
queryMode: input.queryMode,
|
|
1706
1764
|
limit: input.limit,
|
|
1707
|
-
cursor: input.cursor
|
|
1765
|
+
cursor: input.cursor,
|
|
1766
|
+
queryNamespace: input.queryNamespace,
|
|
1767
|
+
sourcePriority: input.sourcePriority
|
|
1708
1768
|
});
|
|
1709
1769
|
}));
|
|
1710
1770
|
server.tool("get-artifact-file", "Get full source file content by artifactId and file path.", getArtifactFileShape, { readOnlyHint: true }, async (args) => runTool("get-artifact-file", args, getArtifactFileSchema, async (input) => sourceService.getArtifactFile({
|
|
@@ -1712,7 +1772,12 @@ server.tool("get-artifact-file", "Get full source file content by artifactId and
|
|
|
1712
1772
|
filePath: input.filePath,
|
|
1713
1773
|
maxBytes: input.maxBytes
|
|
1714
1774
|
})));
|
|
1715
|
-
server.tool("list-artifact-files", "List source file paths in an artifact with optional prefix filter and cursor-based pagination.", listArtifactFilesShape, { readOnlyHint: true }, async (args) => runTool("list-artifact-files", args, listArtifactFilesSchema, async (input) => sourceService.listArtifactFiles(
|
|
1775
|
+
server.tool("list-artifact-files", "List source file paths in an artifact with optional prefix filter and cursor-based pagination.", listArtifactFilesShape, { readOnlyHint: true }, async (args) => runTool("list-artifact-files", args, listArtifactFilesSchema, async (input) => sourceService.listArtifactFiles({
|
|
1776
|
+
artifactId: input.artifactId,
|
|
1777
|
+
prefix: input.prefix,
|
|
1778
|
+
limit: input.limit,
|
|
1779
|
+
cursor: input.cursor
|
|
1780
|
+
})));
|
|
1716
1781
|
server.tool("trace-symbol-lifecycle", "Trace which Minecraft versions contain a specific class method and report first/last seen versions.", traceSymbolLifecycleShape, { readOnlyHint: true }, async (args) => runTool("trace-symbol-lifecycle", args, traceSymbolLifecycleSchema, async (input) => sourceService.traceSymbolLifecycle({
|
|
1717
1782
|
symbol: input.symbol,
|
|
1718
1783
|
descriptor: input.descriptor,
|
|
@@ -1741,6 +1806,7 @@ server.tool("find-mapping", "Find symbol mapping candidates between namespaces u
|
|
|
1741
1806
|
sourceMapping: input.sourceMapping,
|
|
1742
1807
|
targetMapping: input.targetMapping,
|
|
1743
1808
|
sourcePriority: input.sourcePriority,
|
|
1809
|
+
signatureMode: input.signatureMode,
|
|
1744
1810
|
disambiguation: input.disambiguation,
|
|
1745
1811
|
maxCandidates: input.maxCandidates
|
|
1746
1812
|
})));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O(1) doubly-linked list + Map LRU.
|
|
3
|
+
* head = oldest, tail = newest.
|
|
4
|
+
*/
|
|
5
|
+
export declare class LruList<T> {
|
|
6
|
+
private map;
|
|
7
|
+
private head;
|
|
8
|
+
private tail;
|
|
9
|
+
get size(): number;
|
|
10
|
+
/** O(1) — move to tail (most recent), return value reference. */
|
|
11
|
+
touch(key: string): T | undefined;
|
|
12
|
+
/** O(1) — insert or update + move to tail. Returns previous value if existed. */
|
|
13
|
+
upsert(key: string, value: T): T | undefined;
|
|
14
|
+
/** O(1) — remove by key, return removed value. */
|
|
15
|
+
remove(key: string): T | undefined;
|
|
16
|
+
/** O(1) — peek at oldest (head) without removing. */
|
|
17
|
+
peekOldest(): {
|
|
18
|
+
key: string;
|
|
19
|
+
value: T;
|
|
20
|
+
} | undefined;
|
|
21
|
+
/** O(1) — clear all entries. */
|
|
22
|
+
clear(): void;
|
|
23
|
+
/** O(n) — iterate oldest → newest. */
|
|
24
|
+
toArray(): Array<{
|
|
25
|
+
key: string;
|
|
26
|
+
value: T;
|
|
27
|
+
}>;
|
|
28
|
+
private unlink;
|
|
29
|
+
private appendToTail;
|
|
30
|
+
private moveToTail;
|
|
31
|
+
}
|
package/dist/lru-list.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O(1) doubly-linked list + Map LRU.
|
|
3
|
+
* head = oldest, tail = newest.
|
|
4
|
+
*/
|
|
5
|
+
export class LruList {
|
|
6
|
+
map = new Map();
|
|
7
|
+
head = null;
|
|
8
|
+
tail = null;
|
|
9
|
+
get size() {
|
|
10
|
+
return this.map.size;
|
|
11
|
+
}
|
|
12
|
+
/** O(1) — move to tail (most recent), return value reference. */
|
|
13
|
+
touch(key) {
|
|
14
|
+
const node = this.map.get(key);
|
|
15
|
+
if (!node) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
this.moveToTail(node);
|
|
19
|
+
return node.value;
|
|
20
|
+
}
|
|
21
|
+
/** O(1) — insert or update + move to tail. Returns previous value if existed. */
|
|
22
|
+
upsert(key, value) {
|
|
23
|
+
const existing = this.map.get(key);
|
|
24
|
+
if (existing) {
|
|
25
|
+
const prev = existing.value;
|
|
26
|
+
existing.value = value;
|
|
27
|
+
this.moveToTail(existing);
|
|
28
|
+
return prev;
|
|
29
|
+
}
|
|
30
|
+
const node = { key, value, prev: null, next: null };
|
|
31
|
+
this.map.set(key, node);
|
|
32
|
+
this.appendToTail(node);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/** O(1) — remove by key, return removed value. */
|
|
36
|
+
remove(key) {
|
|
37
|
+
const node = this.map.get(key);
|
|
38
|
+
if (!node) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
this.unlink(node);
|
|
42
|
+
this.map.delete(key);
|
|
43
|
+
return node.value;
|
|
44
|
+
}
|
|
45
|
+
/** O(1) — peek at oldest (head) without removing. */
|
|
46
|
+
peekOldest() {
|
|
47
|
+
if (!this.head) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return { key: this.head.key, value: this.head.value };
|
|
51
|
+
}
|
|
52
|
+
/** O(1) — clear all entries. */
|
|
53
|
+
clear() {
|
|
54
|
+
this.map.clear();
|
|
55
|
+
this.head = null;
|
|
56
|
+
this.tail = null;
|
|
57
|
+
}
|
|
58
|
+
/** O(n) — iterate oldest → newest. */
|
|
59
|
+
toArray() {
|
|
60
|
+
const result = [];
|
|
61
|
+
let current = this.head;
|
|
62
|
+
while (current) {
|
|
63
|
+
result.push({ key: current.key, value: current.value });
|
|
64
|
+
current = current.next;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
unlink(node) {
|
|
69
|
+
if (node.prev) {
|
|
70
|
+
node.prev.next = node.next;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.head = node.next;
|
|
74
|
+
}
|
|
75
|
+
if (node.next) {
|
|
76
|
+
node.next.prev = node.prev;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.tail = node.prev;
|
|
80
|
+
}
|
|
81
|
+
node.prev = null;
|
|
82
|
+
node.next = null;
|
|
83
|
+
}
|
|
84
|
+
appendToTail(node) {
|
|
85
|
+
if (!this.tail) {
|
|
86
|
+
this.head = node;
|
|
87
|
+
this.tail = node;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
node.prev = this.tail;
|
|
91
|
+
this.tail.next = node;
|
|
92
|
+
this.tail = node;
|
|
93
|
+
}
|
|
94
|
+
moveToTail(node) {
|
|
95
|
+
if (node === this.tail) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.unlink(node);
|
|
99
|
+
this.appendToTail(node);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=lru-list.js.map
|
|
@@ -4,6 +4,14 @@ export interface MappingPipelineInput {
|
|
|
4
4
|
target: SourceTargetInput;
|
|
5
5
|
resolved: ResolvedSourceArtifact;
|
|
6
6
|
runtimeNamesUnobfuscated?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* When true and the mojang request lands on a binary-only artifact, the pipeline
|
|
9
|
+
* authorizes a downstream tiny-remap (obfuscated -> mojang) followed by decompile
|
|
10
|
+
* instead of throwing MAPPING_NOT_APPLIED. The caller is responsible for
|
|
11
|
+
* pre-resolving tiny-remapper jar, mojang tiny mappings, and verifying mapping
|
|
12
|
+
* health before setting this flag.
|
|
13
|
+
*/
|
|
14
|
+
allowBinaryRemap?: boolean;
|
|
7
15
|
}
|
|
8
16
|
export interface MappingPipelineResult {
|
|
9
17
|
mappingApplied: SourceMapping;
|
|
@@ -15,6 +23,7 @@ export interface MappingPipelineResult {
|
|
|
15
23
|
* Current implementation enforces explicit guarantees:
|
|
16
24
|
* - obfuscated: always pass-through
|
|
17
25
|
* - mojang: requires source-backed artifacts on legacy obfuscated versions,
|
|
18
|
-
* but unobfuscated runtime jars can pass through directly
|
|
26
|
+
* but unobfuscated runtime jars can pass through directly,
|
|
27
|
+
* or binary-only artifacts may be remapped + decompiled when allowBinaryRemap=true
|
|
19
28
|
*/
|
|
20
29
|
export declare function applyMappingPipeline(input: MappingPipelineInput): MappingPipelineResult;
|