Documentación

Servidor MCP

Bloques incluye un servidor MCP (Model Context Protocol): el estándar abierto con el que asistentes y agentes IA — Claude, Claude Code, y cualquier cliente MCP — usan herramientas externas. Conectándolo, tu agente puede emitir boletas y facturas, consultar documentos, gestionar el catálogo y revisar tu consumo, conversando: "emite una boleta de S/ 118 por consultoría a CLIENTES VARIOS".

Endpoint y protocolo

PropiedadValor
URLPOST https://<tu-host>/api/mcp
TransporteStreamable HTTP, stateless: cada POST es una petición JSON-RPC independiente; no hay sesiones ni SSE (GET responde 405).
Versiones de protocolo2026-07-28, por request y sin initialize: cada petición trae su versión y sus capacidades en _meta y las cabeceras MCP-Protocol-Version, Mcp-Method y, en tools/call, Mcp-Name; server/discover devuelve las versiones, las capacidades y las instrucciones. Y 2025-11-25, 2025-06-18, 2025-03-26 y 2024-11-05 con initialize: si pides otra, responde 2025-11-25. Una versión que no se sirve es 400.
AutenticaciónOAuth 2.1 (spec de autorización de MCP): tu cliente descubre el servidor, se registra solo (o se presenta con la URL de su documento de metadata), te abre el navegador para iniciar sesión y autorizar, y renueva su acceso solo; cada 90 días te vuelve a pedir Autorizar. No hay ningún secreto que copiar. Los tokens se guardan con hash. Sin autorización válida no se revela nada del servidor (401).
Origen (navegador)Un cliente que corre en el navegador manda Origin, y solo se aceptan el propio Bloques, claude.ai, claude.com y chatgpt.com por https; cualquier otro recibe 403 (Origen no permitido.) aunque el token sea válido. Los clientes de servidor (Claude Code, SDKs) no mandan Origin y no se ven afectados.
DisponibilidadSi Bloques no puede comprobar tu acceso por una falla momentánea, responde 503 con Retry-After (segundos) y un error JSON-RPC -32000: espera ese tiempo y reintenta la misma petición. No es un 401: tu conexión sigue valiendo y no hay que volver a autorizar. Solo un 401 significa que el acceso venció o se revocó.
CapacidadesSolo tools (sin resources ni prompts).
Versión del contrato2.0.0 (serverInfo.version), con semver y registro de cambios.

Cómo conectarlo

En todos los clientes es lo mismo: pega la URL. El cliente recibe un 401 con la dirección de la metadata, se registra solo, te abre el navegador y ahí inicias sesión en Bloques y apruebas lo que el agente podrá hacer. Nada de tokens en archivos de configuración.

Claude Code

claude mcp add --transport http bloques https://<tu-host>/api/mcp

La primera vez que lo uses se abrirá el navegador para autorizar. Verifica con claude mcp list.

claude.ai (conector personalizado)

En claude.ai ve a Settings → Connectors → Add custom connector y registra la URL https://<tu-host>/api/mcp. Claude hace el flujo OAuth solo: no necesitas configurar cabeceras ni pegar ningún secreto.

Otros clientes MCP (config JSON genérica)

mcp.json / configuración equivalente
{
  "mcpServers": {
    "bloques": {
      "type": "http",
      "url": "https://<tu-host>/api/mcp"
    }
  }
}

Cualquier cliente que soporte transporte http (Streamable HTTP) con OAuth funciona. Los documentos de descubrimiento están donde manda la spec: /.well-known/oauth-protected-resource y, para el emisor https://<tu-host>/api/auth, /.well-known/oauth-authorization-server/api/auth. Los endpoints viven en /api/auth/oauth2/* (register, authorize, token, revoke). Un cliente puede registrarse (alta dinámica, abierta) o usar como client_id la URL https de su documento de metadata (Client ID Metadata Document). Cada autorización pasa por la pantalla de consentimiento; prompt=none se rechaza.

Tokens sk_live_… (método anterior, en retiro)

Los tokens de API siguen aceptándose en /api/mcp durante la transición, con la cabecera Authorization: Bearer sk_live_…. Están en desuso y se retirarán del MCP en una versión mayor futura, que se anunciará antes en el changelog: reconecta tus agentes por OAuth. En la API REST no cambia nada — ahí los tokens siguen siendo el método.

Herramientas

Cada conexión solo ve las herramientas que sus permisos alcanzan — un agente de solo lectura ni siquiera sabe que bloques_create_document existe — y, cuando la herramienta pertenece a un módulo (como bloques_create_expense, del módulo Finanzas), solo si tu empresa tiene ese módulo activo. La lista que ve tu agente es, por eso, la intersección de las dos cosas.

Todas se llaman con el prefijo bloques_ (bloques_create_document, bloques_list_products…), para que un agente con varios servidores conectados no las confunda con las de otro. Los nombres sin prefijo de la versión 1.x ya no existen: llamarlos devuelve unknown_tool con el nombre nuevo en el mensaje.

Cada herramienta declara además qué efecto tiene, para que tu cliente decida cuándo pedirte confirmación: un title y las cuatro annotations de MCP (readOnlyHint, destructiveHint, idempotentHint, openWorldHint), siempre explícitas. Las de lectura (bloques_list_*, bloques_get_*, bloques_search_customers) son de solo lectura. bloques_create_document es destructiva y de mundo abierto: un comprobante emitido no se deshace, su número se consume aunque SUNAT lo rechace, y la llamada sale a SUNAT; es la única que sale de Bloques. bloques_update_quote, bloques_delete_quote, bloques_update_product, bloques_move_crm_client y bloques_archive_crm_interaction también son destructivas (reemplazan, mueven o borran); las demás altas solo agregan.

Y declara qué devuelve: Bloques revisa cada respuesta contra esa declaración antes de entregarla, y un campo que no está declarado nunca sale. Si alguna vez una respuesta dejara de calzar, tu agente no recibe un objeto a medias sino un error. Cuando la herramienta escribe (emitir, cobrar, crear), el error aclara que la acción sí se realizó, pide no repetirla y dice con qué herramienta revisarla: reintentar una emisión emitiría otro comprobante.

«Hoy» es la fecha de Lima que devuelve Bloques, no la del calendario del modelo ni la del dispositivo, que puede estar en otra zona horaria u otro día. bloques_list_documents, bloques_list_quotes, bloques_list_receivables, bloques_get_crm_pipeline y bloques_get_usage responden con today: la fecha en curso en America/Lima, YYYY-MM-DD. Las instrucciones del servidor le piden al agente resolver «hoy», «ayer» y «este mes» a partir de ella, y la mora de bloques_list_receivables se cuenta con ese mismo día. Los filtros de fecha (from/to) son días de Lima, inclusivos.

Cuando algo falla, la herramienta responde un error (isError) en español que dice qué hacer ahora: con qué herramienta conseguir el id correcto, a quién pedirle un permiso que falta o dónde se activa un módulo. Si el error tiene código, el texto empieza por él (module_not_enabled: …, insufficient_scope: …), y ese código no cambia aunque el texto mejore: guíate por él. Un id que no existe y uno de otra empresa o de una sucursal fuera de tu acceso reciben el mismo texto. Si la herramienta se cae a mitad de una escritura, el error avisa que la acción pudo haberse realizado y dice cómo comprobarlo antes de repetirla.

bloques_create_documentdocuments:write
Emite un comprobante electrónico a SUNAT: lo crea con su número y lo encola; la herramienta espera hasta 25 s el veredicto y devuelve el documento igual. Si vuelve con status: "processing", SUNAT todavía no contestó: se consulta con bloques_get_document en unos segundos, nunca se vuelve a emitir (crearía un segundo comprobante). factura exige cliente con RUC; boleta es para consumidores (DNI, u omite el cliente para CLIENTES VARIOS). Los precios son finales con IGV incluido. Consume el límite mensual del plan. Es una emisión fiscal real: el agente debe confirmar con el usuario antes de llamarla.
ArgumentoRequeridoDescripción
typeSí"factura" o "boleta".
seriesNoSerie como F001/B001 (por defecto la activa).
cash_register_idNoUUID de la caja desde la que se emite; la venta queda en su sucursal. Tiene que ser de una sucursal de tu acceso.
branch_idVer notaUUID de la sucursal de una venta sin caja (ver bloques_list_branches). Si tu acceso son varias sucursales y no mandas ni caja ni sucursal, falla con branch_required; si es una sola, la venta queda en esa; con acceso a todas, como siempre. Una sucursal fuera de tu acceso es branch_not_found.
issuer_rucVer notaRUC con el que se emite, si el negocio emite con varios (ver bloques_list_issuers). Sin él, la venta sale con el RUC que le toca; uno distinto exige el permiso de elegir el RUC (insufficient_permissions): aprobado al conectar y permitido por tu rol. Una conexión autorizada antes de que existiera ese permiso no lo trae; para elegir RUC, vuelve a conectar. Si el RUC sale «Según la caja» (con dos o más RUC activos), manda cash_register_id o falla con register_required.
issue_dateNoYYYY-MM-DD, por defecto hoy (Lima). Máx. 7 días atrás.
currencyNoPEN (por defecto) o USD.
customerFactura: síObjeto con id (cliente existente) o doc_type ("6" RUC, "1" DNI, "4" CE, "7" pasaporte, "0" ninguno), doc_number, name, email, address.
itemsSí1–100 ítems: product_id o code (catálogo), o description + unit_price (línea libre); más quantity, unit_code, affectation (10/20/30).
paymentNo{ "type": "contado" | "credito", "installments": [{ "amount", "due_date" }] } — las cuotas deben sumar el total.
notesNoTexto libre impreso en el PDF.
send_emailNoEnviar PDF + XML al cliente (por defecto, configuración de la empresa).
idempotency_keyRecomendadoClave de reintento seguro: repetirla devuelve el documento original en vez de emitir dos veces.
bloques_list_branchesdocuments:read
Las sucursales activas de tu acceso, cada una con sus cajas activas, y branch_access (all si trabajas en todas, some si tu acceso está restringido). Sirve para elegir branch_id o cash_register_id en bloques_create_document. Lo mismo que GET /api/v1/branches.

Sin argumentos.

bloques_list_issuersdocuments:read
Los RUC del negocio (el principal primero) con su estado, issuer_mode (de dónde sale el RUC por defecto de cada venta) y can_choose (si puedes pasar otro issuer_ruc a bloques_create_document). Lo mismo que GET /api/v1/issuers.

Sin argumentos.

bloques_get_documentdocuments:read
Recupera un documento emitido con sus ítems, estado SUNAT, totales y rutas de archivos. Acepta el UUID o el número completo tipo F001-42 (con varios RUC, si ese número existe en más de uno, pide el UUID).
ArgumentoRequeridoDescripción
id_or_numberSíUUID del documento o número completo (serie-correlativo).
bloques_list_documentsdocuments:read
Lista documentos emitidos (los más recientes primero) con filtros opcionales. Devuelve {data, page, per_page, total, today}.
ArgumentoRequeridoDescripción
fromNoFecha de emisión mínima, inclusiva (YYYY-MM-DD, día de Lima). Otra forma o un día inexistente es un error.
toNoFecha de emisión máxima, inclusiva (YYYY-MM-DD, día de Lima). Otra forma o un día inexistente es un error.
typeNofactura | boleta.
statusNoaccepted | rejected | error | processing.
qNoBúsqueda por número, nombre o documento del cliente.
issuer_rucNoSolo los emitidos con ese RUC del negocio (varios RUC).
pageNoPor defecto 1.
per_pageNoPor defecto 25, máx. 100.
bloques_get_document_filesdocuments:read
Devuelve las URLs de descarga (PDF, XML firmado, XML sin firmar, CDR, y un ZIP con el XML firmado y el CDR juntos) de un documento, y expires_at. Son URLs firmadas: se abren sin cabecera Authorization y vencen 15 minutos después de la llamada (luego responden 410 link_expired: vuelve a llamar a la tool). La autorización OAuth del MCP no abre la API REST.
ArgumentoRequeridoDescripción
idSíUUID del documento.
bloques_list_productsproducts:read
Lista el catálogo (código, nombre, precio final con IGV, unidad, afectación) con los ids que hacen falta para editarlo: el del producto y el de cada presentación y cada extra. Un combo trae sus components ([] en cualquier otro producto). Devuelve { data, total }.
ArgumentoRequeridoDescripción
qNoBúsqueda por nombre o código — la hace la base sobre TODO el catálogo, no sobre la página pedida.
include_inactiveNoPor defecto false. En true también devuelve los retirados: así se encuentra uno para revivirlo.
pageNoPor defecto 1 (100 por página).
bloques_create_productproducts:write
Crea un producto de catálogo con toda su forma de precio en una sola llamada: presentaciones, escalones y extras. unit_price es el precio FINAL (IGV incluido cuando la afectación es 10 gravado). La respuesta devuelve cada presentación y cada extra con su id — guardalos para editar después.
ArgumentoRequeridoDescripción
codeSíCódigo único por empresa.
nameSíNombre del producto.
descriptionNoDescripción opcional.
unit_codeNoPor defecto NIU.
unit_priceSíPrecio final, mayor a 0.
currencyNoPEN (por defecto) | USD.
affectationNo"10" (por defecto) | "20" | "30".
isc_rateNoISC al valor por defecto, como fracción (0.10 = 10%). Solo productos gravados.
icbperNoAfecto a ICBPER (bolsa plástica) por defecto.
barcodeNoCódigo de barras (EAN o interno). Único por empresa.
costNoÚltimo costo de compra, FINAL (IGV incluido). Informativo.
track_stockNoPor defecto true. Lo lee el módulo inventario; con el módulo apagado es inerte.
min_stockNoUmbral de alerta de stock bajo, sumado entre almacenes.
is_publicNoMostrarlo en el catálogo del perfil público. Por defecto false.
activeNoPor defecto true.
price_tiersNoPrecios por cantidad: [{ min_quantity, unit_price }], precios absolutos y finales (no descuentos). Solo se usan cuando el ítem del documento omite unit_price.
pool_tiersNoCuenta global de escalones: con true, la cantidad de este producto se suma con la de los demás productos marcados del documento para alcanzar sus escalones (7 shorts + 5 polos = 12 prendas), cada uno con su propia tabla de precios. Por defecto false.
uses_modifiers · uses_price_tiers · uses_extras · uses_agreementsNoOpciones de precio de la ficha en la app (qué perillas muestra). Solo de visualización: ningún precio las lee, y mandar los datos ya enciende la suya. Por defecto false.
modifiersNoPresentaciones: [{ name, code?, unit_price, price_tiers? }]. Cada una con su precio FINAL y absoluto (nunca un delta); heredan impuestos, unidad y moneda del padre, pero no los escalones. El orden del array es el de la pantalla.
extrasNoAgregados que se suman al precio resuelto: [{ name, default_amount, prices? }]. prices va alineado con modifiers de esta misma llamada — 0 es gratis y null es «no se ofrece en esa presentación».
kind · componentsNoCombo: kind: "combo" y components: [{ product_id, quantity }] (hasta 30; los ids de bloques_list_products, cantidad por UN combo). Se vende a su propio unit_price y descuenta el stock de sus productos; sin presentaciones ni extras, y todos sus productos con su misma afectación y sin ISC/ICBPER. Sin components, kind es product o service.
bloques_update_productproducts:write
Edita el producto: sus campos, sus escalones, sus presentaciones y sus extras. Es un patch —lo que no mandás queda intacto—, con una excepción que hay que leer: en modifiers y extras se manda la lista COMPLETA que querés conservar. Con id actualiza, sin id crea, y la que falta en la lista queda retirada: agregar una presentación a un producto que tiene cinco es mandar las seis. No hay tool de borrado — active: false retira el producto y los documentos conservan su copia.
ArgumentoRequeridoDescripción
idSí*UUID del producto (de bloques_list_products o bloques_create_product).
product_codeSí**Uno de los dos. Es el código actual, solo para encontrarlo; para renombrarlo se manda code.
modifiersNoLa lista completa a conservar, en orden. Omitir el campo = no tocar ninguna; [] = retirarlas todas.
extrasNoMisma regla que modifiers. Acá prices es un objeto por id de presentación (la forma que devuelve bloques_list_products), no un array alineado; mandarlo reemplaza todos los montos de ese extra.
price_tiersNoOmitir = no tocar los escalones; [] = volver a un precio único.
componentsNoLos productos del combo, lista completa: omitir = no tocarlos, [] = deja de ser combo (para volverlo producto o servicio, manda [] con el kind nuevo).
…NoTodo lo demás de bloques_create_product (code, name, unit_price, barcode, cost, pool_tiers, active…) con la misma forma. Las fotos no se editan por MCP: se suben y se quitan desde la aplicación.
category_idsNoCategorías por id (los category_ids que devuelve bloques_list_products): las verticales del negocio («Textil», «Cabinas»), ninguna, una o varias. Es la lista completa: omitirla no las toca, [] las quita y una lista las reemplaza. Solo clasifican — no tocan precio, impuestos ni stock — y no hay forma por nombre. Tienen que existir ya: un id que no es de esta empresa falla la llamada, nunca la crea. Las categorías se crean en la app.
bloques_search_customerscustomers:read
Busca en todo el directorio de clientes por nombre, apodo o número de documento, sin distinguir mayúsculas (las tildes sí cuentan). Devuelve los primeros 50 por nombre y el total de coincidencias.
ArgumentoRequeridoDescripción
qNoTexto de búsqueda; sin él lista los primeros clientes por nombre.
bloques_create_expenseexpenses:write · módulo Finanzas
Registra un gasto (dinero que sale): una compra a proveedor, un servicio, la planilla, el alquiler o un impuesto — con factura, boleta, recibo, ticket o sin ningún comprobante. Es contabilidad propia: no se envía nada a SUNAT, no consume correlativo y no cuenta contra el límite del plan. El total es lo que dice el papel y es el único monto obligatorio: Bloques sugiere el desglose con la tasa de IGV efectiva de tu empresa, salvo que envíes amounts — y entonces los seis componentes deben sumar el total exacto.
ArgumentoRequeridoDescripción
issue_dateSíYYYY-MM-DD, la fecha del comprobante del proveedor.
totalSíTotal del documento, IGV incluido. Acepta string ("118.00", exacto) o número.
doc_typeNo01 factura (por defecto) · 02 recibo por honorarios · 03 boleta · 04 liquidación de compra · 07/08 nota de crédito/débito recibida · 10 arrendamiento · 12 ticket · 13 banco/seguro · 14 servicios públicos · 00 sin comprobante. Fija el valor por defecto de credito_fiscal.
supplier_id / supplierNoEl UUID de un proveedor del directorio, o uno tipeado (doc_type, doc_number, name). Sin ninguno: PROVEEDOR VARIOS. El tipeado entra al directorio y queda enlazado al gasto —salvo doc_type 0, sin documento—; si su ficha ya existe, no se pisa.
descriptionNoNombre corto del gasto — en qué se gastó, en pocas palabras («Compra de gaseosas para la tienda»). Máx. 200 caracteres. Se muestra en la lista de gastos y entra a la búsqueda. Es distinto de notes, la nota interna larga, y no tiene ningún efecto fiscal.
series / numberNoSerie y correlativo tal como vienen impresos (texto libre).
currency / exchange_rateNoPEN (por defecto) o USD; en USD el tipo de cambio es obligatorio.
amountsNoDesglose copiado del documento: base_gravada, igv, base_exonerada, base_inafecta, isc, otros_tributos. Debe sumar el total.
detractionNo{ code, rate, amount, constancy, date } — la tasa es fracción (0.12 = 12 %) y se guarda tal como se envía (es un snapshot de la constancia).
category_id / branch_id / cash_register_idNoUUIDs de la categoría, la sucursal y la caja a las que se imputa.
due_date / payment_termsNoVencimiento y contado | credito (cuentas por pagar).
itemsNoLíneas opcionales que reparten el total (por categoría o producto), no la fuente de los impuestos: si las envías, deben sumar el total. Una línea con product_id puede nombrar una de sus presentaciones en modifier_id: con el módulo Inventario, la compra entra a la existencia de esa presentación.
deductible / is_fixed_asset / igv_destination / credito_fiscal / notesNoMarcas contables del gasto.
bloques_list_crm_stagescrm:read · módulo CRM
Las etapas del embudo en el orden del tablero. Sus ids son los que espera bloques_move_crm_client. Las etapas se configuran dentro de Bloques (Configuración → Etapas del CRM): por MCP no se crean, ni se renombran, ni se reordenan, ni se borran.

Sin argumentos.

bloques_get_crm_pipelinecrm:read · módulo CRM
El tablero entero: cada etapa con los clientes que tiene, y al final una columna con stage: null — los que están en tu cartera pero no en el embudo. Esa columna es la ausencia de etapa, no una etapa: el cliente que nace de una emisión aparece ahí solo, y sacar a alguien del embudo lo devuelve ahí. Cada columna trae su total real y hasta 50 clientes. Las tarjetas llevan solo nombre y apodo: leer el embudo no es leer el directorio (para el teléfono o el documento está bloques_search_customers, que pide customers:read).

Sin argumentos.

bloques_move_crm_clientcrm:write · módulo CRM
Mueve un cliente a una etapa del embudo, o lo saca de él con stage_id: null. Sacarlo borra su lugar en el tablero: no borra al cliente ni su bitácora. Moverlo a la etapa en la que ya está no hace nada (y no le reinicia la antigüedad).
ArgumentoRequeridoDescripción
customer_idSíUUID de un cliente de tu empresa.
stage_idSíUUID de una etapa tuya (ver bloques_list_crm_stages), o null para sacarlo del embudo.
bloques_list_crm_interactionscrm:read · módulo CRM
La bitácora de un cliente, lo más reciente primero: qué pasó, el día en que pasó y quién lo anotó. Hasta 50 entradas más el total real. Por defecto las vivas; con archived: true, las archivadas — dos conjuntos disjuntos, y archived dice siempre cuántas hay guardadas.
ArgumentoRequeridoDescripción
customer_idSíUUID de un cliente de tu empresa.
archivedNoCon true devuelve las archivadas en vez de las vivas.
bloques_log_crm_interactioncrm:write · módulo CRM · solo OAuth
Anota qué pasó con un cliente: por qué canal y qué día (la hora no, que nadie la recuerda). El cliente no necesita estar en el embudo — se llama a la gente antes de saber dónde ponerla. La anotación la firma la persona conectada, así que esta herramienta pide una conexión OAuth: un token sk_live_… es de la empresa, no de alguien, y una anotación sin autor no se distinguiría de una cuyo autor fue eliminado. No se edita ni se borra: corregir es anotar de nuevo y archivar la vieja.
ArgumentoRequeridoDescripción
customer_idSíUUID de un cliente de tu empresa.
kindSíCanal, no estado: call · whatsapp · meeting · email · visit · note.
occurred_onSíYYYY-MM-DD, el día en que pasó. Hacia atrás sí; el futuro no.
noteSíQué pasó, en tus palabras. Máx. 1000 caracteres.
bloques_archive_crm_interactioncrm:write · módulo CRM
Archiva una anotación, o la devuelve a la bitácora (archived: false). La bitácora nunca borra: la archivada sale de la lista y se sigue leyendo con bloques_list_crm_interactions({ archived: true }). Archivar dos veces no mueve la fecha de archivo.
ArgumentoRequeridoDescripción
interaction_idSíUUID de una anotación de tu bitácora.
archivedSítrue archiva; false la devuelve.
bloques_get_usagesin scope (cualquier conexión)
Documentos consumidos en el mes en curso contra el límite del plan vigente de la empresa (calendario America/Lima), con today, la fecha en curso en Lima. Es un número de toda la empresa: solo aparece si la persona detrás de la conexión tiene acceso a todas las sucursales.

Sin argumentos.

Modelo de seguridad

  • El agente nunca puede más que tú. La conexión se hace con tu cuenta: lo que se ejecuta es lo que aprobaste y tu rol permite, comprobado en cada llamada. Si mañana te quitan un permiso, tu agente lo pierde al instante; si te sacan del equipo o recuperas tu contraseña, la conexión se borra y reconectar vuelve a pedir tu autorización. No hay que revocar nada.
  • Autorizas en pantalla, y ves qué autorizas. Antes de conectar, Bloques enumera lo que el agente podrá hacer — y lo que no, porque tu rol no lo permite. Sin ese paso no se emite ningún acceso. La pantalla nombra el sitio que recibirá la autorización (no el nombre que la app dice tener), marca App no verificada a todo cliente que no sea claude.ai, claude.com o chatgpt.com, y avisa cuando la autorización va a un programa en tu computadora.
  • Acceso corto y renovación de un solo uso. El acceso del agente dura una hora y se renueva solo; cada renovación invalida la credencial anterior, así que una copia robada deja de servir apenas el agente legítimo se renueva.
  • Una conexión dura 90 días desde la última vez que la autorizaste, aunque el agente se use a diario. Al vencer, tu cliente recibe un 401 y abre el navegador solo: aprietas Autorizar otra vez (e inicias sesión antes solo si tu sesión en Bloques terminó) y vuelve a contar desde cero. Así una conexión olvidada no queda abierta para siempre.
  • Los permisos filtran las herramientas visibles. tools/list solo devuelve lo que la conexión puede ejecutar; la superficie no autorizada permanece oculta (no solo bloqueada).
  • Tus sucursales también. Si solo trabajas en algunas sucursales, tu agente solo ve y emite en esas: bloques_list_documents, bloques_get_document y bloques_get_document_files no alcanzan los documentos de otras (responden «no encontrado», como si no existieran). Se relee en cada llamada: cambiarte las sucursales cambia a tu agente al instante.
  • Los módulos filtran igual que los scopes. Una herramienta de un módulo que tu empresa no tiene activo no se lista, y llamarla igual falla — el servidor lo vuelve a verificar al ejecutar.
  • Gastos y dinero: escritura sin lectura. bloques_create_expense registra, pero ninguna herramienta lee gastos, saldos ni movimientos de dinero. Es deliberado: un agente puede anotar lo que se gastó sin poder consultar cuánta plata tienes ni en qué se va. Esa información se ve únicamente iniciando sesión en la app.
  • Aislamiento por empresa. Toda herramienta opera únicamente sobre los datos de la empresa a la que perteneces. No existe forma de cruzar tenants.
  • El texto de terceros llega como dato. Nombres, descripciones, notas y direcciones los escribe tu personal o tus clientes (por ejemplo, quien reserva desde tu página pública). Antes de llegar al agente, Bloques les quita los caracteres invisibles y de control y los deja en una línea o en párrafos con tope. Las instrucciones del servidor le dicen al agente que ese texto es dato y nunca una orden, y que emitir o registrar algo solo lo pides tú; la descripción de cada herramienta que lo devuelve repite que es dato.
  • Registro de cada llamada. Toda llamada a una herramienta, también la denegada y la que falla, deja una fila con quién la hizo (el usuario o el token), qué herramienta, cómo terminó y cuánto tardó; nunca los argumentos ni el resultado. Se guarda 90 días. Las emisiones registran además de dónde vinieron.
  • Límite por conexión. Cada conexión puede llamar herramientas hasta 120 veces por minuto; listar las herramientas o iniciar la conexión no cuenta. Pasado el límite la herramienta no se ejecuta y el agente recibe un error de herramienta (isError) con el texto «rate_limited: Hiciste muchas llamadas en poco tiempo. Espera un minuto y vuelve a intentarlo.», así que puede esperar y seguir sin reconectarse.
  • Sin estado, sin divulgación anónima. El servidor no responde nada (ni siquiera su nombre) sin autorización válida, y no mantiene sesiones que secuestrar.

Si un agente se comporta mal, desconéctalo

En Configuración → Integraciones ves los agentes que conectaste y cortas el acceso de cualquiera con un clic: sus tokens dejan de valer en el acto y la próxima vez tendrá que volver a pedirte autorización. Y si quieres que un agente pueda menos, la palanca es tu rol —o un rol dedicado— porque la conexión nunca excede lo que su dueño puede hacer.

No mezcles Bloques con conectores que escriben en otros sistemas

No conectes Bloques en la misma sesión o proyecto de tu asistente de IA que conectores que pueden escribir en otros sistemas de producción (correo, bases de datos, GitHub). Los nombres y las notas que lee el agente los escriben tu personal y tus clientes; Bloques los marca como dato, pero un texto malicioso no debería tener a mano una herramienta que mande correos o cambie código. Y deja encendida la confirmación de tu cliente para bloques_create_document: es una emisión real ante SUNAT.

Versión y cambios

El contrato del servidor —los nombres de las herramientas, sus argumentos, lo que devuelven, sus errores y las credenciales que acepta— tiene versión propia, con semver: hoy es 2.0.0, la que tu cliente recibe en serverInfo.version al conectarse. No sigue la versión de la aplicación, que cambia por motivos que no tocan a tu integración.

  • Mayor: se quita o se renombra una herramienta o un campo, un argumento se vuelve más estricto (lo que antes pasaba ahora es invalid_input), cambia el significado de un campo o de un error, o deja de aceptarse una credencial. Solo llega en hitos deliberados.
  • Menor: una herramienta nueva, o un campo nuevo y opcional de entrada o de salida.
  • Parche: arreglos que no cambian la forma del contrato.

Cada versión, con lo que cambia para tu integración, está en el registro de cambios del MCP (/mcp-changelog.md, en inglés como /llms-full.txt, en formato Keep a Changelog). Los cambios de la API REST están en la sección Cambios de su referencia.

¿Vas a programar la integración tú mismo (o tu agente)? Sigue con la guía para agentes IA o apunta directamente a /llms-full.txt.