shellward 0.5.13 → 0.5.15

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.
@@ -18,6 +18,7 @@
18
18
  // }
19
19
  import { ShellWard } from './core/engine.js';
20
20
  import { readFileSync } from 'fs';
21
+ import { createInterface } from 'readline';
21
22
  import { fileURLToPath } from 'url';
22
23
  import { dirname, join } from 'path';
23
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -220,7 +221,7 @@ function handleRequest(req) {
220
221
  jsonrpc: '2.0',
221
222
  id: id ?? null,
222
223
  result: {
223
- protocolVersion: '2024-11-05',
224
+ protocolVersion: '2025-03-26',
224
225
  capabilities: { tools: {} },
225
226
  serverInfo: {
226
227
  name: 'shellward',
@@ -290,48 +291,33 @@ function handleRequest(req) {
290
291
  }
291
292
  }
292
293
  // ===== Stdio Transport =====
293
- // Use raw Buffer to handle UTF-8 multi-byte characters correctly.
294
- // Content-Length is in bytes, not characters.
295
- let rawBuffer = Buffer.alloc(0);
296
- process.stdin.on('data', (chunk) => {
297
- rawBuffer = Buffer.concat([rawBuffer, chunk]);
298
- while (true) {
299
- const headerEnd = rawBuffer.indexOf('\r\n\r\n');
300
- if (headerEnd === -1)
301
- break;
302
- const header = rawBuffer.slice(0, headerEnd).toString('ascii');
303
- const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
304
- if (!lengthMatch) {
305
- rawBuffer = rawBuffer.slice(headerEnd + 4);
306
- continue;
307
- }
308
- const contentLength = parseInt(lengthMatch[1], 10);
309
- const bodyStart = headerEnd + 4;
310
- if (rawBuffer.length < bodyStart + contentLength)
311
- break;
312
- const body = rawBuffer.slice(bodyStart, bodyStart + contentLength).toString('utf8');
313
- rawBuffer = rawBuffer.slice(bodyStart + contentLength);
314
- try {
315
- const req = JSON.parse(body);
316
- const res = handleRequest(req);
317
- if (res) {
318
- send(res);
319
- }
320
- }
321
- catch {
322
- send({
323
- jsonrpc: '2.0',
324
- id: null,
325
- error: { code: -32700, message: 'Parse error' },
326
- });
294
+ // MCP stdio: newline-delimited JSON-RPC messages (no Content-Length framing).
295
+ // Each message is a single JSON object followed by \n.
296
+ // Messages MUST NOT contain embedded newlines.
297
+ const rl = createInterface({ input: process.stdin, terminal: false });
298
+ rl.on('line', (line) => {
299
+ const trimmed = line.trim();
300
+ if (!trimmed)
301
+ return;
302
+ try {
303
+ const req = JSON.parse(trimmed);
304
+ const res = handleRequest(req);
305
+ if (res) {
306
+ send(res);
327
307
  }
328
308
  }
309
+ catch {
310
+ send({
311
+ jsonrpc: '2.0',
312
+ id: null,
313
+ error: { code: -32700, message: 'Parse error' },
314
+ });
315
+ }
329
316
  });
330
317
  function send(msg) {
331
- const body = JSON.stringify(msg);
332
- const header = `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n`;
333
- process.stdout.write(header + body);
318
+ process.stdout.write(JSON.stringify(msg) + '\n');
334
319
  }
335
- process.stdin.on('end', () => process.exit(0));
320
+ rl.on('close', () => process.exit(0));
321
+ process.stdin.on('error', () => process.exit(1));
336
322
  // Log to stderr so it doesn't interfere with stdio protocol
337
323
  process.stderr.write(`[ShellWard MCP] Server started (mode: ${guard.config.mode}, locale: ${guard.locale})\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shellward",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "mcpName": "io.github.jnMetaCode/shellward",
5
5
  "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.",
6
6
  "keywords": [
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/jnMetaCode/shellward",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.5.13",
9
+ "version": "0.5.15",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "shellward",
14
- "version": "0.5.13",
14
+ "version": "0.5.15",
15
15
  "runtime": "node",
16
16
  "transport": {
17
17
  "type": "stdio"
package/src/mcp-server.ts CHANGED
@@ -19,6 +19,7 @@
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
 
@@ -254,7 +255,7 @@ function handleRequest(req: JsonRpcRequest): JsonRpcResponse | null {
254
255
  jsonrpc: '2.0',
255
256
  id: id ?? null,
256
257
  result: {
257
- protocolVersion: '2024-11-05',
258
+ protocolVersion: '2025-03-26',
258
259
  capabilities: { tools: {} },
259
260
  serverInfo: {
260
261
  name: 'shellward',
@@ -332,55 +333,37 @@ 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
- process.stdin.on('data', (chunk: Buffer) => {
341
- rawBuffer = Buffer.concat([rawBuffer, chunk])
342
+ rl.on('line', (line: string) => {
343
+ const trimmed = line.trim()
344
+ if (!trimmed) return
342
345
 
343
- while (true) {
344
- const headerEnd = rawBuffer.indexOf('\r\n\r\n')
345
- if (headerEnd === -1) break
346
-
347
- const header = rawBuffer.slice(0, headerEnd).toString('ascii')
348
- const lengthMatch = header.match(/Content-Length:\s*(\d+)/i)
349
- if (!lengthMatch) {
350
- rawBuffer = rawBuffer.slice(headerEnd + 4)
351
- continue
352
- }
353
-
354
- const contentLength = parseInt(lengthMatch[1], 10)
355
- const bodyStart = headerEnd + 4
356
- if (rawBuffer.length < bodyStart + contentLength) break
357
-
358
- const body = rawBuffer.slice(bodyStart, bodyStart + contentLength).toString('utf8')
359
- rawBuffer = rawBuffer.slice(bodyStart + contentLength)
360
-
361
- try {
362
- const req = JSON.parse(body) as JsonRpcRequest
363
- const res = handleRequest(req)
364
- if (res) {
365
- send(res)
366
- }
367
- } catch {
368
- send({
369
- jsonrpc: '2.0',
370
- id: null,
371
- error: { code: -32700, message: 'Parse error' },
372
- })
346
+ try {
347
+ const req = JSON.parse(trimmed) as JsonRpcRequest
348
+ const res = handleRequest(req)
349
+ if (res) {
350
+ send(res)
373
351
  }
352
+ } catch {
353
+ send({
354
+ jsonrpc: '2.0',
355
+ id: null,
356
+ error: { code: -32700, message: 'Parse error' },
357
+ })
374
358
  }
375
359
  })
376
360
 
377
361
  function send(msg: JsonRpcResponse) {
378
- const body = JSON.stringify(msg)
379
- const header = `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n`
380
- process.stdout.write(header + body)
362
+ process.stdout.write(JSON.stringify(msg) + '\n')
381
363
  }
382
364
 
383
- process.stdin.on('end', () => process.exit(0))
365
+ rl.on('close', () => process.exit(0))
366
+ process.stdin.on('error', () => process.exit(1))
384
367
 
385
368
  // Log to stderr so it doesn't interfere with stdio protocol
386
369
  process.stderr.write(`[ShellWard MCP] Server started (mode: ${guard.config.mode}, locale: ${guard.locale})\n`)