591 lines
22 KiB
Plaintext
591 lines
22 KiB
Plaintext
const require_chunk = require('./chunk-Bnu9O96Y.cjs');
|
|
const require_mcp = require('./mcp-BumlNux0.cjs');
|
|
const require_src = require('./src-BhnGYybN.cjs');
|
|
let _modelcontextprotocol_server__shims = require("@modelcontextprotocol/server/_shims");
|
|
|
|
//#region src/server/stdio.ts
|
|
/**
|
|
* Marks a stdout `'error'` listener that a closed transport left attached purely to
|
|
* swallow late write failures (see {@link StdioServerTransport.close}). A fresh
|
|
* transport taking over the same stream removes listeners carrying this tag in
|
|
* `start()`, so repeated create→close cycles on the process-global `process.stdout`
|
|
* don't accumulate listeners.
|
|
*/
|
|
const swallowsErrorsAfterClose = Symbol("swallowsErrorsAfterClose");
|
|
/**
|
|
* Server transport for stdio: this communicates with an MCP client by reading from the current process' `stdin` and writing to `stdout`.
|
|
*
|
|
* This transport is only available in Node.js environments.
|
|
*
|
|
* When the client closes its end of the pipe (stdin reaches end-of-file), the transport
|
|
* closes itself and fires `onclose`, per the MCP stdio binding's guidance that servers
|
|
* should exit promptly when their standard input is closed. A server that holds no other
|
|
* keep-alive handles will then exit naturally. Requests still in flight when stdin ends are
|
|
* aborted and not answered; a client that expects responses keeps stdin open until it has
|
|
* read them.
|
|
*
|
|
* @example
|
|
* ```ts source="./stdio.examples.ts#StdioServerTransport_basicUsage"
|
|
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
* const transport = new StdioServerTransport();
|
|
* await server.connect(transport);
|
|
* ```
|
|
*/
|
|
var StdioServerTransport = class {
|
|
_readBuffer;
|
|
_started = false;
|
|
_closed = false;
|
|
constructor(_stdin = _modelcontextprotocol_server__shims.process.stdin, _stdout = _modelcontextprotocol_server__shims.process.stdout, options) {
|
|
this._stdin = _stdin;
|
|
this._stdout = _stdout;
|
|
this._readBuffer = new require_src.ReadBuffer({ maxBufferSize: options?.maxBufferSize });
|
|
}
|
|
onclose;
|
|
onerror;
|
|
onmessage;
|
|
_ondata = (chunk) => {
|
|
try {
|
|
this._readBuffer.append(chunk);
|
|
this.processReadBuffer();
|
|
} catch (error) {
|
|
this.onerror?.(error);
|
|
this.close().catch(() => {});
|
|
}
|
|
};
|
|
_onerror = (error) => {
|
|
this.onerror?.(error);
|
|
};
|
|
_onstdouterror = (error) => {
|
|
if (this._closed) return;
|
|
this.onerror?.(error);
|
|
this.close().catch(() => {});
|
|
};
|
|
_onstdinclose = () => {
|
|
this.close().catch(() => {});
|
|
};
|
|
/**
|
|
* Starts listening for messages on `stdin`.
|
|
*/
|
|
async start() {
|
|
if (this._started) throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
this._started = true;
|
|
for (const listener of this._stdout.listeners("error")) if (listener[swallowsErrorsAfterClose]) this._stdout.off("error", listener);
|
|
if (this._stdin.readableEnded || this._stdin.destroyed) setImmediate(this._onstdinclose);
|
|
this._stdin.on("data", this._ondata);
|
|
this._stdin.on("error", this._onerror);
|
|
this._stdin.on("end", this._onstdinclose);
|
|
this._stdin.on("close", this._onstdinclose);
|
|
this._stdout.on("error", this._onstdouterror);
|
|
}
|
|
processReadBuffer() {
|
|
while (true) try {
|
|
const message = this._readBuffer.readMessage();
|
|
if (message === null) break;
|
|
this.onmessage?.(message);
|
|
} catch (error) {
|
|
this.onerror?.(error);
|
|
}
|
|
}
|
|
async close() {
|
|
if (this._closed) return;
|
|
this._closed = true;
|
|
this._stdin.off("data", this._ondata);
|
|
this._stdin.off("error", this._onerror);
|
|
this._stdin.off("end", this._onstdinclose);
|
|
this._stdin.off("close", this._onstdinclose);
|
|
this._onstdouterror[swallowsErrorsAfterClose] = true;
|
|
if (this._stdin.listenerCount("data") === 0) this._stdin.pause();
|
|
this._readBuffer.clear();
|
|
this.onclose?.();
|
|
}
|
|
send(message) {
|
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
return new Promise((resolve, reject) => {
|
|
const json = require_src.serializeMessage(message);
|
|
let settled = false;
|
|
const onError = (error) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
this._stdout.off("error", onError);
|
|
this._stdout.off("drain", onDrain);
|
|
reject(error);
|
|
};
|
|
const onDrain = () => {
|
|
if (settled) return;
|
|
settled = true;
|
|
this._stdout.off("error", onError);
|
|
this._stdout.off("drain", onDrain);
|
|
resolve();
|
|
};
|
|
this._stdout.once("error", onError);
|
|
if (this._stdout.write(json)) {
|
|
if (settled) return;
|
|
settled = true;
|
|
this._stdout.off("error", onError);
|
|
resolve();
|
|
} else if (!settled) this._stdout.once("drain", onDrain);
|
|
});
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/server/serveStdio.ts
|
|
/**
|
|
* How long the probe-discard path waits for the probe instance to answer the
|
|
* requests it was delivered before closing it. The wait normally settles as
|
|
* soon as the DiscoverResult is handed to the wire (or immediately, when a
|
|
* delivered cancellation already settled the probe); the bound is a backstop
|
|
* so no edge can ever hold the connection's inbound pump indefinitely behind
|
|
* the discard.
|
|
*/
|
|
const DISCARD_ANSWER_TIMEOUT_MS = 3e3;
|
|
/**
|
|
* The transport a pinned instance is connected to: a thin channel that writes
|
|
* through to the entry-owned wire transport and receives the messages the
|
|
* entry forwards. The wire transport itself is never handed to an instance —
|
|
* that is what lets the entry discard an optimistic probe instance (close the
|
|
* channel) without tearing down the connection.
|
|
*/
|
|
var StdioConnectionChannel = class {
|
|
onclose;
|
|
onerror;
|
|
onmessage;
|
|
_closed = false;
|
|
/** Request ids the entry delivered to the instance that the instance has not yet answered. */
|
|
_pendingRequests = /* @__PURE__ */ new Set();
|
|
_drainWaiters = [];
|
|
constructor(_wire, _onInstanceClose, _outboundIntercept) {
|
|
this._wire = _wire;
|
|
this._onInstanceClose = _onInstanceClose;
|
|
this._outboundIntercept = _outboundIntercept;
|
|
}
|
|
async start() {}
|
|
async send(message, options) {
|
|
if (require_src.isJSONRPCResultResponse(message) || require_src.isJSONRPCErrorResponse(message)) {
|
|
const { id } = message;
|
|
if (id !== void 0) this._settle(id);
|
|
}
|
|
if (this._closed) return;
|
|
if (this._outboundIntercept?.(message) === "handled") return;
|
|
return this._wire.send(message, options);
|
|
}
|
|
setProtocolVersion = (version) => {
|
|
this._wire.setProtocolVersion?.(version);
|
|
};
|
|
/** Forwards one inbound message to the connected instance. */
|
|
deliver(message, extra) {
|
|
if (this._closed) return;
|
|
if (require_src.isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
|
|
else if (require_src.isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
|
|
const cancelledId = message.params?.requestId;
|
|
if (cancelledId !== void 0) this._settle(cancelledId);
|
|
}
|
|
this.onmessage?.(message, extra);
|
|
}
|
|
/**
|
|
* Resolves once every request delivered to the instance has been answered
|
|
* through {@linkcode send}, settled by a delivered cancellation, or the
|
|
* channel has been closed and nothing further can be answered. The wait is
|
|
* bounded by `timeoutMs` as a backstop so no edge can hold the caller
|
|
* indefinitely; resolves `false` only when the bound elapsed with requests
|
|
* still unanswered. Used by the probe-discard path so a probe request the
|
|
* entry accepted is never silently dropped.
|
|
*/
|
|
async whenRequestsAnswered(timeoutMs) {
|
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
return await new Promise((resolve) => {
|
|
const waiter = () => {
|
|
clearTimeout(timer);
|
|
resolve(true);
|
|
};
|
|
const timer = setTimeout(() => {
|
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
resolve(false);
|
|
}, timeoutMs);
|
|
this._drainWaiters.push(waiter);
|
|
});
|
|
}
|
|
async close() {
|
|
if (this._closed) return;
|
|
this._closed = true;
|
|
this._pendingRequests.clear();
|
|
this._releaseDrainWaiters();
|
|
try {
|
|
this._onInstanceClose();
|
|
} finally {
|
|
this.onclose?.();
|
|
}
|
|
}
|
|
_settle(id) {
|
|
this._pendingRequests.delete(id);
|
|
if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
|
|
}
|
|
_releaseDrainWaiters() {
|
|
const waiters = this._drainWaiters;
|
|
this._drainWaiters = [];
|
|
for (const waiter of waiters) waiter();
|
|
}
|
|
};
|
|
/**
|
|
* Classifies one message of the opening exchange with the same body-primary
|
|
* rules the HTTP entry applies per request: `initialize` is the legacy
|
|
* handshake unless it carries a valid modern envelope claim; a present claim
|
|
* is validated (never silently ignored); a claim-less message is 2025-era
|
|
* traffic. There is no header layer on stdio, so the body is the only signal.
|
|
*/
|
|
function classifyOpeningMessage(message) {
|
|
const params = message.params;
|
|
if (message.method === "initialize" && !require_src.carriesValidModernEnvelopeClaim(params)) {
|
|
const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
|
|
return {
|
|
kind: "legacy",
|
|
reason: "initialize",
|
|
...requestedVersion !== void 0 && { requestedVersion }
|
|
};
|
|
}
|
|
if (!require_src.hasEnvelopeClaim(params)) return {
|
|
kind: "legacy",
|
|
reason: "no-claim"
|
|
};
|
|
const meta = require_src.requestMetaOf(params);
|
|
const firstIssue = (meta === void 0 ? [] : require_src.validateEnvelopeMeta(meta))[0];
|
|
if (firstIssue !== void 0) return {
|
|
kind: "invalid-envelope",
|
|
issue: firstIssue
|
|
};
|
|
const claimedVersion = require_src.envelopeClaimVersion(params);
|
|
if (claimedVersion === void 0 || !require_src.SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
|
|
kind: "unsupported-revision",
|
|
requested: claimedVersion ?? "unknown"
|
|
};
|
|
return {
|
|
kind: "modern",
|
|
revision: claimedVersion,
|
|
classification: {
|
|
era: "modern",
|
|
revision: claimedVersion
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Serves MCP over stdio from a server factory, owning the era decision for
|
|
* the connection: the opening exchange selects the era, ONE instance from the
|
|
* factory is pinned for the connection lifetime, and everything after passes
|
|
* straight through to it. See the module documentation for the opening rules.
|
|
*
|
|
* ```ts
|
|
* import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
*
|
|
* serveStdio(() => {
|
|
* const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
* // register tools/resources/prompts once — the same factory serves both eras
|
|
* return server;
|
|
* });
|
|
* ```
|
|
*/
|
|
function serveStdio(factory, options = {}) {
|
|
const legacyMode = options.legacy ?? "serve";
|
|
const wire = options.transport ?? new StdioServerTransport();
|
|
let state = { phase: "opening" };
|
|
/** Channel currently being discarded (its close must not tear the connection down). */
|
|
let discarding;
|
|
let closing = false;
|
|
/**
|
|
* Whether the connection has been torn down (`handle.close()` or the wire
|
|
* closing). The opening arms re-check this after every await: a close can
|
|
* race factory construction, and the continuation must neither resurrect
|
|
* the connection state nor keep a late-resolved instance around.
|
|
*/
|
|
const isTornDown = () => closing || state.phase === "closed";
|
|
const reportError = (error) => {
|
|
try {
|
|
options.onerror?.(error);
|
|
} catch {}
|
|
};
|
|
const writeErrorResponse = (id, code, message, data) => wire.send({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
error: {
|
|
code,
|
|
message,
|
|
...data !== void 0 && { data }
|
|
}
|
|
}).catch((error) => reportError(toError(error)));
|
|
/**
|
|
* Entry-handled `subscriptions/listen` for this connection: holds the
|
|
* active subscriptions, serves inbound listen / cancelled-of-listen
|
|
* before the pinned instance is consulted, and rewrites the instance's
|
|
* outbound change notifications onto the active subscriptions. Only
|
|
* consulted on a modern-pinned connection — on a legacy connection
|
|
* change notifications pass straight through (the 2025 unsolicited
|
|
* delivery model is unchanged).
|
|
*/
|
|
const listenRouter = new require_mcp.StdioListenRouter(options.maxSubscriptions ?? require_mcp.DEFAULT_MAX_SUBSCRIPTIONS);
|
|
/** Outbound intercept installed on a modern instance's channel. */
|
|
const modernOutboundIntercept = (message) => {
|
|
if (!require_src.isJSONRPCNotification(message)) return void 0;
|
|
const routed = listenRouter.routeOutbound(message);
|
|
if (routed === "passthrough") return void 0;
|
|
for (const stamped of routed) wire.send({
|
|
jsonrpc: "2.0",
|
|
...stamped
|
|
}).catch((error) => reportError(toError(error)));
|
|
return "handled";
|
|
};
|
|
/**
|
|
* Entry-handled inbound listen routing for a modern-pinned connection.
|
|
* Returns `true` when the message was served at the entry and must NOT
|
|
* be delivered to the pinned instance.
|
|
*/
|
|
const tryServeListen = async (message) => {
|
|
if (require_src.isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
|
|
const meta = require_src.requestMetaOf(message.params);
|
|
const issue = require_src.hasEnvelopeClaim(message.params) ? (meta === void 0 ? [] : require_src.validateEnvelopeMeta(meta))[0] : {
|
|
key: "_meta",
|
|
problem: "the per-request envelope is required on protocol revision 2026-07-28"
|
|
};
|
|
const claimedVersion = require_src.envelopeClaimVersion(message.params);
|
|
let reply;
|
|
if (issue !== void 0) reply = {
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
error: {
|
|
code: -32602,
|
|
message: `Invalid _meta envelope: ${issue.key}: ${issue.problem}`
|
|
}
|
|
};
|
|
else if (claimedVersion === void 0 || !require_src.SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
|
|
const error = new require_src.UnsupportedProtocolVersionError({
|
|
supported: [...require_src.SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
requested: claimedVersion ?? "unknown"
|
|
});
|
|
reply = {
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
error: {
|
|
code: error.code,
|
|
message: error.message,
|
|
data: error.data
|
|
}
|
|
};
|
|
} else reply = listenRouter.serve(message);
|
|
await wire.send("error" in reply ? reply : {
|
|
jsonrpc: "2.0",
|
|
method: reply.method,
|
|
params: reply.params
|
|
}).catch((error) => reportError(toError(error)));
|
|
return true;
|
|
}
|
|
if (require_src.isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
|
|
const cancelledId = message.params?.requestId;
|
|
if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
|
|
}
|
|
return false;
|
|
};
|
|
/** Answers a 2025-era request the entry will not serve (the modern-only rejection cells). */
|
|
const answerLegacyRejection = (request, reason, requestedVersion) => {
|
|
const rejection = require_src.modernOnlyStrictRejection({
|
|
kind: "legacy",
|
|
reason,
|
|
...requestedVersion !== void 0 && { requestedVersion }
|
|
}, require_src.SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
if (rejection === void 0) return Promise.resolve();
|
|
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection.cell}): ${rejection.message}`));
|
|
return writeErrorResponse(request.id, rejection.code, rejection.message, rejection.data);
|
|
};
|
|
const onInstanceClosed = (channel) => {
|
|
if (closing || channel === discarding) return;
|
|
closeAll();
|
|
};
|
|
const connectInstance = async (era, revision) => {
|
|
const product = await factory({ era });
|
|
const server = product instanceof require_mcp.McpServer ? product.server : product;
|
|
if (era === "modern") {
|
|
require_src.setNegotiatedProtocolVersion(server, revision);
|
|
require_mcp.installModernOnlyHandlers(server, require_src.SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
listenRouter.setServerCapabilities(server.getCapabilities(), require_mcp.serverIdentityOf(server));
|
|
}
|
|
const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
|
|
await product.connect(channel);
|
|
return {
|
|
product,
|
|
channel
|
|
};
|
|
};
|
|
/** Closes an instance whose factory resolved only after the connection was torn down. */
|
|
const disposeLateInstance = (instance) => instance.product.close().catch((error) => reportError(toError(error)));
|
|
const discardProbeInstance = async (instance) => {
|
|
discarding = instance.channel;
|
|
try {
|
|
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`));
|
|
await instance.product.close();
|
|
} catch (error) {
|
|
reportError(toError(error));
|
|
} finally {
|
|
discarding = void 0;
|
|
}
|
|
};
|
|
const processMessage = async (message) => {
|
|
if (state.phase === "closed") return;
|
|
if (state.phase === "pinned") {
|
|
if (state.era === "modern" && require_src.isJSONRPCRequest(message) && message.method === "initialize" && !require_src.carriesValidModernEnvelopeClaim(message.params)) {
|
|
await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
|
|
return;
|
|
}
|
|
if (state.era === "modern" && await tryServeListen(message)) return;
|
|
if (isTornDown()) return;
|
|
state.instance.channel.deliver(message);
|
|
return;
|
|
}
|
|
if (!require_src.isJSONRPCRequest(message) && !require_src.isJSONRPCNotification(message)) {
|
|
reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
|
|
return;
|
|
}
|
|
const opening = classifyOpeningMessage(message);
|
|
switch (opening.kind) {
|
|
case "invalid-envelope": {
|
|
const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
|
|
if (require_src.isJSONRPCRequest(message)) await writeErrorResponse(message.id, require_src.ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
|
|
else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
|
|
return;
|
|
}
|
|
case "unsupported-revision":
|
|
if (require_src.isJSONRPCRequest(message)) {
|
|
const error = new require_src.UnsupportedProtocolVersionError({
|
|
supported: [...require_src.SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
requested: opening.requested
|
|
});
|
|
reportError(error);
|
|
await writeErrorResponse(message.id, error.code, error.message, error.data);
|
|
} else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
|
|
return;
|
|
case "modern":
|
|
if (require_src.isJSONRPCRequest(message) && message.method === "server/discover") {
|
|
if (state.phase === "probe") {
|
|
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
return;
|
|
}
|
|
const instance = await connectInstance("modern", opening.revision);
|
|
if (isTornDown()) {
|
|
await disposeLateInstance(instance);
|
|
return;
|
|
}
|
|
state = {
|
|
phase: "probe",
|
|
instance
|
|
};
|
|
instance.channel.deliver(message, { classification: opening.classification });
|
|
return;
|
|
}
|
|
if (state.phase === "probe") {
|
|
if (require_src.isJSONRPCNotification(message)) {
|
|
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
return;
|
|
}
|
|
state = {
|
|
phase: "pinned",
|
|
era: "modern",
|
|
instance: state.instance
|
|
};
|
|
} else {
|
|
const instance = await connectInstance("modern", opening.revision);
|
|
if (isTornDown()) {
|
|
await disposeLateInstance(instance);
|
|
return;
|
|
}
|
|
state = {
|
|
phase: "pinned",
|
|
era: "modern",
|
|
instance
|
|
};
|
|
}
|
|
if (await tryServeListen(message)) return;
|
|
if (isTornDown()) return;
|
|
state.instance.channel.deliver(message, { classification: opening.classification });
|
|
return;
|
|
case "legacy": {
|
|
if (legacyMode === "reject") {
|
|
if (require_src.isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
|
|
return;
|
|
}
|
|
if (state.phase === "probe") {
|
|
await discardProbeInstance(state.instance);
|
|
if (isTornDown()) return;
|
|
state = { phase: "opening" };
|
|
}
|
|
const instance = await connectInstance("legacy");
|
|
if (isTornDown()) {
|
|
await disposeLateInstance(instance);
|
|
return;
|
|
}
|
|
state = {
|
|
phase: "pinned",
|
|
era: "legacy",
|
|
instance
|
|
};
|
|
state.instance.channel.deliver(message);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
const queue = [];
|
|
let pumping = false;
|
|
const pump = async () => {
|
|
if (pumping) return;
|
|
pumping = true;
|
|
try {
|
|
while (queue.length > 0) {
|
|
const message = queue.shift();
|
|
try {
|
|
await processMessage(message);
|
|
} catch (error) {
|
|
if (require_src.isJSONRPCRequest(message)) await writeErrorResponse(message.id, require_src.ProtocolErrorCode.InternalError, "Internal server error");
|
|
reportError(toError(error));
|
|
}
|
|
}
|
|
} finally {
|
|
pumping = false;
|
|
}
|
|
};
|
|
const closeAll = async () => {
|
|
if (closing || state.phase === "closed") return;
|
|
closing = true;
|
|
const current = state;
|
|
state = { phase: "closed" };
|
|
for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error) => reportError(toError(error)));
|
|
if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error) => reportError(toError(error)));
|
|
await wire.close().catch((error) => reportError(toError(error)));
|
|
};
|
|
wire.onmessage = (message) => {
|
|
queue.push(message);
|
|
pump();
|
|
};
|
|
wire.onerror = (error) => {
|
|
reportError(error);
|
|
if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error);
|
|
};
|
|
wire.onclose = () => {
|
|
if (closing || state.phase === "closed") return;
|
|
closing = true;
|
|
const current = state;
|
|
state = { phase: "closed" };
|
|
if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error) => reportError(toError(error)));
|
|
};
|
|
const started = wire.start().catch((error) => {
|
|
reportError(toError(error));
|
|
throw error;
|
|
});
|
|
started.catch(() => {});
|
|
return { close: async () => {
|
|
await started.catch(() => {});
|
|
await closeAll();
|
|
} };
|
|
}
|
|
function toError(value) {
|
|
return value instanceof Error ? value : new Error(String(value));
|
|
}
|
|
|
|
//#endregion
|
|
exports.StdioServerTransport = StdioServerTransport;
|
|
exports.serveStdio = serveStdio;
|
|
//# sourceMappingURL=stdio.cjs.map |