@adhisang/minecraft-modding-mcp 6.3.0 → 7.0.0-rc.1

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +13 -3
  3. package/dist/cache-policy.d.ts +71 -0
  4. package/dist/cache-policy.js +83 -0
  5. package/dist/cache-registry.js +45 -8
  6. package/dist/cli.js +74 -3
  7. package/dist/compat-stdio-transport.d.ts +1 -1
  8. package/dist/compat-stdio-transport.js +13 -1
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +8 -2
  11. package/dist/decompiler/vineflower.d.ts +1 -0
  12. package/dist/decompiler/vineflower.js +8 -5
  13. package/dist/entry-tools/analyze-mod-service.d.ts +70 -136
  14. package/dist/entry-tools/analyze-symbol-service.d.ts +112 -150
  15. package/dist/entry-tools/compare-minecraft-service.d.ts +59 -145
  16. package/dist/entry-tools/entry-tool-schema.d.ts +38 -4
  17. package/dist/entry-tools/entry-tool-schema.js +4 -1
  18. package/dist/entry-tools/inspect-minecraft/internal.d.ts +235 -799
  19. package/dist/entry-tools/inspect-minecraft/internal.js +50 -13
  20. package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
  21. package/dist/entry-tools/manage-cache-service.d.ts +81 -91
  22. package/dist/entry-tools/validate-project/cases/mixin.js +26 -6
  23. package/dist/entry-tools/validate-project/cases/project-summary.d.ts +7 -7
  24. package/dist/entry-tools/validate-project-service.d.ts +164 -592
  25. package/dist/entry-tools/verify-mixin-target-service.d.ts +3 -19
  26. package/dist/entry-tools/verify-mixin-target-service.js +19 -1
  27. package/dist/era-classifier.d.ts +161 -0
  28. package/dist/era-classifier.js +292 -0
  29. package/dist/error-mapping.d.ts +76 -0
  30. package/dist/error-mapping.js +116 -8
  31. package/dist/index.d.ts +42 -4
  32. package/dist/index.js +636 -473
  33. package/dist/java-process.d.ts +2 -0
  34. package/dist/java-process.js +22 -2
  35. package/dist/json-rpc-framing.d.ts +77 -1
  36. package/dist/json-rpc-framing.js +249 -13
  37. package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
  38. package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
  39. package/dist/mapping/loaders/tiny-loom.js +45 -33
  40. package/dist/mapping/loaders/tiny-maven.js +6 -11
  41. package/dist/mapping/parsers/tiny.d.ts +57 -0
  42. package/dist/mapping/parsers/tiny.js +99 -22
  43. package/dist/mapping-service.d.ts +19 -0
  44. package/dist/mapping-service.js +93 -9
  45. package/dist/maven-resolver.d.ts +18 -0
  46. package/dist/maven-resolver.js +20 -0
  47. package/dist/mcp-helpers.d.ts +19 -2
  48. package/dist/mcp-helpers.js +58 -9
  49. package/dist/minecraft-explorer-service.d.ts +1 -1
  50. package/dist/mixin/types.d.ts +8 -0
  51. package/dist/mod-analyzer.js +7 -7
  52. package/dist/mod-decompile-service.js +1 -0
  53. package/dist/nbt/java-nbt-codec.js +12 -2
  54. package/dist/nbt/json-patch.js +14 -3
  55. package/dist/nbt/pipeline.js +40 -3
  56. package/dist/nbt/typed-json.js +26 -1
  57. package/dist/registration-adapter.d.ts +32 -0
  58. package/dist/registration-adapter.js +52 -0
  59. package/dist/repo-downloader.d.ts +165 -0
  60. package/dist/repo-downloader.js +568 -10
  61. package/dist/request-context.d.ts +7 -0
  62. package/dist/request-context.js +9 -0
  63. package/dist/resources.d.ts +1 -1
  64. package/dist/resources.js +25 -19
  65. package/dist/server-identity.d.ts +27 -0
  66. package/dist/server-identity.js +26 -0
  67. package/dist/source/access-validate.js +53 -0
  68. package/dist/source/artifact-resolver.d.ts +81 -2
  69. package/dist/source/artifact-resolver.js +227 -15
  70. package/dist/source/class-source.d.ts +36 -0
  71. package/dist/source/class-source.js +222 -38
  72. package/dist/source/did-you-mean.d.ts +12 -1
  73. package/dist/source/did-you-mean.js +6 -2
  74. package/dist/source/file-access.js +150 -46
  75. package/dist/source/indexer.js +1 -0
  76. package/dist/source/shared-utils.d.ts +21 -0
  77. package/dist/source/shared-utils.js +23 -0
  78. package/dist/source-resolver.js +224 -57
  79. package/dist/source-service.d.ts +12 -1
  80. package/dist/stdio-supervisor.d.ts +357 -2
  81. package/dist/stdio-supervisor.js +1031 -80
  82. package/dist/storage/db.d.ts +2 -1
  83. package/dist/storage/db.js +15 -8
  84. package/dist/synthetic-decorator.d.ts +24 -0
  85. package/dist/synthetic-decorator.js +48 -0
  86. package/dist/tool-guidance.d.ts +17 -1
  87. package/dist/tool-guidance.js +323 -18
  88. package/dist/tool-schema-registry.d.ts +2 -0
  89. package/dist/tool-schema-registry.js +4 -0
  90. package/dist/tool-schemas.d.ts +2212 -3919
  91. package/dist/tool-schemas.js +33 -7
  92. package/dist/types.d.ts +35 -0
  93. package/dist/v1-parity-schemas.d.ts +7 -0
  94. package/dist/v1-parity-schemas.js +5584 -0
  95. package/dist/version-diff-service.d.ts +33 -0
  96. package/dist/version-diff-service.js +148 -3
  97. package/dist/version-service.js +36 -14
  98. package/dist/warning-details.js +18 -1
  99. package/docs/README-ja.md +5 -3
  100. package/docs/tool-reference.md +196 -19
  101. package/package.json +13 -8
@@ -137,29 +137,13 @@ export declare class VerifyMixinTargetService {
137
137
  */
138
138
  private applyAutoRemap;
139
139
  }
140
- export declare const verifyMixinTargetMemberSchemaExport: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
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
- }, "strip", z.ZodTypeAny, {
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
- }, "strip", z.ZodTypeAny, {
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 {};
@@ -221,7 +221,25 @@ export class VerifyMixinTargetService {
221
221
  throw createError({
222
222
  code: ERROR_CODES.CONTEXT_UNRESOLVED,
223
223
  message: `verify-mixin-target requires a binary jar but artifact "${resolved.artifactId}" has none.`,
224
- details: { artifactId: resolved.artifactId, owner }
224
+ details: {
225
+ artifactId: resolved.artifactId,
226
+ owner,
227
+ // `ERR_CONTEXT_UNRESOLVED` classifies as `code_issue` by code, which
228
+ // is right for the sibling case (a caller naming a version no
229
+ // artifact carries) and for a `target: { kind: "jar", ... }` request:
230
+ // there the caller named the exact jar, and having none for its own
231
+ // sources is exactly the input the caller can change. It is wrong
232
+ // only when the artifact was picked by the TOOL from a non-jar target
233
+ // (version/coordinate/workspace/dependency), where whether it ships a
234
+ // binary jar is not something the request can express. Published as
235
+ // caller-fixable in that case it invites an endless retry of an input
236
+ // that was never at fault, so this site overrides the default only
237
+ // then.
238
+ issueOrigin: input.target?.kind === "jar" ? undefined : "tool_issue",
239
+ nextAction: `verify-mixin-target reads the target's members from bytecode, so it needs an artifact with a binary jar. `
240
+ + `Re-target with target: { kind: "jar", value: "<path to the jar>" }, or with a version whose artifact carries one `
241
+ + `(resolve-artifact reports binaryJarPath for the target you pass).`
242
+ }
225
243
  });
226
244
  }
227
245
  let signature;
@@ -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
@@ -57,6 +57,14 @@ export type ProblemDetails = {
57
57
  exampleCalls?: ExampleCall[];
58
58
  /** Ranked near-miss candidates for a class/symbol that was not found. */
59
59
  didYouMean?: DidYouMeanCandidate[];
60
+ /**
61
+ * Nested-jar inventory of the shell jar the lookup ran against, as jar-entry
62
+ * names (e.g. "META-INF/jars/api.jar"). A shell jar holds no classes of its
63
+ * own, so a class-not-found error on one is only actionable with the list of
64
+ * inner jars the caller can target next. Like `didYouMean`, it travels as a
65
+ * dedicated typed field: `context` is primitive-only and can never carry it.
66
+ */
67
+ nestedJars?: string[];
60
68
  failedStage?: string;
61
69
  context?: Record<string, string | number | boolean>;
62
70
  };
@@ -67,6 +75,34 @@ export type ProblemDetails = {
67
75
  * whole rather than partially published.
68
76
  */
69
77
  export declare function extractDidYouMean(details: unknown): DidYouMeanCandidate[] | undefined;
78
+ /**
79
+ * Validates and extracts a `nestedJars` inventory from error details.
80
+ * `buildClassSourceNotFoundError` records it whenever the lookup ran against a
81
+ * shell jar, but `context` is primitive-only, so without this the inventory
82
+ * never left the process. Malformed payloads are dropped whole rather than
83
+ * partially published, matching {@link extractDidYouMean}. An empty inventory
84
+ * is dropped too: the producing site omits the key entirely in that case, so an
85
+ * empty array carries no information a caller could act on.
86
+ */
87
+ export declare function extractNestedJars(details: unknown): string[] | undefined;
88
+ /**
89
+ * Per-throw-site {@link IssueOrigin} override carried on an AppError's
90
+ * `details.issueOrigin`.
91
+ *
92
+ * The default classification is keyed purely on the error CODE, which is right
93
+ * for codes whose every throw site shares one origin. A few codes do not:
94
+ * `ERR_CONTEXT_UNRESOLVED` covers both a caller naming a version no artifact
95
+ * carries (genuinely `code_issue`) and the tool resolving an artifact with no
96
+ * binary jar behind the caller's back (`tool_issue`). Publishing the latter as
97
+ * caller-fixable sends an agent into a retry loop over input it cannot repair.
98
+ *
99
+ * This override is deliberately opt-in and per-error: `issueOriginForErrorCode`
100
+ * and its default sets stay untouched, so the exhaustive classification map in
101
+ * tests/runtime/error-mapping.test.ts keeps forcing a conscious decision for
102
+ * every new code. The key is not in `CONTEXT_ALLOWLIST`, so it cannot leak into
103
+ * the public `context` blob.
104
+ */
105
+ export declare function extractIssueOriginOverride(details: unknown): IssueOrigin | undefined;
70
106
  export declare function statusForErrorCode(code: string): number;
71
107
  /**
72
108
  * Single source of truth mapping an error code to its {@link RetryClass}. Used
@@ -85,6 +121,46 @@ export declare function retryClassForErrorCode(code: string): RetryClass;
85
121
  * not satisfy).
86
122
  */
87
123
  export declare function issueOriginForErrorCode(code: string): IssueOrigin;
124
+ /**
125
+ * The single source of truth for a ProblemDetails classification pair, and the
126
+ * ONLY supported way to compute one for an error the server caught. Every site
127
+ * that turns a caught error into a published envelope spreads this result — the
128
+ * tool envelope (`mapErrorToProblem`), batch entries, the batch-aborted
129
+ * sentinel, and the `mc://` resource read — so one failure always publishes the
130
+ * same `{ retryClass, issueOrigin }` no matter which access path the caller
131
+ * used.
132
+ *
133
+ * KNOWN EXCEPTION — the synthetic supervisor replies in `src/stdio-supervisor.ts`
134
+ * (`buildSupervisorQueueLimitReply`, `buildValidateProjectTimeoutReply`) write
135
+ * the pair as literals and never import this builder. They are not emission
136
+ * sites for a caught error at all: the supervisor answers a request the worker
137
+ * never got to run, so there is no source `AppError` whose `details` could carry
138
+ * an override and nothing to pass as the required `details` argument. Their
139
+ * literals are `transient` / `tool_issue`, which is exactly what this function
140
+ * returns for `ERR_LIMIT_EXCEEDED` and `ERR_TOOL_TIMEOUT` today — but that is a
141
+ * duplicated value held in agreement by hand, not a second call into this
142
+ * classifier. Reclassifying either code here must be mirrored there.
143
+ *
144
+ * It exists because that pair used to be assembled by hand at each site. The
145
+ * `issueOrigin` half has a per-throw-site override (see
146
+ * {@link extractIssueOriginOverride}), and honouring it was copy-pasted into
147
+ * some sites and forgotten in others — which is exactly how one identical
148
+ * AppError came to publish `tool_issue` through a tool call and `code_issue`
149
+ * through a resource read. Composing the pair here removes the opportunity:
150
+ * emission modules import this function and no longer reach the component
151
+ * classifiers at all.
152
+ *
153
+ * `details` is REQUIRED, deliberately without a default. A site that genuinely
154
+ * has no AppError behind it (a ZodError, a sanitized non-AppError, the
155
+ * batch-aborted sentinel) must pass `undefined` explicitly, so skipping the
156
+ * override can only ever be a written-down decision rather than an omission.
157
+ *
158
+ * Only `issueOrigin` is overridable per throw site. `retryClass` stays purely
159
+ * code-derived: it is a documented wire contract, and changing the value an
160
+ * existing code publishes is a Breaking change, so a `retryClass` override seam
161
+ * was deliberately deferred rather than added alongside this one.
162
+ */
163
+ export declare function problemClassification(code: string, details: unknown): Pick<ProblemDetails, "retryClass" | "issueOrigin">;
88
164
  /**
89
165
  * Pick the allowlisted, primitive-valued fields out of an AppError's `details`
90
166
  * so the public envelope can carry structured repair context without leaking