MCP server¶
crossverify ships an MCP server so an
autonomous agent can drive the verification directly: write a Python + R
analysis, call a tool, get back a structured pass/fail/info result, and
iterate — without parsing console text or shelling out to the CLI.
Install and run¶
The server lives behind an optional extra (the MCP SDK needs Python ≥ 3.10):
Register it with an MCP client (stdio):
Tools¶
| Tool | Runs analysis code? | Returns |
|---|---|---|
verify_analysis(project_path, phases?, skip_r?, seed?) |
yes (sandboxed subprocess) | full result: verdict, totals, per-check checks, Python-vs-R comparison, output_paths, scope_caveat |
validate_project(project_path) |
no | {ok, problems} |
scaffold_project(target_dir) |
no | {target, written, skipped} |
inspect_dataset(csv_path) |
no | {rows, columns, checks, artifacts} |
Results are JSON the agent branches on — which checks failed and why, the
per-statistic |Δ| between Python and R — never a console summary to parse.
Trust boundary¶
verify_analysis executes the analysis code the project points at
An agent that chooses project_path can cause arbitrary code execution.
Two guardrails are enforced, but they are not a sandbox:
- Path containment (enforced by the server). A project whose
data/python.module/r.scriptresolve outside the project folder comes backverdict="invalid"— no code runs. The server forces this regardless of the project file, so an agent that authors the project cannot disable it withallow_external_paths: true. The opt-out belongs to the operator, not the project: setCROSSVERIFY_MCP_ALLOW_EXTERNAL=1in the server's environment to honor the project flag again, and only for projects you trust. - Bounded subprocess. Each
verify_analysisruns in its own process group with a timeout (CROSSVERIFY_MCP_TIMEOUT, default 300 s) and a minimal, allowlisted environment. On timeout the whole group is killed, including theRscriptgrandchild, so a runaway or hostile analysis is genuinely time-bounded and cannot read the server's tokens or credentials.
Run the server in a sandbox (container/VM, no credentials in the environment, least-privilege filesystem). The read-only tools do not execute analysis code.
Every verify_analysis result carries a scope_caveat: agreement across Python
and R is strong evidence a number is not a tool-specific artifact, but it is
not proof the analysis is correct (you write both sides, so a shared
specification error agrees perfectly). An agent must not report a verified
result as "correct" — see The Protocol for the full scope.
The auto-researcher loop¶
A typical agent session:
- Scaffold.
scaffold_project("study/")writesproject.yaml,analysis.py, andanalysis.Rto fill in. - Write the analysis. The agent writes the Python adapter (
run(df, seed)→ dict of statistics) and the independent R replication emitting the same names, and pointsdata:at the dataset. - Validate.
validate_project("study/project.yaml")→ fix anyproblemsbefore running code. -
Verify.
verify_analysis("study/project.yaml"). Ifverdict == "fail", the result names the failing checks and the Python-vs-R deltas, e.g.: -
Revise. The agent reads that
coef_xdisagrees beyond tolerance, finds the cause (say anna.rm/dropnamismatch between the two implementations), fixes it, and callsverify_analysisagain — looping untilverdict == "pass".
Because a failed check is a normal result (not an error) and the deltas are machine-readable, the agent can close this loop on its own.