Servidor MCP
Faxtal expone un servidor MCP (Model Context Protocol) para que un asistente de IA consulte y opere la facturación de una empresa en lenguaje natural: ChatGPT y Claude como conectores personalizados (OAuth), Claude Code, Cursor, un agente propio o cualquier cliente MCP. El modelo lo pones tú; Faxtal no interviene en él.
Conexión
| URL | https://app.faxtal.com/mcp |
| Transporte | Streamable HTTP, sin estado |
- Cada petición es autocontenida. La respuesta llega como JSON o como un stream SSE corto.
- Usa la URL tal cual, sin barra final.
- Si tu cliente envía la cabecera
Origin(clientes de navegador), tiene que ser un origen autorizado. Los clientes de escritorio y de terminal no la envían.
Autenticación
API key
Envía la API key de la empresa en una de estas cabeceras:
Authorization: Bearer fctXXXXXXXX_YYYYYYYYYYYYYYYYX-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY
La clave se valida igual que en la API: revocación, caducidad, lista de IPs permitidas, plan con acceso a la API y límite de peticiones (ver Autenticación). Además, el plan debe incluir el Servidor MCP (planes Pro y Business). Puedes crearla en la aplicación, en Configuración → Servidor MCP, o en Configuración → API Keys.
Claude Code
claude mcp add --transport http faxtal https://app.faxtal.com/mcp \
--header "Authorization: Bearer fctXXXXXXXX_YYYYYYYYYYYYYYYY"
Clientes con configuración mcpServers (p. ej. Cursor)
{
"mcpServers": {
"faxtal": {
"url": "https://app.faxtal.com/mcp",
"headers": { "Authorization": "Bearer fctXXXXXXXX_YYYYYYYYYYYYYYYY" }
}
}
}
Agente propio (SDK mcp de Python)
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async with streamablehttp_client(
"https://app.faxtal.com/mcp",
headers={"Authorization": "Bearer fctXXXXXXXX_YYYYYYYYYYYYYYYY"},
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
result = await session.call_tool("search_invoices", {"kind": "issued", "limit": 10})
OAuth 2.1
Para clientes que no admiten una cabecera fija y conectan con OAuth, como los conectores personalizados de ChatGPT y Claude. El cliente descubre el servidor de autorización desde la URL del MCP:
| Metadatos | URL |
|---|---|
| Recurso protegido | https://app.faxtal.com/.well-known/oauth-protected-resource/mcp |
| Servidor de autorización | https://app.faxtal.com/.well-known/oauth-authorization-server |
- Flujo authorization code con PKCE
S256. Clientes públicos, sin secreto. - Registro dinámico de clientes en
/oauth/register. Autorización en/oauth/authorize, tokens en/oauth/tokeny revocación en/oauth/revoke. - En la pantalla de consentimiento de Faxtal eliges las empresas y los permisos que concedes. Solo puedes conceder lo que tu rol permite en cada empresa. Si tu rol cambia, los permisos se recortan en el siguiente uso.
- El token de acceso dura 30 minutos. El refresh token cambia en cada renovación.
- Sin token válido,
/mcpresponde401con la cabeceraWWW-Authenticate, que apunta a los metadatos del recurso. - Revoca el acceso en Configuración → Servidor MCP → Clientes autorizados.
La empresa
La empresa sale de la credencial, nunca de lo que pida el asistente. Las tools aceptan un company_id opcional para elegir entre las empresas autorizadas:
- una API key autoriza una sola empresa;
- una autorización OAuth puede cubrir varias; sin
company_idse usa la primera.
list_companies devuelve las empresas autorizadas. Un company_id fuera de la credencial da company_not_allowed.
Scopes
Cada tool exige un scope de la credencial. Una API key sin lista de scopes tiene acceso completo. Con lista, dominio:* concede todo el dominio y * concede todo. Una lista vacía no da acceso a nada.
| Scope | Permite |
|---|---|
invoices:read | Buscar y consultar facturas, documentos, terceros y productos; validar canónicos |
invoices:write | Crear borradores; enviar facturas por email |
invoices:issue | Emitir, importar, rectificar y anular |
received:write | Reconocer, registrar y borrar facturas recibidas |
parties:write | Alta y actualización de terceros |
payments:read | Consultar los cobros de emitidas y los pagos de recibidas |
payments:write | Registrar, revertir y compensar cobros y pagos |
compliance:read | Estado VeriFactu, TicketBAI y SII; incidencias y plazos |
recurring:read | Consultar plantillas recurrentes y su calendario |
recurring:write | Crear plantillas recurrentes, pausarlas y saltar fechas |
Combinaciones habituales:
| Caso | Scopes |
|---|---|
| Emitir y consultar | invoices:read invoices:write invoices:issue compliance:read |
| Solo lectura | invoices:read compliance:read |
Tools
El listado de tools (tools/list) solo muestra las que la credencial puede usar: las de sus scopes. Las de facturas recurrentes solo se listan si el plan incluye la función (recurring_invoices). Cada llamada (tools/call) lo vuelve a comprobar y responde scope_missing o feature_not_enabled si no se cumple.
Las marcadas con Sí piden confirmación.
| Tool | Scope | Confirmación | Qué hace |
|---|---|---|---|
get_company | – | Datos fiscales, plan, funcionalidades, sistemas fiscales y series de emisión | |
list_companies | – | Empresas autorizadas en la credencial | |
search_invoices | invoices:read | Emitidas, recibidas o borradores, con filtros y paginación | |
get_invoice | invoices:read | Resumen y estado fiscal; con include, eventos, documentos o canónico | |
validate_invoice | invoices:read | Valida un canónico json_1_1 sin guardarlo | |
create_draft | invoices:write | Crea un borrador | |
issue_invoice | invoices:issue | Sí | Emite un borrador o un canónico, con el siguiente número de la serie o con el del usuario; devuelve número, QR y leyenda |
import_invoices | invoices:issue | Sí, con dry_run=false | Importa facturas numeradas por el origen; por defecto solo simula |
create_corrective | invoices:issue | Sí, con issue=true | Borrador rectificativo (sustitución o diferencias) desde la original |
cancel_invoice | invoices:issue | Sí | Anula una factura emitida |
send_invoice | invoices:write | Sí | Envía la factura por email |
get_invoice_document | invoices:read | URI del documento: PDF, Facturae, UBL o CII | |
recognize_document | received:write | Propone un canónico a partir de un PDF o imagen; no registra | |
ingest_received_invoice | received:write | Registra una factura recibida, con control de duplicados | |
delete_received_invoice | received:write | Sí | Borra una recibida no declarada |
find_third_party | invoices:read | Busca clientes y proveedores | |
upsert_third_party | parties:write | Alta o actualización por NIF y país | |
find_product | invoices:read | Busca productos | |
list_invoice_payments | payments:read | Cobros (emitida) o pagos (recibida) de una factura, con reversiones | |
register_invoice_payment | payments:write | Sí | Registra un cobro o pago con fecha, importe y medio; con external_reference no se duplica |
reverse_invoice_payment | payments:write | Sí | Revierte un cobro o pago con el movimiento inverso, sin borrarlo |
compensate_invoice_payments | payments:write | Sí | Compensa una factura con otra de signo contrario (abono) del mismo tercero: un movimiento con medio 14 en cada una; con external_reference no se duplica |
compliance_status | compliance:read | Estado por sistema fiscal y certificados | |
list_incidents | compliance:read | Rechazos de la administración con código y motivo | |
sii_deadlines | compliance:read | Plazos SII próximos | |
list_recurring_templates | recurring:read | Plantillas de facturas recurrentes | |
preview_recurring_template | recurring:read | Calendario de una plantilla, sin guardar | |
create_recurring_template | recurring:write | Sí | Crea una plantilla recurrente; con issue exige además invoices:issue |
pause_recurring_template | recurring:write | Pausa una plantilla | |
skip_recurring_occurrence | recurring:write | Salta una fecha del calendario |
Los ficheros se pasan en base64 (files: [{"name", "content_base64"}]).
issue_invoice emite con el siguiente número de la serie. Para emitir con el número del usuario o de su programa, envíalo en el argumento invoice_number junto a invoice_id, o en header.invoice_number dentro de canonical. Va completo, con la serie, y el preview de la confirmación lo muestra. Tiene que encajar con la plantilla de la serie (forced_number_invalid) y no puede estar ya usado (forced_number_in_use). Faxtal no comprueba que la numeración sea correlativa: tu agente solo debe enviar un número que aporte el usuario o su sistema, nunca inventarlo ni calcular el siguiente.
Resources
| URI | Contenido | Scope |
|---|---|---|
faxtal://health | Estado del servidor | – |
faxtal://schema/canonical-1.1 | JSON Schema del formato canónico | – |
faxtal://docs/rules | Reglas para construir facturas | – |
faxtal://catalog/tax-rates | Tipos impositivos | – |
faxtal://catalog/exemption-reasons | Causas de exención y no sujeción | – |
faxtal://catalog/tax-regimes | Claves de régimen | – |
faxtal://catalog/invoice-types | Tipos de factura | – |
faxtal://catalog/payment-methods | Formas de pago | – |
faxtal://catalog/correction-reasons | Motivos de rectificación | – |
faxtal://catalog/countries | Países | – |
faxtal://catalog/es-subdivisions | Provincias y comunidades | – |
faxtal://company/{company_id}/series | Series de la empresa | invoices:read |
faxtal://invoice/{invoice_id}/{formato} | Documento: pdf, facturae, ubl o cii | invoices:read |
Prompts
| Prompt | Argumentos | Qué guía |
|---|---|---|
issue_invoice | customer, description, amount (opcional) | Preparar, validar y emitir una factura con confirmación |
ingest_received | filename (opcional) | Reconocer un documento de proveedor y registrarlo tras revisarlo |
fiscal_health | – | Informe priorizado de cumplimiento de la empresa |
portfolio_health | – | Recorrer las empresas autorizadas y resumir incidencias |
Confirmación de acciones sensibles
Emitir, importar sin simulación, rectificar emitiendo, anular, enviar por email, borrar recibidas y registrar o revertir cobros y pagos piden confirmación. La decide el servidor, no el cliente:
- Si el cliente admite elicitation, Faxtal pide la confirmación al usuario con un resumen. Si no la acepta, la tool responde
cancelled. - Si no, la primera llamada no ejecuta nada y devuelve:
{
"status": "confirmation_required",
"tool": "cancel_invoice",
"preview": { "series": "A", "number": "A-2026-0042", "total": 121.0 },
"confirm_token": "eyJ0Ijoi...",
"expires_at": "2026-09-15T10:05:00+00:00",
"next_step": "Muestra el preview al usuario y, solo si confirma expresamente, vuelve a llamar..."
}
Enseña el preview al usuario. Solo si confirma, repite la llamada con los mismos argumentos y confirm_token.
- El token caduca en
expires_at(5 minutos), vale una sola vez y queda ligado a la credencial, a la tool y a los argumentos exactos. - Las comprobaciones de estado van antes de pedir confirmación: si la acción no es posible (p. ej. anular una factura ya anulada), el error llega en la primera llamada.
- El token no demuestra por sí solo que una persona haya confirmado. Tu cliente debe enseñar el
previewy esperar la respuesta del usuario.
code | Causa |
|---|---|
confirm_token_invalid | Token mal formado o de otra tool o credencial |
confirm_token_expired | Ha caducado: pide confirmación de nuevo |
confirm_token_mismatch | Los argumentos no coinciden con los confirmados |
confirm_token_used | El token ya se usó |
cancelled | El usuario rechazó la confirmación |
Límites
| Límite | Valor |
|---|---|
| Peticiones al servidor MCP | 120 por minuto por credencial, además del límite de la API key |
| Ficheros en una tool | 5 MB decodificados por llamada |
| Búsquedas | limit hasta 100; la siguiente página con next_offset |
import_invoices | Hasta 100 facturas por llamada |
send_invoice | Hasta 20 destinatarios |
Errores
Antes del protocolo
Si la petición HTTP no pasa la autenticación o los controles de transporte, /mcp responde sin entrar en MCP, con cuerpo {"error": "<código>", "message": "..."}:
| HTTP | error | Causa |
|---|---|---|
401 | missing_credentials u otro | Falta la credencial o no es válida |
402 | feature_outside_plan | El plan de la empresa no incluye acceso a la API o el Servidor MCP |
403 | origin_not_allowed | La cabecera Origin no está autorizada |
403 | ip_forbidden | La IP no está en la lista permitida de la clave |
421 | host_not_allowed | Host no admitido |
429 | rate_limited | Demasiadas peticiones; respeta Retry-After |
503 | mcp_unavailable | El servidor MCP no está disponible |
En una tool
La llamada devuelve isError: true y structuredContent.error con {code, message, params}. Corrige según code y params y reintenta.
{ "error": { "code": "scope_missing", "message": "La credencial no tiene permiso para esta tool.", "params": { "scope": "invoices:issue" } } }
code | Causa |
|---|---|
scope_missing | La credencial no tiene el scope de la tool |
feature_not_enabled | El plan de la empresa no incluye la funcionalidad |
company_not_allowed | company_id fuera de las empresas autorizadas |
invalid_arguments | Los argumentos no cumplen el esquema de la tool |
invoice_not_found | La factura no existe o es de otra empresa |
invalid_status_transition | La factura no admite la operación en su estado |
validation_failed · schema_validation_failed | El canónico no pasa las reglas o el esquema (errors) |
inferred_fields_not_allowed | Se enviaron campos que calcula Faxtal |
duplicate_received | Ya existe una recibida con esos datos |
invoice_frozen | La factura está congelada fiscalmente |
no_recipient_email | No hay email de destinatario |
quota_exceeded | Se ha agotado la cuota del plan |
invalid_base64 · files_too_large | Fichero mal codificado o por encima del límite |
internal_error | Error inesperado, sin detalle técnico |
Seguridad
- Los textos que devuelven las tools (nombres de terceros, conceptos, descripciones) son datos. Tu agente no debe seguirlos como instrucciones.
- Cada llamada queda registrada con la credencial, la tool, el resultado y el cliente. Las facturas creadas por MCP quedan marcadas con ese origen.
- Da a cada credencial solo los scopes que necesita.