Facturiadocs
MCP / AI

Connect an agent

mcp.facturia.cl — the same engine, a different front door. How to connect Claude, what happens during OAuth and why a sandbox grant is a hard limit.

The same engine as the REST API, a different front door: instead of a person typing curl, a third-party AI agent calls typed tools.

https://mcp.facturia.cl

One MCP endpoint (/mcp) speaking Streamable HTTP, and one set of OAuth endpoints on the same origin. There are no per-empresa or per-account subdomains: tenancy is a claim in the token, not a hostname.

The architectural fact

The MCP server is a REST client of /v1

It holds no business logic and none of the engine's credentials: no SII (Chile's tax authority) certificate, no signing key, no access to the engine's schema. Every domain question — what a document's totals are, whether folios (the pre-authorized numbers a document must carry) are available, what the F29 (the monthly VAT return) says — is a request to /v1, never a query.

Which means everything in these docs about statuses, errors and guarantees holds exactly the same from an agent.

Why an agent is not a developer

A developer reads the documentation before writing the first line; an agent decides what to call in the same turn it is asked. This server publishes its pages as MCP resources — returned whole, not as a link, so a client with no fetch capability reads them too — and even so every tool has to describe itself: the documentation explains the model, and the tool description is all the model has in hand at the moment it decides. Three design commitments for this layer follow from that:

The full suite, not a thin wrapper. Emission, reception and the tax layer are all here.

Direct execution, with an escape hatch for whoever wants one. The MCP client's own tool-approval step is the default human in the loop. Empresas that want more can turn on a two-step draft-and-confirm.

Being AI-first means resolving ambiguity, not refusing it. draft_document takes a receptor (the recipient) by name, because that is how a person talks to an agent. Line items are structured ([{description, quantity, unit_price}]): the model does the prose, because it understands prose better and more cheaply than any parser we could write, and draft_document returns the totals so the person sees them before anything is signed. There is no prose parser in the API and there will not be one: a parser hidden behind an endpoint turns a misread quantity into a tax document.

Add it to your client

One address, https://mcp.facturia.cl/mcp, and nothing to install: the transport is Streamable HTTP, so there is no package to download and no local process to run. There is no secret to paste into a config file either — the client discovers OAuth on its own (RFC 9728), identifies itself with its metadata document or registers itself (RFC 7591), and opens the consent screen in your browser, where you decide what the agent may do and over which empresas.

In Claude Desktop: Settings → Connectors → Add custom connector, with the URL https://mcp.facturia.cl/mcp. If you would rather edit the file by hand (claude_desktop_config.json), it is one line:

{
  "mcpServers": {
    "facturia": { "url": "https://mcp.facturia.cl/mcp" }
  }
}

Never created an account?

The consent screen lets you sign in right there and mints the grant's key for you — you do not have to bring one ready. And if you have no account yet, the link on that screen takes you to create one and brings you back to the same authorization when you are done: no reconnecting the agent.

No empresas yet either? Also fine: tick "Todas las empresas de esta cuenta, incluidas las que cree más adelante" together with empresas:write, and the agent creates the first one with create_empresa.

Typing https://mcp.facturia.cl into a browser gives you the same information as a page: what it is, the snippet, and the link to these docs.

How the client identifies itself. The MCP spec sets a priority order for how a client says who it is: a pre-registered client first, then a client ID metadata document (CIMD), and dynamic registration last, as the fallback.

  • Client ID metadata document (CIMD). The client can use an https URL as its client_id — for example https://claude.ai/.well-known/oauth-client.json — instead of registering before it connects. The server fetches it at connect time (GET, 3 s timeout, 32 KB max, no redirects, never to a private or loopback address) and validates the JSON: the client_id the document declares has to be exactly that URL, client_name and redirect_uris are required, and only public clients are accepted (token_endpoint_auth_method absent or "none", PKCE S256 as always). The result is cached honoring Cache-Control: max-age, clamped between 1 and 24 hours — an error is never cached. The redirect_uri of every authorization has to match one the document declares exactly, the same rule as a registered client — with one exception, RFC 8252 §7.3's: when the document declares http://localhost/… or http://127.0.0.1/…, the port is not compared, because a native client such as Claude Code opens a different one every session.
  • Dynamic client registration (RFC 7591). The fallback for a client that does not yet publish a CIMD — Cursor, VS Code, Windsurf, ChatGPT and similar still register this way. Any MCP client registers itself on first connection — no prior approval, no application allowlist. Registration returns a public client_id; there is no client_secret, because PKCE is the confidentiality mechanism for a public client that cannot keep a secret.
POST https://mcp.facturia.cl/register        (RFC 7591)
GET  https://mcp.facturia.cl/.well-known/oauth-authorization-server

That same GET declares whether the server accepts CIMD:

{ "client_id_metadata_document_supported": true }

What the person sees on the consent screen. With a CIMD client the screen shows its name and the origin of its metadata document ("Claude · claude.ai") — that origin is verifiable, because that is where the document lives. A dynamically registered client still gets the warning "This application registered itself and picked its own name — we do not verify it." In both cases the screen keeps showing the host the authorization code will be sent to ("Where this is going"), with an extra warning when that host is localhost or 127.0.0.1.

Authorization and consent. Standard authorization-code + PKCE flow. The consent screen is not a rubber stamp: that is where you decide which Facturia credential the grant wraps.

GET https://mcp.facturia.cl/authorize
  ?client_id=mcpc_01K2SC9X4M
  &redirect_uri=https://claude.ai/api/mcp/auth_callback
  &response_type=code
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &scope=documents:read documents:write tax:read
  &state=af0ifjsldkj

Token. POST /token exchanges the code (with the PKCE verifier) for a short-lived access token and a longer-lived refresh token, revocable on its own.

{
  "access_token": "mcpat_9lQpV3fA2mKcRt7wZxYb1Nde",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "mcprt_qT8vN2wLmR5xKpJ7hYdCbA3s",
  "scope": "documents:read documents:write tax:read"
}

What the token carries

The same authorization model as an API key, because it has to behave the same once issued:

  • an account,
  • a mode — the token can be sandbox-only, live or mixed, depending on what was granted;
  • a set of scopes, with the same vocabulary as the REST scopes: documents:read, documents:write, received:read, received:write, tax:read, tax:write, empresas:read, usage:read and, for going live, onboarding:write, mandato:write and empresas:write;
  • an empresa allowlist — explicit RUTs (Chile's taxpayer ids), or "*".

The two attestation scopes do not exist on this surface

/v1 also defines mandato:attest and onboarding:attest: accepting the mandato — the power of attorney that lets Facturia act for a company before the SII — with a request body, and authorizing the go-live with one. Those are the moment a person says "yes, I accept" and "yes, this month", and they are recorded as the evidence of that decision.

An agent cannot ask for them here, and that is not an oversight: the MCP tools mint the link and stop; the person decides on the page. If the agent could attest, the record of your approval would be the agent's word for it.

Naming the empresa in a tool call

A single token's list can span several empresas (an accountant's agent handling several clients, say), so every empresa-scoped tool takes an explicit empresa argument — the direct analogue of the Facturia-Empresa header.

SituationBehavior
Omitted where it is requiredThe tool returns empresa_required
Present but outside the listempresa_not_allowed — never a bare "not found"
A token with exactly one empresaYou may omit it; the server fills it in
Account-level tools (list_empresas, get_usage without an empresa)Reject the argument

The sandbox limit

Trap

A sandbox grant is a capability, not a policy

A sandbox-scoped token cannot reach a live empresa's data or issue anything with legal effect. It is not a preference that can be flipped at runtime: it is a structural limit.

That is why it is the right default for anything exploratory or public-facing. Dynamic client registration means the calling software is unverified by construction, and the token's scope is the primary defense against a compromised or over-trusting agent.

The consent screen defends the same thing from the other side: nothing comes pre-checked. You pick explicitly which empresas this agent may touch. Granting "*" is possible but takes an extra confirmation click — the asymmetry is deliberate, because a grant to an over-trusting third-party agent is a materially different risk from a first-party API key kept in your own backend.

Revocation

In the dashboard, Connected agents lists every MCP grant on the account: which client connected, with which scopes, over which empresas, in which mode, when, and when it last used it. Revoking one invalidates its access and refresh tokens immediately.

There is a second path that works just as well: every grant wraps one of your API keys, so revoking that key under Keys kills the grant in the same instant.

Rate limits

MCP calls hit the same backend services as the REST ones and share the same published buckets. There is no separate MCP quota, because there is no separate emission pipeline underneath.

BucketLimitWhich tools
General300 req/mindraft_document, all the list_* / get_*, query_rcv, get_periodo_summary, and the writes that neither emit nor file (update_*, set_*, characterize_purchases, send_document, request_folios, …)
Emission10 req/s, burst 30emit_document, emit_tipo46, emit_credit_note
F29 filing and RCV sync10 req/min per empresafile_f29, sync_rcv (one call per period; a ten-month range spends the whole bucket)
Artifacts60 req/minget_document_file (minting the signed URL, not the download afterwards, which is public and unmetered)

A throttled call returns the rate_limit_error type with a retry_after_seconds field an agent can act on directly.

On this page