@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +21 -5
  3. package/dist/cache-policy.d.ts +71 -0
  4. package/dist/cache-policy.js +83 -0
  5. package/dist/cache-registry.js +6 -6
  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 +65 -28
  20. package/dist/entry-tools/inspect-minecraft-service.d.ts +372 -1736
  21. package/dist/entry-tools/inspect-minecraft-service.js +1 -1
  22. package/dist/entry-tools/manage-cache-service.d.ts +81 -91
  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/era-classifier.d.ts +161 -0
  27. package/dist/era-classifier.js +292 -0
  28. package/dist/error-mapping.js +9 -2
  29. package/dist/index.d.ts +42 -4
  30. package/dist/index.js +637 -475
  31. package/dist/java-process.d.ts +2 -0
  32. package/dist/java-process.js +22 -2
  33. package/dist/json-rpc-framing.d.ts +77 -1
  34. package/dist/json-rpc-framing.js +249 -13
  35. package/dist/mapping/loaders/tiny-loom-selection.d.ts +88 -0
  36. package/dist/mapping/loaders/tiny-loom-selection.js +223 -0
  37. package/dist/mapping/loaders/tiny-loom.js +45 -33
  38. package/dist/mapping/loaders/tiny-maven.js +6 -11
  39. package/dist/mapping/parsers/tiny.d.ts +57 -0
  40. package/dist/mapping/parsers/tiny.js +99 -22
  41. package/dist/mapping-service.d.ts +19 -0
  42. package/dist/mapping-service.js +93 -9
  43. package/dist/mcp-helpers.d.ts +19 -2
  44. package/dist/mcp-helpers.js +48 -6
  45. package/dist/minecraft-explorer-service.d.ts +1 -1
  46. package/dist/mixin/types.d.ts +8 -0
  47. package/dist/mod-analyzer.js +7 -7
  48. package/dist/mod-decompile-service.js +1 -0
  49. package/dist/nbt/java-nbt-codec.js +12 -2
  50. package/dist/nbt/json-patch.js +14 -3
  51. package/dist/nbt/pipeline.js +40 -3
  52. package/dist/nbt/typed-json.js +26 -1
  53. package/dist/registration-adapter.d.ts +32 -0
  54. package/dist/registration-adapter.js +52 -0
  55. package/dist/request-context.d.ts +7 -0
  56. package/dist/request-context.js +9 -0
  57. package/dist/resources.d.ts +1 -1
  58. package/dist/resources.js +25 -19
  59. package/dist/server-identity.d.ts +27 -0
  60. package/dist/server-identity.js +26 -0
  61. package/dist/source/access-validate.js +53 -0
  62. package/dist/source/artifact-resolver.d.ts +69 -1
  63. package/dist/source/artifact-resolver.js +215 -14
  64. package/dist/source/class-source.d.ts +22 -0
  65. package/dist/source/class-source.js +162 -29
  66. package/dist/source/did-you-mean.d.ts +12 -1
  67. package/dist/source/did-you-mean.js +6 -2
  68. package/dist/source/file-access.js +150 -46
  69. package/dist/source/indexer.js +1 -0
  70. package/dist/source/nested-jars.d.ts +19 -0
  71. package/dist/source/nested-jars.js +90 -21
  72. package/dist/source/shared-utils.d.ts +21 -0
  73. package/dist/source/shared-utils.js +23 -0
  74. package/dist/source-service.d.ts +12 -0
  75. package/dist/source-service.js +3 -0
  76. package/dist/stdio-supervisor.d.ts +357 -2
  77. package/dist/stdio-supervisor.js +1031 -80
  78. package/dist/storage/db.d.ts +2 -1
  79. package/dist/storage/db.js +15 -8
  80. package/dist/synthetic-decorator.d.ts +24 -0
  81. package/dist/synthetic-decorator.js +48 -0
  82. package/dist/tool-contract-manifest.js +1 -1
  83. package/dist/tool-guidance.d.ts +17 -1
  84. package/dist/tool-guidance.js +417 -13
  85. package/dist/tool-schema-registry.d.ts +2 -0
  86. package/dist/tool-schema-registry.js +4 -0
  87. package/dist/tool-schemas.d.ts +2214 -3915
  88. package/dist/tool-schemas.js +34 -7
  89. package/dist/types.d.ts +35 -0
  90. package/dist/v1-parity-schemas.d.ts +7 -0
  91. package/dist/v1-parity-schemas.js +5584 -0
  92. package/dist/version-diff-service.d.ts +33 -0
  93. package/dist/version-diff-service.js +148 -3
  94. package/dist/version-service.js +36 -14
  95. package/dist/warning-details.js +18 -1
  96. package/docs/README-ja.md +9 -3
  97. package/docs/tool-reference.md +267 -22
  98. 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<"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 {};
@@ -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
@@ -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
- cleaned.push({ className, matchReason });
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/sdk/server/mcp.js";
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 };