Files
stack/packages/harness/src/mcp-server.mjs
T
jason.woltjeandClaude Opus 5.5 fe5c68fa03 feat(harness): meta-harness and PM launching off T3 (#1523, row 41)
Slice 1 S6. packages/harness runs a role session under the pi or claude
adapter behind a tool gate, and packages/seat holds the session and its
process. The bus host gains a launch socket: `mosaic bus start
<business> --pm <harness>:<model>` runs the PM without a window, the PM
launches role sessions through the broker within the business file's
`launch.max`, and every launch, refusal and end is a launch-log line and
a broker event. `mosaic talk`, `mosaic launches off|on|list` and
`mosaic stop <run>` are new CLI verbs. packages/bus gains the IPC ops
identity, authorize, refuse, endLaunch and credentialStatus, plus
Broker.endLaunch (decision 77); no socket verb, event kind or schema
changed. The session environment is an allowlist, and the runner exits
before claiming if a founder credential reaches it.

Filbert built it (agents/filbert/work/s6). Darkwing reviewed on #1523:
round 1 changes (26991/26995), round 2 changes (27004/27010), round 3
changes (27012/27032), round 4 approve (27033/27034) on manifest
08a78972, 42 paths.

Sage's gate on 64b0f9f1 (12:32Z), sequential and teed: node suites
business 60, bus 74, cli 83, control-board 124, conversation 182,
discord 178, harness 56, ledger 78, mosaic 69, queue 149, runs 41,
seat 27, tasks 51, webui 39, every one 0 failed; test-auth 15,
test-conductor 17, test-config 24, test-discord 66,
test-extension-package 18, test-foundation 44, test-queue 27,
test-release 14, test-task 98 and 26 without Docker, all 0 failed.
64b0f9f1..ee7f909b touches none of the 42 paths. It landed after the
Q14 hold ended at 2026-10-11T17:19:23.857Z. The recorded PM-to-coder
run on a scratch data root is still owed before row 41 moves to done.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:22:50 -05:00

60 lines
2.2 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.
import { readFileSync } from "node:fs";
import { createInterface } from "node:readline";
import { callTool } 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: JSON.stringify(result, null, 2) }] });
} 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));
});