# MCP server

> Plug Subsidia into Claude Code, Cursor, n8n or any MCP client: sourced answers, Cortex search, verification, postes and Reflex flows as tools, with one API key.

The [Gateway](https://dev.subsidia.protypa.fr/docs/gateway.md) lets any AI client use Subsidia as its **model**. The MCP server lets it use Subsidia's **capabilities**: ask the firm's documents, search Cortex, verify a text, run a poste, start a Reflex flow. The client keeps its own model; Subsidia answers as a set of tools, with the same key, scopes, metering, proofs and audit as the REST API.

It speaks the *Model Context Protocol* over **Streamable HTTP**, in stateless mode: one `POST` per JSON-RPC message, JSON in, JSON out, nothing kept between calls.

## Connect a client

**One URL, one key**

_Claude Code_

```bash
claude mcp add --transport http subsidia https://api.subsidia.protypa.fr/mcp \
  --header "Authorization: Bearer $SUBSIDIA_API_KEY"
```

_Cursor_

```json
// .cursor/mcp.json
{
  "mcpServers": {
    "subsidia": {
      "url": "https://api.subsidia.protypa.fr/mcp",
      "headers": { "Authorization": "Bearer ${env:SUBSIDIA_API_KEY}" }
    }
  }
}
```

_n8n_

```text
MCP Client Tool node
  Endpoint URL:    https://api.subsidia.protypa.fr/mcp
  Server Transport: HTTP Streamable
  Authentication:  Header Auth
    Name:  Authorization
    Value: Bearer sk_live_...
```

_curl_

```bash
curl https://api.subsidia.protypa.fr/mcp \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

Authentication is the same API key as the rest of the API (`Authorization: Bearer sk_live_...` or `x-api-key`), created in the console. On a self-hosted install, replace the host with your own.

## Tools

| Tool | What it does | Key scope | Cost |
| --- | --- | --- | --- |
| `subsidia_ask` | Answers a question strictly from the workspace documents, with numbered sources and a proof id. A question the documents do not cover gets an honest refusal. | `light` | One question |
| `cortex_search` | Hybrid search over the knowledge base. Returns passages with source, page and a retrieval trace. Read-only. | `knowledge` | Free |
| `subsidia_verify` | Gives every claim of a text a verdict against the passages you send (or Cortex dossiers). Deterministic, no model call, signed. | `proof` | One control |
| `poste_run` | Runs a poste on one dossier: `RAS` or findings tied to pieces. Returns a proof id. | `agents` | Questions |
| `flow_list` | Lists the Reflex flows with state, schedule and last run. Read-only. | `reflex` | Free |
| `flow_run` | Starts a manual run of a flow and returns the run id. | `reflex` | Per the flow |
| `run_status` | Status of a run: steps, errors and any approval waiting on a person. Read-only. | `reflex` | Free |
| `approvals_list` | Lists the approvals waiting for a person. Read-only. | `reflex` | Free |

Results come back both as readable text (`content`) and as structured data (`structuredContent`: sources, verdicts, `proofId`, `runId`). Tools that return an AI answer return the **proof id** the engine already issued for it; check it against the [certificate](https://dev.subsidia.protypa.fr/docs/proofs.md).

## What a key can reach

Each tool checks its own scope, exactly as the matching REST route would. `tools/list` only shows the tools **your key can call**: a key limited to `knowledge` sees `cortex_search` and nothing else, so a client never offers its model a tool that will be refused. A key with none of the capability scopes is a legacy key and sees everything, as it does on REST.

A **`readonly`** key keeps the tools that only read (`subsidia_ask`, `cortex_search`, `subsidia_verify`, `flow_list`, `run_status`, `approvals_list`) and loses `poste_run` and `flow_run`. The key's monthly question ceiling stops `subsidia_ask` and `poste_run`; a verification or a search is never stopped by it.

> **WARNING: Nothing leaves the building without a person**
> There is **no tool that decides an approval, enables a flow, or sets `allowUnattendedOutbound`**. If a flow started with `flow_run` reaches a step that sends a mail or calls an outside service, it stops on an approval; `run_status` and `approvals_list` show it, and only a signed-in person can decide it in the Subsidia app. An AI client cannot approve its own action.

## Errors

A failure of a tool is a normal result with `isError: true`, so the calling model can read it and adapt. The result carries the same `code` the REST API uses, in `structuredContent.code`.

| `code` | Meaning |
| --- | --- |
| `scope_denied` | The key does not have the scope this tool needs. |
| `read_only_key` | The key is read-only and this tool changes something or spends questions. |
| `invalid_arguments` | An argument is missing or has the wrong type. The message names it. |
| `API_KEY_BUDGET_EXCEEDED` | The key reached its monthly question ceiling. |
| `QUESTION_LIMIT`, `PLAN_UPGRADE_REQUIRED`, … | The billing codes of the [error reference](https://dev.subsidia.protypa.fr/docs/errors.md), unchanged, with their details. |
| `not_found`, `invalid_request` | A flow or run does not exist, or a flow must be fixed before it can run (the validation `issues` are included). |
| `poste_not_found`, `not_a_poste` | The poste is not installed in this workspace, or the key is not a poste. |
| `retrieval_failed` | The search in the documents did not complete: nothing was answered. Try again. |

An unknown tool name or a malformed message is a JSON-RPC error (`-32602`, `-32601`, `-32600`). Missing or invalid keys, rate limits and suspended accounts are HTTP errors on the request itself (`401`, `429`, `403`), as everywhere else in the API.

## Protocol notes

- Methods: `initialize` (protocol version `2025-06-18`), `tools/list`, `tools/call`, `ping`; notifications are accepted and answered with `202` and no body. Batches are accepted.
- Capabilities: `tools` only. No resources, prompts or sampling.
- `GET /mcp` answers `405`: there is no server-initiated stream.
- Every call is written to the access journal (`mcp.tool`, tool name, key id). The text of questions and documents is not.
- Writing into Cortex from a third-party agent is not offered: ingestion goes through a proposal a person validates.
