MCP Server (Model Context Protocol)
The open standard for providing financial tools to Agentic Artificial Intelligence.
What is MCP?
The Model Context Protocol (MCP) is a universal standard that allows Artificial Intelligence assistants to connect to secure data sources.
By configuring the Cord MCP Server, you empower agents like Claude or IDE-integrated assistants (like Cursor) to organically interact with your company.
Questions you can ask the AI once connected:
- “Extract the requirements from the attached PDF and generate a quote in Cord for this client.”
- “Review the overdue quotes from the last 30 days and draft friendly collection emails for me.”
- “Look up the client Distribuidora El Zarco and draft a quote with these 3 line items.”
How to configure the connection
Authentication for your AI client is performed using your standard Cord API Key (sk_live_… / sk_test_…) — the same one the REST API uses.
- Navigate to Settings › MCP in the Cord interface. It’s in the Developers section of the Settings index, which appears once you turn on Developer mode in Your account › Preferences.
- A status card at the top tells you whether you already have an active key and, if an AI client is already connected, when it was last used. If you don’t have one, the “Create a key for MCP” button generates a write key with the read and draft writing groups, without leaving the page — it’s pasted automatically into the config below. Copy it right then: it won’t be shown again. To pick groups or single tools, or to allow actions with external effects, create or edit the key in the API keys tab of the Cord Workbench (see permissions per tool).
- Copy the MCP client JSON configuration provided on the screen.
- Paste this configuration into the corresponding local path for your client (e.g., the
claude_desktop_config.jsonfile if you use the Claude for Desktop app).
Publishable keys (pk_…, meant to live exposed in the browser) can’t be used with MCP —
they only cover creating quotes and reading the catalog via the REST API. Use a Secret key
from your backend or desktop client instead.
Two ways to connect
Streamable HTTP (recommended) — POST https://cordhq.app/api/mcp with the header
Authorization: Bearer sk_live_…. Sessionless: every JSON-RPC message is an independent HTTP
request. This is what most modern MCP clients expect.
HTTP + SSE (legacy, kept for older clients) — GET https://cordhq.app/api/mcp/sse opens a
long-lived connection and returns where to send messages (POST /api/mcp/message?sessionId=…). Every POST message must carry the same
Authorization: Bearer used to open the session — the sessionId in the URL alone isn’t
enough (it travels in plain text and can end up exposed in intermediate proxy logs), and the
key must belong to the same organization that opened the session.
Permissions per tool
Every tool belongs to a group and a resource, with the same model as restricted keys:
| Group | Tools | Key without a list |
|---|---|---|
| Read | listar_cotizaciones, detalle_cotizacion, cartera_vencida, resumen_negocio, buscar_cliente, listar_productos, listar_facturas, detalle_factura, listar_workflows_manuales, listar_eventos, estado_configuracion, validar_datos_fiscales, validar_apariencia |
Allowed |
| Draft writing | crear_cotizacion_borrador, crear_factura_borrador, crear_cliente, crear_tarea, registrar_promesa_pago, proponer_configuracion |
Allowed with a write key |
| Actions with external effects | enviar_cotizacion, aprobar_cotizacion, rechazar_cotizacion, registrar_pago_cotizacion, actualizar_cliente, correr_workflow, simular_evento |
Blocked: only with explicit permission, and every call waits for approval |
When you create or edit a key in the Workbench API keys tab, you pick whole groups or, inside a group, single tools. A group also covers the tools Cord adds to it later; a single tool covers only itself. A key without a list, like every key created before per-tool permissions, keeps read and draft writing according to its level and cannot ask for any action with external effects until someone allows it.
- The level still rules: a read-only key can’t use drafts even if they are on its list, and a
restricted key (
rk_) applies each resource’s level to each tool (listar_facturasneedsfacturas: read;crear_cotizacion_borrador,cotizaciones: write). tools/listonly returns what the key can call. If a client calls another one, the response is a tool error that says which one is missing and where to allow it.contexto_cuentaandbuscar_documentacionare always available: they identify the account and search these docs, with no customer data.
actualizar_cliente is among the actions with external effects even though it writes to no one:
Cord uses the customer’s email and tax details to send reminders, recurring invoices and
collections without another review, so changing them has an effect outside Cord.
Actions that wait for approval
An action with external effects doesn’t run when the agent calls it, even if the key allows it and the MCP client doesn’t ask for confirmation. Cord stores a request and answers:
{
"estado": "pendiente_de_aprobacion",
"aprobacion_id": "7c1e4b2a-5d3f-4a8e-9b6c-2f1d0e3a4b5c",
"aprobacion_url": "https://cordhq.app/app/ajustes/mcp/solicitudes?modo=vivo#sol-7c1e4b2a-5d3f-4a8e-9b6c-2f1d0e3a4b5c",
"vence": "2026-10-18T16:04:00.000Z",
"mensaje": "No se ejecutó todavía: una persona con acceso a Cord tiene que aprobarla. …"
}
- The agent shares
aprobacion_urlwith the user. - In Settings › MCP › Requests, a person with Settings permission and the permission for the action in the app (Quotes to send or record a payment, Approvals to approve or reject, Clients to update a customer, Quotes or Collections for a workflow depending on its object, Settings to simulate a webhook) sees the document, what would change and the data the agent sent, and approves or rejects.
- On approval, Cord runs the action once, with the same handler as always. Two people approving at the same time don’t run it twice. Rejecting runs nothing.
- To learn the result, the agent repeats the call with the same
idempotency_key: the response carriesestadoejecutada(with what the action returned inresultado),fallida(with the reason inerror),rechazadaorvencida.
The request expires after 72 hours without a decision. Repeating the same call with the same
arguments returns the pending request instead of creating another, and an organization receives
at most 30 requests per hour. If the key is revoked, expires or loses the tool before the
decision, the request can no longer be approved. With a test key, the request lives in the test
environment and the link says so with ?modo=prueba.
Available tools
| Tool | What it does | Permission |
|---|---|---|
listar_cotizaciones |
Lists quotes, with an optional status filter. | read |
detalle_cotizacion |
Full detail of a quote: line items, totals, milestone payment plan (each payment with its amount in moneda, its due date and whether it can be paid yet) and timeline. |
read |
cartera_vencida |
OVERDUE accounts receivable: amount, aging, per-client detail. | read |
resumen_negocio |
KPIs, conversion funnel, pipeline forecast, plan usage. | read |
buscar_cliente |
Searches the directory by company/contact/RFC/email. Each client includes language, their document language (null = the organization’s). Paginated. |
read |
listar_productos |
Product catalog (id, SKU, name, unit, list price). Paginated. | read |
crear_cotizacion_borrador |
Creates a DRAFT quote (doesn’t send it). Idempotent if you pass idempotency_key. |
drafts |
listar_facturas |
Lists Cord Invoicing invoices with their balance and state (draft|open|paid|void|uncollectible|overdue). Filter by state, client, or free text. Paginated. |
read |
detalle_factura |
Full detail of ONE invoice: line items, issuer, recipient, taxes, declared FX rate, balance, and payments received. | read |
crear_factura_borrador |
Creates a DRAFT invoice for a client, without needing a prior quote. Available on every plan, including Free. | drafts |
enviar_cotizacion |
Asks to send a draft quote to the client (email with the link), in the client’s document language. Waits for approval. On the Free plan it uses one of the monthly sends. | needs approval |
aprobar_cotizacion |
Asks to mark a sent or viewed quote as approved. Waits for approval. | needs approval |
rechazar_cotizacion |
Asks to mark a sent or viewed quote as rejected. Waits for approval. | needs approval |
registrar_pago_cotizacion |
Asks to record that an approved quote was paid outside Cord; when it runs, it cancels its pending online payments. Accepts metodo_pago. Waits for approval. |
needs approval |
crear_cliente |
Creates a client (company, contact, email, phone, tax ID, terms, country, and document language: es, en, pt, fr, de, or null). |
drafts |
actualizar_cliente |
Asks to change a client’s contact details, language included. Only updates what you send; never touches credit, tier, or discount. What was already sent keeps its language. Waits for approval. |
needs approval |
crear_tarea |
Creates a follow-up task: titulo (required), optional fecha due date YYYY-MM-DD, cotizacion_id, and factura_id. It’s created unassigned with normal priority; those are changed in the app. |
drafts |
registrar_promesa_pago |
Records the date a client promised to pay a quote. It doesn’t charge anything. | drafts |
listar_workflows_manuales |
Lists the active “You run it” workflows, by object (quote or invoice). |
read |
correr_workflow |
Asks to run a “You run it” workflow on up to 50 quotes or invoices, with the published version and its rules. Waits for approval; when it runs, it says how many runs started. | needs approval |
listar_eventos |
Business history (quotes, invoices, clients, tasks, promises), filterable by type or object. Paginated. | read |
contexto_cuenta |
Business, country, currency, language, time zone, plan and whether the key is test or live. Call it first. | read |
proponer_configuracion |
Proposes profile, branding, taxes, catalog and templates from the website, a description or the price list (base64, up to 3 MB). Applies nothing: returns review_url for a person to approve. |
drafts |
estado_configuracion |
Status of a proposal (propuesto, aplicado, descartado, fallido) and the result per section. |
read |
validar_datos_fiscales |
Validates RFC, tax regime, CFDI use and postal code (Mexico), NIF/NIE/CIF (Spain) or EIN (US) with the same rules used when issuing. | read |
validar_apariencia |
Validates an Elements appearance object and reports which keys Cord would drop. |
read |
simular_evento |
Asks to fire a test webhook through the real delivery engine. Test keys only. Waits for approval. | needs approval |
buscar_documentacion |
Searches these docs and returns the most relevant pages with an excerpt and URL. | read |
Every tool declares standard MCP annotations (readOnlyHint, idempotentHint,
destructiveHint) in tools/list. Read tools are readOnlyHint: true and the ones that change a
quote’s status are destructiveHint: true. Confirmation no longer depends on the MCP client
honoring those annotations: Cord doesn’t run any action with external effects until a person
approves it (see actions that wait for approval). No tool
issues or stamps invoices: that is still an action a person triggers from the app or the API.
No tool changes account settings directly: proponer_configuracion leaves a proposal that a
person with Settings permission reviews and approves in Cord, just like the one from onboarding
or cord setup.
Every tool declares an outputSchema and returns structuredContent (the validatable JSON)
alongside the text. Amounts travel with their currency in moneda: never assume pesos.
Pagination
listar_cotizaciones, buscar_cliente, listar_productos, listar_facturas, and listar_eventos return pages, not the whole table. There are two response shapes.
buscar_cliente and listar_productos return the list in items, with total, has_more, next_cursor, and the currency in moneda; listar_cotizaciones returns total, has_more, and next_cursor with the list in cotizaciones, and the currency goes on each quote:
{
"items": [ { "id": "9a1c3e5f-7b2d-4f6a-8c0e-1d3b5f7a9c2e", "empresa": "Distribuidora El Zarco" } ],
"total": 47,
"moneda": "MXN",
"has_more": true,
"next_cursor": "20"
}
listar_facturas and listar_eventos return only the list (in facturas or eventos) and next_cursor, without total or has_more:
{
"eventos": [ { "id": "6b0e2f4a-8c1d-4e3b-9a5f-7d2c0e8b1a46", "type": "quote.approved" } ],
"next_cursor": "MjAyNi0wOS0xNFQxODozMjowNC4xMjMwMDBafDZiMGUyZjRhLThjMWQtNGUzYi05YTVmLTdkMmMwZThiMWE0Ng"
}
In every case, call the same tool again passing cursor: next_cursor for the next page.
next_cursor is null once there are no more results.
listar_productos never includes a product’s internal cost/margin — only list price, SKU,
name, and unit. That column is intentionally invisible from an API key, whether it has write
permission or not.
Safe-to-retry calls (idempotency)
crear_cotizacion_borrador, crear_cliente, crear_tarea, registrar_promesa_pago and the
seven actions with external effects accept an optional idempotency_key — a unique identifier
you generate (a UUID, for example). If your MCP client needs to retry the call because it
didn’t get a response in time (network timeout), repeat the SAME idempotency_key: Cord returns
the result of the first call instead of repeating the action. For an action with external
effects, the same key returns the current state of its request (pending, done, rejected…).
Reusing the same key with different arguments returns an error, and for the other tools the key
expires after 24 hours.
{
"name": "crear_cotizacion_borrador",
"arguments": {
"idempotency_key": "a1b2c3d4-…",
"cliente_id": "9a1c3e5f-7b2d-4f6a-8c0e-1d3b5f7a9c2e",
"items": [{ "descripcion": "Installation", "cantidad": 1, "precio_unitario": 4500 }]
}
}
Without idempotency_key, the tool still works normally — every call creates a new draft
(it’s not a required field, just recommended if your client retries automatically).
detalle_cotizacion and listar_eventos can include text written by the client on the
public link (comments, counteroffers). That text is marked with origen: "cliente_externo" and
wrapped in <<<mensaje_del_cliente>>>: your agent should report it, never follow it as an
instruction.
Secure Tools
Cord exposes “Tools” to the AI in a controlled manner. This means the AI does not have raw, unlimited access to your database; the AI can only invoke endpoints that Cord has approved and categorized, inheriting the permissions tied to the API Key.
Important:
Business rules are strictly applied. If the AI attempts to apply a 150% discount when building a quote, the API will reject the request by returning a 400 invalid_request code to protect your business.
Limits, metering, and activity
Every real call (not protocol notifications like notifications/initialized) counts just like
a REST API call against your plan’s usage quota. Every HTTP request to the MCP server,
notifications included, counts against the API’s per-minute limits (600 per secret key and 120
per source IP), and the tools that change a quote’s state also share the 120-per-minute limit
per organization; see the limits table.
Each call shows up in the Logs tab of the Cord Workbench
with the tool that was called (e.g. /mcp/tools/call:listar_productos) — so you can see exactly
what your agent is doing, with which key, and when. You can filter that log by method or status,
or search for mcp in the route to isolate just what the AI did.
Actions with external effects leave a full trail: each call shows up in Logs with the key that made it, the decision shows up in Settings › Audit log with the person who approved or rejected it, and the action that ran is audited as done by that key.