@assistant-ui/mcp-docs-server 0.3.0 → 0.3.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.
- package/dist/proxy.js +22 -7
- package/dist/proxy.js.map +1 -1
- package/package.json +3 -3
- package/src/proxy.test.ts +135 -0
- package/src/proxy.ts +24 -9
package/dist/proxy.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { StreamableHTTPClientTransport, isInitializeRequest, isJSONRPCRequest, isJSONRPCResultResponse } from "@modelcontextprotocol/client";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
3
|
+
import { finished } from "node:stream";
|
|
3
4
|
//#region src/proxy.ts
|
|
4
5
|
const DEFAULT_URL = "https://www.assistant-ui.com/mcp";
|
|
5
6
|
const logError = (message, error) => {
|
|
6
7
|
console.error(`assistant-ui MCP proxy: ${message}`, error);
|
|
7
8
|
};
|
|
8
9
|
async function runProxy({ url = new URL(process.env.ASSISTANT_UI_MCP_URL ?? DEFAULT_URL), stdin = process.stdin, stdout = process.stdout } = {}) {
|
|
9
|
-
const
|
|
10
|
+
const input = stdin;
|
|
11
|
+
const stdio = new StdioServerTransport(input, stdout);
|
|
10
12
|
const http = new StreamableHTTPClientTransport(url);
|
|
11
13
|
let initializeRequestId;
|
|
12
14
|
let closing = false;
|
|
@@ -21,9 +23,14 @@ async function runProxy({ url = new URL(process.env.ASSISTANT_UI_MCP_URL ?? DEFA
|
|
|
21
23
|
logError("failed to close transport", error);
|
|
22
24
|
}).then(resolveClosed);
|
|
23
25
|
};
|
|
26
|
+
const logUnlessClosing = (message, error) => {
|
|
27
|
+
if (closing) return;
|
|
28
|
+
logError(message, error);
|
|
29
|
+
};
|
|
24
30
|
stdio.onmessage = (message) => {
|
|
25
|
-
if (isInitializeRequest(message)) initializeRequestId = message.id;
|
|
31
|
+
if (isJSONRPCRequest(message) && isInitializeRequest(message)) initializeRequestId = message.id;
|
|
26
32
|
http.send(message).catch(async (error) => {
|
|
33
|
+
if (closing) return;
|
|
27
34
|
logError("failed to forward message to hosted endpoint", error);
|
|
28
35
|
if (!isJSONRPCRequest(message)) return;
|
|
29
36
|
await stdio.send({
|
|
@@ -34,7 +41,7 @@ async function runProxy({ url = new URL(process.env.ASSISTANT_UI_MCP_URL ?? DEFA
|
|
|
34
41
|
message: "Failed to proxy request to the hosted MCP server"
|
|
35
42
|
}
|
|
36
43
|
}).catch((sendError) => {
|
|
37
|
-
|
|
44
|
+
logUnlessClosing("failed to return proxy error response", sendError);
|
|
38
45
|
});
|
|
39
46
|
});
|
|
40
47
|
};
|
|
@@ -44,14 +51,14 @@ async function runProxy({ url = new URL(process.env.ASSISTANT_UI_MCP_URL ?? DEFA
|
|
|
44
51
|
if (typeof protocolVersion === "string") http.setProtocolVersion(protocolVersion);
|
|
45
52
|
}
|
|
46
53
|
stdio.send(message).catch((error) => {
|
|
47
|
-
|
|
54
|
+
logUnlessClosing("failed to forward message to stdio", error);
|
|
48
55
|
});
|
|
49
56
|
};
|
|
50
57
|
stdio.onerror = (error) => {
|
|
51
|
-
|
|
58
|
+
logUnlessClosing("stdio transport error", error);
|
|
52
59
|
};
|
|
53
60
|
http.onerror = (error) => {
|
|
54
|
-
|
|
61
|
+
logUnlessClosing("HTTP transport error", error);
|
|
55
62
|
};
|
|
56
63
|
stdio.onclose = () => {
|
|
57
64
|
closeCounterpart(http);
|
|
@@ -59,16 +66,24 @@ async function runProxy({ url = new URL(process.env.ASSISTANT_UI_MCP_URL ?? DEFA
|
|
|
59
66
|
http.onclose = () => {
|
|
60
67
|
closeCounterpart(stdio);
|
|
61
68
|
};
|
|
69
|
+
let releaseInput;
|
|
62
70
|
try {
|
|
63
71
|
await http.start();
|
|
64
72
|
await stdio.start();
|
|
73
|
+
releaseInput = finished(input, { writable: false }, () => {
|
|
74
|
+
stdio.close().catch((error) => {
|
|
75
|
+
logError("failed to close transport", error);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
await closed;
|
|
65
79
|
} catch (error) {
|
|
66
80
|
closing = true;
|
|
67
81
|
await Promise.allSettled([http.close(), stdio.close()]);
|
|
68
82
|
resolveClosed();
|
|
69
83
|
throw error;
|
|
84
|
+
} finally {
|
|
85
|
+
releaseInput?.();
|
|
70
86
|
}
|
|
71
|
-
await closed;
|
|
72
87
|
}
|
|
73
88
|
//#endregion
|
|
74
89
|
export { runProxy };
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["import {\n StreamableHTTPClientTransport,\n isInitializeRequest,\n isJSONRPCRequest,\n isJSONRPCResultResponse,\n} from \"@modelcontextprotocol/client\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/server/stdio\";\nimport type
|
|
1
|
+
{"version":3,"file":"proxy.js","names":[],"sources":["../src/proxy.ts"],"sourcesContent":["import {\n StreamableHTTPClientTransport,\n isInitializeRequest,\n isJSONRPCRequest,\n isJSONRPCResultResponse,\n} from \"@modelcontextprotocol/client\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/server/stdio\";\nimport { type Readable, type Writable, finished } from \"node:stream\";\n\nconst DEFAULT_URL = \"https://www.assistant-ui.com/mcp\";\n\nconst logError = (message: string, error: unknown) => {\n console.error(`assistant-ui MCP proxy: ${message}`, error);\n};\n\nexport async function runProxy({\n url = new URL(process.env.ASSISTANT_UI_MCP_URL ?? DEFAULT_URL),\n stdin = process.stdin,\n stdout = process.stdout,\n}: {\n url?: URL;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n} = {}) {\n const input = stdin as Readable;\n const stdio = new StdioServerTransport(input, stdout as Writable);\n const http = new StreamableHTTPClientTransport(url);\n let initializeRequestId: string | number | undefined;\n let closing = false;\n let resolveClosed = () => {};\n const closed = new Promise<void>((resolve) => {\n resolveClosed = resolve;\n });\n\n const closeCounterpart = (counterpart: { close(): Promise<void> }) => {\n if (closing) return;\n closing = true;\n void counterpart\n .close()\n .catch((error: unknown) => {\n logError(\"failed to close transport\", error);\n })\n .then(resolveClosed);\n };\n\n const logUnlessClosing = (message: string, error: unknown) => {\n if (closing) return;\n logError(message, error);\n };\n\n stdio.onmessage = (message) => {\n if (isJSONRPCRequest(message) && isInitializeRequest(message)) {\n initializeRequestId = message.id;\n }\n\n void http.send(message).catch(async (error: unknown) => {\n if (closing) return;\n logError(\"failed to forward message to hosted endpoint\", error);\n if (!isJSONRPCRequest(message)) return;\n\n await stdio\n .send({\n jsonrpc: \"2.0\",\n id: message.id,\n error: {\n code: -32603,\n message: \"Failed to proxy request to the hosted MCP server\",\n },\n })\n .catch((sendError: unknown) => {\n logUnlessClosing(\"failed to return proxy error response\", sendError);\n });\n });\n };\n\n http.onmessage = (message) => {\n if (\n isJSONRPCResultResponse(message) &&\n message.id === initializeRequestId\n ) {\n const protocolVersion = (\n message.result as { protocolVersion?: unknown } | null\n )?.protocolVersion;\n if (typeof protocolVersion === \"string\") {\n http.setProtocolVersion(protocolVersion);\n }\n }\n\n void stdio.send(message).catch((error: unknown) => {\n logUnlessClosing(\"failed to forward message to stdio\", error);\n });\n };\n\n stdio.onerror = (error) => {\n logUnlessClosing(\"stdio transport error\", error);\n };\n http.onerror = (error) => {\n logUnlessClosing(\"HTTP transport error\", error);\n };\n stdio.onclose = () => {\n closeCounterpart(http);\n };\n http.onclose = () => {\n closeCounterpart(stdio);\n };\n\n let releaseInput: (() => void) | undefined;\n\n try {\n await http.start();\n await stdio.start();\n releaseInput = finished(input, { writable: false }, () => {\n void stdio.close().catch((error: unknown) => {\n logError(\"failed to close transport\", error);\n });\n });\n await closed;\n } catch (error) {\n closing = true;\n await Promise.allSettled([http.close(), stdio.close()]);\n resolveClosed();\n throw error;\n } finally {\n releaseInput?.();\n }\n}\n"],"mappings":";;;;AASA,MAAM,cAAc;AAEpB,MAAM,YAAY,SAAiB,UAAmB;CACpD,QAAQ,MAAM,2BAA2B,WAAW,KAAK;AAC3D;AAEA,eAAsB,SAAS,EAC7B,MAAM,IAAI,IAAI,QAAQ,IAAI,wBAAwB,WAAW,GAC7D,QAAQ,QAAQ,OAChB,SAAS,QAAQ,WAKf,CAAC,GAAG;CACN,MAAM,QAAQ;CACd,MAAM,QAAQ,IAAI,qBAAqB,OAAO,MAAkB;CAChE,MAAM,OAAO,IAAI,8BAA8B,GAAG;CAClD,IAAI;CACJ,IAAI,UAAU;CACd,IAAI,sBAAsB,CAAC;CAC3B,MAAM,SAAS,IAAI,SAAe,YAAY;EAC5C,gBAAgB;CAClB,CAAC;CAED,MAAM,oBAAoB,gBAA4C;EACpE,IAAI,SAAS;EACb,UAAU;EACV,YACG,MAAM,CAAC,CACP,OAAO,UAAmB;GACzB,SAAS,6BAA6B,KAAK;EAC7C,CAAC,CAAC,CACD,KAAK,aAAa;CACvB;CAEA,MAAM,oBAAoB,SAAiB,UAAmB;EAC5D,IAAI,SAAS;EACb,SAAS,SAAS,KAAK;CACzB;CAEA,MAAM,aAAa,YAAY;EAC7B,IAAI,iBAAiB,OAAO,KAAK,oBAAoB,OAAO,GAC1D,sBAAsB,QAAQ;EAGhC,KAAU,KAAK,OAAO,CAAC,CAAC,MAAM,OAAO,UAAmB;GACtD,IAAI,SAAS;GACb,SAAS,gDAAgD,KAAK;GAC9D,IAAI,CAAC,iBAAiB,OAAO,GAAG;GAEhC,MAAM,MACH,KAAK;IACJ,SAAS;IACT,IAAI,QAAQ;IACZ,OAAO;KACL,MAAM;KACN,SAAS;IACX;GACF,CAAC,CAAC,CACD,OAAO,cAAuB;IAC7B,iBAAiB,yCAAyC,SAAS;GACrE,CAAC;EACL,CAAC;CACH;CAEA,KAAK,aAAa,YAAY;EAC5B,IACE,wBAAwB,OAAO,KAC/B,QAAQ,OAAO,qBACf;GACA,MAAM,kBACJ,QAAQ,QACP;GACH,IAAI,OAAO,oBAAoB,UAC7B,KAAK,mBAAmB,eAAe;EAE3C;EAEA,MAAW,KAAK,OAAO,CAAC,CAAC,OAAO,UAAmB;GACjD,iBAAiB,sCAAsC,KAAK;EAC9D,CAAC;CACH;CAEA,MAAM,WAAW,UAAU;EACzB,iBAAiB,yBAAyB,KAAK;CACjD;CACA,KAAK,WAAW,UAAU;EACxB,iBAAiB,wBAAwB,KAAK;CAChD;CACA,MAAM,gBAAgB;EACpB,iBAAiB,IAAI;CACvB;CACA,KAAK,gBAAgB;EACnB,iBAAiB,KAAK;CACxB;CAEA,IAAI;CAEJ,IAAI;EACF,MAAM,KAAK,MAAM;EACjB,MAAM,MAAM,MAAM;EAClB,eAAe,SAAS,OAAO,EAAE,UAAU,MAAM,SAAS;GACxD,MAAW,MAAM,CAAC,CAAC,OAAO,UAAmB;IAC3C,SAAS,6BAA6B,KAAK;GAC7C,CAAC;EACH,CAAC;EACD,MAAM;CACR,SAAS,OAAO;EACd,UAAU;EACV,MAAM,QAAQ,WAAW,CAAC,KAAK,MAAM,GAAG,MAAM,MAAM,CAAC,CAAC;EACtD,cAAc;EACd,MAAM;CACR,UAAU;EACR,eAAe;CACjB;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/mcp-docs-server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Stdio proxy for the hosted assistant-ui documentation MCP server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"@modelcontextprotocol/server": "^2.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
38
|
+
"@assistant-ui/x-buildutils": "0.0.26",
|
|
39
39
|
"@modelcontextprotocol/core": "^2.0.0",
|
|
40
|
-
"@types/node": "^26.4.
|
|
40
|
+
"@types/node": "^26.4.1",
|
|
41
41
|
"tsx": "^4.23.13",
|
|
42
42
|
"vitest": "^4.1.11"
|
|
43
43
|
},
|
package/src/proxy.test.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { once } from "node:events";
|
|
1
3
|
import {
|
|
2
4
|
createServer,
|
|
3
5
|
type IncomingMessage,
|
|
@@ -147,6 +149,139 @@ const createRpcClient = (stdin: PassThrough, stdout: PassThrough) => {
|
|
|
147
149
|
};
|
|
148
150
|
|
|
149
151
|
describe("runProxy", () => {
|
|
152
|
+
it.each([
|
|
153
|
+
{
|
|
154
|
+
scenario: "stdin reached EOF before startup",
|
|
155
|
+
createStdin: async () => {
|
|
156
|
+
const stdin = new PassThrough();
|
|
157
|
+
const ended = once(stdin, "end");
|
|
158
|
+
stdin.resume();
|
|
159
|
+
stdin.end();
|
|
160
|
+
await ended;
|
|
161
|
+
return stdin;
|
|
162
|
+
},
|
|
163
|
+
stopInput: (stdin: PassThrough) => stdin.end(),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
scenario: "stdin reaches EOF after startup",
|
|
167
|
+
createStdin: async () => new PassThrough(),
|
|
168
|
+
stopInput: (stdin: PassThrough) => stdin.end(),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
scenario: "stdin ends without emitting close",
|
|
172
|
+
createStdin: async () => new PassThrough({ autoDestroy: false }),
|
|
173
|
+
stopInput: (stdin: PassThrough) => stdin.end(),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
scenario: "stdin is destroyed without EOF",
|
|
177
|
+
createStdin: async () => new PassThrough(),
|
|
178
|
+
stopInput: (stdin: PassThrough) => stdin.destroy(),
|
|
179
|
+
},
|
|
180
|
+
])("closes when $scenario", async ({ createStdin, stopInput }) => {
|
|
181
|
+
const stdin = await createStdin();
|
|
182
|
+
const stdout = new PassThrough();
|
|
183
|
+
const stderr = vi
|
|
184
|
+
.spyOn(console, "error")
|
|
185
|
+
.mockImplementation(() => undefined);
|
|
186
|
+
const proxy = runProxy({
|
|
187
|
+
url: new URL("https://example.invalid/mcp"),
|
|
188
|
+
stdin,
|
|
189
|
+
stdout,
|
|
190
|
+
});
|
|
191
|
+
let closed = false;
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
195
|
+
stopInput(stdin);
|
|
196
|
+
await expect(
|
|
197
|
+
withTimeout(proxy, "proxy shutdown after stdin closed"),
|
|
198
|
+
).resolves.toBeUndefined();
|
|
199
|
+
closed = true;
|
|
200
|
+
} finally {
|
|
201
|
+
stdout.destroy(closed ? undefined : new Error("Proxy test cleanup"));
|
|
202
|
+
await proxy;
|
|
203
|
+
stdin.destroy();
|
|
204
|
+
stderr.mockRestore();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("does not log errors caused by clean shutdown", async () => {
|
|
209
|
+
let resolvePendingRequest = () => {};
|
|
210
|
+
const pendingRequest = new Promise<void>((resolve) => {
|
|
211
|
+
resolvePendingRequest = resolve;
|
|
212
|
+
});
|
|
213
|
+
const httpServer = createServer(async (request, response) => {
|
|
214
|
+
if (request.method !== "POST") {
|
|
215
|
+
response.statusCode = 405;
|
|
216
|
+
response.end();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let body = "";
|
|
221
|
+
for await (const chunk of request) body += chunk.toString();
|
|
222
|
+
const message = JSON.parse(body) as { method: string; id?: number };
|
|
223
|
+
if (message.method === "initialize") {
|
|
224
|
+
response.setHeader("content-type", "application/json");
|
|
225
|
+
response.end(
|
|
226
|
+
JSON.stringify({
|
|
227
|
+
jsonrpc: "2.0",
|
|
228
|
+
id: message.id,
|
|
229
|
+
result: {
|
|
230
|
+
protocolVersion: "2025-11-25",
|
|
231
|
+
capabilities: {},
|
|
232
|
+
serverInfo: { name: "proxy-test", version: "1.0.0" },
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
response.writeHead(200, {
|
|
240
|
+
"content-type": "text/event-stream",
|
|
241
|
+
"cache-control": "no-cache",
|
|
242
|
+
});
|
|
243
|
+
response.write(": keep-alive\n\n");
|
|
244
|
+
resolvePendingRequest();
|
|
245
|
+
});
|
|
246
|
+
const url = await listen(httpServer);
|
|
247
|
+
const proxyStdin = new PassThrough();
|
|
248
|
+
const proxyStdout = new PassThrough();
|
|
249
|
+
const stderr = vi
|
|
250
|
+
.spyOn(console, "error")
|
|
251
|
+
.mockImplementation(() => undefined);
|
|
252
|
+
const proxy = runProxy({ url, stdin: proxyStdin, stdout: proxyStdout });
|
|
253
|
+
const client = createRpcClient(proxyStdin, proxyStdout);
|
|
254
|
+
let proxyClosed = false;
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const initialize = await client.request("initialize", {
|
|
258
|
+
protocolVersion: "2025-11-25",
|
|
259
|
+
capabilities: {},
|
|
260
|
+
clientInfo: { name: "proxy-test-client", version: "1.0.0" },
|
|
261
|
+
});
|
|
262
|
+
expect(initialize.error).toBeUndefined();
|
|
263
|
+
|
|
264
|
+
client.notify("notifications/initialized");
|
|
265
|
+
await withTimeout(pendingRequest, "pending request");
|
|
266
|
+
proxyStdin.end();
|
|
267
|
+
await expect(
|
|
268
|
+
withTimeout(proxy, "proxy shutdown after stdin closed"),
|
|
269
|
+
).resolves.toBeUndefined();
|
|
270
|
+
proxyClosed = true;
|
|
271
|
+
|
|
272
|
+
expect(stderr).not.toHaveBeenCalled();
|
|
273
|
+
} finally {
|
|
274
|
+
if (!proxyClosed) {
|
|
275
|
+
proxyStdin.destroy();
|
|
276
|
+
await proxy.catch(() => undefined);
|
|
277
|
+
}
|
|
278
|
+
await closeHttpServer(httpServer);
|
|
279
|
+
proxyStdout.destroy();
|
|
280
|
+
proxyStdin.destroy();
|
|
281
|
+
stderr.mockRestore();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
150
285
|
it("proxies MCP requests and returns transport failures", async () => {
|
|
151
286
|
const inputSchema = fromJsonSchema<{ text: string }>({
|
|
152
287
|
type: "object",
|
package/src/proxy.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isJSONRPCResultResponse,
|
|
6
6
|
} from "@modelcontextprotocol/client";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
8
|
-
import type
|
|
8
|
+
import { type Readable, type Writable, finished } from "node:stream";
|
|
9
9
|
|
|
10
10
|
const DEFAULT_URL = "https://www.assistant-ui.com/mcp";
|
|
11
11
|
|
|
@@ -22,7 +22,8 @@ export async function runProxy({
|
|
|
22
22
|
stdin?: NodeJS.ReadableStream;
|
|
23
23
|
stdout?: NodeJS.WritableStream;
|
|
24
24
|
} = {}) {
|
|
25
|
-
const
|
|
25
|
+
const input = stdin as Readable;
|
|
26
|
+
const stdio = new StdioServerTransport(input, stdout as Writable);
|
|
26
27
|
const http = new StreamableHTTPClientTransport(url);
|
|
27
28
|
let initializeRequestId: string | number | undefined;
|
|
28
29
|
let closing = false;
|
|
@@ -42,12 +43,18 @@ export async function runProxy({
|
|
|
42
43
|
.then(resolveClosed);
|
|
43
44
|
};
|
|
44
45
|
|
|
46
|
+
const logUnlessClosing = (message: string, error: unknown) => {
|
|
47
|
+
if (closing) return;
|
|
48
|
+
logError(message, error);
|
|
49
|
+
};
|
|
50
|
+
|
|
45
51
|
stdio.onmessage = (message) => {
|
|
46
|
-
if (isInitializeRequest(message)) {
|
|
52
|
+
if (isJSONRPCRequest(message) && isInitializeRequest(message)) {
|
|
47
53
|
initializeRequestId = message.id;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
void http.send(message).catch(async (error: unknown) => {
|
|
57
|
+
if (closing) return;
|
|
51
58
|
logError("failed to forward message to hosted endpoint", error);
|
|
52
59
|
if (!isJSONRPCRequest(message)) return;
|
|
53
60
|
|
|
@@ -61,7 +68,7 @@ export async function runProxy({
|
|
|
61
68
|
},
|
|
62
69
|
})
|
|
63
70
|
.catch((sendError: unknown) => {
|
|
64
|
-
|
|
71
|
+
logUnlessClosing("failed to return proxy error response", sendError);
|
|
65
72
|
});
|
|
66
73
|
});
|
|
67
74
|
};
|
|
@@ -80,15 +87,15 @@ export async function runProxy({
|
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
void stdio.send(message).catch((error: unknown) => {
|
|
83
|
-
|
|
90
|
+
logUnlessClosing("failed to forward message to stdio", error);
|
|
84
91
|
});
|
|
85
92
|
};
|
|
86
93
|
|
|
87
94
|
stdio.onerror = (error) => {
|
|
88
|
-
|
|
95
|
+
logUnlessClosing("stdio transport error", error);
|
|
89
96
|
};
|
|
90
97
|
http.onerror = (error) => {
|
|
91
|
-
|
|
98
|
+
logUnlessClosing("HTTP transport error", error);
|
|
92
99
|
};
|
|
93
100
|
stdio.onclose = () => {
|
|
94
101
|
closeCounterpart(http);
|
|
@@ -97,15 +104,23 @@ export async function runProxy({
|
|
|
97
104
|
closeCounterpart(stdio);
|
|
98
105
|
};
|
|
99
106
|
|
|
107
|
+
let releaseInput: (() => void) | undefined;
|
|
108
|
+
|
|
100
109
|
try {
|
|
101
110
|
await http.start();
|
|
102
111
|
await stdio.start();
|
|
112
|
+
releaseInput = finished(input, { writable: false }, () => {
|
|
113
|
+
void stdio.close().catch((error: unknown) => {
|
|
114
|
+
logError("failed to close transport", error);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
await closed;
|
|
103
118
|
} catch (error) {
|
|
104
119
|
closing = true;
|
|
105
120
|
await Promise.allSettled([http.close(), stdio.close()]);
|
|
106
121
|
resolveClosed();
|
|
107
122
|
throw error;
|
|
123
|
+
} finally {
|
|
124
|
+
releaseInput?.();
|
|
108
125
|
}
|
|
109
|
-
|
|
110
|
-
await closed;
|
|
111
126
|
}
|