Design specification for Cadora's MCP server: the color and type tokens, the five-tool component inventory with signatures and fail-modes, the anatomy of the tool-call trace, the security-posture callout, every return and error state the tools produce, the transport and auth table, and the voice.

Cadora · design spec

The MCP server, by its surfaces

Everything needed to render or extend the control plane — the exact tokens, the five tools with their signatures and fail-modes, the tool-call trace's anatomy, the security-posture callout, and every return and error state. The rendered components are live HTML; edit them here or lift the specs into Figma.

Palette

Semantic first · green proceeds, red is the closed guard, amber degrades soft


Green--scr-green · #16c784started · approved · safe read
Red--scr-red · #fb7185fail-closed · refused · 401
Amber--scr-amber · #ffd08apending · fail-soft error
Blue--scr-blue · #6ba5f0tool names · keys · paths
Ink--scr-bg · #0f172aCard ground
Canvas--scr-canvas · #111c2fInset · state cards
Panel--scr-panel2 · #162237Callout surface
Line--scr-line · #334158Borders
Text--scr-text · #f5f7fbPrimary · run id · values
Muted--scr-muted · #9aa8bcLabels · captions · JSON structure

Type & metrics

Monospace is the instrument voice — the control plane is read as calls and JSON


RoleFamilySize / weightNotes
Tool namemono12px / bluestart_run, review_gate — the callable surface
Signaturemono12px / 400Args + defaults, e.g. review_timeout=3600.0
Returned JSONmono11.5pxKeys blue, strings green, literals amber, braces muted
Error stringmonored / ambererror: path '…' escapes the run workspace
Endpointmonobluehttp://127.0.0.1:8000/mcp
MetricValueMetricValue
Card radius10pxSemantic accent4px left border
Trace line-height1.65Return indent20px
Card padding14px 16pxState padding11px 13px
Default transportstdioDefault bind127.0.0.1

Component — the five tools

The whole control plane · each tool, its signature, its return, its fail-mode


ToolSignatureReturnsFail-mode
start_run (topology, run_id, cwd=".", executor="claude", archive_dir="runs", gate_cmd="ruff check . && pytest -q", gate_setup="auto", review_timeout=3600.0) {run_id, status:"started"} fail-closed timeout
review_gate (run_id) {pending, node_id, docs_dir, artifacts} · else {pending:false, running} read-only
submit_review (run_id, decision, comments="") {submitted:decision} · else {error} fail-soft
get_artifact (run_id, path) <text> · else "error: …" traversal-safe
run_status (run_id) {running, result_path, error} read-only

Two tools drive the run (start_run, run_status), one reads its output (get_artifact), two service the human gate (review_gate, submit_review). decision is one of approve · request_changes · abort. Pass review_timeout=0 to wait indefinitely.

Anatomy — the tool-call trace

One round trip: start, watch, read the gate, decide, finish


12345
start_run(topology="…/aidlc-hitl.topology.yaml", run_id="checkout-api", review_timeout=0)→ {"run_id":"checkout-api", "status":"started"}
run_status(run_id="checkout-api")→ {"running":true, "result_path":null, "error":null}
review_gate(run_id="checkout-api")→ {"pending":true, "node_id":"requirements", "artifacts":["aidlc-docs/inception/requirements/requirements.md"]}
submit_review(run_id="checkout-api", decision="approve")→ {"submitted":"approve"}
run_status(run_id="checkout-api")→ {"running":false, "result_path":"runs/checkout-api/manifest.json"}
1Startstart_run loads the topology, registers every gate it references with gate_cmd, and launches the run on a background thread. Returns {run_id, status:"started"}.
2Watchrun_status reports running while the thread is alive; result_path and error stay null until it finishes.
3Gatereview_gate returns the pending node's node_id, docs_dir, and the artifacts it produced (read each with get_artifact), or {pending:false, running}.
4Decidesubmit_review with approve (proceed), request_changes + comments (re-run the node), or abort (stop). Returns {submitted:decision}.
5Finishrun_status flips running to false; result_path points at the run manifest.

Security-posture callout

The same audit guarantees, exposed over MCP


fail-closed / fail-softthe guarantees that ride the seam
Review fails closed. review_timeout seconds (default 3600) then aborts — a client that walks away can't pin the run thread forever. 0 waits indefinitely.
!Submit fails soft. Invalid decision, request_changes with no comments, or a submit with nothing pending returns {error} — never a raw traceback.
Reads are traversal-safe. A ../ path returns error: path '…' escapes the run workspace and never reads outside the run.
HTTP is gated on auth. Loopback + stdio default; a routable bind needs --auth-token (bearer) or explicit --i-understand-no-auth, TLS in front.

The callout is one component: an amber-flagged header over four .post rows, each with a semantic mark — green a guarantee that holds, red a guard that closes, amber a soft degrade. Auth logic lives in cadora/mcp/auth.py; the review and traversal guards in cadora/mcp/server.py.

States

Every return and error the tools produce


start_run · run launched

start_run → {"status": "started", "run_id": "checkout-api"}

review_gate · gate pending

review_gate → {"pending": true, "node_id": "requirements", "docs_dir": "aidlc-docs", "artifacts": ["aidlc-docs/…/requirements.md"]}

review_gate · nothing pending

review_gate → {"pending": false, "running": true}

submit_review · accepted

submit_review → {"submitted": "approve"}

submit_review · fail-soft (bad input)

submit_review("yep") → {"error": "invalid review decision: 'yep'"} submit_review("request_changes") → {"error": "request_changes requires reviewer comments"}

submit_review · nothing pending

submit_review → {"error": "no review is pending"}

get_artifact · traversal refused

get_artifact("../secrets.env") → "error: path '../secrets.env' escapes the run workspace"

get_artifact · missing file

get_artifact("aidlc-docs/nope.md") → "error: no such artifact 'aidlc-docs/nope.md'"

run_status · finished

run_status → {"running": false, "result_path": "runs/checkout-api/manifest.json", "error": null}

HTTP · missing / wrong bearer token

HTTP 401 {"error": "unauthorized"} WWW-Authenticate: Bearer

The tools never raise through the call. Every failure is a value: submit_review and review_gate/run_status return an {error} dict; get_artifact returns a string that begins error:. An unknown run_id yields unknown run '…' on any tool. Auth is the one boundary that answers before the tool runs — a 401 at the HTTP layer.

Transport & auth

stdio local · http remote · loopback until you prove auth


FlagDefaultEffect
--transportstdiostdio for a local client (Claude Desktop / Code, Codex CLI); http for remote — endpoint http://<host>:<port>/mcp.
--host127.0.0.1Bind host for HTTP. Loopback by default; a non-loopback host is refused unless authenticated or acknowledged.
--port8000Bind port for HTTP.
--auth-tokennoneRequires Authorization: Bearer <token> on every HTTP request (or set CADORA_MCP_TOKEN); a present token also lifts the non-loopback refusal. Still front it with TLS.
--i-understand-no-authoffAllow binding this unauthenticated surface to a non-loopback host. The explicit escape hatch — trusted networks only.

Voice

The control plane returns evidence, not exceptions


DoNot
Return the error as a value — {"error": "no review is pending"}Raise a traceback through the tool call
Name the refusal — path '…' escapes the run workspace“Access denied”
State the decision set — approve | request_changes | abort“yes / no”
Fail closed by default — timeout aborts; 0 is the opt-outWait forever silently
Tokens and behavior are taken verbatim from cadora/mcp/server.py (the five @app.tool()s + serve()) and cadora/mcp/auth.py (the bearer guard). Edit the rendered components in place, or hand the palette and metrics tables to a Figma library.