consult-mcp
# consult-mcp
> MCP server orchestrating local CLI agents (Claude Code, OpenAI Codex, Google Gemini) for cross-validation, second opinions, and persona-driven prompting.
## What it does
When you're working in Claude Code (or any MCP client), `consult-mcp` lets you ask other LLM CLIs for their take — without leaving the conversation. Use cases:
- **Second opinion**: "Claude wrote this — what does Gemini think?"
- **Cross-validation**: send the same question to claude/codex/gemini in parallel and compare
- **Variant generation**: pass `["claude", "claude"]` for two independent responses to the same prompt
- **Specialized framing**: apply a persona (`architect`, `reviewer`, `researcher`, `coder`) to focus the model
Eighteen MCP tools ship in v0.10:
- **Read-only single-shot** (no file mutation, return immediately):
- `consult(agent, prompt, persona?, timeout_seconds?)` — single agent
- `consult_parallel(agents, prompt, persona?, timeout_seconds?)` — fan-out to N agents
- `codereview(agents, files, focus?, timeout_seconds?)` — multi-agent code review with structured findings
- `consensus(agents, question, stances?, synthesizer?, timeout_seconds?)` — multi-agent verdict with optional stance steering
- `challenge(agent, claim, timeout_seconds?)` — anti-sycophancy single-agent pushback
- **Async debate** (returns ``debate_id`` immediately, runs in background):
- `debate_start(agents, topic, max_turns?, history_window?, terminator?, ...)` — kick off round-robin debate
- `debate_status(debate_id)` — poll progress + transcript
- `debate_cancel(debate_id)` — request soft cancellation
- `debate_list(active_only?, limit?)` — list active + archived debates
- `debate_replay(debate_id)` — full transcript from memory or SQLite archive
- `debate_export(debate_id, format?, truncate_body_chars?)` — render archived debate as portable markdown (default) or stable v1 JSON for Obsidian/Notion/programmatic consumers
- **Synchronous streaming debate** (foreground call with progress notifications):
- `debate_run(agents, topic, max_turns?, ...)` — returns when the loop terminates; pushes per-turn progress to MCP clients that honour `progressToken` (e.g. Claude Code). Non-resumable — for long debates use `debate_start` + poll instead
- **Atomic deliberation** (3-stage in one call):
- `council(agents, question, chairman?, persona?, timeout_seconds?)` — independent → cross-rank (anonymized) → synthesis
- **Niche** (focused single-purpose tools):
- `planner(agent, problem, depth?, timeout_seconds?)` — structured task decomposition (JSON tasks with dependencies)
- `diff_review(agents, diff_text?|git_ref?, cwd?, focus?, ...)` — review a git diff for risks/regressions
- `apilookup(query, agent?, timeout_seconds?)` — current docs lookup with web search (default agent: gemini)
- **File-mutating** (activates CLI permission/sandbox bypass flags):
- `implement(agent, plan, base_path, constraints?, timeout_seconds?)` — delegate code changes; returns diff
- `delegate_implementation(task, base_path, planner_agent?, implementer_agent?, reviewer_agents?, ...)` — composite plan→implement→review in one call. Plan via `planner_agent`, write code via `implementer_agent` (mutation rights, same `CONSULT_MCP_ALLOWED_BASE` guard), then fan-out to `reviewer_agents` (auto-routes to `diff_review` for git diffs or `codereview` for mtime-mode workspaces). Default reviewers exclude the implementer.
## Prerequisites
- **Python 3.10+** (the server itself)
- **`uv`** for installation (`pipx install uv` or follow [astral.sh/uv](https://docs.astral.sh/uv/))
- **At least one of** these CLI tools installed and authenticated:
- **Claude Code CLI** — install via [`claude.com/code`](https://claude.com/code), then `claude login`
- **OpenAI Codex CLI** — `npm install -g @openai/codex`, set `OPENAI_API_KEY`
- **Google Gemini CLI** — install via [`geminicli.com`](https://geminicli.com), authenticate per docs
You don't need all three — `consult-mcp` reports per-agent status and skips missing CLIs.
### API keys
Each CLI handles its own auth. Common env-var setup:
| Platform | Set env var |
| --- | --- |
| Windows (PowerShell, persistent) | `setx OPENAI_API_KEY "sk-..."` |
| Windows (current shell) | `$env:OPENAI_API_KEY = "sk-..."` |
| macOS / Linux (current shell) | `export OPENAI_API_KEY="sk-..."` |
| macOS / Linux (persistent) | append `export OPENAI_API_KEY="sk-..."` to `~/.bashrc` or `~/.zshrc` |
Replace `OPENAI_API_KEY` with `GEMINI_API_KEY` for Gemini. Claude Code uses interactive login (`claude login`); no env var.
## Installation
Add this to your MCP client's configuration. For Claude Code, that's `.mcp.json` (project-local) or `~/.claude.json` (global):
```json
{
"mcpServers": {
"consult": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/oblogin/consult-mcp.git",
"consult-mcp"
]
}
}
}
```
Restart your MCP client. All six tools (`consult`, `consult_parallel`, `codereview`, `consensus`, `challenge`, `implement`) should appear.
A copy-pasteable snippet is in [`examples/.mcp.json`](examples/.mcp.json).
## Quick start
```jsonc
// Single-agent consultation:
consult({
"agent": "gemini",
"prompt": "Should we use GAS for an inventory system in Unreal Engine?",
"persona": "architect"
})
// → {
// "status": "success",
// "agent": "gemini",
// "persona": "architect",
// "response": "GAS overkill for simple inventory; trade-offs ...",
// "metadata": { "duration_seconds": 8.4, "returncode": 0 }
// }
// Parallel cross-validation:
consult_parallel({
"agents": ["claude", "codex", "gemini"],
"prompt": "Reply with just the digit: what is 2+2?",
"persona": "default"
})
// → {
// "status": "success",
// "responses": [
// { "agent": "claude", "status": "success", "response": "4", ... },
// { "agent": "codex", "status": "success", "response": "4", ... },
// { "agent": "gemini", "status": "success", "response": "4", ... }
// ],
// "summary": { "total": 3, "succeeded": 3, "failed": 0, "wall_time_seconds": 9.1, "max_duration_seconds": 8.4 }
// }
```
## Specialized tools (v0.2)
### codereview — multi-agent code review
```jsonc
codereview({
"agents": ["codex", "gemini"],
"files": ["src/auth.py", "src/auth_test.py"],
"focus": "auth and session handling"
})
// → { "status": "success",
// "reviews": [
// { "agent": "codex", "status": "success", "response": "...",
// "findings": [
// {"severity": "high", "location": "src/auth.py:42",
// "title": "Missing CSRF check", "description": "..."}
// ]},
// { "agent": "gemini", ... }
// ],
// "summary": { "succeeded": 2, ... } }
```
Each agent is invoked with the `reviewer` persona, which instructs it to return JSON `{summary, findings: [{severity, location, title, description}]}`. The tool parses that JSON; on parse failure, the raw response stays in `reviews[i].response` with a `metadata.findings_parse_error` flag.
### consensus — multi-agent verdict with stance steering
```jsonc
consensus({
"agents": ["claude", "codex", "gemini"],
"question": "Should we adopt SQLite WAL mode for our writes?",
"stances": ["for", "against", "neutral"],
"synthesizer": "claude"
})
// → { "votes": [{...}, {...}, {...}],
// "synthesis": { "agent": "claude", "response": "Both sides agree..." } }
```
Stances (`for`/`against`/`neutral`) prefix each agent's prompt. Optional `synthesizer` runs once more after the votes to produce a verdict.
### challenge — anti-sycophancy pushback
```jsonc
challenge({
"agent": "claude",
"claim": "Premature optimization is always wrong; ignore performance until benchmarks fail."
})
// → { "agent": "claude", "persona": "skeptic",
// "response": "Counter: in real-time loops, even microseconds matter; ..." }
```
Persona is hardcoded to `skeptic`. The agent is instructed to find weaknesses and counter-arguments rather than agree.
### implement — delegate code changes ⚠️ mutation-capable
```jsonc
implement({
"agent": "codex",
"plan": "Add a /healthz endpoint to api/server.py returning {\"ok\": true}.",
"base_path": "F:/repos/my-app",
"constraints": "Don't modify tests/. Use existing FastAPI app."
})
// → { "status": "success",
// "mode": "git",
// "files_modified": ["api/server.py"],
// "files_created": [],
// "diff": { "text": "...", "stat": "1 file changed, 5 insertions(+)", ... } }
```
This tool activates CLI mutation flags (`--permission-mode acceptEdits`, `--dangerously-bypass-approvals-and-sandbox`, `--yolo`). If `base_path` is a git repo → unified diff returned. If not → file list only (mtime detection). See **Security & Limitations** below before using.
## Async debate (v0.3)
For long deliberation between 2-6 agents — round-robin, runs in the background, transcript persists to SQLite for replay.
### When to use vs `council`
| | `council` | `debate_*` |
| --- | --- | --- |
| Latency | Sync, ~min wait | Async, runs minutes-to-hours |
| Stages | 3 (independent → rank → synthesis) | N round-robin turns |
| Cancel | n/a | `debate_cancel(id)` |
| Persistence | Transcript in response | Auto-saved to `~/.consult-mcp/archive.db` |
| Best for | Quick verdict with explicit ranking | Open-ended debate where agents react to each other |
### Quick example
```jsonc
// Start a debate; returns id immediately:
debate_start({
"agents": ["claude", "codex", "gemini"],
"topic": "Should we move our analytics from PostgreSQL to ClickHouse?",
"max_turns": 10,
"history_window": 6,
"terminator": "any"
})
// → { "status": "success", "debate_id": "abc123...", "started_at": "..." }
// Poll progress (call as often as you like):
debate_status({ "debate_id": "abc123..." })
// → { "state": "running", "turn_count": 3, "transcript": [...] }
// When state == "completed":
// → { "state": "completed", "terminated_by": "done", "turn_count": 7, "transcript": [...] }
// Or cancel early:
debate_cancel({ "debate_id": "abc123..." })
// → { "already_finished": false, "requested_at": "..." }
// List debates (memory + archive):
debate_list({ "limit": 20 })
// Replay an old debate from SQLite:
debate_replay({ "debate_id": "abc123..." })
// Export as portable markdown (Obsidian-friendly frontmatter + transcript):
debate_export({ "debate_id": "abc123..." })
// → { "status": "success", "format": "md", "content": "---\nschema_version: 1\n..." }
// Or as stable JSON for programmatic consumers:
debate_export({ "debate_id": "abc123...", "format": "json" })
// → { "status": "success", "format": "json", "content": { "schema_version": 1, "turns": [...] } }
// Long transcripts: truncate per-turn bodies to fit MCP transport limits:
debate_export({ "debate_id": "abc123...", "truncate_body_chars": 2000 })
```
### Export format
`debate_export` returns either markdown (default, copy-pasteable into Obsidian/Notion) or stable JSON. The JSON shape is versioned via `schema_version: 1`; future breaking changes bump the version. Markdown frontmatter is generated through `yaml.safe_dump`, so user-controlled fields (`topic`, `agent_name`, `error_message`) cannot break it through `:`, `\n` or `---`. Total response size is capped at 5 MiB — pass `truncate_body_chars=N` to fit larger transcripts. All five debate states (`pending`, `running`, `completed`, `cancelled`, `error`) are exportable; running snapshots are explicitly marked.
### Terminator semantics
The terminator controls **soft** early-stop (DONE marker emitted by the agent). Hard caps (`max_turns`, external cancel, all_slots_down) are unconditional.
| Spec | Behavior |
| --- | --- |
| `"any"` (default) | OR(max_turns, done) — stops on either |
| `"max_turns_only"` | Ignore DONE; always run to max_turns |
| `"done_only"` | Stop only on DONE (still bounded by max_turns hard cap) |
| `{"and": ["max_turns", "done"]}` | Custom JSON expression |
DONE detection is word-boundary aware: `"abandoned"` does NOT match, `"...DONE"` does.
### Council — atomic 3-stage analysis
Single MCP call, ~min latency. Pattern from llm-council-mcp:
```jsonc
council({
"agents": ["claude", "codex", "gemini"],
"question": "Should we move analytics to ClickHouse?",
"chairman": "claude" // default = first agent
})
// → { "status": "success",
// "stage1": [...], // independent answers (parallel)
// "stage2": [...], // each agent ranks the anonymized responses
// "stage3": { ... }, // chairman synthesizes
// "stage2_parse_summary": { "json": 2, "fuzzy": 1, "unparseable": 0 } }
```
Stage 2 anonymizes responses as `Response A`, `Response B`, etc. so agents rank by content quality, not by author.
## Niche tools (v0.4)
### planner — structured task decomposition
```jsonc
planner({
"agent": "claude",
"problem": "Migrate the API from REST to gRPC, keeping the existing auth flow.",
"depth": "tree"
})
// → { "status": "success", "agent": "claude",
// "plan": {
// "summary": "Phased migration, 8 tasks",
// "tasks": [
// {"id":"T1","title":"Define proto schema","depends_on":[],"estimated_size":"M"},
// {"id":"T2","title":"Generate stubs","depends_on":["T1"],"estimated_size":"S"},
// ...
// ]
// },
// "depth": "tree" }
```
Pass `depth="flat"` to force `depends_on=[]` for every task. On JSON parse failure, the tool returns `parse_error: true` with `raw_response` preserved.
### diff_review — git diff regression review
```jsonc
// With raw diff text:
diff_review({
"agents": ["claude", "gemini"],
"diff_text": "diff --git a/auth.py b/auth.py\n@@ ...",
"focus": "security regressions"
})
// Or pulling from a git repo directly:
diff_review({
"agents": ["claude"],
"git_ref": "HEAD~3..HEAD",
"cwd": "F:/repos/my-app"
})
```
Mutually exclusive: pass either `diff_text` (raw) OR `git_ref` + `cwd`. The reviewer persona is hardcoded.
### apilookup — current-docs lookup
```jsonc
apilookup({
"query": "Latest Anthropic Python SDK version + breaking changes vs 1.x"
})
// → { "agent": "gemini", "persona": "researcher_current",
// "response": "As of 2026-04, the SDK is at 2.x ..." }
```
Default agent is `gemini` (web search built into the headless CLI). Other agents fall back to training-data answers and flag the staleness.
## Personas
Eight built-in system prompts ship in v0.4:
| Persona | Use case |
| --- | --- |
| `default` | Neutral helpful assistant |
| `architect` | Design, trade-offs, system structure |
| `reviewer` | Bugs, edge cases, security — returns severity-tagged JSON findings |
| `researcher` | Surveys alternatives, best practices, reference implementations |
| `researcher_current` | Same focus + emphasizes current docs / web sources / dates |
| `coder` | Minimal practical code, no over-engineering |
| `skeptic` | Anti-sycophancy — finds weaknesses, edge cases, counter-arguments |
| `planner` | Structured task decomposition — outputs JSON task tree |
### Adding your own
Create a markdown file at `~/.consult-mcp/personas/<name>.md`:
```markdown
---
name: security_reviewer
description: Focuses on auth, crypto, and OWASP top 10
---
You are a security-focused code reviewer. Concentrate on auth flaws,
crypto misuse, injection vectors, and OWASP top-10 risks. Skip generic
style feedback. Output severity-tagged findings as JSON.
```
It's available immediately after the next server restart: `consult({"agent":"claude","prompt":"...","persona":"security_reviewer"})`.
The full file format, length limits, naming policy and parser-error matrix are
documented in [`Docs/persona-file-contract.md`](Docs/persona-file-contract.md).
**User personas in `~/.consult-mcp/personas/` override built-ins on name
collision** — be explicit if that's what you want.
### Community pack
A small curated set of ready-to-copy personas lives in
[`examples/personas/community/`](examples/personas/community/) — currently
`security_reviewer`, `perf_reviewer`, and `regex_explainer`. Pick what you want
and `cp` it into `~/.consult-mcp/personas/`. Names are guaranteed not to clash
with built-ins. New PRs welcome — see the community
[README](examples/personas/community/README.md) for contributing rules and the
validator (`python -m scripts.validate_personas examples/personas/community`)
that runs in CI.
## Configuration
Built-in CLI configs live in the package (read-only). To override the command, args, or timeout for any agent — drop a JSON file at `~/.consult-mcp/agents/<name>.json` matching the schema:
```json
{
"name": "claude",
"command": ["claude"],
"internal_args": ["--print", "--model", "opus"],
"timeout_seconds": 180,
"agent_class": "consult_mcp.agents.claude.ClaudeAgent"
}
```
User overrides replace the built-in entirely (no merging). Restart the MCP client to pick up changes.
## Troubleshooting
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `error.type: unknown_agent` | CLI binary not in PATH | `where claude` (Windows) / `which claude` (Unix); install missing CLI |
| `error.type: timeout` | CLI took longer than 120s | pass `timeout_seconds` to the tool, or override `timeout_seconds` in `~/.consult-mcp/agents/<name>.json` |
| `error.type: cli_error` with stderr "API key" | Missing `OPENAI_API_KEY` / `GEMINI_API_KEY` | set the env var (see API keys section) |
| `error.type: cli_error` with stderr "not logged in" | Claude Code not authenticated | run `claude login` once interactively |
| `error.type: cli_error` with stderr about model | Account/CLI version mismatch | upgrade the CLI: `npm i -g @openai/codex@latest` etc. |
| Server starts but no tools shown | Restart MCP client after install or `.mcp.json` edits | full restart (not just reload) |
| `ImportError` on Python 3.9 | Python too old | `python --version` ≥ 3.10 required |
## ⚠️ Security & Limitations
**Read-only by default.** `consult`, `consult_parallel`, `codereview`, `consensus`, and `challenge` invoke CLIs **without** mutation/sandbox-bypass flags. Agents return text answers; they do not edit files or execute commands.
**`implement` is the only mutation-capable tool** (v0.2). It activates `--permission-mode acceptEdits` (Claude), `--dangerously-bypass-approvals-and-sandbox` (Codex), and `--yolo` (Gemini). A malicious or careless prompt can delete files, exfiltrate secrets, or run arbitrary commands. Run `implement` in isolated repositories and code-review the resulting diff before trusting it. Mutation flags are kept in a separate `mutation_args` field per agent config and only applied when `implement` is called — they cannot leak into other tools.
**Local-only.** consult-mcp runs as a single process on a single machine. No cross-machine coordination, no daemon, no shared state.
**Privacy.** Prompts and responses pass through whichever LLM provider you've configured per CLI (Anthropic, OpenAI, Google). Don't send data you wouldn't paste into the respective web UIs. The persona system prompt is appended to every request.
**Restart loss.** Active debates (`state in {pending, running}`) live only in memory. **Restarting the MCP client kills any in-flight debate** — the asyncio task is gone, no resume, the transcript up to that point is NOT archived (archive only writes on terminal state). Completed debates are persisted to `~/.consult-mcp/archive.db` and replayable via `debate_replay`.
**Resource limits.** `debate_start` rejects new starts when ≥ 5 debates are already active (override via `CONSULT_MCP_MAX_ACTIVE_DEBATES` env). This is a guard against accidental fork-bomb when multiple chained tool calls trigger debate creation.
**Privacy of archive.** `~/.consult-mcp/archive.db` is plain SQLite, not encrypted. Anyone with file-system access can read transcripts. Don't run debates on data you wouldn't write to a plain file.
## Roadmap
| Iteration | Focus | Status |
| --- | --- | --- |
| **IT-001** | MVP — `consult`, `consult_parallel`, 5 personas | ✅ shipped (v0.1) |
| **IT-002** | Specialized — `codereview`, `consensus`, `challenge`, `implement` | ✅ shipped (v0.2) |
| **IT-003** | Async debate — `debate_*`, `council`, SQLite archive | ✅ shipped (v0.3) |
| **IT-004** | Niche — `planner`, `diff_review`, `apilookup` | ✅ shipped (v0.4) |
| **IT-006** | Markdown export — `debate_export` | ✅ shipped (v0.6) |
| **IT-007** | Community personas pack + persona-file contract + validator | ✅ shipped (v0.7) |
| **IT-008** | Streaming — `debate_run`, `council` progress + cancellation | ✅ shipped (v0.8) |
| **IT-010** | Composite — `delegate_implementation` (plan→implement→review) | ✅ this release (v0.10) |
### What's next
- Streaming progress notifications for async debates (MCP SSE)
- Web UI / dashboard for archived debate replay
- Community personas (PRs welcome)
- Documentation site (Sphinx/MkDocs) once tool count > 15 or README > 800 lines
Detailed iteration plans live in [`Docs/plans/iterations/`](Docs/plans/iterations/).
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 18 tools
Each tool targets a distinct workflow or function, from single-agent consult to multi-stage debate and code implementation. Descriptions clarify nuances between similar tools (e.g., consult_parallel vs. consensus vs. council), leaving no ambiguity.
All tool names follow a consistent snake_case verb_noun pattern (e.g., consult_parallel, debate_run, delegate_implementation). The convention is uniform and predictable across all 18 tools.
At 18 tools, the server is ambitious but well-scoped for its domain of multi-agent orchestration and code assistance. While extensive, each tool earns its place with specific use cases; the count does not feel excessive for the breadth of functionality.
The tool surface covers the full lifecycle of agent interactions: quick consult, parallel fan-out, structured consensus, multi-round debate with replay/export, planning, implementation, and code review. No obvious gaps exist for the stated purpose of MCP-based consulting.