@timbrix/mcp

MCP (Model Context Protocol) server for Timbrix — lets AI agents (Claude, Cursor, ChatGPT, etc.) stamp, cancel, and query CFDI 4.0 invoices directly, through the same REST API @timbrix/sdk uses.
Full guide: see docs.timbrix.mx/ai-agents for setup, the complete tool reference, LangChain (TS/Python) examples, error handling, and authentication best practices for agents.
Public source, issues, and PRs: github.com/Timbrix/mcp.
Listed in the Official MCP Registry as mx.timbrix/mcp.
v1 tools
timbrix_crear_cfdi_ingreso | Stamp a CFDI 4.0 Ingreso invoice |
timbrix_cancelar_cfdi | Cancel a stamped CFDI by UUID and motivo |
timbrix_consultar_saldo | Get CFDI usage/quota for the current billing month |
timbrix_listar_cfdi | List invoices with page/type/status/date/rfcReceptor filters |
timbrix_crear_emisor (registering a new RFC issuer + CSD) is not available in v1 — organization creation and CSD upload require an authenticated owner session today, not an API key. See the Timbrix dashboard or @timbrix/cli to onboard a new organization.
Installation
No install step is required to try it — npx @timbrix/mcp always runs the
latest published version. To install it globally instead:
npm install -g @timbrix/mcp
TIMBRIX_API_KEY=sk_... timbrix-mcp
Claude Desktop (local, npx)
Add to your claude_desktop_config.json:
{
"mcpServers": {
"timbrix": {
"command": "npx",
"args": ["@timbrix/mcp"],
"env": {
"TIMBRIX_API_KEY": "sk_..."
}
}
}
}
Claude Desktop (hosted, no install)
Point at https://mcp.timbrix.mx/mcp instead — same config file, no local process:
{
"mcpServers": {
"timbrix": {
"url": "https://mcp.timbrix.mx/mcp",
"headers": {
"Authorization": "Bearer sk_..."
}
}
}
}
Any MCP client that supports a url + custom headers remote server config (Cursor included) works the same way.
Environment variables
TIMBRIX_API_KEY | only for stdio (default) | API key created in the Timbrix dashboard, scoped to one organization |
TIMBRIX_API_URL | no | Overrides the API base URL (default https://api.timbrix.mx) |
MCP_TRANSPORT | no | stdio (default, for local agents) or http (for hosted use) |
PORT | no | HTTP transport port when MCP_TRANSPORT=http (default 8787) |
MCP_HTTP_HOST | no | HTTP transport bind address (default 127.0.0.1, loopback only) — see below |
MCP_ALLOWED_HOSTS | only when MCP_HTTP_HOST is non-loopback | Comma-separated hostnames this server is publicly reachable as (Host-header allowlist) |
Running the HTTP transport
MCP_TRANSPORT=http PORT=8787 npx @timbrix/mcp
Endpoints:
POST /mcp | Streamable HTTP — initialize, then every subsequent JSON-RPC request |
GET /mcp | SSE stream for server-to-client messages on an established session |
DELETE /mcp | Explicitly terminate a session |
GET /health | Health check ({ "status": "ok" }) |
Sessions
The endpoint is stateful, as the MCP spec requires. A client's first
POST /mcp carries an initialize request and no session header; the server
creates one MCP server instance for it and returns an Mcp-Session-Id. Every
later request (starting with notifications/initialized) must send that header
back and is routed to the same instance — an unknown or missing session ID is
rejected rather than silently given a fresh, uninitialized server.
A session lives until one of:
- the client sends
DELETE /mcp with its Mcp-Session-Id, or
- it goes 30 minutes without a request, at which point the idle sweep evicts
it (clients that crash, close, or lose the network never send
DELETE, so
without this they would leak).
After eviction, requests on that session ID get 404 Session not found; a client
recovers by re-running initialize.
Security: bind address, Host validation, and authentication
In http mode, each session authenticates independently via the
Authorization: Bearer <api-key> or X-API-Key header sent with the
client's initialize request — there is no single, process-wide API key.
The key is never validated by this package itself; it's forwarded to the
Timbrix API on every call, exactly as stdio mode already does, so the
Timbrix API's own key validation is the source of truth. An initialize
request with neither header is rejected with 401 before any session is
created.
- The server binds to
127.0.0.1 by default — reachable only from the
same machine. Host-header (DNS-rebinding) validation is applied on this
default, so a malicious web page cannot point a hostname it controls at
your loopback server and drive it through the victim's browser.
- Set
MCP_HTTP_HOST (e.g. MCP_HTTP_HOST=0.0.0.0) to expose it further —
this also requires MCP_ALLOWED_HOSTS, since Host-header validation
still applies on a non-loopback bind (the server refuses to start without
it, rather than skipping validation altogether).
- New-session creation is rate-limited per IP (30/minute by default) to
protect the process from unbounded session creation; requests on an
already-established session are never affected by this limit.
GET /health is exempt from Host validation, so infrastructure health
probes (which send their own Host header, e.g. Railway's
healthcheck.railway.app) don't need to be added to
MCP_ALLOWED_HOSTS.
- Once a session is established, its
Mcp-Session-Id header alone
authorizes further requests on it — the API key isn't re-checked per
request — so treat a session ID as sensitive as the credential that
created it for the rest of that session's life.
For local, single-user agents, stdio (the default) still needs no port
at all and is the simplest option.
Development
pnpm --filter @timbrix/mcp dev
pnpm --filter @timbrix/mcp test
pnpm --filter @timbrix/mcp build
License
MIT © Timbrix — see LICENSE.