Skip to content

Commit fdff270

Browse files
committed
fix: switch stdio transport from Content-Length framing to newline-delimited JSON
MCP spec requires stdio messages delimited by newlines, not LSP-style Content-Length headers. mcp-proxy (used by Glama) sends newline-delimited JSON — the old parser never found Content-Length headers and never responded. - Replace raw Buffer + Content-Length parser with readline interface - Send responses as JSON + newline (no Content-Length header) - Bump v0.5.15
1 parent 80f0564 commit fdff270

File tree

3 files changed

+24
-45
lines changed

3 files changed

+24
-45
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "shellward",
3-
"version": "0.5.14",
3+
"version": "0.5.15",
44
"mcpName": "io.github.jnMetaCode/shellward",
55
"description": "AI agent security & MCP security middleware — prompt injection detection, AI firewall, runtime guardrails & data-loss prevention for LLM tool calls. 8-layer defense against data exfiltration & dangerous commands. Zero dependencies. SDK + OpenClaw plugin. Supports LangChain, AutoGPT, Claude Code, Cursor, OpenAI Agents.",
66
"keywords": [

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
"url": "https://github.com/jnMetaCode/shellward",
77
"source": "github"
88
},
9-
"version": "0.5.14",
9+
"version": "0.5.15",
1010
"packages": [
1111
{
1212
"registryType": "npm",
1313
"identifier": "shellward",
14-
"version": "0.5.14",
14+
"version": "0.5.15",
1515
"runtime": "node",
1616
"transport": {
1717
"type": "stdio"

src/mcp-server.ts

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import { ShellWard } from './core/engine.js'
2121
import { readFileSync } from 'fs'
22+
import { createInterface } from 'readline'
2223
import { fileURLToPath } from 'url'
2324
import { dirname, join } from 'path'
2425

@@ -332,58 +333,36 @@ function handleRequest(req: JsonRpcRequest): JsonRpcResponse | null {
332333
}
333334

334335
// ===== 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.
337339

338-
let rawBuffer = Buffer.alloc(0)
340+
const rl = createInterface({ input: process.stdin, terminal: false })
339341

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
342345

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)
376351
}
352+
} catch {
353+
send({
354+
jsonrpc: '2.0',
355+
id: null,
356+
error: { code: -32700, message: 'Parse error' },
357+
})
377358
}
378359
})
379360

380361
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')
384363
}
385364

386-
process.stdin.on('end', () => process.exit(0))
365+
rl.on('close', () => process.exit(0))
387366
process.stdin.on('error', () => process.exit(1))
388367

389368
// Log to stderr so it doesn't interfere with stdio protocol

0 commit comments

Comments
 (0)