Row 89, round 1. The broker's `tasks` read takes optional filter and page arguments and returns a short form. The harness adds `list_tasks` arguments (open tasks by default), a `get_task` tool, and compact JSON replies capped below the 50000-character threshold where Claude Code 2.1.296 saves a tool result to a file outside the workspace, which was the PM's fault in Jason's test. The bundle's tools line names the harness's own tools. Candidate manifest b8c5bb3a (agents/dewey/work/queue-89/), basea3f6e9e6, no drift on the 12 paths. Builder Dewey. Approved by Darkwing (comment 27940). Sage's gate onf04185fcplus the patch 2026-10-12T22:00:55Z-22:03:38Z: bus 108, harness 65, webui 86, cli 115, control-board 124, every scripts/test-*.sh green. Live at the next bus restart (broker) and role session launch (harness), both Mos's call (decision 109); Mos had the notice. Co-Authored-By: Claude Opus 5.5 <[email protected]>
61 lines
2.3 KiB
JavaScript
61 lines
2.3 KiB
JavaScript
// Minimal stdio MCP server that gives a Claude Code session its typed
|
|
// tools: `node mcp-server.mjs <tools.json> <tool socket>`. Newline-delimited
|
|
// JSON-RPC 2.0 with initialize, tools/list, tools/call and ping. Each call
|
|
// goes to the runner's tool socket; the server holds no capability. A
|
|
// reply over REPLY_CAP is a tool error (tools.mjs, replyText).
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { createInterface } from "node:readline";
|
|
import { callTool, replyText } from "./tools.mjs";
|
|
|
|
const [toolsFile, socket] = process.argv.slice(2);
|
|
if (!toolsFile || !socket) {
|
|
process.stderr.write("usage: mcp-server.mjs <tools.json> <tool socket>\n");
|
|
process.exit(2);
|
|
}
|
|
const tools = JSON.parse(readFileSync(toolsFile, "utf8"));
|
|
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
|
|
const send = (message) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", ...message }) + "\n");
|
|
const reply = (id, result) => send({ id, result });
|
|
const error = (id, code, message) => send({ id, error: { code, message } });
|
|
|
|
async function handle(m) {
|
|
const id = m.id;
|
|
switch (m.method) {
|
|
case "initialize":
|
|
return reply(id, {
|
|
protocolVersion: m.params?.protocolVersion ?? "2025-06-18",
|
|
capabilities: { tools: {} },
|
|
serverInfo: { name: "mosaic", version: "1" },
|
|
});
|
|
case "ping":
|
|
return reply(id, {});
|
|
case "tools/list":
|
|
return reply(id, { tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.parameters })) });
|
|
case "tools/call": {
|
|
const name = m.params?.name;
|
|
if (!byName.has(name)) return error(id, -32602, `unknown tool: ${name}`);
|
|
try {
|
|
const result = await callTool(socket, name, m.params?.arguments ?? {});
|
|
return reply(id, { content: [{ type: "text", text: replyText(result) }] });
|
|
} catch (e) {
|
|
return reply(id, { content: [{ type: "text", text: e.message }], isError: true });
|
|
}
|
|
}
|
|
default:
|
|
if (id !== undefined) return error(id, -32601, `method not found: ${m.method}`);
|
|
}
|
|
}
|
|
|
|
createInterface({ input: process.stdin }).on("line", (line) => {
|
|
if (!line.trim()) return;
|
|
let m;
|
|
try {
|
|
m = JSON.parse(line);
|
|
} catch {
|
|
return error(null, -32700, "parse error");
|
|
}
|
|
handle(m).catch((e) => m.id !== undefined && error(m.id, -32603, e.message));
|
|
});
|