Facty MCP server

Facty exposes a remote Model Context Protocol server so you can plug invoicing into any MCP-capable AI client (Claude Desktop, Cursor, your own agents). The agent can emit and cancel CFDIs, manage clients and products, search SAT catalogs, and read your timbre balance — all scoped to a single organization by the API key it connects with.

Endpoint

https://<your-facty-host>/api/mcp
  • Transport: Streamable HTTP (stateless). SSE is not exposed.
  • Methods: POST (and GET/DELETE as the transport negotiates).

Authentication

Two ways to connect, both scoped to a single organization:

  • OAuth 2.1 — for claude.ai's remote connector (no API key; you approve an org and permissions at a consent screen). See claude.ai under Client configuration.
  • API key — for local clients that can send a header (Claude Desktop, Cursor, CLI, your own agents).

Connect with a Facty API key as a bearer token:

Authorization: Bearer fk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Create a key in Configuración → API Keys (requires the org.settings permission). The key's scopes are the agent's permissions — the org is bound to the key, so an agent only ever acts within that one organization. Grant the minimum scopes the integration needs:

  • list_invoices, get_invoiceinvoices.read
  • create_invoice (ingreso/egreso) → invoices.create
  • cancel_invoiceinvoices.cancel
  • list_clients / create_clientclients.read / clients.create
  • list_products / create_productproducts.read / products.create
  • search_sat_catalog → none (any valid key)
  • get_billing_balancebilling.read

A tool called without its scope returns a clear "permiso insuficiente" error rather than failing silently.

Tools

  • list_invoices — list CFDIs with filters (status, type, clientId, date range) and cursor pagination.
  • get_invoice — full CFDI detail + line items; includeDownloadUrls: true adds 5-minute signed PDF/XML URLs.
  • create_invoice — emit & stamp a CFDI. type: "ingreso" (default) or "egreso" (nota de crédito, needs cfdiRelacionados). Consumes one timbre. Returns { id, uuid, total }. Pass a stable idempotencyKey to make retries safe (one is generated if omitted).
  • cancel_invoice — cancel a stamped CFDI with a SAT motivo (01–04); 01 requires folioSustitucion. Consumes one timbre.
  • list_clients / create_client — manage receptors. A client needs regimenFiscal + cp before it can be invoiced.
  • list_products / create_product — manage the reusable concept catalog.
  • search_sat_catalog — look up SAT catalogs needed to build a CFDI: clave_prod_serv (keyword search, needs q), regimen_fiscal, uso_cfdi, forma_pago, metodo_pago, moneda, clave_unidad.
  • get_billing_balance — current timbre balance.

Client configuration

claude.ai (remote connector, OAuth)

The claude.ai web/desktop custom connector speaks OAuth 2.1 — no API key to paste. In claude.ai go to Settings → Connectors → Add custom connector and enter the endpoint:

https://<your-facty-host>/api/mcp

Claude discovers Facty's authorization server automatically (RFC 8414 / 9728 metadata under /.well-known/), registers itself (Dynamic Client Registration), and opens a browser window at Facty. There you sign in, pick the organization, and choose exactly which permissions to grant — the agent can only ever do what you approve on that consent screen. Claude then completes the OAuth exchange (authorization code + PKCE) and connects. Access tokens are short-lived and refresh automatically.

Use this path for claude.ai in the browser. For local clients that can inject a header (Claude Desktop, Cursor, the CLI, your own agents), the API-key methods below are simpler.

Claude Desktop / Cursor (mcp.json)

Most desktop clients reach a remote Streamable HTTP server through the mcp-remote bridge:

{
  "mcpServers": {
    "facty": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://<your-facty-host>/api/mcp",
        "--header",
        "Authorization: Bearer fk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      ]
    }
  }
}

Clients with native remote MCP support can instead point directly at the URL and set the Authorization header in their connection settings.

Programmatic (TypeScript)

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://<your-facty-host>/api/mcp"),
  { requestInit: { headers: { Authorization: `Bearer ${process.env.FACTY_API_KEY}` } } },
);

const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);
const { tools } = await client.listTools();

Notes

  • Emission and cancellation are real fiscal operations and consume timbres. Test against the PAC sandbox first (FACTURAMA_ENV=sandbox).
  • All tools enforce row-level org isolation; a key for org A can never read or write org B's data.
  • Tools return JSON as text; errors (insufficient timbres, validation, PAC errors) come back as readable, non-fatal tool errors the agent can act on.