@adhisang/minecraft-modding-mcp 7.0.0-rc.0 → 7.0.0-rc.2
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 +131 -49
- package/README.md +2 -0
- package/dist/cache-registry.d.ts +10 -0
- package/dist/cache-registry.js +44 -3
- package/dist/entry-tools/batch-class-members-service.js +8 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +21 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +4 -0
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/validate-project/cases/mixin.js +26 -6
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -1
- package/dist/error-mapping.d.ts +118 -0
- package/dist/error-mapping.js +177 -7
- package/dist/index.js +3 -1
- package/dist/json-rpc-framing.d.ts +53 -1
- package/dist/json-rpc-framing.js +190 -25
- package/dist/mapping/loaders/tiny-loom-selection.js +10 -3
- package/dist/mapping/parsers/tiny.js +4 -0
- package/dist/mapping-service.js +24 -3
- package/dist/maven-resolver.d.ts +61 -0
- package/dist/maven-resolver.js +95 -4
- package/dist/maven-token.d.ts +65 -0
- package/dist/maven-token.js +120 -0
- package/dist/mcp-helpers.js +14 -3
- package/dist/minecraft-explorer-service.d.ts +13 -1
- package/dist/minecraft-explorer-service.js +200 -7
- package/dist/repo-downloader.d.ts +260 -0
- package/dist/repo-downloader.js +1083 -14
- package/dist/source/artifact-resolver.d.ts +97 -3
- package/dist/source/artifact-resolver.js +228 -24
- package/dist/source/class-source.d.ts +15 -0
- package/dist/source/class-source.js +163 -18
- package/dist/source/indexer.js +14 -0
- package/dist/source/nested-jars.d.ts +16 -1
- package/dist/source/nested-jars.js +66 -3
- package/dist/source/workspace-target.js +39 -20
- package/dist/source-jar-reader.d.ts +30 -2
- package/dist/source-jar-reader.js +39 -8
- package/dist/source-resolver.d.ts +48 -0
- package/dist/source-resolver.js +735 -81
- package/dist/source-service.d.ts +10 -1
- package/dist/stdio-supervisor.d.ts +75 -1
- package/dist/stdio-supervisor.js +240 -14
- package/dist/storage/artifacts-repo.d.ts +2 -0
- package/dist/storage/artifacts-repo.js +22 -0
- package/dist/storage/db.js +32 -10
- package/dist/tool-guidance.js +17 -7
- package/dist/tool-schemas.d.ts +10 -2
- package/dist/tool-schemas.js +4 -0
- package/dist/v1-parity-schemas.js +16 -0
- package/dist/version-diff-service.js +25 -4
- package/dist/version-service.js +23 -8
- package/dist/warning-details.js +3 -1
- package/dist/workspace-mapping-service.d.ts +8 -1
- package/dist/workspace-mapping-service.js +28 -19
- package/docs/tool-reference.md +31 -7
- package/package.json +6 -4
|
@@ -26,9 +26,16 @@ const MAX_INDEX_ENTRY_BUDGET = 64_000_000;
|
|
|
26
26
|
* overrides it outright.
|
|
27
27
|
*/
|
|
28
28
|
export function resolveTinyIndexEntryBudget(value = process.env.MCP_LOOM_TINY_MAX_INDEX_ENTRIES, heapStats = v8.getHeapStatistics()) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Strict ASCII digits and safe integers only, mirroring `loadMaxFrameBytes` and
|
|
30
|
+
// `loadMaxDownloadBytes`. Number.parseInt stops at the first non-digit, so "1e9",
|
|
31
|
+
// "2_000_000", "10M" and "1.9" used to be accepted as 1, 2, 10 and 1 — a budget
|
|
32
|
+
// that truncates every real Loom load. No floor is applied: an explicit override
|
|
33
|
+
// is allowed to be small on purpose.
|
|
34
|
+
if (/^[0-9]+$/.test(value ?? "")) {
|
|
35
|
+
const override = Number(value);
|
|
36
|
+
if (Number.isSafeInteger(override) && override > 0) {
|
|
37
|
+
return override;
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
const free = Math.max(0, heapStats.heap_size_limit - heapStats.used_heap_size);
|
|
34
41
|
const derived = Math.floor((free * HEAP_BUDGET_FRACTION) / HEAP_BYTES_PER_INDEX_ENTRY);
|
|
@@ -128,6 +128,10 @@ export function parseTinyMappingsInto(target, text, options) {
|
|
|
128
128
|
for (const namespace of header.columns) {
|
|
129
129
|
const value = columns[namespace.columnIndex]?.trim() ?? "";
|
|
130
130
|
if (!value) {
|
|
131
|
+
// A class row that omits this namespace ends the previous class's name
|
|
132
|
+
// for it. Leaving the stale entry made the following field/method rows
|
|
133
|
+
// register under the PREVIOUS class as owner, at confidence 1.
|
|
134
|
+
currentClassNames.delete(namespace.mapping);
|
|
131
135
|
continue;
|
|
132
136
|
}
|
|
133
137
|
currentClassNames.set(namespace.mapping, value);
|
package/dist/mapping-service.js
CHANGED
|
@@ -145,6 +145,10 @@ export class MappingService {
|
|
|
145
145
|
candidates: [],
|
|
146
146
|
candidateCount: 0,
|
|
147
147
|
warnings: [
|
|
148
|
+
// A truncated Loom index (or a source that yielded nothing) is often WHY the
|
|
149
|
+
// path is missing, so the loader's own warnings lead the no-path sentence
|
|
150
|
+
// instead of being replaced by it.
|
|
151
|
+
...graph.warnings,
|
|
148
152
|
`No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
|
|
149
153
|
]
|
|
150
154
|
};
|
|
@@ -164,7 +168,10 @@ export class MappingService {
|
|
|
164
168
|
.map((candidate) => queryRecord.kind === "method" && queryRecord.descriptor
|
|
165
169
|
? projectLookupCandidateDescriptor(candidate, queryRecord.descriptor, projectedDescriptor)
|
|
166
170
|
: candidate);
|
|
167
|
-
|
|
171
|
+
// Seeded from the graph so loader-level warnings (a truncated Loom index, a tiny
|
|
172
|
+
// source that yielded nothing) reach the caller. ensureMappingAvailable,
|
|
173
|
+
// getClassApiMatrix and checkSymbolExists already do this.
|
|
174
|
+
const warnings = [...graph.warnings];
|
|
168
175
|
// signatureMode="exact" on kind=method must not return descriptorless fallback candidates
|
|
169
176
|
// (lookupCandidates adds owner+name fallbacks by design for the loose path). Without this
|
|
170
177
|
// filter a caller who supplied `foo(I)V` could be told `foo(Z)V` is the exact mapping,
|
|
@@ -374,11 +381,18 @@ export class MappingService {
|
|
|
374
381
|
candidates: [],
|
|
375
382
|
candidateCount: 0,
|
|
376
383
|
warnings: [
|
|
384
|
+
// A truncated Loom index (or a source that yielded nothing) is often WHY the
|
|
385
|
+
// path is missing, so the loader's own warnings lead the no-path sentence
|
|
386
|
+
// instead of being replaced by it.
|
|
387
|
+
...graph.warnings,
|
|
377
388
|
`No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
|
|
378
389
|
]
|
|
379
390
|
};
|
|
380
391
|
}
|
|
381
|
-
|
|
392
|
+
// Seeded from the graph so loader-level warnings (a truncated Loom index, a tiny
|
|
393
|
+
// source that yielded nothing) reach the caller. ensureMappingAvailable,
|
|
394
|
+
// getClassApiMatrix and checkSymbolExists already do this.
|
|
395
|
+
const warnings = [...graph.warnings];
|
|
382
396
|
const descriptorProjection = this.projectMethodDescriptorToTarget(graph, path, descriptor);
|
|
383
397
|
const projectedDescriptor = descriptorProjection.complete ? descriptorProjection.descriptor : undefined;
|
|
384
398
|
const rawCandidates = this
|
|
@@ -475,7 +489,14 @@ export class MappingService {
|
|
|
475
489
|
`${method}${descriptor}. ${rejectedByOwner} candidate(s) with that name and descriptor ` +
|
|
476
490
|
`are declared by other classes (${targetMapping}: ${ownerList}). An inherited or relocated ` +
|
|
477
491
|
`member cannot resolve here — use find-mapping for an owner-agnostic lookup, or query the ` +
|
|
478
|
-
`declaring class directly.`
|
|
492
|
+
`declaring class directly.` +
|
|
493
|
+
// An owner with no package never projects along the mapping path (simple-name
|
|
494
|
+
// class matches are deliberately excluded), so it is rejected for its shape
|
|
495
|
+
// rather than for inheritance. Saying so stops the message misdirecting.
|
|
496
|
+
(owner.includes(".")
|
|
497
|
+
? ""
|
|
498
|
+
: ` "${owner}" is also not fully qualified: an unqualified owner cannot be projected ` +
|
|
499
|
+
`into ${targetMapping} and can never match, so supply the fully-qualified owner.`));
|
|
479
500
|
}
|
|
480
501
|
if (descriptorProjection.hadClassReferences && !descriptorProjection.complete) {
|
|
481
502
|
warnings.push(pathUsesSource(graph.pairs, path, "mojang-client-mappings")
|
package/dist/maven-resolver.d.ts
CHANGED
|
@@ -11,7 +11,68 @@ export interface MavenCandidate {
|
|
|
11
11
|
sourceUrl?: string;
|
|
12
12
|
binaryUrl?: string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Parses `group:artifact:version[:classifier]` - and is the single place a
|
|
16
|
+
* caller-supplied coordinate is checked before any of it becomes a path.
|
|
17
|
+
*
|
|
18
|
+
* Every segment ends up as a directory or file-name component downstream (the
|
|
19
|
+
* local `~/.m2` layout, the Gradle cache layout, and a remote repository URL),
|
|
20
|
+
* so a segment carrying `/` or `..` would walk out of the repository root it
|
|
21
|
+
* was resolved against. Validating here rather than at each sink means the nine
|
|
22
|
+
* consumers of this function inherit the rule and cannot drift apart - which is
|
|
23
|
+
* exactly how two different group-to-path builders came to exist.
|
|
24
|
+
*
|
|
25
|
+
* The rule is `isSafeMavenSegment`, the same one the `dependency` route now
|
|
26
|
+
* applies to its `group`, `name` and `version` before it probes anything. That
|
|
27
|
+
* route synthesises a coordinate and lands on these same sinks, so the two
|
|
28
|
+
* cannot be allowed to answer differently. Its `..` clause rejects a groupId
|
|
29
|
+
* like `a..b`; that input is contained under both builders and so is not a
|
|
30
|
+
* safety matter, but a doubled dot yields an empty Maven path segment and is
|
|
31
|
+
* not publishable, and rejecting it keeps the two routes answering identically.
|
|
32
|
+
*
|
|
33
|
+
* `version` and `classifier` additionally admit U+0020, because
|
|
34
|
+
* `net.fabricmc:yarn:1.14 Pre-Release 1+build.10:v2` is a published coordinate
|
|
35
|
+
* (see `SAFE_MAVEN_VALUE_RE`); `groupId` and `artifactId` do not.
|
|
36
|
+
*
|
|
37
|
+
* Each segment is trimmed before it is checked, so `"g : a : 1.0"` names the
|
|
38
|
+
* artifact it obviously means instead of failing on the padding, and a leading
|
|
39
|
+
* or trailing space cannot survive into a `version` either. `coordinate` in the
|
|
40
|
+
* error details echoes the argument this function was handed - which is the
|
|
41
|
+
* caller's own text only when the caller reached it directly; the public
|
|
42
|
+
* artifact route trims `target.value` before calling, so outer padding on a
|
|
43
|
+
* `target.kind="coordinate"` request is gone by the time it gets here.
|
|
44
|
+
*/
|
|
14
45
|
export declare function parseCoordinate(coordinate: string): MavenCoordinate;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the artifacts behind a coordinate may change under a stable name.
|
|
48
|
+
*
|
|
49
|
+
* Maven defines exactly one mutable form: a version ending in `-SNAPSHOT`,
|
|
50
|
+
* which a repository is free to republish for the same coordinate. A
|
|
51
|
+
* timestamped unique snapshot (`1.0.0-20240101.120000-3`) is the resolved,
|
|
52
|
+
* concrete build and never changes, so it is immutable - as is every release
|
|
53
|
+
* version. Callers use this to pick a cache freshness policy: mutable
|
|
54
|
+
* coordinates must be revalidated, immutable ones can be served from cache
|
|
55
|
+
* forever.
|
|
56
|
+
*
|
|
57
|
+
* The suffix is matched case-insensitively, unlike Maven's own case-sensitive
|
|
58
|
+
* `ArtifactUtils.isSnapshot`. The two error directions are not symmetric: being
|
|
59
|
+
* stricter than a repository that publishes `-snapshot` would serve a mutable
|
|
60
|
+
* artifact from cache forever, while being looser costs one conditional request
|
|
61
|
+
* per resolve on a version that almost certainly meant `-SNAPSHOT` anyway.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isMutableMavenCoordinate(coordinate: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The one group-to-path conversion. Exported so no caller writes its own.
|
|
66
|
+
*
|
|
67
|
+
* `split(".").filter(Boolean).join("/")` is not the same as
|
|
68
|
+
* `replace(/\./g, "/")`: dropping the empty segments means a groupId of `.`,
|
|
69
|
+
* `..` or `.a` yields `""`, `""` and `"a"` rather than `"/"`, `"//"` and
|
|
70
|
+
* `"/a"`. A leading slash is an absolute path, so under `path.resolve` the
|
|
71
|
+
* naive form escapes the repository root entirely - no `/` and no `..` needed
|
|
72
|
+
* in the input. This form cannot produce one, which makes the group leg safe by
|
|
73
|
+
* construction rather than by whatever filter runs upstream of it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function groupToPath(groupId: string): string;
|
|
15
76
|
export declare function normalizedCoordinateValue(coordinate: string): string;
|
|
16
77
|
export declare function resolveLocalM2Candidate(localM2Path: string, coordinateValue: string): MavenCandidate;
|
|
17
78
|
export declare function localArtifactPathsFromCoordinate(localM2Path: string, coordinate: string): MavenCandidate;
|
package/dist/maven-resolver.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import { accessSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ERROR_CODES, createError } from "./errors.js";
|
|
4
|
+
import { describeSafeMavenSegmentRule, isSafeMavenSegment } from "./maven-token.js";
|
|
5
|
+
/**
|
|
6
|
+
* Parses `group:artifact:version[:classifier]` - and is the single place a
|
|
7
|
+
* caller-supplied coordinate is checked before any of it becomes a path.
|
|
8
|
+
*
|
|
9
|
+
* Every segment ends up as a directory or file-name component downstream (the
|
|
10
|
+
* local `~/.m2` layout, the Gradle cache layout, and a remote repository URL),
|
|
11
|
+
* so a segment carrying `/` or `..` would walk out of the repository root it
|
|
12
|
+
* was resolved against. Validating here rather than at each sink means the nine
|
|
13
|
+
* consumers of this function inherit the rule and cannot drift apart - which is
|
|
14
|
+
* exactly how two different group-to-path builders came to exist.
|
|
15
|
+
*
|
|
16
|
+
* The rule is `isSafeMavenSegment`, the same one the `dependency` route now
|
|
17
|
+
* applies to its `group`, `name` and `version` before it probes anything. That
|
|
18
|
+
* route synthesises a coordinate and lands on these same sinks, so the two
|
|
19
|
+
* cannot be allowed to answer differently. Its `..` clause rejects a groupId
|
|
20
|
+
* like `a..b`; that input is contained under both builders and so is not a
|
|
21
|
+
* safety matter, but a doubled dot yields an empty Maven path segment and is
|
|
22
|
+
* not publishable, and rejecting it keeps the two routes answering identically.
|
|
23
|
+
*
|
|
24
|
+
* `version` and `classifier` additionally admit U+0020, because
|
|
25
|
+
* `net.fabricmc:yarn:1.14 Pre-Release 1+build.10:v2` is a published coordinate
|
|
26
|
+
* (see `SAFE_MAVEN_VALUE_RE`); `groupId` and `artifactId` do not.
|
|
27
|
+
*
|
|
28
|
+
* Each segment is trimmed before it is checked, so `"g : a : 1.0"` names the
|
|
29
|
+
* artifact it obviously means instead of failing on the padding, and a leading
|
|
30
|
+
* or trailing space cannot survive into a `version` either. `coordinate` in the
|
|
31
|
+
* error details echoes the argument this function was handed - which is the
|
|
32
|
+
* caller's own text only when the caller reached it directly; the public
|
|
33
|
+
* artifact route trims `target.value` before calling, so outer padding on a
|
|
34
|
+
* `target.kind="coordinate"` request is gone by the time it gets here.
|
|
35
|
+
*/
|
|
4
36
|
export function parseCoordinate(coordinate) {
|
|
5
37
|
const values = coordinate.trim().split(":");
|
|
6
38
|
if (values.length !== 3 && values.length !== 4) {
|
|
@@ -10,22 +42,81 @@ export function parseCoordinate(coordinate) {
|
|
|
10
42
|
details: { coordinate }
|
|
11
43
|
});
|
|
12
44
|
}
|
|
13
|
-
const
|
|
45
|
+
const groupId = values[0]?.trim() ?? "";
|
|
46
|
+
const artifactId = values[1]?.trim() ?? "";
|
|
47
|
+
const version = values[2]?.trim() ?? "";
|
|
48
|
+
const classifier = values[3]?.trim() || undefined;
|
|
14
49
|
if (!groupId || !artifactId || !version) {
|
|
50
|
+
// Names the empty segment too. Every other rejection below carries
|
|
51
|
+
// `details.component`, and a caller branching on it should not have to
|
|
52
|
+
// special-case the one branch that dropped it.
|
|
53
|
+
const emptyComponent = !groupId
|
|
54
|
+
? "groupId"
|
|
55
|
+
: !artifactId
|
|
56
|
+
? "artifactId"
|
|
57
|
+
: "version";
|
|
15
58
|
throw createError({
|
|
16
59
|
code: ERROR_CODES.COORDINATE_PARSE_FAILED,
|
|
17
60
|
message: `Invalid maven coordinate "${coordinate}". All fields must be non-empty.`,
|
|
18
|
-
details: { coordinate }
|
|
61
|
+
details: { coordinate, component: emptyComponent }
|
|
19
62
|
});
|
|
20
63
|
}
|
|
64
|
+
const segments = [
|
|
65
|
+
["groupId", groupId],
|
|
66
|
+
["artifactId", artifactId],
|
|
67
|
+
["version", version],
|
|
68
|
+
// An absent classifier is not a segment; an empty or whitespace-only one
|
|
69
|
+
// has already normalised to absent above.
|
|
70
|
+
...(classifier ? [["classifier", classifier]] : [])
|
|
71
|
+
];
|
|
72
|
+
for (const [component, value] of segments) {
|
|
73
|
+
if (!isSafeMavenSegment(value, component)) {
|
|
74
|
+
throw createError({
|
|
75
|
+
code: ERROR_CODES.COORDINATE_PARSE_FAILED,
|
|
76
|
+
message: `Invalid maven coordinate "${coordinate}". ${describeSafeMavenSegmentRule(component)}`,
|
|
77
|
+
details: { coordinate, component }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
21
81
|
return {
|
|
22
82
|
groupId,
|
|
23
83
|
artifactId,
|
|
24
84
|
version,
|
|
25
|
-
classifier
|
|
85
|
+
classifier
|
|
26
86
|
};
|
|
27
87
|
}
|
|
28
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Whether the artifacts behind a coordinate may change under a stable name.
|
|
90
|
+
*
|
|
91
|
+
* Maven defines exactly one mutable form: a version ending in `-SNAPSHOT`,
|
|
92
|
+
* which a repository is free to republish for the same coordinate. A
|
|
93
|
+
* timestamped unique snapshot (`1.0.0-20240101.120000-3`) is the resolved,
|
|
94
|
+
* concrete build and never changes, so it is immutable - as is every release
|
|
95
|
+
* version. Callers use this to pick a cache freshness policy: mutable
|
|
96
|
+
* coordinates must be revalidated, immutable ones can be served from cache
|
|
97
|
+
* forever.
|
|
98
|
+
*
|
|
99
|
+
* The suffix is matched case-insensitively, unlike Maven's own case-sensitive
|
|
100
|
+
* `ArtifactUtils.isSnapshot`. The two error directions are not symmetric: being
|
|
101
|
+
* stricter than a repository that publishes `-snapshot` would serve a mutable
|
|
102
|
+
* artifact from cache forever, while being looser costs one conditional request
|
|
103
|
+
* per resolve on a version that almost certainly meant `-SNAPSHOT` anyway.
|
|
104
|
+
*/
|
|
105
|
+
export function isMutableMavenCoordinate(coordinate) {
|
|
106
|
+
return /-SNAPSHOT$/i.test(parseCoordinate(coordinate).version);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The one group-to-path conversion. Exported so no caller writes its own.
|
|
110
|
+
*
|
|
111
|
+
* `split(".").filter(Boolean).join("/")` is not the same as
|
|
112
|
+
* `replace(/\./g, "/")`: dropping the empty segments means a groupId of `.`,
|
|
113
|
+
* `..` or `.a` yields `""`, `""` and `"a"` rather than `"/"`, `"//"` and
|
|
114
|
+
* `"/a"`. A leading slash is an absolute path, so under `path.resolve` the
|
|
115
|
+
* naive form escapes the repository root entirely - no `/` and no `..` needed
|
|
116
|
+
* in the input. This form cannot produce one, which makes the group leg safe by
|
|
117
|
+
* construction rather than by whatever filter runs upstream of it.
|
|
118
|
+
*/
|
|
119
|
+
export function groupToPath(groupId) {
|
|
29
120
|
return groupId.split(".").filter(Boolean).join("/");
|
|
30
121
|
}
|
|
31
122
|
export function normalizedCoordinateValue(coordinate) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one definition of "safe Maven coordinate segment".
|
|
3
|
+
*
|
|
4
|
+
* Every route that turns caller-supplied text into a filesystem path component
|
|
5
|
+
* - the `coordinate` target parsed by `parseCoordinate`, and the `dependency`
|
|
6
|
+
* target that synthesises a coordinate from `group`/`name`/`version` - has to
|
|
7
|
+
* agree on what a segment may contain, because they share the same downstream
|
|
8
|
+
* sinks. Two copies of this rule would be two chances to disagree.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately a leaf module: it imports nothing. `maven-resolver.ts` is the
|
|
11
|
+
* chokepoint that enforces the rule and pulls in only `node:fs`, `node:path`
|
|
12
|
+
* and `./errors.js`; importing it from `workspace-mapping-service.ts`, where
|
|
13
|
+
* the rule used to live, would drag fast-glob and the whole workspace-detection
|
|
14
|
+
* surface into the resolver's import graph.
|
|
15
|
+
*
|
|
16
|
+
* The name records where the rule was first enforced (a dependency target's
|
|
17
|
+
* `version`). The grammar is now shared by all four segments of a coordinate,
|
|
18
|
+
* with one deliberate split between them - see `SAFE_MAVEN_VALUE_RE` - and one
|
|
19
|
+
* deliberately narrower variant for tokens read off disk rather than supplied
|
|
20
|
+
* by a caller - see `isSafeMavenVersionToken`.
|
|
21
|
+
*/
|
|
22
|
+
/** The four components of `group:artifact:version[:classifier]`. */
|
|
23
|
+
export type MavenSegmentComponent = "groupId" | "artifactId" | "version" | "classifier";
|
|
24
|
+
/**
|
|
25
|
+
* Characters an IDENTIFIER segment (`groupId`, `artifactId`) may contain.
|
|
26
|
+
*
|
|
27
|
+
* Everything else is out, path separators and NUL and control characters
|
|
28
|
+
* included - a segment becomes a directory or file name verbatim.
|
|
29
|
+
*/
|
|
30
|
+
export declare const SAFE_MAVEN_TOKEN_RE: RegExp;
|
|
31
|
+
/**
|
|
32
|
+
* The shared rule for a segment of a CALLER-SUPPLIED coordinate, per component.
|
|
33
|
+
*
|
|
34
|
+
* Both routes go through this and only this: `parseCoordinate` for a
|
|
35
|
+
* `coordinate` target, and `synthesizeDependencyTarget` for the `group`, `name`
|
|
36
|
+
* and explicit `version` of a `dependency` target. They cannot drift apart,
|
|
37
|
+
* because there is no second definition for them to drift towards.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isSafeMavenSegment(value: string, component: MavenSegmentComponent): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The identifier grammar on its own, for a version token DISCOVERED in the
|
|
42
|
+
* workspace rather than stated by a caller: a `gradle.properties` value, a
|
|
43
|
+
* `modules-2` directory entry, an umbrella POM's `<version>`.
|
|
44
|
+
*
|
|
45
|
+
* It deliberately does NOT take the space branch, so this is narrower than
|
|
46
|
+
* `isSafeMavenSegment(value, "version")`. The two inputs are not the same kind
|
|
47
|
+
* of thing. A caller naming `1.14 Pre-Release 1+build.10` has stated which
|
|
48
|
+
* artifact they want, and refusing it loses them a real one. A scan of
|
|
49
|
+
* `~/.gradle/caches/modules-2/.../<name>/` is guessing which of somebody else's
|
|
50
|
+
* directories is a version, and every name it admits becomes a candidate that
|
|
51
|
+
* can make the resolution ambiguous or supply a version the project never
|
|
52
|
+
* declared - so the narrower rule is the right default where nothing is lost by
|
|
53
|
+
* it. Widening it is a separate decision with its own evidence, not a
|
|
54
|
+
* consequence of this one.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isSafeMavenVersionToken(token: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The rule in words, for an error message that must not drift from the code.
|
|
59
|
+
*
|
|
60
|
+
* `label` names the field that failed as the CALLER spells it, so a caller
|
|
61
|
+
* reading the message can tell which of the four segments to fix; it defaults
|
|
62
|
+
* to the component name, and the `dependency` route passes its own field names
|
|
63
|
+
* (`group`, `name`) instead.
|
|
64
|
+
*/
|
|
65
|
+
export declare function describeSafeMavenSegmentRule(component: MavenSegmentComponent, label?: string): string;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one definition of "safe Maven coordinate segment".
|
|
3
|
+
*
|
|
4
|
+
* Every route that turns caller-supplied text into a filesystem path component
|
|
5
|
+
* - the `coordinate` target parsed by `parseCoordinate`, and the `dependency`
|
|
6
|
+
* target that synthesises a coordinate from `group`/`name`/`version` - has to
|
|
7
|
+
* agree on what a segment may contain, because they share the same downstream
|
|
8
|
+
* sinks. Two copies of this rule would be two chances to disagree.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately a leaf module: it imports nothing. `maven-resolver.ts` is the
|
|
11
|
+
* chokepoint that enforces the rule and pulls in only `node:fs`, `node:path`
|
|
12
|
+
* and `./errors.js`; importing it from `workspace-mapping-service.ts`, where
|
|
13
|
+
* the rule used to live, would drag fast-glob and the whole workspace-detection
|
|
14
|
+
* surface into the resolver's import graph.
|
|
15
|
+
*
|
|
16
|
+
* The name records where the rule was first enforced (a dependency target's
|
|
17
|
+
* `version`). The grammar is now shared by all four segments of a coordinate,
|
|
18
|
+
* with one deliberate split between them - see `SAFE_MAVEN_VALUE_RE` - and one
|
|
19
|
+
* deliberately narrower variant for tokens read off disk rather than supplied
|
|
20
|
+
* by a caller - see `isSafeMavenVersionToken`.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Characters an IDENTIFIER segment (`groupId`, `artifactId`) may contain.
|
|
24
|
+
*
|
|
25
|
+
* Everything else is out, path separators and NUL and control characters
|
|
26
|
+
* included - a segment becomes a directory or file name verbatim.
|
|
27
|
+
*/
|
|
28
|
+
export const SAFE_MAVEN_TOKEN_RE = /^[A-Za-z0-9._+-]+$/;
|
|
29
|
+
/**
|
|
30
|
+
* The same set plus U+0020, for a VALUE segment (`version`, `classifier`).
|
|
31
|
+
*
|
|
32
|
+
* Fabric publishes Yarn against Minecraft's own pre-release ids, and those ids
|
|
33
|
+
* contain spaces: `net.fabricmc:yarn:1.14 Pre-Release 1+build.10:v2` is a real,
|
|
34
|
+
* published coordinate. A rule without this branch refuses to resolve it - a
|
|
35
|
+
* false rejection of a genuine artifact, which is the worse of the two failure
|
|
36
|
+
* directions here, because a space is not a path separator and cannot traverse
|
|
37
|
+
* a directory. Permitting it costs nothing in containment: the segment still
|
|
38
|
+
* becomes exactly one path component, and every other escape character stays
|
|
39
|
+
* out.
|
|
40
|
+
*
|
|
41
|
+
* `groupId` and `artifactId` stay on the identifier rule. Maven identifiers
|
|
42
|
+
* never contain spaces and no real ecosystem coordinate uses one there, so
|
|
43
|
+
* widening them would buy no artifact and only enlarge the accepted surface.
|
|
44
|
+
*/
|
|
45
|
+
const SAFE_MAVEN_VALUE_RE = /^[A-Za-z0-9._+ -]+$/;
|
|
46
|
+
/**
|
|
47
|
+
* Upper bound on a segment. A sanity limit ONLY - it is not, and cannot be, a
|
|
48
|
+
* control on the length of the file name a coordinate produces: `artifactId`,
|
|
49
|
+
* `version` and `classifier` are concatenated into one name
|
|
50
|
+
* (`<artifact>-<version>-<classifier>-sources.jar`), so three segments at this
|
|
51
|
+
* bound still build a name past 600 characters. What the bound does buy is that
|
|
52
|
+
* a pathological megabyte-long input cannot be spent building paths; the
|
|
53
|
+
* filesystem's own NAME_MAX is what refuses an overlong file name, and it does
|
|
54
|
+
* so on open, harmlessly.
|
|
55
|
+
*/
|
|
56
|
+
const MAX_MAVEN_TOKEN_LENGTH = 200;
|
|
57
|
+
/** Length, leading dot and `..` - the clauses every token shares. */
|
|
58
|
+
function passesCommonTokenRules(value) {
|
|
59
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_MAVEN_TOKEN_LENGTH) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (value.startsWith(".") || value.includes("..")) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// An already-trimmed value is the contract: a segment with leading or
|
|
66
|
+
// trailing whitespace is rejected rather than repaired here, so the one place
|
|
67
|
+
// that trims (each route, before it validates) stays the one place that
|
|
68
|
+
// decides what "the caller's segment" is. That also keeps the space branch
|
|
69
|
+
// from admitting a version of nothing but spaces.
|
|
70
|
+
return value.trim() === value;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The shared rule for a segment of a CALLER-SUPPLIED coordinate, per component.
|
|
74
|
+
*
|
|
75
|
+
* Both routes go through this and only this: `parseCoordinate` for a
|
|
76
|
+
* `coordinate` target, and `synthesizeDependencyTarget` for the `group`, `name`
|
|
77
|
+
* and explicit `version` of a `dependency` target. They cannot drift apart,
|
|
78
|
+
* because there is no second definition for them to drift towards.
|
|
79
|
+
*/
|
|
80
|
+
export function isSafeMavenSegment(value, component) {
|
|
81
|
+
if (!passesCommonTokenRules(value)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const pattern = component === "version" || component === "classifier" ? SAFE_MAVEN_VALUE_RE : SAFE_MAVEN_TOKEN_RE;
|
|
85
|
+
return pattern.test(value);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The identifier grammar on its own, for a version token DISCOVERED in the
|
|
89
|
+
* workspace rather than stated by a caller: a `gradle.properties` value, a
|
|
90
|
+
* `modules-2` directory entry, an umbrella POM's `<version>`.
|
|
91
|
+
*
|
|
92
|
+
* It deliberately does NOT take the space branch, so this is narrower than
|
|
93
|
+
* `isSafeMavenSegment(value, "version")`. The two inputs are not the same kind
|
|
94
|
+
* of thing. A caller naming `1.14 Pre-Release 1+build.10` has stated which
|
|
95
|
+
* artifact they want, and refusing it loses them a real one. A scan of
|
|
96
|
+
* `~/.gradle/caches/modules-2/.../<name>/` is guessing which of somebody else's
|
|
97
|
+
* directories is a version, and every name it admits becomes a candidate that
|
|
98
|
+
* can make the resolution ambiguous or supply a version the project never
|
|
99
|
+
* declared - so the narrower rule is the right default where nothing is lost by
|
|
100
|
+
* it. Widening it is a separate decision with its own evidence, not a
|
|
101
|
+
* consequence of this one.
|
|
102
|
+
*/
|
|
103
|
+
export function isSafeMavenVersionToken(token) {
|
|
104
|
+
return passesCommonTokenRules(token) && SAFE_MAVEN_TOKEN_RE.test(token);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The rule in words, for an error message that must not drift from the code.
|
|
108
|
+
*
|
|
109
|
+
* `label` names the field that failed as the CALLER spells it, so a caller
|
|
110
|
+
* reading the message can tell which of the four segments to fix; it defaults
|
|
111
|
+
* to the component name, and the `dependency` route passes its own field names
|
|
112
|
+
* (`group`, `name`) instead.
|
|
113
|
+
*/
|
|
114
|
+
export function describeSafeMavenSegmentRule(component, label = component) {
|
|
115
|
+
const allowed = component === "version" || component === "classifier"
|
|
116
|
+
? "[A-Za-z0-9._+-] characters or the space character"
|
|
117
|
+
: "[A-Za-z0-9._+-] characters";
|
|
118
|
+
return `${label} must contain only ${allowed}, must not start with '.', and must not include '..'.`;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=maven-token.js.map
|
package/dist/mcp-helpers.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ResourceNotFoundError } from "@modelcontextprotocol/server";
|
|
|
2
2
|
import { PROBLEM_DETAILS_READ_CACHE_FIELDS } from "./cache-policy.js";
|
|
3
3
|
import { CLIENT_CAPABILITIES_META_KEY, PROTOCOL_VERSION_META_KEY } from "./era-classifier.js";
|
|
4
4
|
import { ERROR_CODES } from "./errors.js";
|
|
5
|
-
import {
|
|
5
|
+
import { problemClassification, extractAllowlistedContext, extractDidYouMean, extractNestedJarsField } from "./error-mapping.js";
|
|
6
6
|
import { toHints, extractValidatedSuggestionAndExamples, extractFieldErrorsFromDetails } from "./tool-guidance.js";
|
|
7
7
|
export function objectResult(data, options = {}) {
|
|
8
8
|
return {
|
|
@@ -90,6 +90,12 @@ export function errorResource(uri, error, ctx) {
|
|
|
90
90
|
const { suggestedCall, exampleCalls } = extractValidatedSuggestionAndExamples(details);
|
|
91
91
|
const fieldErrors = extractFieldErrorsFromDetails(details);
|
|
92
92
|
const context = extractAllowlistedContext(details);
|
|
93
|
+
const didYouMean = extractDidYouMean(details);
|
|
94
|
+
// The field-returning form, so a SHORTENED inventory says so here too. The
|
|
95
|
+
// inventory-only view would publish a trimmed list indistinguishable from a
|
|
96
|
+
// complete one, and a caller reading it would conclude the class is bundled
|
|
97
|
+
// in none of the inner jars.
|
|
98
|
+
const nestedJarsField = extractNestedJarsField(details);
|
|
93
99
|
return {
|
|
94
100
|
// ProblemDetails-read cache override (adopted policy): STRUCTURAL
|
|
95
101
|
// identification — this constructor IS the error-resource path, so the
|
|
@@ -114,12 +120,17 @@ export function errorResource(uri, error, ctx) {
|
|
|
114
120
|
status: statusForResourceErrorCode(code),
|
|
115
121
|
code,
|
|
116
122
|
instance: uri,
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
// Shared with the tool path via the one classification builder, so
|
|
124
|
+
// a throw site that classified itself (e.g. a tool-resolved
|
|
125
|
+
// artifact with no binary jar) is not silently re-labelled
|
|
126
|
+
// caller-fixable just because the caller used a resource URI.
|
|
127
|
+
...problemClassification(code, details),
|
|
119
128
|
...(fieldErrors ? { fieldErrors } : {}),
|
|
120
129
|
...(hints ? { hints } : {}),
|
|
121
130
|
...(suggestedCall ? { suggestedCall } : {}),
|
|
122
131
|
...(exampleCalls ? { exampleCalls } : {}),
|
|
132
|
+
...(didYouMean ? { didYouMean } : {}),
|
|
133
|
+
...nestedJarsField,
|
|
123
134
|
...(context ? { context } : {})
|
|
124
135
|
},
|
|
125
136
|
meta: { uri }
|
|
@@ -5,7 +5,12 @@ export interface ResponseContext {
|
|
|
5
5
|
minecraftVersion: string;
|
|
6
6
|
mappingType: string;
|
|
7
7
|
mappingNamespace: MappingNamespace;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Cheap per-response staleness stamp derived from the jar's path, mtime and
|
|
10
|
+
* size — NOT a hash of the jar's byte content. Named accordingly so callers
|
|
11
|
+
* do not mistake it for a content identity key.
|
|
12
|
+
*/
|
|
13
|
+
jarSignature: string;
|
|
9
14
|
generatedAt: string;
|
|
10
15
|
}
|
|
11
16
|
export interface SignatureMember {
|
|
@@ -27,6 +32,13 @@ export interface GetSignatureInput {
|
|
|
27
32
|
access?: SignatureAccess;
|
|
28
33
|
includeSynthetic?: boolean;
|
|
29
34
|
includeInherited?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* True when the jar being read is a resolved dependency (Maven/Gradle)
|
|
37
|
+
* artifact rather than a vanilla Minecraft version jar. Dependency jars live
|
|
38
|
+
* under cache paths whose embedded numbers are not Minecraft versions, so the
|
|
39
|
+
* path-derived version heuristic must be skipped for them.
|
|
40
|
+
*/
|
|
41
|
+
dependencyOrigin?: boolean;
|
|
30
42
|
}
|
|
31
43
|
export interface GetSignatureOutput {
|
|
32
44
|
classAccessFlags?: number;
|