UNPKG

multi-agent-debate-mcp

Version:

A Model Context Protocol server for structured multi-agent debates

187 lines (185 loc) 7.18 kB
#!/usr/bin/env node /* Multi‑Agent Debate Server * ------------------------- * Lets multiple "personas" argue, rebut, and judge a claim in * structured rounds. See the TOOL description below for usage. */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import chalk from "chalk"; /* ---------- SERVER IMPLEMENTATION ---------- */ class MultiAgentDebateServer { history = []; agents = new Set(); verdict; /* ---- 1. Validation & Normalisation ---- */ validate(input) { const data = input; if (!data.agentId || typeof data.agentId !== "string") { throw new Error("agentId must be a string"); } if (!data.round || typeof data.round !== "number" || data.round < 1) { throw new Error("round must be a positive integer"); } if (!data.action || typeof data.action !== "string") { throw new Error("action missing"); } const action = data.action; if (!["register", "argue", "rebut", "judge"].includes(action)) { throw new Error(`unknown action: ${action}`); } if (typeof data.needsMoreRounds !== "boolean") { throw new Error("needsMoreRounds must be boolean"); } return { agentId: data.agentId, round: data.round, action, content: data.content, targetAgentId: data.targetAgentId, needsMoreRounds: data.needsMoreRounds, }; } /* ---- 2. Pretty console output ---- */ logRecord(rec) { const colour = rec.action === "judge" ? chalk.yellow : rec.agentId === "pro" ? chalk.green : rec.agentId === "con" ? chalk.red : chalk.cyan; const header = `${colour(`[${rec.action.toUpperCase()}]`)} ${rec.agentId} (round ${rec.round})${rec.targetAgentId ? ` → ${rec.targetAgentId}` : ""}`; const border = "─".repeat(Math.max(header.length, rec.content.length) + 4); console.error(` ┌${border}┐ │ ${header.padEnd(border.length - 2)} │ ├${border}┤ │ ${rec.content.padEnd(border.length - 2)} │ └${border}┘ `); } /* ---- 3. Main processing ---- */ process(input) { try { const d = this.validate(input); if (d.action === "register") { this.agents.add(d.agentId); } else { if (!this.agents.has(d.agentId)) { throw new Error(`agent ${d.agentId} is not registered – call action:"register" first`); } if (!d.content || d.content.trim() === "") { throw new Error("content required for this action"); } /* store history */ const rec = { agentId: d.agentId, round: d.round, action: d.action, content: d.content, targetAgentId: d.targetAgentId, ts: Date.now(), }; this.history.push(rec); this.logRecord(rec); /* if judge, record verdict */ if (d.action === "judge") { this.verdict = { for: d.content.split("\n")[0].trim(), // first line: winner / inconclusive rationale: d.content, round: d.round, }; } } /* ---- 4. JSON response to caller ---- */ return { content: [ { type: "text", text: JSON.stringify({ agents: Array.from(this.agents), totalArguments: this.history.length, lastAction: d.action, verdict: this.verdict ?? null, needsMoreRounds: d.needsMoreRounds, }, null, 2), }, ], }; } catch (err) { return { content: [ { type: "text", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }, null, 2), }, ], isError: true, }; } } } /* ---------- MCP TOOL DEFINITION ---------- */ const MULTI_AGENT_DEBATE_TOOL = { name: "multiagentdebate", description: `Structured multi‑persona debate tool. Call sequence (typical): 1. Each persona registers once with action:"register". 2. Personas alternate action:"argue" (fresh point) or "rebut" (counter a targetAgentId). 3. A special persona (or either side) issues action:"judge" with a verdict text (first line should be "pro", "con", or "inconclusive"). 4. Set needsMoreRounds:false only when the debate is finished and a verdict stands. Parameters: - agentId (string) : "pro", "con", "judge", or any custom ID - round (int ≥1) : Debate round number - action (string) : "register" | "argue" | "rebut" | "judge" - content (string, optional) : Argument text or verdict - targetAgentId (string opt.) : Agent being rebutted (only for action:"rebut") - needsMoreRounds (boolean) : True if additional debate rounds desired`, inputSchema: { type: "object", properties: { agentId: { type: "string" }, round: { type: "integer", minimum: 1 }, action: { type: "string", enum: ["register", "argue", "rebut", "judge"] }, content: { type: "string" }, targetAgentId: { type: "string" }, needsMoreRounds: { type: "boolean" }, }, required: ["agentId", "round", "action", "needsMoreRounds"], }, }; /* ---------- MCP SERVER WIRING ---------- */ const server = new Server({ name: "multi-agent-debate-server", version: "0.1.0", }, { capabilities: { tools: {} }, }); const debateServer = new MultiAgentDebateServer(); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [MULTI_AGENT_DEBATE_TOOL], })); server.setRequestHandler(CallToolRequestSchema, async (req) => { if (req.params.name === "multiagentdebate") { return debateServer.process(req.params.arguments); } return { content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }], isError: true, }; }); /* ---------- BOOT ---------- */ (async () => { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Multi‑Agent Debate MCP Server listening on stdio"); })().catch((e) => { console.error("Fatal error running server:", e); process.exit(1); }); //# sourceMappingURL=index.js.map