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(andGET/DELETEas 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_invoice→invoices.readcreate_invoice(ingreso/egreso) →invoices.createcancel_invoice→invoices.cancellist_clients/create_client→clients.read/clients.createlist_products/create_product→products.read/products.createsearch_sat_catalog→ none (any valid key)get_billing_balance→billing.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: trueadds 5-minute signed PDF/XML URLs.create_invoice— emit & stamp a CFDI.type: "ingreso"(default) or"egreso"(nota de crédito, needscfdiRelacionados). Consumes one timbre. Returns{ id, uuid, total }. Pass a stableidempotencyKeyto make retries safe (one is generated if omitted).cancel_invoice— cancel a stamped CFDI with a SATmotivo(01–04);01requiresfolioSustitucion. Consumes one timbre.list_clients/create_client— manage receptors. A client needsregimenFiscal+cpbefore 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, needsq),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.