@adhisang/minecraft-modding-mcp 6.2.0 → 7.0.0-rc.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 +71 -0
- package/README.md +21 -5
- package/dist/cache-policy.d.ts +71 -0
- package/dist/cache-policy.js +83 -0
- package/dist/cache-registry.js +6 -6
- package/dist/cli.js +74 -3
- package/dist/compat-stdio-transport.d.ts +1 -1
- package/dist/compat-stdio-transport.js +13 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -2
- package/dist/decompiler/vineflower.d.ts +1 -0
- package/dist/decompiler/vineflower.js +8 -5
- package/dist/entry-tools/analyze-mod-service.d.ts +70 -136
- package/dist/entry-tools/analyze-symbol-service.d.ts +112 -150
- package/dist/entry-tools/compare-minecraft-service.d.ts +59 -145
- package/dist/entry-tools/entry-tool-schema.d.ts +38 -4
- package/dist/entry-tools/entry-tool-schema.js +4 -1
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +235 -799
- package/dist/entry-tools/inspect-minecraft/internal.js +65 -28
- package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
- package/dist/entry-tools/inspect-minecraft-service.js +1 -1
- package/dist/entry-tools/manage-cache-service.d.ts +81 -91
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
- package/dist/entry-tools/validate-project-service.d.ts +164 -592
- package/dist/entry-tools/verify-mixin-target-service.d.ts +3 -19
- package/dist/era-classifier.d.ts +161 -0
- package/dist/era-classifier.js +292 -0
- package/dist/error-mapping.js +9 -2
- package/dist/index.d.ts +42 -4
- package/dist/index.js +637 -475
- package/dist/java-process.d.ts +2 -0
- package/dist/java-process.js +22 -2
- package/dist/json-rpc-framing.d.ts +77 -1
- package/dist/json-rpc-framing.js +249 -13
- package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
- package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
- package/dist/mapping/loaders/tiny-loom.js +45 -33
- package/dist/mapping/loaders/tiny-maven.js +6 -11
- package/dist/mapping/parsers/tiny.d.ts +57 -0
- package/dist/mapping/parsers/tiny.js +99 -22
- package/dist/mapping-service.d.ts +19 -0
- package/dist/mapping-service.js +93 -9
- package/dist/mcp-helpers.d.ts +19 -2
- package/dist/mcp-helpers.js +48 -6
- package/dist/minecraft-explorer-service.d.ts +1 -1
- package/dist/mixin/types.d.ts +8 -0
- package/dist/mod-analyzer.js +7 -7
- package/dist/mod-decompile-service.js +1 -0
- package/dist/nbt/java-nbt-codec.js +12 -2
- package/dist/nbt/json-patch.js +14 -3
- package/dist/nbt/pipeline.js +40 -3
- package/dist/nbt/typed-json.js +26 -1
- package/dist/registration-adapter.d.ts +32 -0
- package/dist/registration-adapter.js +52 -0
- package/dist/request-context.d.ts +7 -0
- package/dist/request-context.js +9 -0
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +25 -19
- package/dist/server-identity.d.ts +27 -0
- package/dist/server-identity.js +26 -0
- package/dist/source/access-validate.js +53 -0
- package/dist/source/artifact-resolver.d.ts +69 -1
- package/dist/source/artifact-resolver.js +215 -14
- package/dist/source/class-source.d.ts +22 -0
- package/dist/source/class-source.js +162 -29
- package/dist/source/did-you-mean.d.ts +12 -1
- package/dist/source/did-you-mean.js +6 -2
- package/dist/source/file-access.js +150 -46
- package/dist/source/indexer.js +1 -0
- package/dist/source/nested-jars.d.ts +19 -0
- package/dist/source/nested-jars.js +90 -21
- package/dist/source/shared-utils.d.ts +21 -0
- package/dist/source/shared-utils.js +23 -0
- package/dist/source-service.d.ts +12 -0
- package/dist/source-service.js +3 -0
- package/dist/stdio-supervisor.d.ts +357 -2
- package/dist/stdio-supervisor.js +1031 -80
- package/dist/storage/db.d.ts +2 -1
- package/dist/storage/db.js +15 -8
- package/dist/synthetic-decorator.d.ts +24 -0
- package/dist/synthetic-decorator.js +48 -0
- package/dist/tool-contract-manifest.js +1 -1
- package/dist/tool-guidance.d.ts +17 -1
- package/dist/tool-guidance.js +417 -13
- package/dist/tool-schema-registry.d.ts +2 -0
- package/dist/tool-schema-registry.js +4 -0
- package/dist/tool-schemas.d.ts +2214 -3915
- package/dist/tool-schemas.js +34 -7
- package/dist/types.d.ts +35 -0
- package/dist/v1-parity-schemas.d.ts +7 -0
- package/dist/v1-parity-schemas.js +5584 -0
- package/dist/version-diff-service.d.ts +33 -0
- package/dist/version-diff-service.js +148 -3
- package/dist/version-service.js +36 -14
- package/dist/warning-details.js +18 -1
- package/docs/README-ja.md +9 -3
- package/docs/tool-reference.md +267 -22
- package/package.json +12 -9
|
@@ -137,29 +137,13 @@ export declare class VerifyMixinTargetService {
|
|
|
137
137
|
*/
|
|
138
138
|
private applyAutoRemap;
|
|
139
139
|
}
|
|
140
|
-
export declare const verifyMixinTargetMemberSchemaExport: z.ZodDiscriminatedUnion<
|
|
140
|
+
export declare const verifyMixinTargetMemberSchemaExport: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
141
141
|
kind: z.ZodLiteral<"method">;
|
|
142
142
|
name: z.ZodString;
|
|
143
143
|
descriptor: z.ZodOptional<z.ZodString>;
|
|
144
|
-
},
|
|
145
|
-
name: string;
|
|
146
|
-
kind: "method";
|
|
147
|
-
descriptor?: string | undefined;
|
|
148
|
-
}, {
|
|
149
|
-
name: string;
|
|
150
|
-
kind: "method";
|
|
151
|
-
descriptor?: string | undefined;
|
|
152
|
-
}>, z.ZodObject<{
|
|
144
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
153
145
|
kind: z.ZodLiteral<"field">;
|
|
154
146
|
name: z.ZodString;
|
|
155
147
|
descriptor: z.ZodOptional<z.ZodString>;
|
|
156
|
-
},
|
|
157
|
-
name: string;
|
|
158
|
-
kind: "field";
|
|
159
|
-
descriptor?: string | undefined;
|
|
160
|
-
}, {
|
|
161
|
-
name: string;
|
|
162
|
-
kind: "field";
|
|
163
|
-
descriptor?: string | undefined;
|
|
164
|
-
}>]>;
|
|
148
|
+
}, z.core.$strip>], "kind">;
|
|
165
149
|
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type JSONRPCMessage, type JSONRPCResponse } from "@modelcontextprotocol/server";
|
|
2
|
+
/**
|
|
3
|
+
* Era classification for the dual-era stdio supervisor.
|
|
4
|
+
*
|
|
5
|
+
* The process serves TWO protocol eras: the legacy
|
|
6
|
+
* initialize/notifications/initialized handshake (five protocol versions) and
|
|
7
|
+
* the modern stateless era (protocol revision 2026-07-28) whose requests carry
|
|
8
|
+
* a per-request `_meta` envelope. The SUPERVISOR is the sole era gatekeeper at
|
|
9
|
+
* admission — the worker's own era classification is unreliable for gating (a
|
|
10
|
+
* claim-less frame silently pins the worker connection legacy) — so the
|
|
11
|
+
* shallow envelope check, the era types, and the machine-readable rejection
|
|
12
|
+
* builders live here.
|
|
13
|
+
*/
|
|
14
|
+
/** Reserved `_meta` key carrying the per-request protocol version claim. */
|
|
15
|
+
export declare const PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
16
|
+
/** Reserved `_meta` key carrying the per-request client capabilities. */
|
|
17
|
+
export declare const CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
18
|
+
/**
|
|
19
|
+
* Reserved `_meta` key carrying the OPTIONAL per-request client
|
|
20
|
+
* implementation info. NOT part of the era-signal check; only captured (as-is)
|
|
21
|
+
* into request snapshots when present on a shallow-valid modern signal.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo";
|
|
24
|
+
/** The five legacy handshake protocol versions the worker supports. */
|
|
25
|
+
export declare const LEGACY_PROTOCOL_VERSIONS: readonly ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
26
|
+
/** The modern per-request `_meta` protocol revision. */
|
|
27
|
+
export declare const MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
28
|
+
/** All protocol versions this process serves, across both eras. */
|
|
29
|
+
export declare const ERA_SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
30
|
+
/** Process-lifetime era state. The lock is one-way and survives worker restarts. */
|
|
31
|
+
export type Era = "unselected" | "legacy" | "modern";
|
|
32
|
+
export type EraSignalClassification = "modern-signal" | "claim-less" | "claim-shaped-invalid";
|
|
33
|
+
export type EraSignal = {
|
|
34
|
+
classification: EraSignalClassification;
|
|
35
|
+
/** Absent required io.modelcontextprotocol/* keys. */
|
|
36
|
+
missing: string[];
|
|
37
|
+
/** Present-but-wrong-shallow-type required io.modelcontextprotocol/* keys. */
|
|
38
|
+
invalid: string[];
|
|
39
|
+
};
|
|
40
|
+
type RequestId = string | number;
|
|
41
|
+
/**
|
|
42
|
+
* Shallow era-signal classification of a request's (or notification's)
|
|
43
|
+
* params. Mirrors the SDK RequestMetaEnvelopeSchema's REQUIREDNESS without
|
|
44
|
+
* deep value validation: `params._meta` must be an object carrying a string
|
|
45
|
+
* `io.modelcontextprotocol/protocolVersion` and a non-null non-array object
|
|
46
|
+
* `io.modelcontextprotocol/clientCapabilities`. `io.modelcontextprotocol/clientInfo`
|
|
47
|
+
* is optional and NOT part of the check.
|
|
48
|
+
*
|
|
49
|
+
* A shallow-VALID envelope is the modern era signal even when the version
|
|
50
|
+
* VALUE is unsupported (e.g. "2027-01-01") — deep value validation belongs to
|
|
51
|
+
* the worker, which answers -32022 with {supported, requested}.
|
|
52
|
+
*
|
|
53
|
+
* claim-less: params/_meta missing, `_meta` not a plain object, or `_meta`
|
|
54
|
+
* carrying NEITHER required key. claim-shaped-invalid: at least one required
|
|
55
|
+
* key present but the shallow check fails (`missing` = absent required keys,
|
|
56
|
+
* `invalid` = present-but-wrong-shallow-type keys).
|
|
57
|
+
*/
|
|
58
|
+
export declare function classifyEraSignal(params: unknown): EraSignal;
|
|
59
|
+
/**
|
|
60
|
+
* Per-request modern protocol context, captured SHALLOW from `params._meta`
|
|
61
|
+
* at supervisor admission. Values are copied as-is (no deep validation — the
|
|
62
|
+
* worker validates values, e.g. answering -32022 for an unsupported
|
|
63
|
+
* protocolVersion). `clientInfo` is present only when the optional
|
|
64
|
+
* io.modelcontextprotocol/clientInfo key is present, and is passed through
|
|
65
|
+
* verbatim even when it is not Implementation-shaped.
|
|
66
|
+
*/
|
|
67
|
+
export type ModernRequestContext = {
|
|
68
|
+
protocolVersion: string;
|
|
69
|
+
clientCapabilities: Record<string, unknown>;
|
|
70
|
+
clientInfo?: unknown;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Shallow extraction of the {@link ModernRequestContext} from a request's
|
|
74
|
+
* params. Returns undefined unless the params classify as a shallow-valid
|
|
75
|
+
* modern signal — the guard is the same pure {@link classifyEraSignal} check
|
|
76
|
+
* the admission gate runs, so it cannot diverge from an admission-time
|
|
77
|
+
* classification of the same params object. The returned values ALIAS the
|
|
78
|
+
* inbound params._meta objects (no clone): callers must never mutate an
|
|
79
|
+
* inbound params graph after admission, or captured snapshots would change
|
|
80
|
+
* retroactively.
|
|
81
|
+
*/
|
|
82
|
+
export declare function extractModernRequestContext(params: unknown): ModernRequestContext | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Strips the modern era-claim keys from a request's `params._meta` IN PLACE,
|
|
85
|
+
* deleting `_meta` entirely when nothing else remains. Used by the supervisor
|
|
86
|
+
* for accepted legacy `initialize` frames: the admission rule classifies every
|
|
87
|
+
* initialize as the legacy era signal regardless of its envelope, but the
|
|
88
|
+
* SDK's opening classifier treats an initialize carrying a valid modern claim
|
|
89
|
+
* as MODERN — the strip keeps the worker's classification aligned with the
|
|
90
|
+
* admission decision. Mutation is in place deliberately: the framing-mode
|
|
91
|
+
* registry is keyed by the exact inbound frame object, and no
|
|
92
|
+
* {@link ModernRequestContext} ever aliases an initialize's `_meta` (an
|
|
93
|
+
* initialize never classifies as a modern signal at admission).
|
|
94
|
+
*/
|
|
95
|
+
export declare function stripModernEraClaimInPlace(params: unknown): void;
|
|
96
|
+
export declare const ERA_CONFLICT_MODERN_MESSAGE: string;
|
|
97
|
+
export declare const ERA_CONFLICT_LEGACY_MESSAGE: string;
|
|
98
|
+
export declare const MISSING_META_UNSELECTED_MESSAGE: string;
|
|
99
|
+
export declare const MISSING_META_MODERN_MESSAGE: string;
|
|
100
|
+
/**
|
|
101
|
+
* One-way era conflict rejection. selectedEra "modern" rejects a legacy
|
|
102
|
+
* initialize with -32601; selectedEra "legacy" rejects a modern-signal
|
|
103
|
+
* request with -32600. Machine-readable discrimination is by code +
|
|
104
|
+
* data.kind/selectedEra/requestedEra/supported only.
|
|
105
|
+
*/
|
|
106
|
+
export declare function buildEraConflictRejection(id: RequestId, selectedEra: "legacy" | "modern"): JSONRPCResponse;
|
|
107
|
+
/**
|
|
108
|
+
* Rejection for a request carrying no valid era signal: in the unselected
|
|
109
|
+
* state (era stays UNSELECTED; the message names both recovery paths) or in
|
|
110
|
+
* the modern era (the message names the required envelope). `invalid` is
|
|
111
|
+
* included only when non-empty; `missing` is always present.
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildMissingMetaRejection(id: RequestId, signal: EraSignal, era: "unselected" | "modern"): JSONRPCResponse;
|
|
114
|
+
/**
|
|
115
|
+
* Whether a frame is a COMPLETE, schema-valid MCP `initialize` request.
|
|
116
|
+
*
|
|
117
|
+
* Admission classifies any frame whose method is `initialize` as the legacy
|
|
118
|
+
* era signal, and that lock is one-way for the process lifetime. Generic
|
|
119
|
+
* JSON-RPC parsing is far too weak a basis for a permanent decision: a frame
|
|
120
|
+
* as empty as `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}`
|
|
121
|
+
* passes it while carrying none of the fields the handshake needs. Burning the
|
|
122
|
+
* lock on such a frame left the process legacy-locked with NO valid era
|
|
123
|
+
* opening ever completed, and the modern era permanently unreachable.
|
|
124
|
+
*
|
|
125
|
+
* The check is the SDK's own public `isInitializeRequest` — the exact
|
|
126
|
+
* `InitializeRequestSchema` the worker would apply — so admission and the
|
|
127
|
+
* worker can never disagree about what an initialize is. Extra params
|
|
128
|
+
* (including a `_meta` envelope) are tolerated; `protocolVersion` (string),
|
|
129
|
+
* `capabilities` (object) and `clientInfo` ({name, version}) are required.
|
|
130
|
+
* Version VALUES are not judged here: an unknown legacy version negotiates
|
|
131
|
+
* down at the worker, exactly as documented.
|
|
132
|
+
*/
|
|
133
|
+
export declare function isCompleteInitializeRequest(message: JSONRPCMessage): boolean;
|
|
134
|
+
export declare const INVALID_INITIALIZE_MESSAGE: string;
|
|
135
|
+
/**
|
|
136
|
+
* Rejection for a frame that claims to be an `initialize` but fails the MCP
|
|
137
|
+
* initialize schema. Answered at admission, BEFORE the legacy lock is
|
|
138
|
+
* committed, so the era stays UNSELECTED and every recovery path — including
|
|
139
|
+
* the modern era — remains open.
|
|
140
|
+
*/
|
|
141
|
+
export declare function buildInvalidInitializeRejection(id: RequestId): JSONRPCResponse;
|
|
142
|
+
/**
|
|
143
|
+
* Unsupported modern `protocolVersion` VALUE rejection (`-32022` with
|
|
144
|
+
* `data.supported` / `data.requested`).
|
|
145
|
+
*
|
|
146
|
+
* Built from the SDK's own {@link UnsupportedProtocolVersionError} so the
|
|
147
|
+
* code, message and data stay byte-identical to the worker-produced answer
|
|
148
|
+
* clients already receive on the pre-pin path — the supervisor merely makes
|
|
149
|
+
* the check apply to EVERY modern request instead of only the one that
|
|
150
|
+
* happened to open the connection.
|
|
151
|
+
*/
|
|
152
|
+
export declare function buildUnsupportedProtocolVersionRejection(id: RequestId, requested: string): JSONRPCResponse;
|
|
153
|
+
/**
|
|
154
|
+
* Plain -32601 admission rejection for subscriptions/listen in the modern era
|
|
155
|
+
* (no data.kind — deliberately indistinguishable from an ordinary unknown
|
|
156
|
+
* method). Defense in depth: if admission were ever bypassed, the worker
|
|
157
|
+
* (maxSubscriptions: 0) would answer -32603 "Subscription limit reached";
|
|
158
|
+
* intercepting at admission keeps that internal detail off the wire.
|
|
159
|
+
*/
|
|
160
|
+
export declare function buildMethodNotFoundRejection(id: RequestId): JSONRPCResponse;
|
|
161
|
+
export {};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { UnsupportedProtocolVersionError, isInitializeRequest } from "@modelcontextprotocol/server";
|
|
2
|
+
/**
|
|
3
|
+
* Era classification for the dual-era stdio supervisor.
|
|
4
|
+
*
|
|
5
|
+
* The process serves TWO protocol eras: the legacy
|
|
6
|
+
* initialize/notifications/initialized handshake (five protocol versions) and
|
|
7
|
+
* the modern stateless era (protocol revision 2026-07-28) whose requests carry
|
|
8
|
+
* a per-request `_meta` envelope. The SUPERVISOR is the sole era gatekeeper at
|
|
9
|
+
* admission — the worker's own era classification is unreliable for gating (a
|
|
10
|
+
* claim-less frame silently pins the worker connection legacy) — so the
|
|
11
|
+
* shallow envelope check, the era types, and the machine-readable rejection
|
|
12
|
+
* builders live here.
|
|
13
|
+
*/
|
|
14
|
+
/** Reserved `_meta` key carrying the per-request protocol version claim. */
|
|
15
|
+
export const PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
16
|
+
/** Reserved `_meta` key carrying the per-request client capabilities. */
|
|
17
|
+
export const CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
18
|
+
/**
|
|
19
|
+
* Reserved `_meta` key carrying the OPTIONAL per-request client
|
|
20
|
+
* implementation info. NOT part of the era-signal check; only captured (as-is)
|
|
21
|
+
* into request snapshots when present on a shallow-valid modern signal.
|
|
22
|
+
*/
|
|
23
|
+
export const CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo";
|
|
24
|
+
/** The five legacy handshake protocol versions the worker supports. */
|
|
25
|
+
export const LEGACY_PROTOCOL_VERSIONS = [
|
|
26
|
+
"2025-11-25",
|
|
27
|
+
"2025-06-18",
|
|
28
|
+
"2025-03-26",
|
|
29
|
+
"2024-11-05",
|
|
30
|
+
"2024-10-07"
|
|
31
|
+
];
|
|
32
|
+
/** The modern per-request `_meta` protocol revision. */
|
|
33
|
+
export const MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
34
|
+
/** All protocol versions this process serves, across both eras. */
|
|
35
|
+
export const ERA_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
36
|
+
...LEGACY_PROTOCOL_VERSIONS,
|
|
37
|
+
MODERN_PROTOCOL_VERSION
|
|
38
|
+
];
|
|
39
|
+
const REQUIRED_META_KEYS = [PROTOCOL_VERSION_META_KEY, CLIENT_CAPABILITIES_META_KEY];
|
|
40
|
+
function isPlainObject(value) {
|
|
41
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Shallow era-signal classification of a request's (or notification's)
|
|
45
|
+
* params. Mirrors the SDK RequestMetaEnvelopeSchema's REQUIREDNESS without
|
|
46
|
+
* deep value validation: `params._meta` must be an object carrying a string
|
|
47
|
+
* `io.modelcontextprotocol/protocolVersion` and a non-null non-array object
|
|
48
|
+
* `io.modelcontextprotocol/clientCapabilities`. `io.modelcontextprotocol/clientInfo`
|
|
49
|
+
* is optional and NOT part of the check.
|
|
50
|
+
*
|
|
51
|
+
* A shallow-VALID envelope is the modern era signal even when the version
|
|
52
|
+
* VALUE is unsupported (e.g. "2027-01-01") — deep value validation belongs to
|
|
53
|
+
* the worker, which answers -32022 with {supported, requested}.
|
|
54
|
+
*
|
|
55
|
+
* claim-less: params/_meta missing, `_meta` not a plain object, or `_meta`
|
|
56
|
+
* carrying NEITHER required key. claim-shaped-invalid: at least one required
|
|
57
|
+
* key present but the shallow check fails (`missing` = absent required keys,
|
|
58
|
+
* `invalid` = present-but-wrong-shallow-type keys).
|
|
59
|
+
*/
|
|
60
|
+
export function classifyEraSignal(params) {
|
|
61
|
+
const meta = isPlainObject(params) ? params._meta : undefined;
|
|
62
|
+
if (!isPlainObject(meta)) {
|
|
63
|
+
return { classification: "claim-less", missing: [...REQUIRED_META_KEYS], invalid: [] };
|
|
64
|
+
}
|
|
65
|
+
const versionPresent = PROTOCOL_VERSION_META_KEY in meta;
|
|
66
|
+
const capabilitiesPresent = CLIENT_CAPABILITIES_META_KEY in meta;
|
|
67
|
+
if (!versionPresent && !capabilitiesPresent) {
|
|
68
|
+
return { classification: "claim-less", missing: [...REQUIRED_META_KEYS], invalid: [] };
|
|
69
|
+
}
|
|
70
|
+
const missing = [];
|
|
71
|
+
const invalid = [];
|
|
72
|
+
if (!versionPresent) {
|
|
73
|
+
missing.push(PROTOCOL_VERSION_META_KEY);
|
|
74
|
+
}
|
|
75
|
+
else if (typeof meta[PROTOCOL_VERSION_META_KEY] !== "string") {
|
|
76
|
+
invalid.push(PROTOCOL_VERSION_META_KEY);
|
|
77
|
+
}
|
|
78
|
+
if (!capabilitiesPresent) {
|
|
79
|
+
missing.push(CLIENT_CAPABILITIES_META_KEY);
|
|
80
|
+
}
|
|
81
|
+
else if (!isPlainObject(meta[CLIENT_CAPABILITIES_META_KEY])) {
|
|
82
|
+
invalid.push(CLIENT_CAPABILITIES_META_KEY);
|
|
83
|
+
}
|
|
84
|
+
if (missing.length === 0 && invalid.length === 0) {
|
|
85
|
+
return { classification: "modern-signal", missing, invalid };
|
|
86
|
+
}
|
|
87
|
+
return { classification: "claim-shaped-invalid", missing, invalid };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Shallow extraction of the {@link ModernRequestContext} from a request's
|
|
91
|
+
* params. Returns undefined unless the params classify as a shallow-valid
|
|
92
|
+
* modern signal — the guard is the same pure {@link classifyEraSignal} check
|
|
93
|
+
* the admission gate runs, so it cannot diverge from an admission-time
|
|
94
|
+
* classification of the same params object. The returned values ALIAS the
|
|
95
|
+
* inbound params._meta objects (no clone): callers must never mutate an
|
|
96
|
+
* inbound params graph after admission, or captured snapshots would change
|
|
97
|
+
* retroactively.
|
|
98
|
+
*/
|
|
99
|
+
export function extractModernRequestContext(params) {
|
|
100
|
+
if (classifyEraSignal(params).classification !== "modern-signal") {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const meta = params._meta;
|
|
104
|
+
const context = {
|
|
105
|
+
protocolVersion: meta[PROTOCOL_VERSION_META_KEY],
|
|
106
|
+
clientCapabilities: meta[CLIENT_CAPABILITIES_META_KEY]
|
|
107
|
+
};
|
|
108
|
+
if (CLIENT_INFO_META_KEY in meta) {
|
|
109
|
+
context.clientInfo = meta[CLIENT_INFO_META_KEY];
|
|
110
|
+
}
|
|
111
|
+
return context;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Strips the modern era-claim keys from a request's `params._meta` IN PLACE,
|
|
115
|
+
* deleting `_meta` entirely when nothing else remains. Used by the supervisor
|
|
116
|
+
* for accepted legacy `initialize` frames: the admission rule classifies every
|
|
117
|
+
* initialize as the legacy era signal regardless of its envelope, but the
|
|
118
|
+
* SDK's opening classifier treats an initialize carrying a valid modern claim
|
|
119
|
+
* as MODERN — the strip keeps the worker's classification aligned with the
|
|
120
|
+
* admission decision. Mutation is in place deliberately: the framing-mode
|
|
121
|
+
* registry is keyed by the exact inbound frame object, and no
|
|
122
|
+
* {@link ModernRequestContext} ever aliases an initialize's `_meta` (an
|
|
123
|
+
* initialize never classifies as a modern signal at admission).
|
|
124
|
+
*/
|
|
125
|
+
export function stripModernEraClaimInPlace(params) {
|
|
126
|
+
if (!isPlainObject(params))
|
|
127
|
+
return;
|
|
128
|
+
const meta = params._meta;
|
|
129
|
+
if (!isPlainObject(meta))
|
|
130
|
+
return;
|
|
131
|
+
delete meta[PROTOCOL_VERSION_META_KEY];
|
|
132
|
+
delete meta[CLIENT_CAPABILITIES_META_KEY];
|
|
133
|
+
delete meta[CLIENT_INFO_META_KEY];
|
|
134
|
+
if (Object.keys(meta).length === 0) {
|
|
135
|
+
delete params._meta;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const SUPPORTED_VERSIONS_SENTENCE = `Supported protocol versions: ${LEGACY_PROTOCOL_VERSIONS.join(", ")} (legacy initialize handshake) ` +
|
|
139
|
+
`and ${MODERN_PROTOCOL_VERSION} (modern per-request _meta).`;
|
|
140
|
+
/** Launcher-neutral fresh-process recovery preamble shared by both era-conflict messages. */
|
|
141
|
+
const FRESH_PROCESS_RECOVERY = "start a fresh process: close this transport, terminate and respawn the configured server command " +
|
|
142
|
+
"as a fresh stdio process, discard or re-issue any pending request ids, then";
|
|
143
|
+
export const ERA_CONFLICT_MODERN_MESSAGE = `initialize rejected: this server process is era-locked to protocol revision ${MODERN_PROTOCOL_VERSION} ` +
|
|
144
|
+
"(modern per-request _meta era), so the legacy initialize handshake can no longer be accepted. " +
|
|
145
|
+
`${SUPPORTED_VERSIONS_SENTENCE} ` +
|
|
146
|
+
`To use the legacy handshake, ${FRESH_PROCESS_RECOVERY} send initialize followed by notifications/initialized.`;
|
|
147
|
+
// Claim-agnostic wording: ANY string protocolVersion value classifies as the
|
|
148
|
+
// modern signal, so the message names the envelope, not a specific revision.
|
|
149
|
+
export const ERA_CONFLICT_LEGACY_MESSAGE = "Modern per-request _meta request rejected: this server process is era-locked to the legacy initialize handshake, " +
|
|
150
|
+
"so requests carrying the modern per-request _meta envelope can no longer be accepted. " +
|
|
151
|
+
`${SUPPORTED_VERSIONS_SENTENCE} ` +
|
|
152
|
+
`To use the modern era, ${FRESH_PROCESS_RECOVERY} send a request carrying the required io.modelcontextprotocol/* _meta envelope.`;
|
|
153
|
+
const REQUIRED_KEYS_SENTENCE = `the required io.modelcontextprotocol/* keys (${PROTOCOL_VERSION_META_KEY} and ${CLIENT_CAPABILITIES_META_KEY}) in params._meta`;
|
|
154
|
+
export const MISSING_META_UNSELECTED_MESSAGE = "Request rejected: no protocol era is selected yet and this request carries no valid era signal. " +
|
|
155
|
+
"Either send initialize followed by notifications/initialized to select the legacy handshake, " +
|
|
156
|
+
`or include ${REQUIRED_KEYS_SENTENCE} to select protocol revision ${MODERN_PROTOCOL_VERSION}.`;
|
|
157
|
+
export const MISSING_META_MODERN_MESSAGE = `Request rejected: this server process is era-locked to protocol revision ${MODERN_PROTOCOL_VERSION} ` +
|
|
158
|
+
"and the request lacks the required per-request _meta envelope. " +
|
|
159
|
+
`Include ${REQUIRED_KEYS_SENTENCE}.`;
|
|
160
|
+
/**
|
|
161
|
+
* One-way era conflict rejection. selectedEra "modern" rejects a legacy
|
|
162
|
+
* initialize with -32601; selectedEra "legacy" rejects a modern-signal
|
|
163
|
+
* request with -32600. Machine-readable discrimination is by code +
|
|
164
|
+
* data.kind/selectedEra/requestedEra/supported only.
|
|
165
|
+
*/
|
|
166
|
+
export function buildEraConflictRejection(id, selectedEra) {
|
|
167
|
+
const modernSelected = selectedEra === "modern";
|
|
168
|
+
return {
|
|
169
|
+
jsonrpc: "2.0",
|
|
170
|
+
id,
|
|
171
|
+
error: {
|
|
172
|
+
code: modernSelected ? -32601 : -32600,
|
|
173
|
+
message: modernSelected ? ERA_CONFLICT_MODERN_MESSAGE : ERA_CONFLICT_LEGACY_MESSAGE,
|
|
174
|
+
data: {
|
|
175
|
+
kind: "era_conflict",
|
|
176
|
+
selectedEra,
|
|
177
|
+
requestedEra: modernSelected ? "legacy" : "modern",
|
|
178
|
+
supported: [...ERA_SUPPORTED_PROTOCOL_VERSIONS]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Rejection for a request carrying no valid era signal: in the unselected
|
|
185
|
+
* state (era stays UNSELECTED; the message names both recovery paths) or in
|
|
186
|
+
* the modern era (the message names the required envelope). `invalid` is
|
|
187
|
+
* included only when non-empty; `missing` is always present.
|
|
188
|
+
*/
|
|
189
|
+
export function buildMissingMetaRejection(id, signal, era) {
|
|
190
|
+
const data = { kind: "missing_meta", missing: [...signal.missing] };
|
|
191
|
+
if (signal.invalid.length > 0) {
|
|
192
|
+
data.invalid = [...signal.invalid];
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
jsonrpc: "2.0",
|
|
196
|
+
id,
|
|
197
|
+
error: {
|
|
198
|
+
code: -32602,
|
|
199
|
+
message: era === "modern" ? MISSING_META_MODERN_MESSAGE : MISSING_META_UNSELECTED_MESSAGE,
|
|
200
|
+
data
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Whether a frame is a COMPLETE, schema-valid MCP `initialize` request.
|
|
206
|
+
*
|
|
207
|
+
* Admission classifies any frame whose method is `initialize` as the legacy
|
|
208
|
+
* era signal, and that lock is one-way for the process lifetime. Generic
|
|
209
|
+
* JSON-RPC parsing is far too weak a basis for a permanent decision: a frame
|
|
210
|
+
* as empty as `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}`
|
|
211
|
+
* passes it while carrying none of the fields the handshake needs. Burning the
|
|
212
|
+
* lock on such a frame left the process legacy-locked with NO valid era
|
|
213
|
+
* opening ever completed, and the modern era permanently unreachable.
|
|
214
|
+
*
|
|
215
|
+
* The check is the SDK's own public `isInitializeRequest` — the exact
|
|
216
|
+
* `InitializeRequestSchema` the worker would apply — so admission and the
|
|
217
|
+
* worker can never disagree about what an initialize is. Extra params
|
|
218
|
+
* (including a `_meta` envelope) are tolerated; `protocolVersion` (string),
|
|
219
|
+
* `capabilities` (object) and `clientInfo` ({name, version}) are required.
|
|
220
|
+
* Version VALUES are not judged here: an unknown legacy version negotiates
|
|
221
|
+
* down at the worker, exactly as documented.
|
|
222
|
+
*/
|
|
223
|
+
export function isCompleteInitializeRequest(message) {
|
|
224
|
+
return isInitializeRequest(message);
|
|
225
|
+
}
|
|
226
|
+
const INITIALIZE_REQUIRED_FIELDS = ["protocolVersion", "capabilities", "clientInfo"];
|
|
227
|
+
export const INVALID_INITIALIZE_MESSAGE = "initialize rejected: the request is not a valid MCP initialize request. params must carry " +
|
|
228
|
+
"protocolVersion (string), capabilities (object) and clientInfo ({ name, version }). " +
|
|
229
|
+
"No protocol era has been selected, so this is fully recoverable: retry with a well-formed " +
|
|
230
|
+
`initialize, or select protocol revision ${MODERN_PROTOCOL_VERSION} by including ` +
|
|
231
|
+
"the required io.modelcontextprotocol/* keys in params._meta.";
|
|
232
|
+
/**
|
|
233
|
+
* Rejection for a frame that claims to be an `initialize` but fails the MCP
|
|
234
|
+
* initialize schema. Answered at admission, BEFORE the legacy lock is
|
|
235
|
+
* committed, so the era stays UNSELECTED and every recovery path — including
|
|
236
|
+
* the modern era — remains open.
|
|
237
|
+
*/
|
|
238
|
+
export function buildInvalidInitializeRejection(id) {
|
|
239
|
+
return {
|
|
240
|
+
jsonrpc: "2.0",
|
|
241
|
+
id,
|
|
242
|
+
error: {
|
|
243
|
+
code: -32602,
|
|
244
|
+
message: INVALID_INITIALIZE_MESSAGE,
|
|
245
|
+
data: {
|
|
246
|
+
kind: "invalid_initialize",
|
|
247
|
+
required: [...INITIALIZE_REQUIRED_FIELDS],
|
|
248
|
+
eraSelected: false
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Unsupported modern `protocolVersion` VALUE rejection (`-32022` with
|
|
255
|
+
* `data.supported` / `data.requested`).
|
|
256
|
+
*
|
|
257
|
+
* Built from the SDK's own {@link UnsupportedProtocolVersionError} so the
|
|
258
|
+
* code, message and data stay byte-identical to the worker-produced answer
|
|
259
|
+
* clients already receive on the pre-pin path — the supervisor merely makes
|
|
260
|
+
* the check apply to EVERY modern request instead of only the one that
|
|
261
|
+
* happened to open the connection.
|
|
262
|
+
*/
|
|
263
|
+
export function buildUnsupportedProtocolVersionRejection(id, requested) {
|
|
264
|
+
const error = new UnsupportedProtocolVersionError({
|
|
265
|
+
supported: [MODERN_PROTOCOL_VERSION],
|
|
266
|
+
requested
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
jsonrpc: "2.0",
|
|
270
|
+
id,
|
|
271
|
+
error: {
|
|
272
|
+
code: error.code,
|
|
273
|
+
message: error.message,
|
|
274
|
+
data: error.data
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Plain -32601 admission rejection for subscriptions/listen in the modern era
|
|
280
|
+
* (no data.kind — deliberately indistinguishable from an ordinary unknown
|
|
281
|
+
* method). Defense in depth: if admission were ever bypassed, the worker
|
|
282
|
+
* (maxSubscriptions: 0) would answer -32603 "Subscription limit reached";
|
|
283
|
+
* intercepting at admission keeps that internal detail off the wire.
|
|
284
|
+
*/
|
|
285
|
+
export function buildMethodNotFoundRejection(id) {
|
|
286
|
+
return {
|
|
287
|
+
jsonrpc: "2.0",
|
|
288
|
+
id,
|
|
289
|
+
error: { code: -32601, message: "Method not found" }
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=era-classifier.js.map
|
package/dist/error-mapping.js
CHANGED
|
@@ -17,11 +17,18 @@ export function extractDidYouMean(details) {
|
|
|
17
17
|
if (!entry || typeof entry !== "object") {
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
|
-
const { className, matchReason } = entry;
|
|
20
|
+
const { className, matchReason, artifactId } = entry;
|
|
21
21
|
if (typeof className !== "string" || typeof matchReason !== "string") {
|
|
22
22
|
return undefined;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
// `artifactId` marks a candidate found in an artifact the caller did not name
|
|
25
|
+
// (the internal binary fallback or nested-jar redirect). It is optional and
|
|
26
|
+
// dropped when malformed, so a bad value cannot suppress the whole array.
|
|
27
|
+
cleaned.push({
|
|
28
|
+
className,
|
|
29
|
+
matchReason,
|
|
30
|
+
...(typeof artifactId === "string" && artifactId ? { artifactId } : {})
|
|
31
|
+
});
|
|
25
32
|
}
|
|
26
33
|
return cleaned.slice(0, MAX_DID_YOU_MEAN_ENTRIES);
|
|
27
34
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import { McpServer, type McpRequestContext } from "@modelcontextprotocol/server";
|
|
2
2
|
import { SourceService } from "./source-service.js";
|
|
3
|
+
import { SERVER_VERSION } from "./server-identity.js";
|
|
3
4
|
import { applyErrorMetaExtensions, mapErrorToProblem } from "./tool-guidance.js";
|
|
4
5
|
export { mapErrorToProblem, applyErrorMetaExtensions };
|
|
5
|
-
declare const SERVER_VERSION: string;
|
|
6
|
-
declare const server: McpServer;
|
|
7
6
|
declare const config: import("./types.js").Config;
|
|
8
7
|
declare const sourceService: SourceService;
|
|
8
|
+
/**
|
|
9
|
+
* Builds a fully-registered McpServer instance. serveStdio calls its factory
|
|
10
|
+
* PER INSTANCE, not per connection: a modern `server/discover` opening builds
|
|
11
|
+
* a probe instance which a following legacy `initialize` DISCARDS
|
|
12
|
+
* (`product.close()`) before calling the factory again — and the probe path
|
|
13
|
+
* MUTATES the instance (installModernOnlyHandlers adds "2026-07-28" support
|
|
14
|
+
* and a server/discover handler). Every call therefore constructs a FRESH
|
|
15
|
+
* McpServer so probe-instance mutations can never leak onto the re-pinned
|
|
16
|
+
* legacy instance (frozen negotiate-down contract). Module-scope services,
|
|
17
|
+
* config, and helpers stay shared; only the McpServer and its registrations
|
|
18
|
+
* are per-instance.
|
|
19
|
+
*
|
|
20
|
+
* Tool registration ORDER: the CALL-SITE order inside this function is the
|
|
21
|
+
* frozen legacy tools/list order — do not reorder the call sites. The calls
|
|
22
|
+
* are captured as deferred thunks and executed at the bottom of the
|
|
23
|
+
* function: in call-site order for legacy/ctx-less instances (byte-frozen
|
|
24
|
+
* golden contract), and in raw tool-name-ascending order when serveStdio
|
|
25
|
+
* constructs a MODERN-era instance (`ctx.era === "modern"` — the SDK's
|
|
26
|
+
* documented era-parameterized factory seam; the v2 SDK itself emits
|
|
27
|
+
* registration order and never sorts). Each instance serves exactly one era,
|
|
28
|
+
* so the two orders can never mix on one connection. (Adopted ordering
|
|
29
|
+
* policy.)
|
|
30
|
+
*
|
|
31
|
+
* Cache hints (adopted policy, src/cache-policy.ts): the constructor options
|
|
32
|
+
* configure the non-zero resources/list + resources/templates/list rows;
|
|
33
|
+
* per-resource rows live in registerResources(); all hints ride the SDK's
|
|
34
|
+
* never-serialized carrier, so 2025-era responses are unaffected.
|
|
35
|
+
*
|
|
36
|
+
* NOTE: the function body below intentionally keeps the original module-scope
|
|
37
|
+
* indentation of the registration block to preserve a reviewable minimal diff.
|
|
38
|
+
*/
|
|
39
|
+
declare function buildServer(ctx?: McpRequestContext): McpServer;
|
|
40
|
+
/**
|
|
41
|
+
* Module-scope singleton: populates the register-once tool-schema registry at
|
|
42
|
+
* load time and serves in-process consumers (tests drive its request handlers
|
|
43
|
+
* directly). The stdio wire path does NOT serve this instance — serveStdio's
|
|
44
|
+
* factory builds a fresh one per pinned/probe instance (see buildServer).
|
|
45
|
+
*/
|
|
46
|
+
declare const server: McpServer;
|
|
9
47
|
export declare function startServer(): Promise<void>;
|
|
10
|
-
export { server, sourceService, config, SERVER_VERSION };
|
|
48
|
+
export { server, sourceService, config, SERVER_VERSION, buildServer };
|