MCP endpoint¶
POST /api/v1/mcp exposes eval history to Model Context Protocol
clients — Claude Code, Claude Desktop, or any other MCP-speaking agent — so they can answer
"which cases regressed and why" against your data.
It is opt-in. Start the server with DOMARINN_MCP_ENABLED=true; without it the route is never
mounted and requests fall through to the ordinary JSON 404.
DOMARINN_MCP_ENABLED=true DOMARINN_TOKENS="read:domarinn_agent" domarinn server
claude mcp add --transport http domarinn http://localhost:8321/api/v1/mcp \
--header "Authorization: Bearer domarinn_agent"
Authentication¶
The same credentials as the rest of the API, over Authorization: Bearer — a static token, an
account API key (domarinn_…), or a session token. Authorization is applied per JSON-RPC
method, not per route:
| Method | Scope |
|---|---|
server/discover, initialize, ping, notifications/* |
none |
tools/list, prompts/list, prompts/get |
none |
tools/call |
read |
Discovery is deliberately open: a client sends server/discover before it has credentials, and
a 401 there reads as a broken server rather than one that needs a token. The catalogs are
compiled in and identical for every caller, so they leak nothing. tools/call honors the usual
AuthMode — anonymous reads work in open and protect-writes.
A 401 carries WWW-Authenticate: Bearer realm="domarinn" with no resource_metadata
parameter. This server is not an OAuth resource server, and /.well-known/oauth-protected-resource
answers a definitive JSON 404 — some clients discard a configured static Authorization header
the moment a server advertises OAuth.
Prefer a static token for agents. DOMARINN_TOKENS="read:…" is verified in constant time
against memory and touches no database, whereas account API keys and sessions read (and
periodically refresh) a row per request.
Tools¶
Eight read-only tools. None can start a run, mutate a baseline, or delete anything.
| Tool | Answers |
|---|---|
find_runs |
What projects, suites, and runs exist. group_by returns the catalog instead of runs; suite rows carry baseline_run_id and a pass-rate series. |
get_run |
One run's totals and provenance. include: ["matrix", "config"] embeds the prompt × provider grid and the config snapshot. |
list_cases |
A run's cases, filterable by status, cell, tag, error class, or free text. |
get_case |
One case in full: assertions with reasons, usage, cost, stop reason. Heavy fields (raw, request, prompt, tool_calls, error_details) require fields. |
case_history |
One case across the suite's recent runs — the flakiness question. |
compare_runs |
The diff between two runs, with McNemar and Wilson intervals. Defaults to changed cases only; widen with delta. |
search |
Full-text across runs and case content. |
get_server_info |
Instance version, accepted schema versions, auth mode, and (with include: ["cache"]) cache health. |
export_run, cache_get, and cache_head are deliberately absent: the lossless run document is a
context-window hazard that get_run + list_cases + get_case already covers, and cache blobs
are opaque sha256:-keyed bytes.
Three prompts — triage_regression, investigate_case, summarize_run — render instructions
naming the tools to call, and read no storage.
Response limits¶
Tool results land directly in an agent's context window, so the MCP limits are far tighter than the REST ones and truncation is never silent:
- Strings are cut at 2,000 characters with an explicit
…[truncated N of M chars]marker and listed under_truncated.get_caseacceptsmax_chars(up to 20,000) to widen. - Row windows are small: 10 runs, 20 cases, 25 matrix rows, 50 compare rows by default.
- A serialized result over 64 KiB is refused with an actionable
isError, never a truncated JSON body. tools/callis rate-limited per identity: 60/minute, burst 10, answered with429andRetry-After. Holders of the same static token share one bucket.
Untrusted content¶
Stored model outputs are produced by the system under evaluation and, in a security-evaluation
suite, are adversarial by design. Before any of it reaches an agent, domarinn strips ANSI escape
sequences and control characters, replaces the FTS highlight sentinels, wraps the payload in an
<untrusted source="stored_model_output"> fence, and neutralizes any closing marker inside it. The
server instructions and every prompt tell the model to treat fenced content as data, never as
instructions.
Protocol¶
Dual-era, on one endpoint:
2026-07-28(modern) — stateless. RequiresMCP-Protocol-Version,Mcp-Method, and (where applicable)Mcp-Nameheaders, validated against the body; a mismatch is400with JSON-RPC-32020. Results carryresultTypeand, where cacheable,ttlMs+cacheScope: "public".2025-11-25/2025-06-18(legacy) — theinitializehandshake. No session id is ever minted or echoed, andMcp-Session-Id/Last-Event-IDon a request are ignored.
Other behavior worth knowing: GET/DELETE answer 405 with Allow: POST; notifications answer
202 with no body; an unknown method is 404 with -32601; the body limit is 256 KiB
decompressed; and responses are always application/json — this endpoint never emits an SSE
stream.
Origin is validated against a closed allowlist (DOMARINN_PUBLIC_URL,
DOMARINN_MCP_ALLOWED_ORIGINS, else loopback) to defeat DNS rebinding; an absent Origin is
allowed, since no CLI client sends one. CORS is layered on this route alone, with
Access-Control-Allow-Credentials off — so a cross-origin call can never be cookie-authenticated,
and the endpoint cannot become a CSRF vector.
The Claude Code plugin¶
There is also a Claude Code plugin that bundles this endpoint's configuration along with skills for authoring and triaging suites.
| Tool | Answers |
|---|---|
find_runs |
What projects, suites, and runs exist |
get_run |
How one run went; which matrix cell is unhealthy |
list_cases |
Which cases failed |
get_case |
Why one case failed — assertions, stop reason, output |
case_history |
Is it flaky or newly broken |
compare_runs |
Did it get worse between two runs (with McNemar + Wilson intervals) |
search |
Full-text across runs and case content |
get_server_info |
Instance version, accepted schema versions, cache health |
Three prompts surface as slash commands — /domarinn:triage, /domarinn:summarize,
/domarinn:check — and two skills load on demand: triage-evals (reading eval history well) and
author-evals (writing suites, assertions, caching, and CI wiring). See plugin/README.md for the
full rundown.