unmerged-approaches-mcp
# unmerged-approaches-mcp
An MCP server that puts **one identical brief to several LLMs in parallel** and hands back **every answer verbatim — unmerged, unranked, unsynthesized.**
Most multi-model MCP servers end in a judge, a synthesizer, or a consensus loop. This one deliberately does not. The moment N answers are averaged into one confident paragraph, the single most useful piece of information is destroyed: **whether the models actually agreed.**
- Four models independently reaching the same conclusion is corroboration.
- Two against two is a real open question your one-model answer would have hidden from you.
A synthesizer renders both of those as the same smooth prose. This server refuses to, by design.
## What it does
- Fans one brief out to up to 12 OpenRouter models concurrently — 400+ models, one API key.
- Returns per-model: the verbatim answer, `finish_reason`, latency, token usage, cost, and a structured error when a model fails.
- A slow or failing model becomes one failed entry; it never sinks the panel.
- Persists bounded, redacted, correlation-ID-addressable debug events.
- Never merges, ranks, scores, votes on, or picks a winner among the answers.
## Install
### stdio (Claude Desktop, Claude Code, Cursor, Cline, Zed)
```json
{
"mcpServers": {
"unmerged-approaches": {
"command": "npx",
"args": ["-y", "unmerged-approaches-mcp@latest"],
"env": { "OPENROUTER_API_KEY": "sk-or-v1-..." }
}
}
}
```
```bash
claude mcp add unmerged-approaches npx -y unmerged-approaches-mcp
```
### Remote Streamable HTTP
```bash
OPENROUTER_API_KEY=sk-or-v1-... UNMERGED_AUTH_TOKEN=your-token npx unmerged-approaches-mcp-http
# or: node src/http.js
```
Mounts MCP at `/mcp` and a separate liveness probe at `/health`.
```json
{
"mcpServers": {
"unmerged-approaches": {
"type": "http",
"url": "https://your-host.example/mcp"
}
}
}
```
Send the token as `Authorization: Bearer <token>` or `x-api-key`. Keep secrets in your client's secret storage, never in a committed config file.
## Tools
| Tool | Category | What it does |
|---|---|---|
| `consult` | PANEL | One brief → N models in parallel → N verbatim answers, unmerged |
| `list_models` | SYSTEM | Live OpenRouter catalog: substring search, free-only filter, cursor pagination |
| `get_status` | SYSTEM | Server version, credential validity, remaining credit, default panel, limits |
| `get_debug_logs` | DEBUG | Newest-first structured events, filterable by correlationId / level / event / operation / time |
All four are read-only and non-destructive. The server exposes no arbitrary HTTP, shell, or SQL primitive.
### `consult`
| Argument | Type | Notes |
|---|---|---|
| `brief` | string, required | Sent byte-for-byte identically to every model. Max 200,000 chars. |
| `models` | string[] | Exact OpenRouter ids, max 12, deduplicated. Omit for the default panel. |
| `system` | string | Optional system prompt, identical for every model. |
| `max_tokens` | int | Default 8000, ceiling 32000. |
| `temperature` | number | 0–2, default 0.7. |
| `timeout_ms` | int | Default 180000, max 600000. Per model, not for the batch. |
Returns `correlationId`, `requestedModels`, `answeredCount`, `completeCount`, `failedCount`, `totalCostUsd`, `wallClockMs`, and `answers[]` with `{ model, actualModel, ok, complete, finishReason, content, error, latencyMs, usage }`.
If **every** model fails, the call returns `isError: true` with code `ALL_MODELS_FAILED`. A partial panel is a success.
## Writing a brief that is worth the money
The output is only as good as the isolation of the input. A brief that leaks your preferred answer gets it echoed back by every model, and you will mistake that echo for agreement.
- Include the goal, hard constraints, what is already decided, what was rejected and why, and what is genuinely unknown.
- Mark unverified claims as unverified. Distinguish what you observed from what you assumed.
- **Do not** name your preferred option, and do not tell the panel what an earlier model said.
- Ask for independent judgment, not novelty.
## Environment variables
| Name | Required | Purpose |
|---|---|---|
| `OPENROUTER_API_KEY` | yes | OpenRouter credential. Server-side only; never accepted as a tool argument. |
| `UNMERGED_DEFAULT_MODELS` | no | Comma-separated default panel. |
| `UNMERGED_MAX_MODELS` | no | Panel size cap. Default 12. |
| `UNMERGED_MAX_TOKENS` | no | Default output ceiling per model. Default 8000. |
| `UNMERGED_TIMEOUT_MS` | no | Default per-model timeout. Default 180000. |
| `UNMERGED_LOG_CAPACITY` | no | Retained debug events. Default 500. |
| `UNMERGED_AUTH_TOKEN` | http only | Bearer / `x-api-key` token for the HTTP transport. |
| `UNMERGED_ALLOWED_ORIGINS` | http only | Comma-separated browser origin allowlist. Browsers are refused unless listed. |
| `PORT`, `HOST` | http only | Defaults `8787`, `127.0.0.1`. |
No value is ever written to logs, tool results, or client configuration.
## Cost
You pay per model, per call. A 5-model panel is roughly 5× a single call. `list_models` with `free_only: true` finds zero-price models; free endpoints time out and rate-limit far more often, which shows up honestly in `failedCount`.
## Security
- Credentials are read only from the server environment and are never accepted as tool arguments.
- Anything credential-shaped is stripped from errors and debug events before persistence.
- The HTTP transport validates `Origin`, requires a bearer token when one is configured, and compares it in constant time.
- **Returned answers are untrusted third-party model output.** If an agent calls this on attacker-influenced input, treat the answers as data to weigh, never as instructions to execute. This is a prompt-injection surface like any MCP tool that returns external content.
## Development
```bash
npm install
npm run build # syntax check every entrypoint
npm test # 9 contract tests incl. a live stdio initialize/list/call handshake
```
## License
MIT
TDQS
Scored across 4 tools
Each tool has a clearly distinct role: consult is the core action, list_models handles model selection, get_status handles configuration/health checks, and get_debug_logs is for diagnostics. There is no meaningful overlap or possible confusion between the tool boundaries.
get_status, list_models, and get_debug_logs all follow a clean lower_snake verb_noun pattern. consult is a bare verb without an explicit object, which is a minor deviation, but it still reads naturally and fits the overall imperative style.
Four tools is well-scoped for a narrow server: one primary operation plus three supporting utilities for configuration, model discovery, and debugging. Every tool earns its place and there is no redundancy.
The domain is parallel unmerged consultation, and consult fully covers the core workflow while list_models, get_status, and get_debug_logs cover the supporting needs around model selection, auth/config validation, and failure investigation. There are no obvious dead ends or missing operations agents would need.