# Integración MCP

Accessful aloja un servidor MCP con el que tu aplicación de IA puede procesar PDF, consultar el progreso y obtener el resultado.
Necesitas una clave API de Accessful y un cliente que admita **servidores remotos mediante Streamable HTTP y cabeceras HTTP personalizadas**.

## Configurar la conexión

1. En el portal de Accessful, abre **API-Keys** y crea una clave para esta integración.
   Cópiala al crearla; solo se muestra una vez. Consulta [Crear una clave de API](https://docs.accessful.de/es/authentication/#crear-una-clave-de-api).
2. Añade en tu cliente un servidor MCP remoto llamado **Accessful** e introduce estos valores:

   | Parámetro | Valor |
   | --- | --- |
   | URL del servidor | `https://api.accessful.de/mcp` |
   | Transporte | Streamable HTTP |
   | Nombre de la cabecera | `X-API-Key` |
   | Valor de la cabecera | Tu clave API personal de Accessful |

3. Guarda la clave en el almacén de secretos del cliente, guarda la configuración y conecta. La cabecera debe enviarse en **cada petición**.
   Usa una clave independiente y revocable por usuario e integración. **El inicio de sesión OAuth no está disponible en V1**.

Conecta tus propias aplicaciones web a través de su backend. Las peticiones directas del navegador desde otro origen se rechazan con `403`.

## Comprobar la conexión

Actualiza la lista de herramientas del cliente. Deben aparecer estas **cuatro herramientas**:

| Herramienta | Entrada | Resultado |
| --- | --- | --- |
| `accessful_start_pdf` | `url`, `filename`, `requestId` | Inicia un trabajo desde un enlace de descarga; devuelve `caseId`, `pollAfterSeconds` |
| `accessful_start_pdf_file` | `file` (lo rellena el host) | Inicia un trabajo desde un archivo adjunto al chat; devuelve `caseId`, `pollAfterSeconds` |
| `accessful_get_status` | `caseId` | Devuelve `jobStatus`, `stage`, `terminal` y los datos de progreso disponibles |
| `accessful_get_result` | `caseId` | Devuelve `downloadUrl`, `expiresAt`, `filename`, `mimeType` |

<details>
<summary>Opcional: comprobar con MCP Inspector</summary>

Con Node.js 22.19 o posterior y tu clave en la variable de entorno `ACCESSFUL_API_KEY`,
ejecuta este comando en una terminal Bash o Zsh. Lista las herramientas sin crear un trabajo PDF:

```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
```

La salida debe contener los cuatro nombres anteriores. Consulta la [documentación de MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).

</details>

## Procesar un PDF

Inicia un trabajo con **una de las dos herramientas de inicio**, consulta después el estado y descarga el resultado.
Máximo **50 MiB**, un PDF por trabajo.

### Opción A: archivo adjunto al chat

Algunos hosts MCP pasan los archivos adjuntos del chat directamente a las herramientas que declaran un parámetro de archivo en `_meta["openai/fileParams"]`.
`accessful_start_pdf_file` declara así su parámetro `file`. Si tu host lo admite, el usuario adjunta el PDF
y el host rellena `file` con un enlace de descarga temporal, de modo que el modelo nunca tiene que construir una URL:

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

La `download_url` del host debe ser accesible para Accessful mediante HTTPS público, sin iniciar sesión ni cabeceras adicionales,
y seguir siendo válida hasta que Accessful haya descargado el archivo. La descarga comienza poco después de aceptar el trabajo.
No se necesita `requestId`: repetir la llamada idéntica devuelve el mismo `caseId`, mientras que un enlace renovado para el mismo archivo inicia un trabajo nuevo.

Los hosts sin esta compatibilidad muestran `file` como un objeto normal. Usa allí la opción B; no dejes que el modelo invente una `download_url`.

### Opción B: enlace de descarga

Prepara un **enlace HTTPS de descarga directa del PDF original** que Accessful pueda abrir sin iniciar sesión ni enviar cabeceras adicionales.
Un enlace a un PDF público ya existente sirve. Para archivos privados, tu aplicación debe generar un enlace de descarga firmado.
Una ruta local no basta; si el host no admite archivos, la aplicación debe proporcionar el archivo mediante una URL.
El enlace debe seguir siendo válido durante al menos **15 minutos**.

Llama a `accessful_start_pdf` con los siguientes argumentos. Sustituye la URL de ejemplo por el enlace a tu PDF
y genera un UUID `requestId` nuevo para cada trabajo nuevo:

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

En los reintentos, conserva **el mismo `requestId`, URL y nombre de archivo**, incluso tras cambiar la clave API.

### Consultar el estado y descargar el resultado

1. Guarda el `caseId` que devuelve la herramienta de inicio. Llama con él a `accessful_get_status` cada **10 segundos**, hasta que `terminal` sea `true`.
   Tras **120 segundos** de espera activa, muestra el estado actual y el `caseId`; el procesamiento continúa, así que consulta de nuevo más tarde.
2. Cuando `jobStatus` sea `completed`, llama a `accessful_get_result` con el mismo `caseId` y muestra `downloadUrl` como enlace de descarga.
   Funciona sin clave API durante **cinco minutos**. Repite la llamada para obtener un enlace nuevo cuando caduque.

La aplicación debe gestionar los reintentos y las consultas periódicas y, si el host no admite archivos, también el acceso al archivo; añadir el servidor MCP no implementa ese flujo.
No incluyas claves, URL firmadas ni contenido PDF en registros o prompts. Muestra el enlace del resultado solo al usuario destinatario.

## Límites y errores frecuentes

| Problema | Solución |
| --- | --- |
| `401` o `AUTHENTICATION_REQUIRED` | Comprueba que se envía `X-API-Key` en cada petición y que la clave es válida. |
| `403` al conectar desde el navegador | Conecta a través de tu backend; se rechazan las peticiones de otros orígenes. |
| `RESULT_NOT_READY` | Consulta el estado. Solicita el resultado solo después de `completed`. |
| `IDEMPOTENCY_CONFLICT` | Usa la URL y el nombre de archivo originales con ese `requestId`. Renovar una URL de origen firmada también cambia la entrada. |
| `UNSUPPORTED_FILE_TYPE` | El archivo adjunto no es un PDF. Adjunta el PDF original. |
| `failed` con el error `DOWNLOAD_FAILED` | Accessful no pudo descargar el original. Comprueba que el enlace sea accesible públicamente mediante HTTPS y siga siendo válido, y luego inicia un trabajo nuevo; si es un archivo adjunto al chat, vuelve a adjuntarlo. |
| Timeout | El trabajo puede existir ya. Reintenta el inicio con los mismos datos: el mismo `requestId` o el mismo objeto `file`. |
| Enlace del resultado caducado | Vuelve a llamar a `accessful_get_result`. |
| `quota_exceeded` | El procesamiento terminó porque se agotó tu cuota. Consulta [Límites y retención](https://docs.accessful.de/es/limits/). |

<details>
<summary>Referencia técnica</summary>

- `filename`: hasta 255 caracteres, debe cumplir `^[A-Za-z0-9_-]+\.pdf$`, por ejemplo `document.pdf`.
- `url`: hasta 8192 caracteres, sin credenciales incrustadas ni fragmentos. Se rechazan destinos de redes privadas;
  la descarga del original tiene un límite de **120 segundos**. Consulta [Subir por URL](https://docs.accessful.de/es/reference/upload-by-url/).
- `file`: objeto con las cadenas obligatorias `download_url` y `file_id` y las cadenas opcionales `mime_type` y `file_name`;
  se rechazan otras propiedades. `download_url` sigue las mismas reglas que `url`. `file_name` se convierte en un `filename` válido
  (valor por defecto `document.pdf`); un `mime_type` distinto de `application/pdf` produce `UNSUPPORTED_FILE_TYPE`.
- `caseId` y `requestId` son UUID. Los argumentos inválidos producen `INVALID_ARGUMENT`.
- Estados finales: `completed`, `failed`, `analyzer_failed`, `canceled`, `quota_exceeded`.
  `queued`, `running` y `quota_pending` siguen activos. Solo `completed` permite descargar el resultado.
- El estado puede incluir `queuePosition`, `queueTotal`, `estimatedWaitSeconds` y `error`; `score` solo aparece en `completed`.
  La ausencia de campos de cola no indica un estado final.
- Las respuestas correctas contienen JSON idéntico en `structuredContent` y un bloque de texto. Los resultados también incluyen un `resource_link` PDF.
  Los enlaces permiten acceder a una versión del resultado; trátalos como secretos.
- Los errores de herramienta contienen `isError: true` y `{code, message, retryable}`. Los errores HTTP de conexión pueden tener un cuerpo vacío;
  no esperes el formato Problem Details de la API REST.

</details>