ai-consensus-connector 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +80 -0
  2. package/index.js +96 -0
  3. package/package.json +15 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # AI Consensus — coding-agent connector
2
+
3
+ Make AI Consensus a **standing part of your development model**. Your coding
4
+ agent — Claude Code, Cursor, Cline, any MCP client — routes every decision of
5
+ consequence through three frontier AIs (Claude, GPT, Gemini) that answer
6
+ **independently**, then **cross-examine each other**, and return one
7
+ recommendation **plus the strongest dissent**. You keep working inside your
8
+ agent; the panel reviews as you go.
9
+
10
+ This is not a tool you reach for occasionally. It's a review layer you switch
11
+ on once, and from then on everything that matters gets debated by three
12
+ independent models before it ships. (Honest bill: a run takes a few minutes
13
+ and, on the bring-your-own-keys plan, costs roughly $0.40–$1 of your own
14
+ provider tokens.)
15
+
16
+ ## Setup — Claude Code (about 2 minutes)
17
+
18
+ 1. Get an API key: sign in at https://ai-consensus.ai → **Developer API** → create a key (copy it; it's shown once).
19
+ 2. Add the connector:
20
+
21
+ ```bash
22
+ claude mcp add ai-consensus --scope user \
23
+ --env AI_CONSENSUS_API_KEY=aic_live_your_key_here \
24
+ -- npx -y ai-consensus-connector
25
+ ```
26
+
27
+ (Self-hosting? add `--env AI_CONSENSUS_API_URL=https://your-instance`.)
28
+
29
+ 3. Verify it's connected (free — no run is started):
30
+
31
+ > "Use the check_ai_consensus_connection tool."
32
+
33
+ 4. Make it your standing rule — add to your project's `CLAUDE.md`:
34
+
35
+ ```
36
+ STANDING RULE: This project develops with AI Consensus as its review layer.
37
+ Route every decision of consequence — architecture, design, implementation
38
+ approach, tricky bugs, anything whose cost of being wrong is real — through
39
+ consult_ai_consensus as a matter of course, and weigh its recommendation and
40
+ strongest dissent before acting. Purely mechanical edits (formatting, typos,
41
+ exact instructed changes) don't need a run. When in doubt, consult.
42
+ ```
43
+
44
+ Teams that want *everything* routed can use the strict variant instead:
45
+
46
+ ```
47
+ STRICT MODE: Consult consult_ai_consensus before every non-trivial change of
48
+ any kind, and weigh the dissent before acting.
49
+ ```
50
+
51
+ ## Setup — Cursor / Cline / other MCP clients
52
+
53
+ Add a stdio MCP server with command `npx -y ai-consensus-connector` and the
54
+ `AI_CONSENSUS_API_KEY` environment variable. For Cursor, in `mcp.json`:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "ai-consensus": {
60
+ "command": "npx",
61
+ "args": ["-y", "ai-consensus-connector"],
62
+ "env": { "AI_CONSENSUS_API_KEY": "aic_live_your_key_here" }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ Then put the standing rule in your project's agent rules file (`.cursorrules`,
69
+ `.clinerules`, etc.).
70
+
71
+ ## Tools
72
+ - `consult_ai_consensus` — route a decision/task through the panel and wait for the result (a few minutes).
73
+ - `start_ai_consensus` / `get_ai_consensus_result` — fire-and-forget + collect later.
74
+ - `check_ai_consensus_connection` — zero-cost setup check (reachability, key accepted, provider keys present).
75
+ - `cancel_ai_consensus_run` — stop a run.
76
+
77
+ Your key authenticates to your account and bills your plan (unlimited on your
78
+ own keys, or prepaid credits). Keep it secret — and note that pasting the
79
+ `--env` form above stores the key in your shell history; use your client's
80
+ config file if that concerns you.
package/index.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ // AI Consensus — MCP connector. Makes AI Consensus a standing review layer in
3
+ // your development loop: your coding agent (Claude Code, Cursor, Cline — any
4
+ // MCP client) routes decisions of consequence through three frontier AIs that
5
+ // answer independently, cross-examine each other, and return one
6
+ // recommendation plus the strongest dissent. Config via env:
7
+ // AI_CONSENSUS_API_KEY (required) aic_live_...
8
+ // AI_CONSENSUS_API_URL (optional) defaults to the hosted API
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { z } from "zod";
12
+
13
+ const API_BASE = (process.env.AI_CONSENSUS_API_URL || "https://ai-consensus.ai").replace(/\/$/, "");
14
+ const API_KEY = process.env.AI_CONSENSUS_API_KEY || "";
15
+
16
+ async function api(path, opts = {}) {
17
+ const res = await fetch(API_BASE + path, {
18
+ ...opts,
19
+ headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", ...(opts.headers || {}) },
20
+ });
21
+ let json;
22
+ try { json = JSON.parse(await res.text()); } catch { json = {}; }
23
+ return { status: res.status, json };
24
+ }
25
+
26
+ function render(r) {
27
+ if (r?.result?.artifact) return r.result.artifact;
28
+ const v = r?.result?.verdict;
29
+ if (v) {
30
+ const out = [v.consensusReached ? "RECOMMENDATION:" : "A JUDGEMENT CALL — WHAT TO WEIGH:", v.consensusReached ? v.consensusDecision : v.escalation];
31
+ if (v.dissent) out.push("", "STRONGEST DISSENT:", v.dissent);
32
+ return out.join("\n");
33
+ }
34
+ return `Status: ${r?.status}`;
35
+ }
36
+
37
+ const server = new McpServer({ name: "ai-consensus", version: "0.12.0" });
38
+
39
+ server.tool(
40
+ "consult_ai_consensus",
41
+ "Route a decision, question, or task through three frontier AIs (Claude, GPT, Gemini) that answer independently, then cross-examine each other — returning one recommendation plus the strongest dissent. Intended as a STANDING part of the development loop: consult it as a matter of course for any decision of consequence (architecture, design, approach, tricky bugs, anything whose cost of being wrong is real) — not just rare emergencies. Purely mechanical edits do not need a run. A run takes a few minutes.",
42
+ { prompt: z.string().describe("The decision, question, or task to run through AI Consensus.") },
43
+ async ({ prompt }) => {
44
+ if (!API_KEY) return { content: [{ type: "text", text: "AI_CONSENSUS_API_KEY is not set." }], isError: true };
45
+ const sub = await api("/api/v1/runs", { method: "POST", body: JSON.stringify({ question: prompt }) });
46
+ if (sub.status !== 202) return { content: [{ type: "text", text: `Could not start the run: ${JSON.stringify(sub.json)}` }], isError: true };
47
+ const runId = sub.json.run_id;
48
+ for (let i = 0; i < 30; i++) {
49
+ const w = await api(`/api/v1/runs/${runId}/wait`, { method: "POST" });
50
+ if (w.json?.status === "succeeded" || w.json?.status === "failed") return { content: [{ type: "text", text: render(w.json) }] };
51
+ }
52
+ return { content: [{ type: "text", text: `Still running — collect it later with get_ai_consensus_result and run id ${runId}.` }] };
53
+ },
54
+ );
55
+
56
+ server.tool("start_ai_consensus", "Start an AI Consensus run without waiting; returns a run id to collect later.",
57
+ { prompt: z.string() },
58
+ async ({ prompt }) => {
59
+ const sub = await api("/api/v1/runs", { method: "POST", body: JSON.stringify({ question: prompt }) });
60
+ return { content: [{ type: "text", text: sub.status === 202 ? `Started. run_id: ${sub.json.run_id}` : `Error: ${JSON.stringify(sub.json)}` }] };
61
+ });
62
+
63
+ server.tool("get_ai_consensus_result", "Fetch the status/result of an AI Consensus run by id.",
64
+ { run_id: z.string() },
65
+ async ({ run_id }) => {
66
+ const r = await api(`/api/v1/runs/${run_id}`);
67
+ return { content: [{ type: "text", text: r.json?.status === "succeeded" ? render(r.json) : `Status: ${r.json?.status ?? r.status}` }] };
68
+ });
69
+
70
+ server.tool("cancel_ai_consensus_run", "Cancel an in-progress AI Consensus run by id.",
71
+ { run_id: z.string() },
72
+ async ({ run_id }) => {
73
+ const r = await api(`/api/v1/runs/${run_id}/cancel`, { method: "POST" });
74
+ return { content: [{ type: "text", text: JSON.stringify(r.json) }] };
75
+ });
76
+
77
+ server.tool(
78
+ "check_ai_consensus_connection",
79
+ "Zero-cost setup check: verifies the API is reachable, your AI Consensus key is accepted, and (on bring-your-own-keys plans) which provider keys are present. Does NOT start a deliberation and costs nothing. Use right after installing the connector.",
80
+ {},
81
+ async () => {
82
+ if (!API_KEY) return { content: [{ type: "text", text: "AI_CONSENSUS_API_KEY is not set." }], isError: true };
83
+ const r = await api("/api/v1/status");
84
+ if (r.status !== 200 || !r.json?.ok) {
85
+ return { content: [{ type: "text", text: `Not connected (HTTP ${r.status}): ${JSON.stringify(r.json)}` }], isError: true };
86
+ }
87
+ const lines = [`Connected ✓ — key accepted, plan: ${r.json.plan_type ?? "unknown"}.`];
88
+ if (r.json.provider_keys_present) {
89
+ const p = r.json.provider_keys_present;
90
+ lines.push(`Provider keys present: anthropic=${p.anthropic}, openai=${p.openai}, gemini=${p.gemini} (presence only — validated on your first real run).`);
91
+ }
92
+ return { content: [{ type: "text", text: lines.join("\n") }] };
93
+ },
94
+ );
95
+
96
+ await server.connect(new StdioServerTransport());
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "ai-consensus-connector",
3
+ "version": "0.12.0",
4
+ "mcpName": "io.github.thestevenjohnstone/ai-consensus",
5
+ "description": "Make AI Consensus a standing review layer for your coding agent — Claude, GPT and Gemini answer independently, cross-examine each other, and return one recommendation plus the strongest dissent.",
6
+ "type": "module",
7
+ "bin": { "ai-consensus-connector": "index.js" },
8
+ "files": ["index.js", "README.md"],
9
+ "dependencies": {
10
+ "@modelcontextprotocol/sdk": "^1.0.0",
11
+ "zod": "^3.23.8"
12
+ },
13
+ "engines": { "node": ">=18" },
14
+ "license": "UNLICENSED"
15
+ }