# MCP tools

Four tools, one per decision the pipeline needs from a model, and the phase machine that sequences them.

The MCP server exposes four tools. Each corresponds to a point where the pipeline
needs a judgement it cannot make itself.

The server retrieves, expands, counts coverage, shortlists, verifies and emits. It
never writes queries, never scores a paper and never composes prose. You supply
every judgement, using the skill's rubrics.

import McpDiagram from '@components/McpDiagram.astro';

<McpDiagram />

## The response envelope

Every call returns the same shape:

```json
{
  "scan_id": "…",
  "phase": "screen",
  "status": "ok",
  "next_action": "screen_candidates",
  "progress": { },
  "payload": { }
}
```

**Follow `next_action` on every response.** It tells you what the pipeline needs
next, and the payload carries what you need to decide it.

| `next_action` | What to send back |
|---|---|
| `screen_candidates` | `scan_continue` with `screen_scores` for the batch in the payload. |
| `write_gap_queries` | `scan_continue` with `gap_queries`. An empty list skips the gap round. |
| `rank_shortlist` | `scan_continue` with `ranked_entries` for the page in the payload. |
| `verify_ranked` | `scan_verify`. |
| `complete` | `scan_result`. |

Phases are `screen`, `gap`, `rank` and `complete`.

**Never issue two mutating calls for the same `scan_id` at once.** A call that
arrives while another is running is answered `in_progress` — do not resubmit;
poll `scan_result`, which is read-only and never blocked.

## `scan_start`

> Open a scan and run retrieval. Returns the first screening batch.

| Parameter | Type | Notes |
|---|---|---|
| `scan_id` | string | **You generate it**: a fresh canonical lowercase UUIDv4, one per brief. |
| `brief` | string | The project brief in Markdown, or the question. |
| `queries` | QueryPlan | The full query plan you wrote under the plan rubric. The server never writes queries for you. |
| `profile` | `quick`/`standard`/`deep` | Default `standard`. |
| `top` | int | Papers to emit. Default 10. |
| `foundational` | int | Out-of-window slots within `top`. Default 2. |
| `since`, `until` | string | Window bounds, `YYYY-MM`. |
| `slug` | string | Run name. |
| `max_candidates`, `per_query` | int | Override the profile's depth and cap. |
| `force_gap_round` | bool | Run the gap round whatever the profile says. |

Retrieval is the longest call in a scan. If it fails or times out, **call it again
with the same `scan_id` and the same arguments** — the server resumes the run it
already has instead of starting a second one.

The same arguments means the same. A changed brief or plan under an id already in
use is refused with `scan_id_conflict` rather than silently applied, and the error
names which fields differ.

## `scan_continue`

> Submit the artifact the current phase asked for, and get the next decision.

| Parameter | Type | When |
|---|---|---|
| `scan_id` | string | Always. |
| `screen_scores` | list | `next_action` is `screen_candidates`. Score **every** item in the batch. |
| `gap_queries` | list | `next_action` is `write_gap_queries`. |
| `ranked_entries` | list | `next_action` is `rank_shortlist`. |

Send **exactly one** of the three. Send none of them to poll the current phase.

An empty `gap_queries` list is a meaningful answer: it means you found nothing
worth reformulating, and the gap round is skipped.

The shortlist is handed to you a page at a time; the payload carries the records
for the page you are on.

## `scan_verify`

> Verify every ranked paper against the live record, then emit the evidence packet.

| Parameter | Type | Notes |
|---|---|---|
| `scan_id` | string | Always. |
| `ranked_entries` | list | Optional. Any final entries you still hold are merged first. |

Call it once `next_action` is `verify_ranked`.

**Verification is the engine's.** DOIs and titles are checked, never repaired — the
same rule that governs [the CLI stage](/architecture/verification/).

## `scan_result`

> Read a scan's result. Read-only, never blocked, safe to poll while a call is running.

Takes `scan_id` only.

On a finished scan the payload carries the evidence packets' summary rows, the
unverified list, the counts, `coverage.json` and the rendered `evidence.md`.

Because it is read-only, it is also the correct way to check on a scan whose
mutating call is still in flight.

The narrative parts of a report — why these papers, what the coverage risks are —
are yours to write. The server does not compose prose.

## Where runs live

The adapter keeps one directory per `scan_id` under
`$RESEARCH_SCAN_MCP_DATA`, defaulting to
`~/.local/share/research-scan-mcp/runs/`. Inside it is an ordinary run directory
with the same files a CLI-driven scan produces, so everything in
[File formats](/reference/file-formats/) applies unchanged.

The adapter drives the CLI as a subprocess and reads its exit codes. It never
imports a stage's internals, and it makes no judgement about papers.

## Errors

Failures come back as a refusal you can act on, carrying a reason code — a bad
artifact, the wrong phase for the call, or a stage that failed — with the CLI's
own structured error rather than a stack trace.

`scan_id_conflict` is the one worth handling explicitly: it means the id is in use
with different inputs. Use a fresh id for a different brief.
