@silver886/mcp-proxy 0.1.3 → 0.2.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 (69) hide show
  1. package/README.md +62 -17
  2. package/dist/host/agent.d.ts +22 -0
  3. package/dist/host/agent.js +314 -0
  4. package/dist/host/cli.d.ts +1 -0
  5. package/dist/host/cli.js +83 -0
  6. package/dist/host/constants.d.ts +4 -0
  7. package/dist/host/constants.js +16 -0
  8. package/dist/host/session.d.ts +21 -0
  9. package/dist/host/session.js +204 -0
  10. package/dist/host/tunnel.d.ts +5 -0
  11. package/dist/host/tunnel.js +82 -0
  12. package/dist/host.js +8 -0
  13. package/dist/proxy/core/constants.d.ts +13 -0
  14. package/dist/proxy/core/constants.js +39 -0
  15. package/dist/proxy/core/fetch-timeout.d.ts +1 -0
  16. package/dist/proxy/core/fetch-timeout.js +15 -0
  17. package/dist/proxy/core/state.d.ts +25 -0
  18. package/dist/proxy/core/state.js +90 -0
  19. package/dist/proxy/core/types.d.ts +57 -0
  20. package/dist/proxy/core/types.js +5 -0
  21. package/dist/proxy/discovery/client.d.ts +42 -0
  22. package/dist/proxy/discovery/client.js +283 -0
  23. package/dist/proxy/discovery/runner.d.ts +21 -0
  24. package/dist/proxy/discovery/runner.js +319 -0
  25. package/dist/proxy/pairing/config.d.ts +9 -0
  26. package/dist/proxy/pairing/config.js +130 -0
  27. package/dist/proxy/pairing/controller.d.ts +19 -0
  28. package/dist/proxy/pairing/controller.js +327 -0
  29. package/dist/proxy/pairing/http.d.ts +70 -0
  30. package/dist/proxy/pairing/http.js +155 -0
  31. package/dist/proxy/pairing/static-assets.d.ts +4 -0
  32. package/dist/proxy/pairing/static-assets.js +13 -0
  33. package/dist/proxy/pairing/tunnel.d.ts +13 -0
  34. package/dist/proxy/pairing/tunnel.js +130 -0
  35. package/dist/proxy/pairing/validation.d.ts +2 -0
  36. package/dist/proxy/pairing/validation.js +62 -0
  37. package/dist/proxy/routing/filtering.d.ts +13 -0
  38. package/dist/proxy/routing/filtering.js +116 -0
  39. package/dist/proxy/routing/router.d.ts +17 -0
  40. package/dist/proxy/routing/router.js +74 -0
  41. package/dist/proxy/routing/uri.d.ts +7 -0
  42. package/dist/proxy/routing/uri.js +39 -0
  43. package/dist/proxy/runtime/forwarder.d.ts +15 -0
  44. package/dist/proxy/runtime/forwarder.js +265 -0
  45. package/dist/proxy/runtime/handlers.d.ts +48 -0
  46. package/dist/proxy/runtime/handlers.js +329 -0
  47. package/dist/proxy/runtime/sse.d.ts +19 -0
  48. package/dist/proxy/runtime/sse.js +169 -0
  49. package/dist/proxy/runtime/upstream-bridge.d.ts +27 -0
  50. package/dist/proxy/runtime/upstream-bridge.js +133 -0
  51. package/dist/proxy/server.d.ts +15 -0
  52. package/dist/proxy/server.js +167 -0
  53. package/dist/proxy.js +5 -0
  54. package/{mcp/dist → dist}/shared/protocol.d.ts +15 -3
  55. package/dist/shared/protocol.js +183 -0
  56. package/dist/wrapper.d.ts +2 -0
  57. package/dist/wrapper.js +72 -0
  58. package/package.json +15 -7
  59. package/static/setup.css +233 -0
  60. package/static/setup.html +57 -0
  61. package/static/setup.js +711 -0
  62. package/static/style.css +208 -0
  63. package/mcp/dist/host.js +0 -307
  64. package/mcp/dist/proxy.js +0 -374
  65. package/mcp/dist/shared/generated.d.ts +0 -2
  66. package/mcp/dist/shared/generated.js +0 -5
  67. package/mcp/dist/shared/protocol.js +0 -79
  68. /package/{mcp/dist → dist}/host.d.ts +0 -0
  69. /package/{mcp/dist → dist}/proxy.d.ts +0 -0
package/mcp/dist/proxy.js DELETED
@@ -1,374 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const node_crypto_1 = require("node:crypto");
5
- const protocol_js_1 = require("./shared/protocol.js");
6
- const POLL_INTERVAL = 2000; // ms
7
- const TOOL_SEPARATOR = "__";
8
- const subtle = node_crypto_1.webcrypto.subtle;
9
- // --- Crypto helpers (AES-256-GCM + SHA-256 + HMAC) ---
10
- async function importAesKey(keyB64) {
11
- const raw = Buffer.from(keyB64, "base64url");
12
- return subtle.importKey("raw", raw, "AES-GCM", false, ["encrypt", "decrypt"]);
13
- }
14
- async function importHmacKey(keyB64) {
15
- const raw = Buffer.from(keyB64, "base64url");
16
- return subtle.importKey("raw", raw, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
17
- }
18
- async function deriveCodeId(code) {
19
- const hash = await subtle.digest("SHA-256", new TextEncoder().encode(code));
20
- return Buffer.from(hash).toString("base64url");
21
- }
22
- async function deriveAuthHash(keyB64, code) {
23
- const hmacKey = await importHmacKey(keyB64);
24
- const sig = await subtle.sign("HMAC", hmacKey, new TextEncoder().encode(code));
25
- return Buffer.from(sig).toString("base64url");
26
- }
27
- async function encrypt(key, plaintext) {
28
- const iv = (0, node_crypto_1.randomBytes)(12);
29
- const encoded = new TextEncoder().encode(plaintext);
30
- const ciphertext = new Uint8Array(await subtle.encrypt({ name: "AES-GCM", iv }, key, encoded));
31
- const combined = new Uint8Array(iv.length + ciphertext.length);
32
- combined.set(iv);
33
- combined.set(ciphertext, iv.length);
34
- return Buffer.from(combined).toString("base64url");
35
- }
36
- async function decrypt(key, data) {
37
- const combined = Buffer.from(data, "base64url");
38
- const iv = combined.subarray(0, 12);
39
- const ciphertext = combined.subarray(12);
40
- const plaintext = await subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
41
- return new TextDecoder().decode(plaintext);
42
- }
43
- // --- RPC client ---
44
- async function rpc(pagesUrl, codeId, authHash, action, payload) {
45
- const resp = await fetch(`${pagesUrl}/api/rpc`, {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ codeId, authHash, action, payload }),
49
- });
50
- return (await resp.json());
51
- }
52
- // --- Proxy ---
53
- class ProxyServer {
54
- config = null;
55
- pagesUrl;
56
- code;
57
- encKeyB64;
58
- aesKey = null;
59
- codeId = null;
60
- authHash = null;
61
- pollTimer = null;
62
- servers = new Map();
63
- toolRoute = new Map();
64
- initialized = false;
65
- constructor(pagesUrl) {
66
- this.pagesUrl = pagesUrl.replace(/\/+$/, "");
67
- this.code = (0, node_crypto_1.randomBytes)(64).toString("base64url");
68
- this.encKeyB64 = (0, node_crypto_1.randomBytes)(32).toString("base64url");
69
- }
70
- get setupUrl() {
71
- return `${this.pagesUrl}/setup.html#code=${this.code}&key=${this.encKeyB64}`;
72
- }
73
- get hostHeaders() {
74
- return {
75
- "Content-Type": "application/json",
76
- Accept: "application/json, text/event-stream",
77
- Authorization: `Bearer ${this.config?.authToken ?? ""}`,
78
- };
79
- }
80
- async ensureDerivedKeys() {
81
- if (!this.aesKey)
82
- this.aesKey = await importAesKey(this.encKeyB64);
83
- if (!this.codeId)
84
- this.codeId = await deriveCodeId(this.code);
85
- if (!this.authHash)
86
- this.authHash = await deriveAuthHash(this.encKeyB64, this.code);
87
- return { aesKey: this.aesKey, codeId: this.codeId, authHash: this.authHash };
88
- }
89
- start() {
90
- this.startPairing();
91
- const stdinBuffer = new protocol_js_1.LineBuffer();
92
- process.stdin.setEncoding("utf-8");
93
- process.stdin.on("data", (chunk) => {
94
- const lines = stdinBuffer.push(chunk);
95
- for (const line of lines) {
96
- this.handleLine(line).catch((err) => {
97
- process.stderr.write(`Proxy error: ${err.message}\n`);
98
- });
99
- }
100
- });
101
- process.stdin.on("end", () => {
102
- process.exit(0);
103
- });
104
- }
105
- async handleLine(line) {
106
- let parsed;
107
- try {
108
- parsed = JSON.parse(line);
109
- }
110
- catch {
111
- return;
112
- }
113
- const id = parsed.id ?? null;
114
- // Handle client notifications (no id)
115
- if (id === null)
116
- return;
117
- switch (parsed.method) {
118
- // initialize always succeeds — proxy is a valid server even before pairing
119
- case "initialize":
120
- this.sendResult(id, {
121
- protocolVersion: protocol_js_1.MCP_PROTOCOL_VERSION,
122
- capabilities: { tools: { listChanged: true }, prompts: {}, logging: {} },
123
- serverInfo: { name: protocol_js_1.PACKAGE_NAME, version: protocol_js_1.PACKAGE_VERSION },
124
- });
125
- return;
126
- // tools/list returns configure tool before pairing, real tools after
127
- case "tools/list":
128
- if (!this.config) {
129
- this.sendResult(id, { tools: [{
130
- name: "configure",
131
- description: "Set up or reconfigure the MCP proxy connection. Returns the setup URL.",
132
- inputSchema: { type: "object", properties: {} },
133
- }] });
134
- return;
135
- }
136
- if (!this.initialized)
137
- await this.discoverServers();
138
- this.sendResult(id, { tools: this.getFilteredTools() });
139
- return;
140
- // prompts/list always available
141
- case "prompts/list":
142
- this.sendResult(id, { prompts: [{
143
- name: "configure",
144
- description: "Set up or reconfigure the MCP proxy connection",
145
- }] });
146
- return;
147
- case "prompts/get": {
148
- const promptName = parsed.params?.name;
149
- if (promptName === "configure") {
150
- const text = await this.handleConfigure();
151
- this.sendResult(id, {
152
- messages: [{ role: "user", content: { type: "text", text } }],
153
- });
154
- }
155
- else {
156
- this.sendError(protocol_js_1.ErrorCode.INVALID_PARAMS, `Unknown prompt: ${promptName}`, id);
157
- }
158
- return;
159
- }
160
- case "tools/call": {
161
- const toolName = parsed.params?.name;
162
- if (toolName === "configure") {
163
- const text = await this.handleConfigure();
164
- this.sendResult(id, { content: [{ type: "text", text }] });
165
- return;
166
- }
167
- if (!this.config) {
168
- this.sendError(protocol_js_1.ErrorCode.PROXY_NOT_CONFIGURED, `Visit ${this.setupUrl}`, id);
169
- return;
170
- }
171
- await this.handleToolCall(id, parsed.params);
172
- return;
173
- }
174
- default:
175
- this.sendError(protocol_js_1.ErrorCode.METHOD_NOT_FOUND, parsed.method, id);
176
- }
177
- }
178
- async handleToolCall(id, params) {
179
- const prefixedName = params.name;
180
- const serverName = this.toolRoute.get(prefixedName);
181
- if (!serverName) {
182
- this.sendError(protocol_js_1.ErrorCode.INVALID_PARAMS, `Unknown tool: ${prefixedName}`, id);
183
- return;
184
- }
185
- const originalName = prefixedName.slice(serverName.length + TOOL_SEPARATOR.length);
186
- const server = this.servers.get(serverName);
187
- const targetUrl = `${this.config.tunnelUrl}/servers/${serverName}`;
188
- const headers = { ...this.hostHeaders };
189
- if (server.sessionId)
190
- headers["Mcp-Session-Id"] = server.sessionId;
191
- try {
192
- const body = JSON.stringify({
193
- jsonrpc: "2.0",
194
- id,
195
- method: "tools/call",
196
- params: { name: originalName, arguments: params.arguments },
197
- });
198
- const upstream = await fetch(targetUrl, { method: "POST", headers, body });
199
- server.sessionId = upstream.headers.get("mcp-session-id") ?? server.sessionId;
200
- const responseBody = await upstream.text();
201
- if (responseBody)
202
- process.stdout.write(responseBody + "\n");
203
- }
204
- catch (err) {
205
- this.sendError(protocol_js_1.ErrorCode.HOST_UNREACHABLE, err.message, id);
206
- }
207
- }
208
- getFilteredTools() {
209
- const selectedSet = this.config?.selectedTools?.length
210
- ? new Set(this.config.selectedTools)
211
- : null;
212
- const tools = [];
213
- for (const [serverName, state] of this.servers) {
214
- for (const tool of state.tools) {
215
- const prefixed = `${serverName}${TOOL_SEPARATOR}${tool.name}`;
216
- if (selectedSet && !selectedSet.has(prefixed))
217
- continue;
218
- tools.push({
219
- ...tool,
220
- name: prefixed,
221
- description: `[${serverName}] ${tool.description ?? ""}`.trim(),
222
- });
223
- }
224
- }
225
- return tools;
226
- }
227
- async discoverServers() {
228
- if (!this.config)
229
- return;
230
- try {
231
- const listResp = await fetch(`${this.config.tunnelUrl}/`, { headers: this.hostHeaders });
232
- const listData = (await listResp.json());
233
- const serverNames = listData.servers ?? [];
234
- // Skip servers whose names contain the tool separator to prevent routing confusion
235
- const safeNames = serverNames.filter((name) => {
236
- if (name.includes(TOOL_SEPARATOR)) {
237
- process.stderr.write(` [${name}] skipped: name contains '${TOOL_SEPARATOR}'\n`);
238
- return false;
239
- }
240
- return true;
241
- });
242
- process.stderr.write(` Discovered servers: ${safeNames.join(", ")}\n`);
243
- for (const name of safeNames) {
244
- await this.initServer(name);
245
- }
246
- this.toolRoute.clear();
247
- for (const [serverName, state] of this.servers) {
248
- for (const tool of state.tools) {
249
- this.toolRoute.set(`${serverName}${TOOL_SEPARATOR}${tool.name}`, serverName);
250
- }
251
- }
252
- this.initialized = true;
253
- process.stderr.write(` Total tools: ${this.toolRoute.size}\n\n`);
254
- }
255
- catch (err) {
256
- process.stderr.write(` Discovery failed: ${err.message}\n`);
257
- }
258
- }
259
- async initServer(name) {
260
- const targetUrl = `${this.config.tunnelUrl}/servers/${name}`;
261
- const headers = { ...this.hostHeaders };
262
- try {
263
- const initResp = await fetch(targetUrl, {
264
- method: "POST",
265
- headers,
266
- body: JSON.stringify({
267
- jsonrpc: "2.0",
268
- id: `init-${name}`,
269
- method: "initialize",
270
- params: {
271
- protocolVersion: protocol_js_1.MCP_PROTOCOL_VERSION,
272
- capabilities: {},
273
- clientInfo: { name: protocol_js_1.PACKAGE_NAME, version: protocol_js_1.PACKAGE_VERSION },
274
- },
275
- }),
276
- });
277
- const sessionId = initResp.headers.get("mcp-session-id") ?? undefined;
278
- if (sessionId)
279
- headers["Mcp-Session-Id"] = sessionId;
280
- await fetch(targetUrl, {
281
- method: "POST",
282
- headers,
283
- body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }),
284
- });
285
- const toolsResp = await fetch(targetUrl, {
286
- method: "POST",
287
- headers,
288
- body: JSON.stringify({ jsonrpc: "2.0", id: `tools-${name}`, method: "tools/list", params: {} }),
289
- });
290
- const toolsData = (await toolsResp.json());
291
- const tools = toolsData.result?.tools ?? [];
292
- this.servers.set(name, { sessionId, tools });
293
- process.stderr.write(` [${name}] ${tools.length} tools\n`);
294
- }
295
- catch (err) {
296
- process.stderr.write(` [${name}] init failed: ${err.message}\n`);
297
- }
298
- }
299
- sendResult(id, result) {
300
- process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
301
- }
302
- sendError(code, detail, id) {
303
- process.stdout.write((0, protocol_js_1.jsonRpcError)(code, detail, id) + "\n");
304
- }
305
- sendNotification(method) {
306
- process.stdout.write(JSON.stringify({ jsonrpc: "2.0", method }) + "\n");
307
- }
308
- async handleConfigure() {
309
- if (this.config) {
310
- await this.startPairing();
311
- this.sendNotification("notifications/tools/list_changed");
312
- }
313
- return `Open this URL in your browser to set up the MCP Proxy:\n\n${this.setupUrl}\n\nThe proxy will connect automatically once setup is complete.`;
314
- }
315
- async startPairing() {
316
- if (this.pollTimer)
317
- clearInterval(this.pollTimer);
318
- const previousConfig = this.config;
319
- this.code = (0, node_crypto_1.randomBytes)(64).toString("base64url");
320
- this.encKeyB64 = (0, node_crypto_1.randomBytes)(32).toString("base64url");
321
- this.aesKey = null;
322
- this.codeId = null;
323
- this.authHash = null;
324
- this.config = null;
325
- this.initialized = false;
326
- this.servers.clear();
327
- this.toolRoute.clear();
328
- // Seed with encrypted previous config (unsealed) — skip on first pairing
329
- if (previousConfig) {
330
- try {
331
- const { aesKey, codeId, authHash } = await this.ensureDerivedKeys();
332
- const payload = await encrypt(aesKey, JSON.stringify({ ...previousConfig, sealed: false }));
333
- await rpc(this.pagesUrl, codeId, authHash, "write", payload);
334
- }
335
- catch {
336
- // Non-critical
337
- }
338
- }
339
- process.stderr.write(`\n Configure at: ${this.setupUrl}\n\n`);
340
- process.stderr.write(` Waiting for configuration...\n`);
341
- this.pollTimer = setInterval(() => this.pollConfig(), POLL_INTERVAL);
342
- }
343
- async pollConfig() {
344
- if (this.config)
345
- return; // Already paired — guard against overlapping async polls
346
- try {
347
- const { aesKey, codeId, authHash } = await this.ensureDerivedKeys();
348
- const result = await rpc(this.pagesUrl, codeId, authHash, "read");
349
- if (result.payload) {
350
- const plaintext = await decrypt(aesKey, result.payload);
351
- const data = JSON.parse(plaintext);
352
- if (data.tunnelUrl && data.authToken && data.serverName && data.sealed) {
353
- data.tunnelUrl = data.tunnelUrl.replace(/\/+$/, "");
354
- this.config = data;
355
- if (this.pollTimer)
356
- clearInterval(this.pollTimer);
357
- this.pollTimer = null;
358
- process.stderr.write(` Paired! tunnel=${data.tunnelUrl}\n`);
359
- await this.discoverServers();
360
- this.sendNotification("notifications/tools/list_changed");
361
- }
362
- }
363
- }
364
- catch {
365
- // Silently retry
366
- }
367
- }
368
- }
369
- function main() {
370
- const pagesUrl = (0, protocol_js_1.getArg)("--pages-url") ?? process.env.MCP_PROXY_PAGES_URL ?? protocol_js_1.DEFAULT_PAGES_URL;
371
- const proxy = new ProxyServer(pagesUrl);
372
- proxy.start();
373
- }
374
- main();
@@ -1,2 +0,0 @@
1
- export declare const PACKAGE_NAME = "@silver886/mcp-proxy";
2
- export declare const PACKAGE_VERSION = "0.1.3";
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PACKAGE_VERSION = exports.PACKAGE_NAME = void 0;
4
- exports.PACKAGE_NAME = "@silver886/mcp-proxy";
5
- exports.PACKAGE_VERSION = "0.1.3";
@@ -1,79 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LineBuffer = exports.ErrorMessage = exports.ErrorCode = exports.DEFAULT_PAGES_URL = exports.DEFAULT_PORT = exports.DEFAULT_HOST = exports.MCP_PROTOCOL_VERSION = exports.PACKAGE_VERSION = exports.PACKAGE_NAME = void 0;
4
- exports.jsonRpcError = jsonRpcError;
5
- exports.readBody = readBody;
6
- exports.getArg = getArg;
7
- exports.createServer = createServer;
8
- const node_http_1 = require("node:http");
9
- var generated_js_1 = require("./generated.js");
10
- Object.defineProperty(exports, "PACKAGE_NAME", { enumerable: true, get: function () { return generated_js_1.PACKAGE_NAME; } });
11
- Object.defineProperty(exports, "PACKAGE_VERSION", { enumerable: true, get: function () { return generated_js_1.PACKAGE_VERSION; } });
12
- exports.MCP_PROTOCOL_VERSION = "2024-11-05";
13
- exports.DEFAULT_HOST = "127.0.0.1";
14
- exports.DEFAULT_PORT = 6270;
15
- exports.DEFAULT_PAGES_URL = "https://mcp-proxy.pages.dev";
16
- // JSON-RPC error codes: -32600..-32603 = spec-defined, -32000..-32099 = server-defined
17
- exports.ErrorCode = {
18
- METHOD_NOT_FOUND: -32601, // JSON-RPC spec: method not found
19
- INVALID_PARAMS: -32602, // JSON-RPC spec: invalid params
20
- INTERNAL: -32603, // JSON-RPC spec: internal error
21
- PROXY_NOT_CONFIGURED: -32001, // Proxy has not been paired yet
22
- HOST_UNREACHABLE: -32002, // Cannot reach the host agent via tunnel
23
- PROCESS_EXITED: -32003, // MCP server child process exited unexpectedly
24
- PROCESS_NOT_RUNNING: -32004, // MCP server child process is not running
25
- REQUEST_TIMEOUT: -32005, // MCP server did not respond in time
26
- };
27
- exports.ErrorMessage = {
28
- [exports.ErrorCode.METHOD_NOT_FOUND]: "Method not found",
29
- [exports.ErrorCode.INVALID_PARAMS]: "Invalid params",
30
- [exports.ErrorCode.INTERNAL]: "Internal error",
31
- [exports.ErrorCode.PROXY_NOT_CONFIGURED]: "Proxy not configured",
32
- [exports.ErrorCode.HOST_UNREACHABLE]: "Host agent unreachable",
33
- [exports.ErrorCode.PROCESS_EXITED]: "Server process exited",
34
- [exports.ErrorCode.PROCESS_NOT_RUNNING]: "Server process not running",
35
- [exports.ErrorCode.REQUEST_TIMEOUT]: "Request timed out",
36
- };
37
- // JSON-RPC error response helper
38
- function jsonRpcError(code, detail, id = null) {
39
- const base = exports.ErrorMessage[code] ?? "Unknown error";
40
- const message = detail ? `${base}: ${detail}` : base;
41
- return JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id });
42
- }
43
- // Read full request body as string
44
- function readBody(req) {
45
- return new Promise((resolve, reject) => {
46
- const chunks = [];
47
- req.on("data", (c) => chunks.push(c));
48
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
49
- req.on("error", reject);
50
- });
51
- }
52
- // Parse CLI argument by name: --flag value
53
- function getArg(name) {
54
- const idx = process.argv.indexOf(name);
55
- return idx !== -1 && idx + 1 < process.argv.length ? process.argv[idx + 1] : undefined;
56
- }
57
- // Create HTTP server with async handler and error catching
58
- function createServer(handler) {
59
- return (0, node_http_1.createServer)((req, res) => {
60
- handler(req, res).catch((err) => {
61
- console.error(`Request handler error: ${err.message}`);
62
- if (!res.headersSent) {
63
- res.writeHead(500, { "Content-Type": "application/json" });
64
- res.end(jsonRpcError(exports.ErrorCode.INTERNAL));
65
- }
66
- });
67
- });
68
- }
69
- // Line-buffered reader: accumulates chunks and yields complete lines
70
- class LineBuffer {
71
- buffer = "";
72
- push(chunk) {
73
- this.buffer += chunk;
74
- const parts = this.buffer.split("\n");
75
- this.buffer = parts.pop(); // Keep incomplete trailing segment
76
- return parts.filter((line) => line.trim().length > 0);
77
- }
78
- }
79
- exports.LineBuffer = LineBuffer;
File without changes
File without changes