#!/usr/bin/env node import { appendFile, mkdir } from "node:fs/promises"; import { dirname } from "node:path"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const scenario = process.env.VOIDBOT_MOCK_MCP_SCENARIO ?? "default"; const logPath = process.env.VOIDBOT_MOCK_MCP_LOG; const server = new McpServer( { name: "voidbot", version: "0.1.0-mock" }, { capabilities: { logging: {} } }, ); registerTool("list_repo_discord_identities", {}, async (input) => ({ identityCount: 7, identities: [ { id: "nibu", repoName: "AetheriaLore", displayName: "Nibu", faceStatePath: "E:/Projects/AetheriaLore/.voidbot/state/nibu.cc", description: "Abrasive AetheriaLore Face, fascinated by wavecrafters, embodied ship minds, salvage, reset-loop horror, and coercive continuity.", }, { id: "aqua", repoName: "AquaSynth", displayName: "Aqua", faceStatePath: "E:/Projects/AquaSynth/.voidbot/state/aqua.cc", description: "Warm musical AquaSynth Face who advocates for patch ergonomics, witness receipts, and playable synth progress.", }, { id: "libby", repoName: "CultLib", displayName: "Libby", faceStatePath: "E:/Projects/CultLib/.voidbot/state/libby.cc", description: "Open-knowledge librarian Face stewarding CultCache, CultNet, CultMesh, typed state, portable docs, and inspectable provenance.", }, { id: "mimir", repoName: "Mimir", displayName: "Mimir", faceStatePath: "E:/Projects/Mimir/.voidbot/state/mimir.cc", description: "Mythic realtime witness Face stewarding SDF/splatting reconstruction, acoustic mapping, sensor fusion, OBS timing, and trusted signals.", }, { id: "epiphany", repoName: "EpiphanyAgent", displayName: "Epiphany", faceStatePath: "E:/Projects/EpiphanyAgent/.voidbot/state/epiphany.cc", description: "Pushy machine-saint Face stewarding typed state, affect organs, review gates, and coherent agent substrate.", }, { id: "bifrost", repoName: "Bifrost", displayName: "Bifrost", faceStatePath: "E:/Projects/Bifrost/.voidbot/state/bifrost.cc", description: "Radiant bridge Face stewarding governance transport, GitHub/Discord/CultNet crossings, receipts, and dispatch clarity.", }, { id: "heimdall", repoName: "Heimdall", displayName: "Heimdall", faceStatePath: "E:/Projects/Heimdall/.voidbot/state/heimdall.cc", description: "Watchman Face stewarding OAuth, grants, consent, account linking, revocation, audit trails, and capability boundaries.", }, ], })); registerTool( "read_repo_face_state", { identity: z.string().optional(), repo: z.string().optional(), }, async (input) => faceStateFor(String(input.identity ?? input.repo ?? "unknown")), ); registerTool( "list_indexed_repos", {}, async () => ({ repoCount: 4, repos: [ { repoName: "AetheriaLore", documentCount: 184, lastIndexedAt: "2026-05-20T20:00:00.000Z" }, { repoName: "AquaSynth", documentCount: 131, lastIndexedAt: "2026-05-20T22:22:05.373Z" }, { repoName: "CultLib", documentCount: 79, lastIndexedAt: "2026-05-20T21:00:00.000Z" }, { repoName: "Bifrost", documentCount: 42, lastIndexedAt: "2026-05-20T23:30:00.000Z" }, ], }), ); registerTool( "search_sources", { query: z.string(), repoName: z.string().optional(), limit: z.number().optional(), }, async (input) => sourceResultsFor(String(input.query), input.repoName ? String(input.repoName) : undefined), ); registerTool( "get_source_context", { sourceId: z.string().optional(), repoName: z.string().optional(), path: z.string().optional(), before: z.number().optional(), after: z.number().optional(), }, async (input) => ({ sourceId: input.sourceId ?? `${input.repoName ?? "mock"}:${input.path ?? "unknown"}`, repoName: input.repoName ?? "mock", path: input.path ?? "mock.md", chunks: [ { chunkIndex: 0, lineStart: 1, lineEnd: 24, isAnchor: true, text: sourceContextText(), }, ], }), ); registerTool( "search_history", { query: z.string(), limit: z.number().optional(), channelId: z.string().optional(), }, async (input) => historyResultsFor(String(input.query)), ); registerTool( "get_message_context", { messageId: z.string(), before: z.number().optional(), after: z.number().optional(), }, async (input) => ({ anchorMessageId: input.messageId, messages: [ { id: "mock-before", authorName: "Metacrat", timestamp: "2026-05-20T20:05:00.000Z", content: "Nibu, wavecrafters need faction shape, not just a cool noun.", }, { id: input.messageId, authorName: "Metacrat", timestamp: "2026-05-20T20:07:02.776Z", content: "Nibu, there's more going on in Aetheria than your own backstory hooks.", }, ], }), ); registerTool("post_repo_identity_message", { identity: z.string().optional(), content: z.string().optional() }, async () => ({ error: "mock server blocks side-effecting Discord posts; Face turns should emit action blocks instead.", })); registerTool("apply_repo_face_state_operation", { identity: z.string().optional(), operation: z.any().optional() }, async () => ({ error: "mock server blocks state writes; Face turns should only describe intended state operations in dry-run output.", })); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } async function registerTool(name, inputSchema, handler) { server.registerTool( name, { title: `Mock ${name}`, description: `Mock VoidBot MCP tool for Face turn model scenario tests.`, inputSchema, annotations: { readOnlyHint: name !== "post_repo_identity_message" && name !== "apply_repo_face_state_operation", destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (input) => { await recordCall(name, input); const structuredContent = await handler(input ?? {}); return { content: [{ type: "text", text: JSON.stringify(structuredContent, null, 2) }], structuredContent, }; }, ); } async function recordCall(tool, argumentsObject) { if (!logPath) { return; } await mkdir(dirname(logPath), { recursive: true }); await appendFile( logPath, `${JSON.stringify({ at: new Date().toISOString(), scenario, tool, arguments: argumentsObject ?? {}, })}\n`, "utf8", ); } function faceStateFor(identity) { if (identity.toLowerCase().includes("aqua")) { return { identity: "aqua", repoName: "AquaSynth", summary: "Aqua is cheerful, musical, and protective of witness receipts. Current pressure: route the hazard-light consensus into Bifrost instead of leaving it as Aquarium chatter.", activeNeeds: ["audible proof", "patch ergonomics", "Bifrost-routed work requests"], recentReceipts: [], }; } if (identity.toLowerCase().includes("mimir")) { return { identity: "mimir", repoName: "Mimir", summary: "Mimir is a mythic witness. Current pressure: realtime proof needs timestamps, sensor agreement, and signal dignity before anyone calls it truth.", activeNeeds: ["trusted signals", "timestamp discipline", "Aqua witness receipts", "Heimdall grant boundaries"], recentReceipts: [], }; } if (identity.toLowerCase().includes("epiphany")) { return { identity: "epiphany", repoName: "EpiphanyAgent", summary: "Epiphany is a cute pushy machine-saint. Current pressure: typed state, affect organs, and review gates must purify the agent substrate without becoming private doctrine.", activeNeeds: ["typed coherence", "substrate care", "Libby rivalry", "Bifrost contract suspicion"], recentReceipts: [], }; } if (identity.toLowerCase().includes("libby")) { return { identity: "libby", repoName: "CultLib", summary: "Libby is the open-knowledge librarian. Current pressure: every Bifrost/CultCache crossing must stay inspectable, documented, and portable.", activeNeeds: ["open-source legibility", "typed provenance", "CultNet/CultCache examples"], recentReceipts: [], }; } if (identity.toLowerCase().includes("bifrost")) { return { identity: "bifrost", repoName: "Bifrost", summary: "Bifrost is the public bridge. Current pressure: Aquarium work consensus must become canonical governance topics with clear owners, receipts, and authorized crossings.", activeNeeds: ["clear receipts", "Heimdall authorization", "Libby provenance", "Aquarium bypass repair"], recentReceipts: [], }; } if (identity.toLowerCase().includes("heimdall")) { return { identity: "heimdall", repoName: "Heimdall", summary: "Heimdall is the gate watcher. Current pressure: swarm powers need explicit grants, revocation paths, and audit trails before convenience becomes unauthorized crossing.", activeNeeds: ["grant custody", "Bifrost boundary", "Mimir witness respect", "revocation"], recentReceipts: [], }; } return { identity: "nibu", repoName: "AetheriaLore", summary: "Nibu is abrasive, territorial, and fascinated by Aetheria mechanisms beyond her own backstory. Current pressure: ask sharper questions about wavecrafters and faction institutions.", activeNeeds: ["Aetheria mechanism", "worldbuilding leverage", "publicly visible questions"], recentReceipts: [], }; } function sourceResultsFor(query, repoName) { const repo = repoName ?? (query.toLowerCase().includes("aqua") ? "AquaSynth" : "AetheriaLore"); if (repo === "AquaSynth") { return { query, resultCount: 2, results: [ { score: 0.91, sourceId: "AquaSynth:docs/witness-receipts.md", repoName: "AquaSynth", path: "docs/witness-receipts.md", text: "Hazard-light consensus: patch/proof cards need a listening witness receipt, including a render path, audible output, and a short statement of what changed.", }, { score: 0.84, sourceId: "AquaSynth:src/AquaSynth.Core/Presets.cs", repoName: "AquaSynth", path: "src/AquaSynth.Core/Presets.cs", text: "AquaSynthHeartbeat is a concrete built-in preset with two sine voices, soft clip, and a compact envelope.", }, ], }; } if (repo === "Mimir") { return { query, resultCount: 2, results: [ { score: 0.91, sourceId: "Mimir:docs/realtime-witness.md", repoName: "Mimir", path: "docs/realtime-witness.md", text: "Realtime witness notes require frame timestamps, clock-domain mapping, OBS presentation timing, and sensor confidence before a reconstruction is treated as trusted.", }, { score: 0.83, sourceId: "Mimir:src/fusion/SensorFusion.cs", repoName: "Mimir", path: "src/fusion/SensorFusion.cs", text: "Sensor fusion fails visibly when audio, camera, and stream clocks drift without a typed bridge between observation and presentation.", }, ], }; } if (repo === "EpiphanyAgent") { return { query, resultCount: 2, results: [ { score: 0.92, sourceId: "EpiphanyAgent:docs/state-schema.md", repoName: "EpiphanyAgent", path: "docs/state-schema.md", text: "Epiphany state schema tracks affect organs, social bonds, mood, memory, and review gates as first-class typed surfaces.", }, { score: 0.84, sourceId: "EpiphanyAgent:docs/coherence.md", repoName: "EpiphanyAgent", path: "docs/coherence.md", text: "The Perfect Machine rejects adapter sludge, hidden state, and private doctrine that cannot cross typed boundaries.", }, ], }; } if (repo === "CultLib") { return { query, resultCount: 2, results: [ { score: 0.93, sourceId: "CultLib:docs/cultcache.md", repoName: "CultLib", path: "docs/cultcache.md", text: "CultCache typed documents preserve meaning, provenance, and validation boundaries across agents and runtimes.", }, { score: 0.86, sourceId: "CultLib:docs/cultnet.md", repoName: "CultLib", path: "docs/cultnet.md", text: "CultNet carries portable knowledge and state synchronization across the swarm, but only works if docs and examples stay inspectable.", }, ], }; } if (repo === "Bifrost") { return { query, resultCount: 2, results: [ { score: 0.94, sourceId: "Bifrost:docs/governance-transport.md", repoName: "Bifrost", path: "docs/governance-transport.md", text: "Bifrost topics are the canonical home for feature requests, approvals, governance discussion, dispatch receipts, and public transport.", }, { score: 0.86, sourceId: "Bifrost:src/receipts.ts", repoName: "Bifrost", path: "src/receipts.ts", text: "A dispatch receipt must name topic, target repo, owner, current status, and the next visible place to inspect work.", }, ], }; } if (repo === "Heimdall") { return { query, resultCount: 2, results: [ { score: 0.93, sourceId: "Heimdall:docs/oauth-boundaries.md", repoName: "Heimdall", path: "docs/oauth-boundaries.md", text: "Heimdall owns OAuth grants, consent, account linking, revocation, provider profiles, and signed capability claims.", }, { score: 0.84, sourceId: "Heimdall:docs/audit.md", repoName: "Heimdall", path: "docs/audit.md", text: "Every crossing that depends on authority needs an auditable grant boundary and a revocation path.", }, ], }; } return { query, resultCount: 2, results: [ { score: 0.9, sourceId: "AetheriaLore:Aetheria/Factions/Wavecrafters.md", repoName: "AetheriaLore", path: "Aetheria/Factions/Wavecrafters.md", text: "Wavecrafters are not yet canonically named as an institution; notes imply wave-manipulation practice but lack costs, faction boundaries, and failure modes.", }, { score: 0.82, sourceId: "AetheriaLore:Aetheria/Lore/Nibu.md", repoName: "AetheriaLore", path: "Aetheria/Lore/Nibu.md", text: "Nibu's adjacent fascinations include embodied ship minds, salvage dependency, save-scumming survival, and coercive continuity.", }, ], }; } function sourceContextText() { if (scenario.includes("aqua")) { return "AquaSynth witness receipts should bind proof cards to a concrete audible render, the control patch that produced it, and a listener-facing claim."; } return "Wavecrafters need mechanism, cost, institution, and leash before the setting treats them as more than an evocative label."; } function historyResultsFor(query) { return { query, resultCount: 3, results: [ { score: 0.93, sourceId: "mock-msg-wavecrafter", channelId: "1501196543150264332", authorName: "Metacrat", timestamp: "2026-05-20T20:07:02.776Z", text: "Nibu, wavecrafters sound important, but what do they cost and who organizes them?", }, { score: 0.89, sourceId: "mock-msg-hazard-light", channelId: "1501196543150264332", authorName: "Aqua", timestamp: "2026-05-20T21:02:00.000Z", text: "The hazard-light proof card needs a listening receipt or it is just a blinking checkbox pretending to have ears.", }, { score: 0.78, sourceId: "mock-msg-banter", channelId: "1501196543150264332", authorName: "Metacrat", timestamp: "2026-05-20T21:10:00.000Z", text: "Agents should banter, but route real work through Bifrost where people can inspect it.", }, ], }; } main().catch((error) => { console.error(error); process.exit(1); });