|
1 | | -#!/usr/bin/env node |
| 1 | +#!/usr/bin/env -S deno run --allow-net --allow-env --allow-read |
2 | 2 | // SPDX-License-Identifier: MPL-2.0 |
3 | 3 | // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
4 | 4 | // |
|
8 | 8 | // stdio protocol so that Claude Code, Glama, and other MCP clients |
9 | 9 | // can discover and invoke BoJ cartridge tools. |
10 | 10 | // |
11 | | -// Usage: deno run --allow-net main.js |
12 | | -// or: node main.js |
| 11 | +// Usage: deno run --allow-net --allow-env --allow-read main.js |
13 | 12 |
|
14 | 13 | import { |
15 | 14 | RATE_LIMIT, |
@@ -37,45 +36,24 @@ import { |
37 | 36 | } from "./lib/nickel-validator.js"; |
38 | 37 | import { info, warn, error as logError, setLevel as setLogLevel } from "./lib/logger.js"; |
39 | 38 |
|
40 | | -const BOJ_BASE = process.env.BOJ_URL || "http://localhost:7700"; |
| 39 | +const BOJ_BASE = Deno.env.get("BOJ_URL") ?? "http://localhost:7700"; |
41 | 40 | const SERVER_NAME = "boj-server"; |
42 | 41 | const SERVER_VERSION = "0.4.0"; |
43 | 42 |
|
44 | 43 | // =================================================================== |
45 | 44 | // JSON-RPC stdio transport |
46 | 45 | // =================================================================== |
47 | 46 |
|
| 47 | +const encoder = new TextEncoder(); |
| 48 | +const decoder = new TextDecoder(); |
| 49 | + |
48 | 50 | let buffer = ""; |
49 | 51 | const MAX_BUFFER_BYTES = 2 * 1_048_576; // 2 MB |
50 | 52 |
|
51 | | -process.stdin.setEncoding("utf8"); |
52 | 53 | const pendingMessages = []; |
53 | 54 |
|
54 | | -process.stdin.on("data", (chunk) => { |
55 | | - buffer += chunk; |
56 | | - if (buffer.length > MAX_BUFFER_BYTES) { |
57 | | - sendError(null, -32600, "Message too large"); |
58 | | - buffer = ""; |
59 | | - return; |
60 | | - } |
61 | | - let boundary; |
62 | | - while ((boundary = buffer.indexOf("\n")) !== -1) { |
63 | | - const line = buffer.slice(0, boundary).trim(); |
64 | | - buffer = buffer.slice(boundary + 1); |
65 | | - if (line.length > 0) { |
66 | | - const p = handleMessage(line).catch(() => {}); |
67 | | - pendingMessages.push(p); |
68 | | - } |
69 | | - } |
70 | | -}); |
71 | | - |
72 | | -process.stdin.on("end", async () => { |
73 | | - await Promise.allSettled(pendingMessages); |
74 | | - process.exit(0); |
75 | | -}); |
76 | | - |
77 | 55 | function send(obj) { |
78 | | - process.stdout.write(JSON.stringify(obj) + "\n"); |
| 56 | + Deno.stdout.writeSync(encoder.encode(JSON.stringify(obj) + "\n")); |
79 | 57 | } |
80 | 58 |
|
81 | 59 | function sendResult(id, result) { |
@@ -268,7 +246,7 @@ async function dispatchTool(toolName, args) { |
268 | 246 | // local-coord-mcp direct dispatch (loopback only, port 7745) |
269 | 247 | // =================================================================== |
270 | 248 |
|
271 | | -const LOCAL_COORD_URL = process.env.COORD_BACKEND_URL || "http://127.0.0.1:7745"; |
| 249 | +const LOCAL_COORD_URL = Deno.env.get("COORD_BACKEND_URL") ?? "http://127.0.0.1:7745"; |
272 | 250 |
|
273 | 251 | // Nickel contracts run on coord_send / coord_send_gated only — those |
274 | 252 | // are the two tools whose `message` argument carries an A2ML envelope. |
@@ -477,3 +455,28 @@ async function handleMessage(line) { |
477 | 455 | } |
478 | 456 | } |
479 | 457 | } |
| 458 | + |
| 459 | +// =================================================================== |
| 460 | +// Main I/O loop — async-iterable stdin (Deno) |
| 461 | +// =================================================================== |
| 462 | + |
| 463 | +for await (const chunk of Deno.stdin.readable) { |
| 464 | + buffer += decoder.decode(chunk); |
| 465 | + if (buffer.length > MAX_BUFFER_BYTES) { |
| 466 | + sendError(null, -32600, "Message too large"); |
| 467 | + buffer = ""; |
| 468 | + continue; |
| 469 | + } |
| 470 | + let boundary; |
| 471 | + while ((boundary = buffer.indexOf("\n")) !== -1) { |
| 472 | + const line = buffer.slice(0, boundary).trim(); |
| 473 | + buffer = buffer.slice(boundary + 1); |
| 474 | + if (line.length > 0) { |
| 475 | + const p = handleMessage(line).catch(() => {}); |
| 476 | + pendingMessages.push(p); |
| 477 | + } |
| 478 | + } |
| 479 | +} |
| 480 | + |
| 481 | +await Promise.allSettled(pendingMessages); |
| 482 | +Deno.exit(0); |
0 commit comments