أَغْنِنَا بِحَلَالِكَ عَنْ حَرَامِكَ

No hosted chatbot · no account · no key

Your assistant, pointed at your keel

Some platforms sell a hosted AI that drafts strategies for you, metered against a subscription. keel's answer is smaller and costs nothing to run: keel ships a read-only MCP server, so you point your own assistant at the keel running on your own machine, holding your own keys. The engine has nowhere to send a prompt, so it sends none — keel's side of the exchange never leaves your machine.

The honest boundary

keel does not host an assistant, does not see your conversations, and relays nothing. There is no keel cloud to talk to, no account to meter, no key to paste into a website — nothing to log into. The server is a local process speaking stdio to a client on the same machine.

One honest caveat, stated rather than implied: what your assistant's own provider does with the prompts it receives is your arrangement with that provider. keel is not a party to it and cannot see it. keel's half is local; the assistant's half is whatever you chose when you picked a client.

There is nothing to pay keel for on this page. The page is instructions.

the server's page in the engine repository

What an MCP server is, in one sentence

MCP (Model Context Protocol) is an open standard that lets an AI assistant call tools a program exposes: the assistant asks, the program answers from what it can see.

keel's server speaks it over stdio — one JSON-RPC 2.0 message per line in, one response per line out, until EOF — hand-rolled over the standard library: no MCP SDK, no pydantic, no asyncio, no new dependency. When a client asks who it is talking to, the server answers keel-read-only. The name is the whole proposition.

Point a client at it

Both blocks below are the engine's own, quoted verbatim from its server documentation — adjust the paths to your deployment.

Run the server

uv run keel --db keel.db --config config.yaml mcp

Register it with your client

{
  "mcpServers": {
    "keel": {
      "command": "keel",
      "args": ["--db", "keel.db", "--config", "config.yaml", "mcp"]
    }
  }
}

The engine's instruction is one line: point any stdio MCP client at the command — Claude Desktop, Cursor, Codex, VS Code, Zed all consume this same mcpServers shape. Where each client keeps that config is the client's own documentation; keel's side of the bargain is the command and the fence around it.

Verify in the repository: docs/mcp-server.md · The complete server reference is fetched from the engine at build time and rendered under Docs — this page summarizes; that page is the document.

What the eight tools let an assistant do

Eight tools, each delegating to the same service seams an operator's front-end calls — so an assistant and a terminal cannot be shown two different accounts of one deployment. All lists are bounded; money arrives as strings, never double-rounded by JSON floating point.

  • doctor — The same findings keel doctor computes — subscription freshness, rail states, allowance headroom, recent vetoes, data health, sizing admissibility — each with the CLI fix that resolves it.
  • capabilities — The capability inventory as rows: every action that widens what keel can do without asking again, and the gate covering it.
  • profiles — The deployment's config files as structured rows — allowlist, spend caps, DCA budget, granularities. A file that will not load is listed with its error.
  • orders — Rows from the orders audit log, filtered by mode, product or status, newest last, bounded.
  • veto_log — Parsed rail-veto events from the engine's JSONL log since a timestamp, with each event's rail violations.
  • purification — The §65.9 income purification report — what is owed by asset, units from tainted sources, entries awaiting human classification. Report-only, like the CLI.
  • trials — The research trials ledger: row and decision counts, hash-chain verification errors, the most recent rows. Experiments only — money has its own ledger.
  • reports — The research corpora: list documents, or read one bounded document — a bare name, never a filesystem path.

Verify in the repository: keel/mcp/tools.py

What it deliberately cannot do

The interesting half. The server cannot attest an asset, promote a rule, arm autonomy, or place, halt or release an order — not as a matter of policy but because the write surface does not exist. The engine's own page names the fence, in six walls:

  • A write-verb vocabulary — every tool name and description is scanned against arm, attest, promote, kill, place and the other write verbs. A description may not advertise a write, because a description is what a model reads before choosing a tool.
  • No gated action is reachable by name — nothing in the server references the capability registry's module.function entries.
  • An AST write-deny scan — no write-ish call anywhere; the allowlist is empty and pinned empty. One exemption exists: PRAGMA query_only = ON, the engine-level read-only enforcement itself.
  • The interactive-confirmation gate appears nowhere in the package — a pipe-connected process must not borrow the ceremony that exists for a human at a terminal.
  • A docs pin — a documented tool that does not exist, or an exposed tool the docs hide, both fail CI.
  • A closed import surface — the package imports neither the executor nor the agent; the trading paths start unavailable, not merely unused.

The guard is a test — tests/mcp/test_readonly.py — committed before keel/mcp/ existed, so the server was born inside the fence rather than moved into one later. The engine's page is honest about the limit: a static scan cannot close dynamic dispatch.

And at runtime every connection opens with PRAGMA query_only = ON — row writes are impossible at the engine level, by construction rather than by policy. What will never be exposed is named too: credentials, .env contents, anything from the OS keychain.

Verify in the repository: tests/mcp/test_readonly.py

The honest fit: the assistant proposes, the gate decides

keel's analysis-feed design note states the pattern in five words — the Analyzer proposes, the gate decides: the feed proposes assets for human attestation, keel's deterministic gate decides, and nothing new touches admission, sizing or order placement. The MCP server inherits the same asymmetry. An assistant reading your reports can explain what your keel did and why a rail fired; nothing it says reaches an order.

That boundary is the feature. A chatbot that could draft a strategy and promote it would route around the candidate → paper → live ladder and its overfitting check in a single sentence. keel's answer is not a better-behaved chatbot; it is a server that cannot, checked by a test that fails if it ever learns how.

keel#579 · docs/mcp-server.md

Assistants propose. keel's gates still decide.