> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timbrix.mx/llms.txt
> Use this file to discover all available pages before exploring further.

# Agentes de IA (MCP)

> Cómo timbrar, cancelar y consultar CFDI 4.0 desde un agente de IA (Claude, Cursor, LangChain) usando el servidor MCP oficial de Timbrix. Tiempo estimado: 20 minutos.

Timbrix expone un servidor **MCP** (Model Context Protocol) oficial —
[`@timbrix/mcp`](https://www.npmjs.com/package/@timbrix/mcp) — que le da a
cualquier agente de IA compatible con MCP (Claude Desktop, Cursor, agentes
construidos con LangChain, etc.) acceso directo a la misma API REST que usa
`@timbrix/sdk`, sin que el agente tenga que conocer los detalles del
protocolo HTTP de Timbrix.

<Tip>
  **Tiempo estimado: 20 minutos.** La sección "Inicio rápido" te lleva de cero a
  tu primer CFDI timbrado por un agente en menos de 10 minutos; el resto de la
  página cubre instalación completa, las 4 herramientas disponibles, manejo de
  errores y responsabilidad legal.
</Tip>

## Inicio rápido

### 1. Obtén tu API key de sandbox

Sigue los pasos 1 y 2 de [Quickstart](/quickstart): crea tu cuenta en
[app.timbrix.mx/register](https://app.timbrix.mx/register) y copia la API
key "Sandbox — auto generada" desde **Configuración → API Keys**. Los CFDI
que timbres con esta key no tienen validez fiscal real — es el ambiente
correcto para que un agente experimente sin riesgo.

### 2. Instala el MCP server

No necesitas instalar nada de forma permanente. Agrega esto a la
configuración MCP de tu cliente (Claude Desktop, Cursor, etc.):

```json theme={null}
{
  "mcpServers": {
    "timbrix": {
      "command": "npx",
      "args": ["@timbrix/mcp"],
      "env": {
        "TIMBRIX_API_KEY": "sk_..."
      }
    }
  }
}
```

Ver la sección [Instalación del MCP server](#instalación-del-mcp-server) más
abajo para el path exacto de este archivo según tu cliente.

### 3. Timbra tu primer CFDI desde el agente

Con el MCP server conectado, pídele a tu agente algo como:

> "Usa `timbrix_crear_cfdi_ingreso` para timbrar una factura a Público en
> General por \$100 MXN, forma de pago efectivo (01), uso G03."

El agente resuelve la herramienta `timbrix_crear_cfdi_ingreso` y te regresa
el UUID fiscal, el estatus y el XML timbrado. Si el resultado trae
`isError: true`, revisa la sección [Manejo de errores](#manejo-de-errores)
antes de reintentar — los errores más comunes en sandbox (fecha en UTC, RFC
no inscrito) están documentados ahí con su solución exacta.

## Instalación del MCP server

`@timbrix/mcp` es un paquete npm público que implementa el protocolo MCP
sobre `stdio` (por defecto) o HTTP (para despliegues hospedados).

### Sin instalación (`npx`)

```bash theme={null}
npx @timbrix/mcp
```

Esto siempre ejecuta la última versión publicada. Para instalarlo de forma
global en su lugar:

```bash theme={null}
npm install -g @timbrix/mcp
TIMBRIX_API_KEY=sk_... timbrix-mcp
```

### Configuración en Claude Desktop

Agrega esto a tu `claude_desktop_config.json`:

```json theme={null}
{
  "mcpServers": {
    "timbrix": {
      "command": "npx",
      "args": ["@timbrix/mcp"],
      "env": {
        "TIMBRIX_API_KEY": "sk_..."
      }
    }
  }
}
```

### Configuración en Cursor

Cursor usa el mismo formato `mcpServers` que Claude Desktop. El bloque JSON
de arriba funciona sin cambios; solo cámbialo al archivo de configuración
MCP que use tu versión de Cursor (Cursor expone esta configuración desde
**Settings → MCP**, o un archivo de configuración de proyecto/usuario según
la versión — consulta la documentación oficial de Cursor para el path
exacto, ya que puede variar entre versiones).

### Variables de entorno

| Variable          | Requerida | Descripción                                                               |
| ----------------- | --------- | ------------------------------------------------------------------------- |
| `TIMBRIX_API_KEY` | Sí        | API key creada en el dashboard de Timbrix, ligada a una sola organización |
| `TIMBRIX_API_URL` | No        | Sobrescribe la URL base del API (default `https://api.timbrix.mx`)        |
| `MCP_TRANSPORT`   | No        | `stdio` (default, para agentes locales) o `http` (para uso hospedado)     |

<Warning>
  El transporte `http` no implementa autenticación propia — cualquier proceso
  que pueda alcanzar el puerto puede timbrar y cancelar CFDI reales contra tu
  RFC, a tu costo. Por eso el servidor hace bind a `127.0.0.1` por defecto (solo
  accesible desde la misma máquina). Si necesitas exponerlo más allá de eso
  (`MCP_HTTP_HOST=0.0.0.0`), es tu responsabilidad poner tu propio front door de
  autenticación delante (reverse proxy, API gateway o red privada) — el paquete
  no lo hace por ti. Para agentes locales de un solo usuario, usa el transporte
  `stdio` por defecto: no requiere ningún puerto abierto.
</Warning>

## Operaciones disponibles

El MCP v1 de Timbrix expone **4 herramientas**. No existe una quinta
herramienta para crear organizaciones/emisores (`timbrix_crear_emisor`) —
registrar un nuevo RFC emisor y subir su CSD requiere hoy una sesión
autenticada de owner, no una API key, así que se hace desde el dashboard o
`@timbrix/cli`.

| Herramienta                  | Descripción                                                      |
| ---------------------------- | ---------------------------------------------------------------- |
| `timbrix_crear_cfdi_ingreso` | Timbra un CFDI 4.0 de tipo Ingreso                               |
| `timbrix_cancelar_cfdi`      | Cancela un CFDI ya timbrado por UUID y motivo                    |
| `timbrix_listar_cfdi`        | Lista CFDI de la organización con filtros de página/tipo/estatus |
| `timbrix_consultar_saldo`    | Consulta el consumo/límite de CFDI del mes calendario actual     |

### `timbrix_crear_cfdi_ingreso`

Timbra (sella ante el SAT vía PAC) un CFDI 4.0 de tipo Ingreso para la
organización a la que pertenece tu API key. Devuelve el UUID fiscal, el XML
timbrado y el estatus.

| Parámetro                 | Tipo             | Requerido | Descripción                                                                                                     |
| ------------------------- | ---------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
| `series`                  | `string`         | Sí        | Serie del folio (ej. `"A"`)                                                                                     |
| `folioNumber`             | `string`         | Sí        | Número de folio                                                                                                 |
| `date`                    | `string`         | Sí        | Fecha y hora de emisión, ISO 8601 **sin zona horaria**, ver aviso abajo                                         |
| `paymentForm`             | `string`         | Sí        | Clave SAT de forma de pago (catálogo `c_FormaPago`), ej. `"01"` = Efectivo                                      |
| `paymentMethod`           | `"PUE" \| "PPD"` | No        | Método de pago                                                                                                  |
| `currency`                | `string`         | No        | Moneda (default MXN)                                                                                            |
| `exchange`                | `number`         | No        | Tipo de cambio                                                                                                  |
| `use`                     | `string`         | Sí        | Clave SAT de uso de CFDI (catálogo `c_UsoCFDI`), ej. `"G01"`                                                    |
| `customer`                | `object`         | No\*      | Datos del receptor inline (excluyente con `customerId`)                                                         |
| `customerId`              | `string`         | No\*      | ID de un cliente ya registrado (excluyente con `customer`)                                                      |
| `items`                   | `array`          | Sí        | Conceptos del CFDI (mínimo 1)                                                                                   |
| `idempotencyKey`          | `string`         | No        | Ver [Manejo de errores](#manejo-de-errores) — permite reintentar sin duplicar el timbrado                       |
| `requiere_confirmacion`   | `boolean`        | No        | Ver [Confirmación explícita para timbrados de alto valor](#confirmación-explícita-para-timbrados-de-alto-valor) |
| `umbral_confirmacion_mxn` | `number`         | No\*\*    | Monto MXN a partir del cual se requiere confirmación. Obligatorio si `requiere_confirmacion` es `true`          |
| `confirmado`              | `boolean`        | No        | `true` para timbrar a pesar de superar el umbral, tras confirmación humana                                      |

\* Debes enviar exactamente uno de `customer` o `customerId`.
\*\* Obligatorio solo cuando `requiere_confirmacion` es `true`.

<Warning>
  **`date` va siempre en hora local de Ciudad de México (`America/Mexico_City`,
  UTC-6), nunca en UTC.** El PAC compara este valor directamente contra su
  propio reloj de servidor, que está en hora de México sin ninguna conversión.
  Una fecha en UTC se ve \~6 horas en el futuro y el timbrado falla — y el error
  que reporta el PAC casi nunca dice "fecha inválida": normalmente aparece como
  el confuso `CFDI40102 - El resultado de la digestión debe ser igual al
      resultado de la desencripción del sello`, que parece un problema de
  certificado pero casi siempre es esto. El SAT exige que la fecha esté dentro
  de las 72 horas previas al timbrado; el servidor no la asigna por su cuenta,
  así que un agente debe calcularla explícitamente en la zona horaria correcta
  (ver ejemplos de código más abajo).
</Warning>

### `timbrix_cancelar_cfdi`

Solicita la cancelación de un CFDI ya timbrado ante el SAT. Si el receptor
debe aprobar la cancelación, el resultado queda en estatus `pendiente`.

| Parámetro          | Tipo                           | Requerido           | Descripción                                    |
| ------------------ | ------------------------------ | ------------------- | ---------------------------------------------- |
| `uuid`             | `string`                       | Sí                  | UUID fiscal (folio fiscal) del CFDI a cancelar |
| `motivo`           | `"01" \| "02" \| "03" \| "04"` | Sí                  | Ver tabla de motivos abajo                     |
| `folioSustitucion` | `string`                       | Solo si `motivo=01` | UUID del CFDI que sustituye a este             |

Valores de `motivo`:

| Valor | Significado                                                                |
| ----- | -------------------------------------------------------------------------- |
| `01`  | Comprobante emitido con errores con relación (requiere `folioSustitucion`) |
| `02`  | Comprobante emitido con errores sin relación                               |
| `03`  | No se llevó a cabo la operación                                            |
| `04`  | Operación nominativa relacionada en una factura global                     |

### `timbrix_listar_cfdi`

Lista los CFDI timbrados de la organización, más recientes primero.

| Parámetro | Tipo                        | Requerido | Descripción                               |
| --------- | --------------------------- | --------- | ----------------------------------------- |
| `page`    | `number` (entero positivo)  | No        | Número de página                          |
| `limit`   | `number` (entero, máx. 100) | No        | Resultados por página                     |
| `type`    | `"I" \| "E" \| "T"`         | No        | Filtra por tipo (Ingreso/Egreso/Traslado) |
| `status`  | `"vigente" \| "cancelado"`  | No        | Filtra por estatus                        |

### `timbrix_consultar_saldo`

Sin parámetros. Devuelve cuántos CFDI ha timbrado la organización en el mes
calendario actual (hora Ciudad de México) contra el límite de su plan, y
cuándo se reinicia el periodo.

<Warning>
  `cfdiIncluded` y `cfdiRemaining` son `null` en el plan enterprise (timbrado
  ilimitado), **no cero**. Un agente que reporte "0 timbres restantes" en ese
  caso le está dando información falsa al usuario — siempre distingue `null` de
  `0` antes de comunicar el saldo.
</Warning>

## Ejemplos de flujos

### Caso real: timbrar automáticamente al confirmarse un pago

Un patrón común es que un agente escuche un webhook de un proveedor de
pagos (ej. Stripe `payment_intent.succeeded`) y, al confirmarse el cobro,
arme el cliente, el producto y el CFDI usando las herramientas MCP —sin
intervención humana.

```typescript theme={null}
// Pseudocódigo del flujo, usando las herramientas MCP expuestas por
// @timbrix/mcp a través de un cliente MCP (ver ejemplos de LangChain
// más abajo para cómo obtener `tools` en la práctica).

async function onPaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {
  const { customerRfc, customerName, amount, description } =
    extractInvoiceDataFromPaymentIntent(paymentIntent)

  // 1. El agente decide si el cliente ya existe o necesita datos genéricos
  //    de "Público en General" (XAXX010101000) cuando no se capturó RFC.
  const customer = customerRfc
    ? { legalName: customerName, taxId: customerRfc, taxSystem: "601" }
    : {
        legalName: "PUBLICO EN GENERAL",
        taxId: "XAXX010101000",
        taxSystem: "616",
      }

  // 2. Fecha SIEMPRE en hora de Ciudad de México, nunca en UTC.
  const date = new Date()
    .toLocaleString("sv-SE", { timeZone: "America/Mexico_City" })
    .replace(" ", "T")

  // 3. El agente invoca timbrix_crear_cfdi_ingreso vía MCP.
  const result = await mcpTools.timbrix_crear_cfdi_ingreso({
    series: "A",
    folioNumber: String(paymentIntent.id).slice(-8),
    date,
    paymentForm: "04", // tarjeta de crédito
    paymentMethod: "PUE",
    use: customerRfc ? "G03" : "S01",
    customer,
    items: [
      {
        description,
        quantity: 1,
        unitPrice: amount / 100,
        amount: amount / 100,
        productKey: 84111506,
        unitKey: "E48",
      },
    ],
    // Idempotencia atada al ID del evento de Stripe: un reintento del
    // webhook o un timeout de red no genera un CFDI duplicado.
    idempotencyKey: `stripe-${paymentIntent.id}`,
  })

  if (result.isError) {
    // Ver "Manejo de errores" — el texto libre en result.content[0].text
    // trae el código CFDI##### embebido cuando aplica.
    return notifyOpsOfFailedInvoice(paymentIntent, result.content[0].text)
  }

  const invoice = JSON.parse(result.content[0].text)
  return notifyCustomer(invoice.uuid)
}
```

### TypeScript/Node con LangChain

```typescript theme={null}
import { MultiServerMCPClient } from "@langchain/mcp-adapters"
import { ChatAnthropic } from "@langchain/anthropic"
import { createReactAgent } from "@langchain/langgraph/prebuilt"

const client = new MultiServerMCPClient({
  timbrix: {
    transport: "stdio",
    command: "npx",
    args: ["@timbrix/mcp"],
    env: { TIMBRIX_API_KEY: process.env.TIMBRIX_API_KEY! },
  },
})

const tools = await client.getTools()

const agent = createReactAgent({
  llm: new ChatAnthropic({ model: "claude-sonnet-4-5" }),
  tools,
})

const result = await agent.invoke({
  messages: [
    {
      role: "user",
      content:
        "Timbra un CFDI de ingreso a Público en General por $100 MXN, " +
        "forma de pago efectivo, uso G03.",
    },
  ],
})
```

### Python con LangChain

```python theme={null}
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
import os

client = MultiServerMCPClient(
    {
        "timbrix": {
            "transport": "stdio",
            "command": "npx",
            "args": ["@timbrix/mcp"],
            "env": {"TIMBRIX_API_KEY": os.environ["TIMBRIX_API_KEY"]},
        }
    }
)

tools = await client.get_tools()

agent = create_react_agent(ChatAnthropic(model="claude-sonnet-4-5"), tools)

result = await agent.ainvoke(
    {
        "messages": [
            (
                "user",
                "Timbra un CFDI de ingreso a Público en General por "
                "$100 MXN, forma de pago efectivo, uso G03.",
            )
        ]
    }
)
```

<Warning>
  Los nombres exactos de `MultiServerMCPClient`/`getTools()` (TypeScript) y
  `get_tools()` (Python) corresponden a la API pública documentada de
  `@langchain/mcp-adapters` / `langchain-mcp-adapters` al momento de escribir
  esta guía. Verifica contra la versión que instales antes de publicar código de
  producción basado en estos ejemplos.
</Warning>

## Autenticación

`TIMBRIX_API_KEY` resuelve automáticamente la organización — no necesitas
pasar `organizationId` en ninguna herramienta MCP.

Reglas de seguridad para agentes:

* **Nunca** pongas la API key en el prompt o system message del agente. Un
  LLM puede repetir cualquier texto de su contexto en su output (por un
  error de razonamiento, un prompt injection, o simplemente al depurar en
  voz alta), y eso filtraría la key.
* Pasa siempre la key como **variable de entorno** del proceso que hospeda
  el MCP server (`TIMBRIX_API_KEY=sk_... npx @timbrix/mcp`), nunca como un
  valor que el modelo pueda leer o citar.
* Si tu agente corre en un entorno **multi-tenant** (por ejemplo, un SaaS
  que orquesta agentes para varios clientes de Timbrix), cada tenant
  necesita su **propia** API key con su propio scope de organización — no
  reutilices una sola key para varios tenants. Como la key ya resuelve la
  organización por sí sola, aislar tenants es tan simple como levantar una
  instancia del MCP server (o una sesión, en el transporte HTTP) por key.

## Manejo de errores

`@timbrix/mcp` **nunca lanza una excepción que tumbe el proceso** cuando la
API de Timbrix responde con un error. En su lugar, cualquier error se
devuelve como un resultado MCP normal con `isError: true` y
`content[0].text` conteniendo un mensaje de texto libre. Un agente debe
revisar `result.isError` explícitamente — no puede confiar en un bloque
`try/catch` alrededor de la llamada a la herramienta para detectar fallos.

```typescript theme={null}
const result = await mcpTools.timbrix_crear_cfdi_ingreso(payload)

if (result.isError) {
  const message = result.content[0].text // texto libre, puede traer CFDI#####
  // decide reintentar, escalar a un humano, o registrar el fallo
}
```

Los códigos de catálogo del SAT (formato `CFDI#####`) vienen embebidos
dentro de ese texto libre, no como un campo estructurado aparte. Los más
comunes que verás en sandbox:

| Código                               | Causa                                                                                                                                                                             | Solución                                                                                                          |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `CFDI40130`                          | Falta el nodo `InformacionGlobal` cuando el CFDI es para público en general                                                                                                       | Agregar ese nodo (periodicidad, meses, año) al timbrar                                                            |
| `CFDI40143`                          | El RFC del receptor no está inscrito/vigente ante el SAT                                                                                                                          | Usar `XAXX010101000` (público en general) o un RFC real, especialmente en sandbox                                 |
| `CFDI40102` (a veces falso positivo) | El mensaje dice "digestión no coincide con el sello", pero la causa real casi siempre es `date` en UTC en vez de hora de México, o el mismo problema de RFC no inscrito de arriba | Revisar primero la zona horaria de `date` y el RFC del receptor antes de asumir que es un problema de certificado |

<Tip>
  `timbrix_crear_cfdi_ingreso` acepta un `idempotencyKey` opcional. Úsalo
  siempre que un agente pueda necesitar reintentar una llamada tras un timeout
  de red — sin él, un reintento ciego arriesga timbrar el mismo CFDI dos veces
  (con el consumo y costo fiscal que eso implica).
</Tip>

## Responsabilidad legal

El SAT no tiene una regulación específica para el timbrado ejecutado por
agentes de IA: la responsabilidad del CFDI emitido sigue siendo,
íntegramente, del RFC emisor — el cliente de Timbrix — sin importar si el
comprobante lo generó un humano desde el dashboard o un agente autónomo vía
MCP. Timbrix no valida ni certifica el juicio del agente antes de aceptar
la petición de timbrado.

Si despliegas un agente autónomo capaz de timbrar o cancelar CFDI sin
intervención humana en cada paso, eres responsable de supervisarlo: revisar
sus timbrados, tener alertas sobre montos o volúmenes inusuales, y poder
detenerlo si empieza a operar incorrectamente. `timbrix_crear_cfdi_ingreso`
incluye un hook de confirmación para esto (ver abajo), pero sigue siendo tu
responsabilidad configurarlo y actuar sobre sus mensajes de confirmación
pendiente — Timbrix no supervisa el juicio del agente por ti.

### Confirmación explícita para timbrados de alto valor

Un agente mal configurado que timbra automáticamente un CFDI de \$500,000
MXN sin revisión humana es un riesgo operativo real, aunque el SAT no lo
regule de forma distinta a cualquier otro CFDI. `timbrix_crear_cfdi_ingreso`
acepta tres parámetros opcionales para mitigar esto:

| Parámetro                 | Tipo      | Descripción                                                                                                                                                |
| ------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `requiere_confirmacion`   | `boolean` | Activa la validación de umbral. Default: `false` (no bloquea el flujo estándar).                                                                           |
| `umbral_confirmacion_mxn` | `number`  | Monto en MXN a partir del cual se requiere confirmación. Obligatorio si `requiere_confirmacion` es `true`.                                                 |
| `confirmado`              | `boolean` | Envíalo como `true` en una segunda llamada, después de que un humano confirme explícitamente, para timbrar a pesar de superar el umbral. Default: `false`. |

Cuando `requiere_confirmacion: true` y la suma de `items[].amount` (el
subtotal estimado del CFDI) supera `umbral_confirmacion_mxn`, el tool **no
timbra**: responde con un mensaje estructurado (`requiereConfirmacion: true`,
el subtotal estimado, el umbral configurado y las instrucciones para
proceder) en vez de llamar a la API. El agente debe mostrarle ese mensaje a
un humano y, solo tras obtener su confirmación explícita, volver a invocar
la herramienta con el mismo payload agregando `"confirmado": true`.

<CodeGroup>
  ```json Configuración recomendada theme={null}
  {
    "series": "A",
    "folioNumber": "42",
    "date": "2026-09-11T10:00:00",
    "paymentForm": "03",
    "use": "G03",
    "customerId": "cus_123",
    "items": [{ "productId": "prod_456", "quantity": 1, "amount": 550000 }],
    "requiere_confirmacion": true,
    "umbral_confirmacion_mxn": 100000
  }
  ```

  ```json Respuesta — confirmación pendiente theme={null}
  {
    "requiereConfirmacion": true,
    "subtotalEstimadoMxn": 550000,
    "umbralConfirmacionMxn": 100000,
    "mensaje": "Este CFDI tiene un subtotal estimado de $550000.00 MXN, que supera el umbral de confirmación configurado ($100000.00 MXN). Obtén confirmación explícita de un humano antes de continuar, y vuelve a llamar a esta herramienta con el mismo payload agregando \"confirmado\": true."
  }
  ```
</CodeGroup>

<Tip>
  Un umbral razonable para empezar es el monto a partir del cual tu negocio ya
  pediría una segunda revisión humana para una factura normal — no hay un valor
  único recomendado para todos los casos, ya que depende del ticket promedio de
  cada organización.
</Tip>

## Siguiente paso

<CardGroup cols={2}>
  <Card title="Quickstart de la API REST" icon="rocket" href="/quickstart">
    El mismo flujo cliente → producto → CFDI, sin MCP de por medio.
  </Card>

  <Card title="API Reference" icon="terminal" href="/api-reference/introduction">
    Endpoints, autenticación, límites de tasa y códigos de error.
  </Card>
</CardGroup>
