Skip to main content
Glama

Descartes

doubt, until what remains is certain.

CI

๐Ÿชถ Landing page โ†’  ยท  descartes --epigraph for a passing thought from the man himself.

An MCP server that, on a prompt, doubts every decision in a plan, doubts its own doubts, and answers them โ€” grounding every answer in real evidence โ€” iterating until the plan stops producing new load-bearing doubts. Then it hands back a refined plan plus the few decisions only you can make.

The point: no unexamined assumption reaches the work, so hallucination has no room to hide.


The loop

On a prompt, Descartes runs iterative Cartesian doubt:

  1. Draft the plan / decisions.

  2. Doubt every load-bearing decision (one of 11 doubt operators per doubt).

  3. Doubt the doubts โ€” prune the trivial and manufactured ones.

  4. Answer each surviving doubt only from real evidence:

    • code doubts โ†’ resolved from the context you pass in,

    • world doubts โ†’ resolved via Exa (cited, confidence-scored),

    • ungroundable โ†’ marked NEEDS_HUMAN.

  5. Revise the plan with what survived.

  6. Repeat from step 2.

Stopping rule: the loop stops the moment a full pass produces no new load-bearing doubt โ€” that is convergence. It might be pass 3 or pass 9. 20 is a hard ceiling, never a target. It never manufactures doubt to keep going.

Grounding rule: every answer resolves against real evidence or becomes a question for you. Low-confidence Exa is never asserted. 20 rounds of self-answered, ungrounded doubt would be 20 rounds of confident hallucination โ€” so that is forbidden by construction.


Related MCP server: Deliberate Reasoning Engine

Two engines (auto-selected)

If you setโ€ฆ

Descartes runsโ€ฆ

FIREWORKS_API_KEY

a panel of distinct Fireworks model families. Agreement = settled; disagreement = real uncertainty โ†’ the doubt is escalated.

nothing (in a client that supports MCP sampling)

Claude alone โ€” the client's own model runs the whole loop.

OPENROUTER_API_KEY

single-model "Claude alone" via OpenRouter (fallback when sampling is unavailable).

no keys at all

a deterministic operator-template fallback, so it never hard-fails.

EXA_API_KEY is independent: set it to ground world doubts in any mode.

No keys? It still works โ€” with just Claude

You do not need any API keys. Inside an MCP client that supports sampling (Claude Code), the whole loop runs on the client's own Claude model โ€” code doubts are resolved by reading the context you pass in, and anything that needs an external fact or a human decision is handed back to you.

And if even sampling isn't available, Descartes does not guess. It will not fake-confirm a doubt from keyword matching; it surfaces every load-bearing doubt as a question in needs_user and tells you so. Every doubt() result carries an engine and a plain-English note describing exactly how it was powered, so a degraded run is never silent. The one rule it never breaks: no answer without evidence โ€” otherwise, ask you.


3-step setup

# 1. install
git clone https://github.com/pranjalbhatia710/descartes-mcp && cd descartes-mcp
pip install -e .

# 2. (optional) bring your own keys โ€” copy names only, fill in your .env
cp .env.example .env        # .env is gitignored; never commit it

# 3. run it (stdio MCP server)
descartes                   # or: descartes --selftest   to prove the loop converges

Add to Claude Code

Add this to your Claude Code MCP config (e.g. .mcp.json in your project, or via claude mcp add). Keys live in your shell / config env โ€” never in committed files:

{
  "mcpServers": {
    "descartes": {
      "command": "descartes",
      "args": [],
      "env": {
        "FIREWORKS_API_KEY": "${FIREWORKS_API_KEY}",
        "EXA_API_KEY": "${EXA_API_KEY}",
        "OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}"
      }
    }
  }
}

Then ask Claude to "use descartes to doubt this plan" and pass the relevant files as context.


Tools

doubt(prompt, context="", max_passes=20)

Runs the loop above. Returns:

{
  "passes_used": 4,
  "converged": true,
  "plan": "<the refined, doubt-hardened plan>",
  "doubt_log": [
    { "pass": 1, "operator": "assumption", "doubt": "...",
      "status": "CONFIRMED|REFUTED|UNKNOWN|NEEDS_HUMAN",
      "resolution": "...", "source": "..." }
  ],
  "needs_user": [ "<the few decisions only you can make>" ]
}

needs_user is the product: it separates what Descartes resolved itself from what it genuinely needs you for. Kept short โ€” the truly blocking ones only.

ground(query)

Exa deep search โ†’ cited, confidence-scored facts. Used inside the loop; callable directly. Low confidence โ†’ status: "UNKNOWN" (never asserted).

verdict(result)

Pass a doubt() result. Returns { proceed, blocking_questions }. proceed is true only if the plan converged with no open load-bearing doubt; otherwise you get the blocking questions.


Doubt operators

Every doubt is tagged with one operator (shipped as data in descartes/operators.py):

assumption ยท falsify ยท inversion ยท named_source ยท edge_case ยท quantify ยท root_cause ยท reversibility ยท second_order ยท define ยท deletion


Development & tests

pip install -e ".[dev]"      # pytest + ruff
ruff check descartes/ tests/ benchmark/
pytest -q                    # 69 tests, no network (all backends are mocked)
python -m benchmark.bench    # convergence benchmark (also asserts its invariants)

The suite locks down the load-bearing invariants โ€” the loop always terminates and never exceeds the hard ceiling of 20, it converges as soon as a pass adds no new doubt, nothing is asserted without evidence (ungroundable โ†’ NEEDS_HUMAN, low-confidence Exa โ†’ UNKNOWN), needs_user is exactly the NEEDS_HUMAN doubts, and panel disagreement escalates to the human. tests/test_regressions.py carries one guard per bug found by the adversarial audit. CI (.github/workflows/ci.yml) runs lint + tests + the self-test + the benchmark on Python 3.10โ€“3.13.

Benchmark (instant stub reasoner โ€” measures loop machinery, not network):

scenario

doubt depth

passes

converged

trivial

0

1

โœ…

moderate

3

4

โœ…

deep

11

12

โœ…

spiral-guard

40

20 (capped)

โŒ by design

Convergence is exactly depth + 1 passes and never exceeds 20 โ€” a 40-doubt "spiral" is capped at 20 and reported as not converged.

Safety

  • Bring your own keys. Read from env only (os.environ.get), never stored, never logged, never committed. .env is gitignored; .env.example has names only.

  • Every external call is wrapped in try/except + timeout + retry โ€” one failure never kills a run.

  • Exa results are cached and time-boxed so a pass stays fast; panel size is capped on the live path (DESCARTES_PANEL_SIZE).

License

MIT โ€” see LICENSE.

Install Server
A
license - permissive license
A
quality
B
maintenance

Maintenance

โ€“Maintainers
โ€“Response time
โ€“Release cycle
โ€“Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pranjalbhatia710/descartes-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server