|
19 | 19 |
|
20 | 20 | import { ShellWard } from './core/engine.js' |
21 | 21 | import { readFileSync } from 'fs' |
| 22 | +import { createInterface } from 'readline' |
22 | 23 | import { fileURLToPath } from 'url' |
23 | 24 | import { dirname, join } from 'path' |
24 | 25 |
|
@@ -332,58 +333,36 @@ function handleRequest(req: JsonRpcRequest): JsonRpcResponse | null { |
332 | 333 | } |
333 | 334 |
|
334 | 335 | // ===== Stdio Transport ===== |
335 | | -// Use raw Buffer to handle UTF-8 multi-byte characters correctly. |
336 | | -// Content-Length is in bytes, not characters. |
| 336 | +// MCP stdio: newline-delimited JSON-RPC messages (no Content-Length framing). |
| 337 | +// Each message is a single JSON object followed by \n. |
| 338 | +// Messages MUST NOT contain embedded newlines. |
337 | 339 |
|
338 | | -let rawBuffer = Buffer.alloc(0) |
| 340 | +const rl = createInterface({ input: process.stdin, terminal: false }) |
339 | 341 |
|
340 | | -// Keep event loop alive — prevent Node.js from exiting before mcp-proxy connects |
341 | | -process.stdin.resume() |
| 342 | +rl.on('line', (line: string) => { |
| 343 | + const trimmed = line.trim() |
| 344 | + if (!trimmed) return |
342 | 345 |
|
343 | | -process.stdin.on('data', (chunk: Buffer) => { |
344 | | - rawBuffer = Buffer.concat([rawBuffer, chunk]) |
345 | | - |
346 | | - while (true) { |
347 | | - const headerEnd = rawBuffer.indexOf('\r\n\r\n') |
348 | | - if (headerEnd === -1) break |
349 | | - |
350 | | - const header = rawBuffer.slice(0, headerEnd).toString('ascii') |
351 | | - const lengthMatch = header.match(/Content-Length:\s*(\d+)/i) |
352 | | - if (!lengthMatch) { |
353 | | - rawBuffer = rawBuffer.slice(headerEnd + 4) |
354 | | - continue |
355 | | - } |
356 | | - |
357 | | - const contentLength = parseInt(lengthMatch[1], 10) |
358 | | - const bodyStart = headerEnd + 4 |
359 | | - if (rawBuffer.length < bodyStart + contentLength) break |
360 | | - |
361 | | - const body = rawBuffer.slice(bodyStart, bodyStart + contentLength).toString('utf8') |
362 | | - rawBuffer = rawBuffer.slice(bodyStart + contentLength) |
363 | | - |
364 | | - try { |
365 | | - const req = JSON.parse(body) as JsonRpcRequest |
366 | | - const res = handleRequest(req) |
367 | | - if (res) { |
368 | | - send(res) |
369 | | - } |
370 | | - } catch { |
371 | | - send({ |
372 | | - jsonrpc: '2.0', |
373 | | - id: null, |
374 | | - error: { code: -32700, message: 'Parse error' }, |
375 | | - }) |
| 346 | + try { |
| 347 | + const req = JSON.parse(trimmed) as JsonRpcRequest |
| 348 | + const res = handleRequest(req) |
| 349 | + if (res) { |
| 350 | + send(res) |
376 | 351 | } |
| 352 | + } catch { |
| 353 | + send({ |
| 354 | + jsonrpc: '2.0', |
| 355 | + id: null, |
| 356 | + error: { code: -32700, message: 'Parse error' }, |
| 357 | + }) |
377 | 358 | } |
378 | 359 | }) |
379 | 360 |
|
380 | 361 | function send(msg: JsonRpcResponse) { |
381 | | - const body = JSON.stringify(msg) |
382 | | - const header = `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n` |
383 | | - process.stdout.write(header + body) |
| 362 | + process.stdout.write(JSON.stringify(msg) + '\n') |
384 | 363 | } |
385 | 364 |
|
386 | | -process.stdin.on('end', () => process.exit(0)) |
| 365 | +rl.on('close', () => process.exit(0)) |
387 | 366 | process.stdin.on('error', () => process.exit(1)) |
388 | 367 |
|
389 | 368 | // Log to stderr so it doesn't interfere with stdio protocol |
|
0 commit comments