# MCP integration

Accessful hosts an MCP server that lets your AI application process PDFs, check progress and retrieve the result.
You need an Accessful API key and a client that supports **remote Streamable HTTP and custom HTTP headers**.

## Set up the connection

1. In the Accessful portal, open **API keys** and create a key for this integration.
   Copy it when it appears; it is shown only once. See [Create an API key](https://docs.accessful.de/authentication/#create-an-api-key).
2. Add a remote MCP server named **Accessful** in your client and enter these settings:

   | Setting | Value |
   | --- | --- |
   | Server URL | `https://api.accessful.de/mcp` |
   | Transport | Streamable HTTP |
   | Header name | `X-API-Key` |
   | Header value | Your personal Accessful API key |

3. Store the key in the client's secret storage, save and connect. The client must send the header on **every request**.
   Use a separate revocable key per user and integration. **OAuth login is not available in V1**.

For your own web application, connect through its backend. Direct browser requests from another origin are rejected with `403`.

## Check the connection

Refresh the client's tool list. These **four tools** must appear:

| Tool | Input | Result |
| --- | --- | --- |
| `accessful_start_pdf` | `url`, `filename`, `requestId` | Starts a job from a download link; returns `caseId`, `pollAfterSeconds` |
| `accessful_start_pdf_file` | `file` (supplied by the host) | Starts a job from a chat attachment; returns `caseId`, `pollAfterSeconds` |
| `accessful_get_status` | `caseId` | Returns `jobStatus`, `stage`, `terminal` and available progress details |
| `accessful_get_result` | `caseId` | Returns `downloadUrl`, `expiresAt`, `filename`, `mimeType` |

<details>
<summary>Optional: check with MCP Inspector</summary>

With Node.js 22.19 or newer and your key set as the `ACCESSFUL_API_KEY` environment variable,
run this command in a Bash or Zsh terminal. It lists tools without creating a PDF job:

```bash
npx @modelcontextprotocol/inspector --cli https://api.accessful.de/mcp \
  --transport http --header "X-API-Key: ${ACCESSFUL_API_KEY:?Set ACCESSFUL_API_KEY}" \
  --method tools/list
```

The output must contain the four names above. See the [MCP Inspector documentation](https://modelcontextprotocol.io/docs/tools/inspector).

</details>

## Process a PDF

Start a job with **one of the two start tools**, then check the status and download the result.
Maximum **50 MiB**, one PDF per job.

### Option A: Chat attachment

Some MCP hosts pass chat attachments directly to tools that declare a file parameter in `_meta["openai/fileParams"]`.
`accessful_start_pdf_file` declares its `file` parameter this way. If your host supports this, the user attaches the PDF
and the host fills `file` with a temporary download link, so the model never has to construct a URL:

```json
{
  "file": {
    "download_url": "https://files.example.com/temporary/abc123",
    "file_id": "file_abc123",
    "mime_type": "application/pdf",
    "file_name": "Annual report.pdf"
  }
}
```

The host's `download_url` must be reachable by Accessful over public HTTPS without a login or extra headers
and stay valid until Accessful has downloaded the file. The download starts shortly after the job is accepted.
No `requestId` is needed: repeating the identical call returns the same `caseId`, while a renewed link for the same file starts a new job.

Hosts without this file support show `file` as an ordinary object. Use option B there; do not let the model invent a `download_url`.

### Option B: Download link

Prepare a **direct HTTPS download link to the original PDF**, reachable by Accessful without a login or extra headers.
An existing public PDF link works. For private files, your application must create a signed download link.
A local file path alone is insufficient; without host file support, the application must make the file available by URL.
Keep the link valid for at least **15 more minutes**.

Call `accessful_start_pdf` with the following arguments. Replace the example URL with your PDF link
and generate a new UUID `requestId` for each new job:

```json
{
  "url": "https://files.example.com/document.pdf",
  "filename": "document.pdf",
  "requestId": "96d35d20-7f2a-4de2-9a85-0e7d03cc68a4"
}
```

For retries, reuse the **same `requestId`, URL and filename**, including after API-key rotation.

### Check the status and download the result

1. Save the `caseId` returned by the start tool. Call `accessful_get_status` with it every **10 seconds** until `terminal` is `true`.
   After **120 seconds** of active waiting, show the current state and `caseId`; processing continues, so check again later.
2. When `jobStatus` is `completed`, call `accessful_get_result` with the same `caseId` and display `downloadUrl` as a clickable link.
   It works without an API key for **five minutes**. Call the tool again for a fresh link after expiry.

The application must handle retries and timed polling, and without host file support also file sharing; adding the MCP server does not implement that workflow.
Keep keys, signed URLs and PDF contents out of logs and prompts. Show the result link only to the intended user.

## Limits and common errors

| Symptom | What to do |
| --- | --- |
| `401` or `AUTHENTICATION_REQUIRED` | Check that `X-API-Key` is sent on every request and that the key is valid. |
| `403` when connecting from a browser | Connect through your backend; requests from other origins are rejected. |
| `RESULT_NOT_READY` | Check the job status. Request the result only after `completed`. |
| `IDEMPOTENCY_CONFLICT` | Reuse the original URL and filename with that `requestId`. Renewing a signed source URL also changes the input. |
| `UNSUPPORTED_FILE_TYPE` | The attached file is not a PDF. Attach the original PDF. |
| `failed` with error `DOWNLOAD_FAILED` | Accessful could not download the source. Check that the link is publicly reachable over HTTPS and still valid, then start a new job; for a chat attachment, attach the file again. |
| Timeout | The job may already exist. Retry the start with unchanged inputs: the same `requestId` or the identical `file` object. |
| Result link expired | Call `accessful_get_result` again. |
| `quota_exceeded` | Processing stopped because your quota is exhausted. See [Limits & retention](https://docs.accessful.de/limits/). |

<details>
<summary>Technical reference</summary>

- `filename`: at most 255 characters, matching `^[A-Za-z0-9_-]+\.pdf$`, for example `document.pdf`.
- `url`: at most 8192 characters, without embedded credentials or fragments. Private network targets are rejected;
  source downloads have a **120-second** deadline. See [Upload by URL](https://docs.accessful.de/reference/upload-by-url/).
- `file`: object with the required strings `download_url` and `file_id` and the optional strings `mime_type` and `file_name`;
  other properties are rejected. `download_url` follows the same rules as `url`. `file_name` is converted into a valid `filename`
  (fallback `document.pdf`); a `mime_type` other than `application/pdf` returns `UNSUPPORTED_FILE_TYPE`.
- `caseId` and `requestId` are UUIDs. Invalid arguments return `INVALID_ARGUMENT`.
- Terminal states: `completed`, `failed`, `analyzer_failed`, `canceled`, `quota_exceeded`.
  `queued`, `running` and `quota_pending` remain active. Only `completed` permits a result download.
- Status may include `queuePosition`, `queueTotal`, `estimatedWaitSeconds` and `error`; `score` appears only for `completed`.
  Missing queue fields do not mean the job has ended.
- Successful responses contain identical JSON in `structuredContent` and a text block. Results also include a PDF `resource_link`.
  Download links grant access to one result version; treat them as secrets.
- Tool errors contain `isError: true` and `{code, message, retryable}`. Connection-level HTTP errors may have no response body;
  do not expect the REST API's Problem Details format.

</details>