@modelcontextprotocol/server 2.0.0-alpha.3 → 2.0.0-beta.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 (41) hide show
  1. package/README.md +7 -4
  2. package/dist/{ajvProvider-Birb50r-.mjs → ajvProvider-BQMcjynJ.mjs} +952 -154
  3. package/dist/ajvProvider-BQMcjynJ.mjs.map +1 -0
  4. package/dist/{ajvProvider-DZ_siXcF.d.mts → ajvProvider-Dzgk80kq.d.mts} +58 -11
  5. package/dist/ajvProvider-Dzgk80kq.d.mts.map +1 -0
  6. package/dist/{cfWorkerProvider-BrJKpSFH.mjs → cfWorkerProvider-BDC2rVl3.mjs} +21 -5
  7. package/dist/cfWorkerProvider-BDC2rVl3.mjs.map +1 -0
  8. package/dist/{cfWorkerProvider-DUhk5Ewx.d.mts → cfWorkerProvider-DmvjVsvQ.d.mts} +13 -6
  9. package/dist/cfWorkerProvider-DmvjVsvQ.d.mts.map +1 -0
  10. package/dist/{transport-DMKhEchd.d.mts → createMcpHandler-Du3hjXvf.d.mts} +5283 -1559
  11. package/dist/createMcpHandler-Du3hjXvf.d.mts.map +1 -0
  12. package/dist/index.d.mts +167 -2015
  13. package/dist/index.d.mts.map +1 -1
  14. package/dist/index.mjs +1238 -1281
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/mcp-JttQJlI9.mjs +9998 -0
  17. package/dist/mcp-JttQJlI9.mjs.map +1 -0
  18. package/dist/shimsNode.d.mts +1 -1
  19. package/dist/shimsNode.mjs +1 -1
  20. package/dist/shimsWorkerd.d.mts +1 -1
  21. package/dist/shimsWorkerd.mjs +1 -1
  22. package/dist/stdio.d.mts +61 -3
  23. package/dist/stdio.d.mts.map +1 -1
  24. package/dist/stdio.mjs +457 -2
  25. package/dist/stdio.mjs.map +1 -1
  26. package/dist/types-DBYdVs-n.d.mts +1099 -0
  27. package/dist/types-DBYdVs-n.d.mts.map +1 -0
  28. package/dist/validators/ajv.d.mts +1 -1
  29. package/dist/validators/ajv.mjs +1 -1
  30. package/dist/validators/cfWorker.d.mts +1 -1
  31. package/dist/validators/cfWorker.mjs +1 -1
  32. package/package.json +3 -6
  33. package/dist/ajvProvider-Birb50r-.mjs.map +0 -1
  34. package/dist/ajvProvider-DZ_siXcF.d.mts.map +0 -1
  35. package/dist/cfWorkerProvider-BrJKpSFH.mjs.map +0 -1
  36. package/dist/cfWorkerProvider-DUhk5Ewx.d.mts.map +0 -1
  37. package/dist/src-Pa1iAvsj.mjs +0 -3386
  38. package/dist/src-Pa1iAvsj.mjs.map +0 -1
  39. package/dist/transport-DMKhEchd.d.mts.map +0 -1
  40. package/dist/types-R2RTIcjk.d.mts +0 -66
  41. package/dist/types-R2RTIcjk.d.mts.map +0 -1
@@ -1,3 +1,3 @@
1
- import { t as AjvJsonSchemaValidator } from "./ajvProvider-DZ_siXcF.mjs";
1
+ import { t as AjvJsonSchemaValidator } from "./ajvProvider-Dzgk80kq.mjs";
2
2
  import process from "node:process";
3
3
  export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator, process };
@@ -1,4 +1,4 @@
1
- import { t as AjvJsonSchemaValidator } from "./ajvProvider-Birb50r-.mjs";
1
+ import { t as AjvJsonSchemaValidator } from "./ajvProvider-BQMcjynJ.mjs";
2
2
  import process from "node:process";
3
3
 
4
4
  export { AjvJsonSchemaValidator as DefaultJsonSchemaValidator, process };
@@ -1,4 +1,4 @@
1
- import { t as CfWorkerJsonSchemaValidator } from "./cfWorkerProvider-DUhk5Ewx.mjs";
1
+ import { t as CfWorkerJsonSchemaValidator } from "./cfWorkerProvider-DmvjVsvQ.mjs";
2
2
 
3
3
  //#region src/shimsWorkerd.d.ts
4
4
 
@@ -1,4 +1,4 @@
1
- import { t as CfWorkerJsonSchemaValidator } from "./cfWorkerProvider-BrJKpSFH.mjs";
1
+ import { t as CfWorkerJsonSchemaValidator } from "./cfWorkerProvider-BDC2rVl3.mjs";
2
2
 
3
3
  //#region src/shimsWorkerd.ts
4
4
  /**
package/dist/stdio.d.mts CHANGED
@@ -1,8 +1,66 @@
1
- import { gt as JSONRPCMessage, n as Transport } from "./transport-DMKhEchd.mjs";
1
+ import { cr as JSONRPCMessage, o as McpServerFactory, rt as Transport } from "./createMcpHandler-Du3hjXvf.mjs";
2
2
  import { Readable, Writable } from "node:stream";
3
3
 
4
- //#region src/server/stdio.d.ts
4
+ //#region src/server/serveStdio.d.ts
5
5
 
6
+ /** Options for {@linkcode serveStdio}. */
7
+ interface ServeStdioOptions {
8
+ /**
9
+ * How a 2025-era opening (an `initialize` request, or any claim-less
10
+ * message) is handled:
11
+ *
12
+ * - `'serve'` (default) — the connection is pinned to a 2025-era instance
13
+ * from the same factory and served exactly as a hand-wired stdio server
14
+ * serves it today.
15
+ * - `'reject'` — the opening request is answered with the
16
+ * unsupported-protocol-version error naming the supported modern
17
+ * revisions (claim-less notifications are dropped); the connection
18
+ * stays open for a modern opening.
19
+ */
20
+ legacy?: 'serve' | 'reject';
21
+ /**
22
+ * Bring your own transport (for example a `StdioServerTransport`
23
+ * constructed over a Unix domain socket or TCP stream, per the stdio
24
+ * binding's custom-transport guidance). Defaults to a
25
+ * {@linkcode StdioServerTransport} over the current process's stdio. The
26
+ * entry owns the transport: it starts it, receives every inbound message,
27
+ * and closes it when the connection ends.
28
+ */
29
+ transport?: Transport;
30
+ /** Callback for out-of-band errors (reporting only; it never alters what is written to the wire). */
31
+ onerror?: (error: Error) => void;
32
+ /**
33
+ * Reject a new `subscriptions/listen` with `-32603` 'Subscription limit
34
+ * reached' (in-band, before the ack) when this many subscriptions are
35
+ * already open on this connection.
36
+ * @default 1024
37
+ */
38
+ maxSubscriptions?: number;
39
+ }
40
+ /** The handle returned by {@linkcode serveStdio}. */
41
+ interface StdioServerHandle {
42
+ /** Tears the connection down: closes the pinned instance (if any) and the underlying transport. */
43
+ close(): Promise<void>;
44
+ }
45
+ /**
46
+ * Serves MCP over stdio from a server factory, owning the era decision for
47
+ * the connection: the opening exchange selects the era, ONE instance from the
48
+ * factory is pinned for the connection lifetime, and everything after passes
49
+ * straight through to it. See the module documentation for the opening rules.
50
+ *
51
+ * ```ts
52
+ * import { serveStdio } from '@modelcontextprotocol/server/stdio';
53
+ *
54
+ * serveStdio(() => {
55
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
56
+ * // register tools/resources/prompts once — the same factory serves both eras
57
+ * return server;
58
+ * });
59
+ * ```
60
+ */
61
+ declare function serveStdio(factory: McpServerFactory, options?: ServeStdioOptions): StdioServerHandle;
62
+ //#endregion
63
+ //#region src/server/stdio.d.ts
6
64
  /**
7
65
  * Server transport for stdio: this communicates with an MCP client by reading from the current process' `stdin` and writing to `stdout`.
8
66
  *
@@ -45,5 +103,5 @@ declare class StdioServerTransport implements Transport {
45
103
  send(message: JSONRPCMessage): Promise<void>;
46
104
  }
47
105
  //#endregion
48
- export { StdioServerTransport };
106
+ export { type ServeStdioOptions, type StdioServerHandle, StdioServerTransport, serveStdio };
49
107
  //# sourceMappingURL=stdio.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.mts","names":[],"sources":["../src/server/stdio.ts"],"sourcesContent":[],"mappings":";;;;;;;AAkBA;;;;;;;;;;AAoGkB,cApGL,oBAAA,YAAgC,SAoG3B,CAAA;EAAiB,QAAA,MAAA;EApGU,QAAA,OAAA;EAAS,QAAA,WAAA;;;uBAM9B,oBACC;;;;;;;;;;oBAeH;wBACI;mBAGJ;oBASC;0BAGM;;;;WAUV;;WA4BA;gBAwBD,iBAAiB"}
1
+ {"version":3,"file":"stdio.d.mts","names":[],"sources":["../src/server/serveStdio.ts","../src/server/stdio.ts"],"sourcesContent":[],"mappings":";;;;;;UAoFiB,iBAAA;;;;;;;;;;;;;;;;;;;;;;cAsBD;;oBAEM;;;;;;;;;;UAWL,iBAAA;;WAEJ;;;;;;;;;;;;;;;;;;iBA6PG,UAAA,UAAoB,4BAA2B,oBAAyB;;;;;AAlSxF;AAmCA;AA+PA;;;;;;;;ACpWa,cAAA,oBAAA,YAAgC,SAAX,CAAA;EAMV,QAAA,MAAA;EACC,QAAA,OAAA;EAeH,QAAA,WAAA;EACI,QAAA,QAAA;EAGJ,QAAA,OAAA;EASC,WAAA,CAAA,MAAA,CAAA,EA7BC,QA6BD,EAAA,OAAA,CAAA,EA5BE,QA4BF,EAAA,QAAA,EAAA;IAGM;;;;;;IAtCyB,aAAA,CAAA,EAAA,MAAA;;;oBAsBhC;wBACI;mBAGJ;oBASC;0BAGM;;;;WAUV;;WA4BA;gBAwBD,iBAAiB"}
package/dist/stdio.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as ReadBuffer, d as serializeMessage } from "./src-Pa1iAvsj.mjs";
1
+ import { $ as validateEnvelopeMeta, H as isJSONRPCErrorResponse, K as isJSONRPCResultResponse, M as modernOnlyStrictRejection, Q as requestMetaOf, S as setNegotiatedProtocolVersion, U as isJSONRPCNotification, W as isJSONRPCRequest, X as envelopeClaimVersion, Z as hasEnvelopeClaim, at as ProtocolErrorCode, c as StdioListenRouter, g as ReadBuffer, i as installModernOnlyHandlers, k as carriesValidModernEnvelopeClaim, ot as SUPPORTED_MODERN_PROTOCOL_VERSIONS, rt as UnsupportedProtocolVersionError, s as DEFAULT_MAX_SUBSCRIPTIONS, t as McpServer, y as serializeMessage } from "./mcp-JttQJlI9.mjs";
2
2
  import { process } from "@modelcontextprotocol/server/_shims";
3
3
 
4
4
  //#region src/server/stdio.ts
@@ -102,5 +102,460 @@ var StdioServerTransport = class {
102
102
  };
103
103
 
104
104
  //#endregion
105
- export { StdioServerTransport };
105
+ //#region src/server/serveStdio.ts
106
+ /**
107
+ * How long the probe-discard path waits for the probe instance to answer the
108
+ * requests it was delivered before closing it. The wait normally settles as
109
+ * soon as the DiscoverResult is handed to the wire (or immediately, when a
110
+ * delivered cancellation already settled the probe); the bound is a backstop
111
+ * so no edge can ever hold the connection's inbound pump indefinitely behind
112
+ * the discard.
113
+ */
114
+ const DISCARD_ANSWER_TIMEOUT_MS = 3e3;
115
+ /**
116
+ * The transport a pinned instance is connected to: a thin channel that writes
117
+ * through to the entry-owned wire transport and receives the messages the
118
+ * entry forwards. The wire transport itself is never handed to an instance —
119
+ * that is what lets the entry discard an optimistic probe instance (close the
120
+ * channel) without tearing down the connection.
121
+ */
122
+ var StdioConnectionChannel = class {
123
+ onclose;
124
+ onerror;
125
+ onmessage;
126
+ _closed = false;
127
+ /** Request ids the entry delivered to the instance that the instance has not yet answered. */
128
+ _pendingRequests = /* @__PURE__ */ new Set();
129
+ _drainWaiters = [];
130
+ constructor(_wire, _onInstanceClose, _outboundIntercept) {
131
+ this._wire = _wire;
132
+ this._onInstanceClose = _onInstanceClose;
133
+ this._outboundIntercept = _outboundIntercept;
134
+ }
135
+ async start() {}
136
+ async send(message, options) {
137
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
138
+ const { id } = message;
139
+ if (id !== void 0) this._settle(id);
140
+ }
141
+ if (this._closed) return;
142
+ if (this._outboundIntercept?.(message) === "handled") return;
143
+ return this._wire.send(message, options);
144
+ }
145
+ setProtocolVersion = (version) => {
146
+ this._wire.setProtocolVersion?.(version);
147
+ };
148
+ /** Forwards one inbound message to the connected instance. */
149
+ deliver(message, extra) {
150
+ if (this._closed) return;
151
+ if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
152
+ else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
153
+ const cancelledId = message.params?.requestId;
154
+ if (cancelledId !== void 0) this._settle(cancelledId);
155
+ }
156
+ this.onmessage?.(message, extra);
157
+ }
158
+ /**
159
+ * Resolves once every request delivered to the instance has been answered
160
+ * through {@linkcode send}, settled by a delivered cancellation, or the
161
+ * channel has been closed and nothing further can be answered. The wait is
162
+ * bounded by `timeoutMs` as a backstop so no edge can hold the caller
163
+ * indefinitely; resolves `false` only when the bound elapsed with requests
164
+ * still unanswered. Used by the probe-discard path so a probe request the
165
+ * entry accepted is never silently dropped.
166
+ */
167
+ async whenRequestsAnswered(timeoutMs) {
168
+ if (this._closed || this._pendingRequests.size === 0) return true;
169
+ return await new Promise((resolve) => {
170
+ const waiter = () => {
171
+ clearTimeout(timer);
172
+ resolve(true);
173
+ };
174
+ const timer = setTimeout(() => {
175
+ this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
176
+ resolve(false);
177
+ }, timeoutMs);
178
+ this._drainWaiters.push(waiter);
179
+ });
180
+ }
181
+ async close() {
182
+ if (this._closed) return;
183
+ this._closed = true;
184
+ this._pendingRequests.clear();
185
+ this._releaseDrainWaiters();
186
+ try {
187
+ this._onInstanceClose();
188
+ } finally {
189
+ this.onclose?.();
190
+ }
191
+ }
192
+ _settle(id) {
193
+ this._pendingRequests.delete(id);
194
+ if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
195
+ }
196
+ _releaseDrainWaiters() {
197
+ const waiters = this._drainWaiters;
198
+ this._drainWaiters = [];
199
+ for (const waiter of waiters) waiter();
200
+ }
201
+ };
202
+ /**
203
+ * Classifies one message of the opening exchange with the same body-primary
204
+ * rules the HTTP entry applies per request: `initialize` is the legacy
205
+ * handshake unless it carries a valid modern envelope claim; a present claim
206
+ * is validated (never silently ignored); a claim-less message is 2025-era
207
+ * traffic. There is no header layer on stdio, so the body is the only signal.
208
+ */
209
+ function classifyOpeningMessage(message) {
210
+ const params = message.params;
211
+ if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
212
+ const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
213
+ return {
214
+ kind: "legacy",
215
+ reason: "initialize",
216
+ ...requestedVersion !== void 0 && { requestedVersion }
217
+ };
218
+ }
219
+ if (!hasEnvelopeClaim(params)) return {
220
+ kind: "legacy",
221
+ reason: "no-claim"
222
+ };
223
+ const meta = requestMetaOf(params);
224
+ const firstIssue = (meta === void 0 ? [] : validateEnvelopeMeta(meta))[0];
225
+ if (firstIssue !== void 0) return {
226
+ kind: "invalid-envelope",
227
+ issue: firstIssue
228
+ };
229
+ const claimedVersion = envelopeClaimVersion(params);
230
+ if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
231
+ kind: "unsupported-revision",
232
+ requested: claimedVersion ?? "unknown"
233
+ };
234
+ return {
235
+ kind: "modern",
236
+ revision: claimedVersion,
237
+ classification: {
238
+ era: "modern",
239
+ revision: claimedVersion
240
+ }
241
+ };
242
+ }
243
+ /**
244
+ * Serves MCP over stdio from a server factory, owning the era decision for
245
+ * the connection: the opening exchange selects the era, ONE instance from the
246
+ * factory is pinned for the connection lifetime, and everything after passes
247
+ * straight through to it. See the module documentation for the opening rules.
248
+ *
249
+ * ```ts
250
+ * import { serveStdio } from '@modelcontextprotocol/server/stdio';
251
+ *
252
+ * serveStdio(() => {
253
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
254
+ * // register tools/resources/prompts once — the same factory serves both eras
255
+ * return server;
256
+ * });
257
+ * ```
258
+ */
259
+ function serveStdio(factory, options = {}) {
260
+ const legacyMode = options.legacy ?? "serve";
261
+ const wire = options.transport ?? new StdioServerTransport();
262
+ let state = { phase: "opening" };
263
+ /** Channel currently being discarded (its close must not tear the connection down). */
264
+ let discarding;
265
+ let closing = false;
266
+ /**
267
+ * Whether the connection has been torn down (`handle.close()` or the wire
268
+ * closing). The opening arms re-check this after every await: a close can
269
+ * race factory construction, and the continuation must neither resurrect
270
+ * the connection state nor keep a late-resolved instance around.
271
+ */
272
+ const isTornDown = () => closing || state.phase === "closed";
273
+ const reportError = (error) => {
274
+ try {
275
+ options.onerror?.(error);
276
+ } catch {}
277
+ };
278
+ const writeErrorResponse = (id, code, message, data) => wire.send({
279
+ jsonrpc: "2.0",
280
+ id,
281
+ error: {
282
+ code,
283
+ message,
284
+ ...data !== void 0 && { data }
285
+ }
286
+ }).catch((error) => reportError(toError(error)));
287
+ /**
288
+ * Entry-handled `subscriptions/listen` for this connection: holds the
289
+ * active subscriptions, serves inbound listen / cancelled-of-listen
290
+ * before the pinned instance is consulted, and rewrites the instance's
291
+ * outbound change notifications onto the active subscriptions. Only
292
+ * consulted on a modern-pinned connection — on a legacy connection
293
+ * change notifications pass straight through (the 2025 unsolicited
294
+ * delivery model is unchanged).
295
+ */
296
+ const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS);
297
+ /** Outbound intercept installed on a modern instance's channel. */
298
+ const modernOutboundIntercept = (message) => {
299
+ if (!isJSONRPCNotification(message)) return void 0;
300
+ const routed = listenRouter.routeOutbound(message);
301
+ if (routed === "passthrough") return void 0;
302
+ for (const stamped of routed) wire.send({
303
+ jsonrpc: "2.0",
304
+ ...stamped
305
+ }).catch((error) => reportError(toError(error)));
306
+ return "handled";
307
+ };
308
+ /**
309
+ * Entry-handled inbound listen routing for a modern-pinned connection.
310
+ * Returns `true` when the message was served at the entry and must NOT
311
+ * be delivered to the pinned instance.
312
+ */
313
+ const tryServeListen = async (message) => {
314
+ if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
315
+ const meta = requestMetaOf(message.params);
316
+ const issue = hasEnvelopeClaim(message.params) ? (meta === void 0 ? [] : validateEnvelopeMeta(meta))[0] : {
317
+ key: "_meta",
318
+ problem: "the per-request envelope is required on protocol revision 2026-07-28"
319
+ };
320
+ const claimedVersion = envelopeClaimVersion(message.params);
321
+ let reply;
322
+ if (issue !== void 0) reply = {
323
+ jsonrpc: "2.0",
324
+ id: message.id,
325
+ error: {
326
+ code: -32602,
327
+ message: `Invalid _meta envelope: ${issue.key}: ${issue.problem}`
328
+ }
329
+ };
330
+ else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
331
+ const error = new UnsupportedProtocolVersionError({
332
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
333
+ requested: claimedVersion ?? "unknown"
334
+ });
335
+ reply = {
336
+ jsonrpc: "2.0",
337
+ id: message.id,
338
+ error: {
339
+ code: error.code,
340
+ message: error.message,
341
+ data: error.data
342
+ }
343
+ };
344
+ } else reply = listenRouter.serve(message);
345
+ await wire.send("error" in reply ? reply : {
346
+ jsonrpc: "2.0",
347
+ method: reply.method,
348
+ params: reply.params
349
+ }).catch((error) => reportError(toError(error)));
350
+ return true;
351
+ }
352
+ if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
353
+ const cancelledId = message.params?.requestId;
354
+ if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
355
+ }
356
+ return false;
357
+ };
358
+ /** Answers a 2025-era request the entry will not serve (the modern-only rejection cells). */
359
+ const answerLegacyRejection = (request, reason, requestedVersion) => {
360
+ const rejection = modernOnlyStrictRejection({
361
+ kind: "legacy",
362
+ reason,
363
+ ...requestedVersion !== void 0 && { requestedVersion }
364
+ }, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
365
+ if (rejection === void 0) return Promise.resolve();
366
+ reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection.cell}): ${rejection.message}`));
367
+ return writeErrorResponse(request.id, rejection.code, rejection.message, rejection.data);
368
+ };
369
+ const onInstanceClosed = (channel) => {
370
+ if (closing || channel === discarding) return;
371
+ closeAll();
372
+ };
373
+ const connectInstance = async (era, revision) => {
374
+ const product = await factory({ era });
375
+ const server = product instanceof McpServer ? product.server : product;
376
+ if (era === "modern") {
377
+ setNegotiatedProtocolVersion(server, revision);
378
+ installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
379
+ listenRouter.setServerCapabilities(server.getCapabilities());
380
+ }
381
+ const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
382
+ await product.connect(channel);
383
+ return {
384
+ product,
385
+ channel
386
+ };
387
+ };
388
+ /** Closes an instance whose factory resolved only after the connection was torn down. */
389
+ const disposeLateInstance = (instance) => instance.product.close().catch((error) => reportError(toError(error)));
390
+ const discardProbeInstance = async (instance) => {
391
+ discarding = instance.channel;
392
+ try {
393
+ if (!await instance.channel.whenRequestsAnswered(DISCARD_ANSWER_TIMEOUT_MS)) reportError(/* @__PURE__ */ new Error(`Discarded the probe instance with requests still unanswered after ${DISCARD_ANSWER_TIMEOUT_MS}ms; continuing with the fallback`));
394
+ await instance.product.close();
395
+ } catch (error) {
396
+ reportError(toError(error));
397
+ } finally {
398
+ discarding = void 0;
399
+ }
400
+ };
401
+ const processMessage = async (message) => {
402
+ if (state.phase === "closed") return;
403
+ if (state.phase === "pinned") {
404
+ if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
405
+ await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
406
+ return;
407
+ }
408
+ if (state.era === "modern" && await tryServeListen(message)) return;
409
+ state.instance.channel.deliver(message);
410
+ return;
411
+ }
412
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
413
+ reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
414
+ return;
415
+ }
416
+ const opening = classifyOpeningMessage(message);
417
+ switch (opening.kind) {
418
+ case "invalid-envelope": {
419
+ const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
420
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
421
+ else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
422
+ return;
423
+ }
424
+ case "unsupported-revision":
425
+ if (isJSONRPCRequest(message)) {
426
+ const error = new UnsupportedProtocolVersionError({
427
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
428
+ requested: opening.requested
429
+ });
430
+ reportError(error);
431
+ await writeErrorResponse(message.id, error.code, error.message, error.data);
432
+ } else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
433
+ return;
434
+ case "modern":
435
+ if (isJSONRPCRequest(message) && message.method === "server/discover") {
436
+ if (state.phase === "probe") {
437
+ state.instance.channel.deliver(message, { classification: opening.classification });
438
+ return;
439
+ }
440
+ const instance = await connectInstance("modern", opening.revision);
441
+ if (isTornDown()) {
442
+ await disposeLateInstance(instance);
443
+ return;
444
+ }
445
+ state = {
446
+ phase: "probe",
447
+ instance
448
+ };
449
+ instance.channel.deliver(message, { classification: opening.classification });
450
+ return;
451
+ }
452
+ if (state.phase === "probe") {
453
+ if (isJSONRPCNotification(message)) {
454
+ state.instance.channel.deliver(message, { classification: opening.classification });
455
+ return;
456
+ }
457
+ state = {
458
+ phase: "pinned",
459
+ era: "modern",
460
+ instance: state.instance
461
+ };
462
+ } else {
463
+ const instance = await connectInstance("modern", opening.revision);
464
+ if (isTornDown()) {
465
+ await disposeLateInstance(instance);
466
+ return;
467
+ }
468
+ state = {
469
+ phase: "pinned",
470
+ era: "modern",
471
+ instance
472
+ };
473
+ }
474
+ if (await tryServeListen(message)) return;
475
+ state.instance.channel.deliver(message, { classification: opening.classification });
476
+ return;
477
+ case "legacy": {
478
+ if (legacyMode === "reject") {
479
+ if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
480
+ return;
481
+ }
482
+ if (state.phase === "probe") {
483
+ await discardProbeInstance(state.instance);
484
+ if (isTornDown()) return;
485
+ state = { phase: "opening" };
486
+ }
487
+ const instance = await connectInstance("legacy");
488
+ if (isTornDown()) {
489
+ await disposeLateInstance(instance);
490
+ return;
491
+ }
492
+ state = {
493
+ phase: "pinned",
494
+ era: "legacy",
495
+ instance
496
+ };
497
+ state.instance.channel.deliver(message);
498
+ return;
499
+ }
500
+ }
501
+ };
502
+ const queue = [];
503
+ let pumping = false;
504
+ const pump = async () => {
505
+ if (pumping) return;
506
+ pumping = true;
507
+ try {
508
+ while (queue.length > 0) {
509
+ const message = queue.shift();
510
+ try {
511
+ await processMessage(message);
512
+ } catch (error) {
513
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
514
+ reportError(toError(error));
515
+ }
516
+ }
517
+ } finally {
518
+ pumping = false;
519
+ }
520
+ };
521
+ const closeAll = async () => {
522
+ if (closing || state.phase === "closed") return;
523
+ closing = true;
524
+ const current = state;
525
+ state = { phase: "closed" };
526
+ for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error) => reportError(toError(error)));
527
+ if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error) => reportError(toError(error)));
528
+ await wire.close().catch((error) => reportError(toError(error)));
529
+ };
530
+ wire.onmessage = (message) => {
531
+ queue.push(message);
532
+ pump();
533
+ };
534
+ wire.onerror = (error) => {
535
+ reportError(error);
536
+ if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error);
537
+ };
538
+ wire.onclose = () => {
539
+ if (closing || state.phase === "closed") return;
540
+ closing = true;
541
+ const current = state;
542
+ state = { phase: "closed" };
543
+ if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error) => reportError(toError(error)));
544
+ };
545
+ const started = wire.start().catch((error) => {
546
+ reportError(toError(error));
547
+ throw error;
548
+ });
549
+ started.catch(() => {});
550
+ return { close: async () => {
551
+ await started.catch(() => {});
552
+ await closeAll();
553
+ } };
554
+ }
555
+ function toError(value) {
556
+ return value instanceof Error ? value : new Error(String(value));
557
+ }
558
+
559
+ //#endregion
560
+ export { StdioServerTransport, serveStdio };
106
561
  //# sourceMappingURL=stdio.mjs.map