MCP: el CRM desde tu IA

IA

FusionCRM expone un servidor MCP: conectas Claude, ChatGPT, Cursor o cualquier cliente compatible y la IA consulta y opera el CRM con 219 herramientas, las mismas capacidades de la API REST sin escribir una línea de código.

Qué es MCP

El Model Context Protocol es el estándar abierto con el que los asistentes de IA se conectan a herramientas externas. En vez de copiar y pegar datos en un chat, el asistente llama directamente a FusionCRM: busca un contacto, resume las conversaciones de la semana, crea una cotización o mueve un negocio de etapa, y te muestra el resultado.

Un solo servidor

https://api.fusioncol.com/api/mcp — transporte Streamable HTTP, sin instalar nada en tu equipo.

Con tus permisos

La IA solo puede hacer lo que el conector o el token que uses le permita.

Mismas reglas

Lo que la IA crea pasa por las mismas validaciones, límites del plan y automatizaciones que la app.

Qué puedes pedirle

«¿Qué conversaciones tengo pendientes en la columna Pedidos Medellín?»
«Busca el contacto con el celular 300 000 0000 y dime qué negocios abiertos tiene.»
«Crea una cotización para Ana Ejemplo con 500 manillas Tyvek a $850 cada una.»
«Resume los negocios cerrados este mes por vendedor.»
«Mueve la conversación de Carlos a Oportunidad y agrégale una nota.»
«Envía la plantilla de seguimiento de WhatsApp a los contactos de la lista Feria 2026.»
Nota
El asistente decide qué herramientas llamar a partir de tu pedido. Cuando una acción escribe datos (crear, enviar, borrar), la mayoría de clientes te piden confirmación antes de ejecutarla.

Conectar tu cliente

Hay dos formas de autenticarse; ambas terminan en un token con la organización ya dentro, así que no hay nada más que configurar:

  • • Conector MCP (OAuth): en Configuración → Desarrollador → Conectores MCP creas un conector y obtienes un Client ID y un Secreto. El cliente hace el flujo OAuth solo.
  • • Token de API: el mismo token de la API REST, enviado como Authorization: Bearer. Para clientes que aceptan headers fijos.

Qué credencial usar

Si usasUsaPor qué
Claude web, escritorio o móvilConector MCP: Client ID + SecretoEl cliente hace el flujo OAuth solo y renueva el acceso sin que hagas nada.
Claude Code, Cursor, scripts o n8nToken de API como Authorization: BearerCrea el token con expiración «Sin expiración» y solo los permisos que necesites.
Otros clientes MCP con OAuthConector MCP: Client ID + SecretoSi el cliente admite servidores remotos con OAuth; si solo acepta headers fijos, usa un token de API.
Advertencia
No uses el Client ID y el Secreto para pedir un token a mano y pegarlo como header: ese token dura 1 hora y no se renueva. Si tu cliente solo acepta headers fijos, crea un token de API.

Los tokens de API funcionan en /api/* (la API REST) y en /api/mcp. No sirven en /api/v1/*, que es la API interna de la app web: ahí responden 401 con el código API_TOKEN_NOT_ACCEPTED.

Claude (web y escritorio)

  1. 1. En FusionCRM: Configuración → Desarrollador → Conectores MCP → Crear conector MCP. Guarda el Client ID y el Secreto (el secreto se muestra una sola vez).
  2. 2. En Claude: Configuración → Conectores → Agregar conector personalizado.
  3. 3. Pega la URL del servidor y, en Configuración avanzada, el Client ID y el Secreto.
  4. 4. Autoriza el acceso cuando Claude lo pida. Listo: en un chat nuevo ya verás las herramientas de FusionCRM.
https://api.fusioncol.com/api/mcp

Claude Code

claude mcp add --transport http fusioncrm https://api.fusioncol.com/api/mcp \
  --header "Authorization: Bearer <token de API>"

Cursor y otros clientes

Cualquier cliente que soporte servidores MCP remotos (Streamable HTTP) funciona. En Cursor, el archivo mcp.json queda así:

{
  "mcpServers": {
    "fusioncrm": {
      "url": "https://api.fusioncol.com/api/mcp",
      "headers": { "Authorization": "Bearer <token de API>" }
    }
  }
}

Cursor también acepta el flujo OAuth con un conector (redirige a cursor://anysphere.cursor-mcp/oauth/callback); los metadatos del servidor están en /.well-known/oauth-authorization-server.

Verifica a qué cuenta apunta

Es el primer paso recomendado después de conectar, y es obligatorio si tienes varias organizaciones conectadas: equivocarse de conexión es enviar un WhatsApp desde el número de otra empresa. Pregúntale a la IA «¿a qué organización estoy conectado?»; usará la herramienta get_current_organization. Con un token de API puedes comprobarlo tú mismo:

curl https://api.fusioncol.com/api/me \
  -H "Authorization: Bearer <token de API>"

Respuesta (recortada)

{
  "success": true,
  "data": {
    "organization": { "id": "665f…", "name": "Mi Empresa SAS" },
    "user": { "email": "ana@example.com", "role": "admin" },
    "token": {
      "name": "claude-code",
      "permissions": ["contacts:read", "whatsapp:send"],
      "expiresAt": null
    },
    "plan": { "name": "pro", "modules": { "api": true } }
  }
}
Consejo
Hazlo antes de cualquier envío (WhatsApp, correo, campañas) cuando trabajes con más de una cuenta. expiresAt: null significa que el token no vence.

Permisos y seguridad

  • • Un conector OAuth actúa con los permisos del usuario que lo autorizó, dentro de su organización.
  • • Con un token de API, la IA solo ve las herramientas cuyos permisos tenga el token (por ejemplo, sin whatsapp:send no puede enviar mensajes).
  • • Todo queda acotado a tu organización: no hay forma de leer datos de otra.
  • • Los conectores y tokens se revocan desde la misma pantalla donde se crearon; el cliente pierde acceso al instante.
  • • MCP hace parte del módulo API del plan, igual que la API REST.
  • • El header X-Organization-Id (o el query organizationId) es opcional: sin él se usa la organización del token. Si lo envías y no coincide, la respuesta es 403 ORGANIZATION_ID_MISMATCH (en MCP llega como error JSON-RPC con ese código en data.code).
Advertencia
La IA puede ejecutar acciones reales (enviar un WhatsApp, borrar un contacto). Dale a cada conector solo lo que necesita y revisa en la app lo que hizo, sobre todo al empezar.

Problemas frecuentes

Cada rechazo trae un code que dice exactamente qué pasó:

HTTPcodeCausaSolución
401API_TOKEN_MISSINGLa petición llegó sin token.Revisa el header Authorization. Con un conector OAuth la respuesta trae WWW-Authenticate y el cliente vuelve a pedir el login.
401API_TOKEN_INVALID / API_TOKEN_EXPIRED / API_TOKEN_NOT_FOUNDEl token está mal copiado, venció o fue revocado.Genera un token nuevo (o vuelve a autorizar el conector) y reemplázalo en el cliente.
401API_TOKEN_NOT_ACCEPTEDUsaste un token de API contra /api/v1/*, que es la API interna de la app web.Llama a /api/* (la API REST) o a /api/mcp.
403INSUFFICIENT_API_PERMISSIONSAl token le falta el permiso de esa operación.La respuesta indica requiredPermission: crea o edita el token con ese permiso.
403ORGANIZATION_ID_MISMATCHEnviaste X-Organization-Id (u organizationId) de una organización distinta a la del token.Quita el header o usa el token de esa organización.
403PLAN_MODULE_REQUIREDEl plan de la organización no incluye el módulo API.Actívalo desde Configuración → Plan.

Herramientas disponibles

219 herramientas, generadas desde el servidor en producción. El catálogo completo con los parámetros de cada una está en mcp-tools.json.

Nota
Desde septiembre de 2026 la IA también configura el CRM: pipelines y etapas, campos personalizados, plantillas, reglas de puntuación, actividades, calendario y landing pages. Dos límites a propósito: las automatizaciones y las campañas se crean siempre como borrador (publicarlas o enviarlas se hace en la app, por una persona), y las integraciones solo se leen, sin claves ni contraseñas.

Contactos (11)

add_contacts_to_listAgregar contactos a una lista ESTÁTICA existente por sus IDs. Solo funciona en listas de tipo static.
create_contactCreate (or update if exists) a contact. At least one of email, mobile, or whatsapp_phone required. Buenas prácticas: llena SIEMPRE que se pueda source (de dónde salió el lead), companyType (Personal/Empresa/Proveedor/Cliente/Otro u opciones de la org) y city — un contacto sin segmentación no sirve para filtrar ni para campañas. Si no tienes esos datos, pregúntale al usuario. CIUDAD (catálogo DANE, modo estricto): manda cityCode (5 dígitos, ej. 05001 = Medellín) o un city inequívoco; si city es ambiguo (ej. 'Rionegro': Antioquia 05615 / Santander 68615), está mal escrito o no existe, la API responde 422 CITY_UNRESOLVED con `candidates` [{code, shortName, stateName}] y NO crea el contacto: pregunta el departamento y reintenta con cityCode. Con candidates: [] (mal escrita o desconocida) busca el código con search_municipalities. Un cityCode inexistente responde 400 INVALID_CITY_CODE. Nunca elijas un candidato por el usuario. Sin city/cityCode no se toca nada; con country distinto de Colombia, city es texto libre.
delete_contactEliminar contacto por ID (permiso contacts:delete).
filter_contactsFiltro avanzado cruzado: combina condiciones sobre propiedades de contactos Y condiciones sobre sus deals para retornar una lista paginada. Condiciones para contactFilters: equals | not_equals | contains | not_contains | is_empty | is_not_empty Condiciones para dealFilters: equals | not_equals | contains | not_contains | greater_than | less_than | between | last_deal_before | last_deal_after Campos especiales en dealFilters: - field "hasDeals" (sin condition): filtra si el contacto tiene (true) o no (false) deals que cumplan los demás filtros - field "lastDeal" con condition "last_deal_before"/"last_deal_after": filtra según la fecha del deal más reciente del contacto - field "amount" con greater_than/less_than/between: filtra por monto del deal (between requiere también "valueTo") - cualquier campo real del deal: title, closingDate, createdAt, etc. Ejemplos de uso: - Hoteles sin pedidos en 60 días: contactFilters=[{field:"companyType",condition:"equals",value:"Hoteles y Piscinas"}], dealFilters=[{field:"hasDeals",value:true},{field:"lastDeal",condition:"last_deal_before",value:"2026-02-09"}] - Clientes de Bogotá con deals > $500k: contactFilters=[{field:"lifeCycle",condition:"equals",value:"Cliente"},{field:"city",condition:"equals",value:"Bogota"}], dealFilters=[{field:"amount",condition:"greater_than",value:"500000"}] - Contactos sin categoría: contactFilters=[{field:"companyType",condition:"is_empty"}] - Ciudad por código DANE (Épica Y): contactFilters=[{field:"cityCode",condition:"equals",value:"05001"}] (05001 = Medellín); todo un departamento: {field:"stateCode",condition:"equals",value:"05"} (05 = Antioquia). No filtres cityCode por prefijo: contains escapa el texto (un "^05" se busca literal y nunca casa) y aquí no existe starts_with. Códigos con search_municipalities / list_states. Contactos con ciudad sin confirmar: [{field:"cityCode",condition:"is_empty"},{field:"city",condition:"is_not_empty"}]
get_contactGet a single contact by ID. Devuelve el contacto aplanado (firstName, lastName, email, mobile, company, properties custom…) más `tags` (string[]) y `attribution`: la atribución first-touch (se fija UNA vez, cuando el contacto entra por un formulario/widget/landing) con firstTouchAt (ISO), channel, utmSource/utmMedium/utmCampaign/utmTerm/utmContent, referrer, landingPage (URL), gclid, fbclid, formId, landingPageId y landingPageName; es null si el contacto no tiene atribución (p. ej. creado a mano o por WhatsApp).
list_contactsListar contactos con búsqueda, rango de fechas y filtros por propiedades. Filtros de propiedades (se combinan con AND, case-insensitive por defecto): - Campos canónicos: firstName, lastName, email, mobile, phone, company, position, city, cityCode, stateCode, country, source, idNumber, companyType. - Ciudades (catálogo DANE, Épica Y): cityCode = código DANE del municipio (5 dígitos, ej. 05001 = Medellín; usar matchMode exact); stateCode = código del departamento (2 dígitos, ej. 05 = Antioquia). Para todo un departamento filtra por stateCode (matchMode exact; en filter_contacts: stateCode equals '05'). No filtres cityCode por prefijo: contains escapa el texto ('^05' nunca casa) y filter_contacts no tiene starts_with. city sigue siendo texto (puede estar sin confirmar: cityCode vacío). ¿No sabes el código? search_municipalities. - extraFilters: objeto libre { key: value } para filtrar por CUALQUIER propiedad custom no listada (ej. { industry: 'retail' }). - matchMode: 'contains' (default, regex case-insensitive con escape) o 'exact' (igualdad exacta). Filtros de presencia (ideales para auditar calidad de datos y organizar contactos): - missing: lista separada por comas de claves que DEBEN estar ausentes o vacías. Ej: 'companyType,city' trae contactos sin companyType o con companyType=''. - exists: lista separada por comas de claves que DEBEN existir con valor no vacío. Ej: 'email,mobile' trae contactos con ambos campos. Rango de fechas (createdAt) vía createdFrom/createdTo en ISO (YYYY-MM-DD). Ordenable por createdAt/updatedAt asc|desc. Paginación con page/limit.
list_statesListar los 33 departamentos de Colombia (GET /api/catalogs/states; Bogotá D.C. cuenta como uno, código 11) con su código DANE de 2 dígitos (el stateCode de contactos y empresas y el state de search_municipalities), nombre oficial, nombre corto e ISO 3166-2. Solo lectura; cualquier token, sin permiso especial.
remove_contacts_from_listQuitar contactos de una lista ESTÁTICA existente por sus IDs (DELETE /lists/:id/contacts). No borra los contactos, solo su pertenencia a la lista. Los IDs que no estaban se cuentan en skipped.
search_contact_by_phoneSearch contact by phone number for this organization (coincidencia exacta por dígitos y luego parcial, en mobile o phone). Responde `contact` con properties aplanadas, `tags` y `attribution` first-touch (mismo formato que get_contact; null si no tiene).
search_municipalitiesBuscar municipios de Colombia en el catálogo DANE/DIVIPOLA (GET /api/catalogs/municipalities) para obtener su cityCode (código DANE de 5 dígitos). Solo lectura; cualquier token, sin permiso especial. Úsala cuando create_contact, update_contact, create_company, update_company, import_contacts o submit_form respondan CITY_UNRESOLVED (sobre todo con candidates: [] = ciudad mal escrita o desconocida), y antes de quote_shipment/create_shipment si la ciudad del destino no es inequívoca. Luego reintenta con cityCode. q busca por prefijo o inclusión sobre el nombre oficial y el corto, sin tildes ni mayúsculas, capitales primero (20 resultados por defecto). state acota a un departamento (código DANE de 2 dígitos, ver list_states); un state desconocido → 400 STATE_UNKNOWN. Sin q devuelve el departamento entero (o el catálogo completo de 1.122 municipios: evítalo, manda q). Cada fila trae code (el cityCode), name (oficial), shortName (lo que el CRM guarda en city), stateCode, stateName, stateIso2 y dane8. Hay 66 nombres repetidos entre departamentos (Rionegro, La Unión, Armenia…): mira stateName y, si hay más de un resultado plausible, pregúntale al usuario cuál es. Nunca elijas por él.
update_contactActualizar un contacto por ID (PUT /contacts/:id): merge de properties por key, los campos que no se envían se conservan. companyId null o '' desvincula la empresa. CIUDAD (catálogo DANE, modo estricto): cityCode (5 dígitos) o city inequívoco; ambiguo/desconocido → 422 CITY_UNRESOLVED con candidates (reintenta con cityCode; si candidates viene vacío, búscalo con search_municipalities); cityCode inexistente → 400 INVALID_CITY_CODE. stateCode siempre se deriva del cityCode (el del body se ignora). Sin city/cityCode no se toca la ubicación.

Listas de contactos (7)

create_dynamic_contact_listCrear una lista DINÁMICA de contactos. A diferencia de la estática, los contactos que pertenecen a la lista se recalculan en tiempo real cada vez que se consultan, aplicando los filtros almacenados contra la base de datos actual. Se REQUIERE al menos un filtro. Estructura de un filtro: { key: 'city', operator: 'contains', value: 'Bogotá' } Operadores: contains, equals, 'starts with', 'ends with', 'is empty', 'is not empty', greater_than, less_than. Claves de ejemplo: firstName, lastName, email, mobile, company, city, cityCode, stateCode, country, source, companyType, leadScore. Ciudades (catálogo DANE): { key: 'cityCode', operator: 'equals', value: '05001' } (05001 = Medellín); todo un departamento: { key: 'stateCode', operator: 'equals', value: '05' } o { key: 'cityCode', operator: 'starts with', value: '05' }. city es texto y puede estar sin confirmar (cityCode vacío). Filtro especial: { key: 'hasDeals', operator: 'equals', value: true } para contactos con/sin negocios.
create_static_contact_listCrear una lista ESTÁTICA de contactos. Existen tres modos: 1. Lista vacía: pasar solo name (y opcionalmente description). Se puede poblar después con add_contacts_to_list. 2. Por IDs directos: pasar contactIds (array de IDs de contacto). Máximo 2000 contactos. 3. Por filtros de propiedades: pasar filters para buscar y capturar los contactos que coincidan en el momento de creación. Los filtros son snapshots — el grupo no cambia automáticamente después (para eso usar create_dynamic_contact_list). Estructura de un filtro: { key: 'city', operator: 'contains', value: 'Bogotá' } Operadores soportados: contains, equals, 'starts with', 'ends with', 'is empty', 'is not empty', greater_than, less_than. Claves de ejemplo: firstName, lastName, email, mobile, company, city, country, source, companyType, leadScore. Límite: 5000 coincidencias al filtrar (usar filtros más específicos si se supera).
delete_contact_listEliminar una lista de contactos por ID. No elimina los contactos, solo la lista.
get_contact_listObtener una lista de contactos por su ID.
get_contact_list_contactsObtener los contactos de una lista (estática o dinámica) con paginación.
list_contact_listsObtener todas las listas de contactos (estáticas y dinámicas) de la organización.
update_contact_listActualizar una lista existente (PUT /lists/:id). - name / description: siempre editables. - filters: solo aplican a listas DINÁMICAS. Reemplazan los filtros existentes. Formato idéntico a create_dynamic_contact_list. - contactIds: solo aplican a listas ESTÁTICAS. Reemplazan la lista completa de miembros (útil para eliminar contactos). Si quieres AGREGAR miembros sin reemplazar, usar add_contacts_to_list. No está permitido cambiar el tipo de lista.

Empresas (7)

create_companyCrear una empresa (o actualizarla si ya existe: upsert por idNumber/NIT primero y por name después). Body plano → properties: name, industry, website, email, phone, address, city, cityCode, country, idType, idNumber, size, notes y cualquier propiedad custom. Requiere name o idNumber. Permiso companies:write. CIUDAD (catálogo DANE, modo estricto): cityCode (5 dígitos, ej. 05001 = Medellín) o city inequívoco; ambiguo/desconocido → 422 CITY_UNRESOLVED con candidates (reintenta con cityCode; si candidates viene vacío, búscalo con search_municipalities); cityCode inexistente → 400 INVALID_CITY_CODE. state (departamento en texto) sirve de pista para homónimos; stateCode siempre se deriva del cityCode. Nunca elijas un candidato por el usuario.
delete_companyEliminar empresa por ID (permiso companies:delete). Los contactos y deals vinculados NO se borran: solo se desvinculan de la empresa.
get_companyObtener una empresa por ID, incluyendo sus contactos vinculados, sus deals y un resumen (totalRevenue, totalDeals, totalContacts, lastDeal). Permiso companies:read.
link_contact_to_companyVincular un contacto a una empresa (setea companyId en el contacto y sincroniza su propiedad de texto 'company' con el nombre de la empresa). Enviar companyId null o "" DESVINCULA al contacto de su empresa actual. Para vinculación masiva usar fusion_request con POST /api/companies/:id/contacts { contactIds: [...] }. Permisos contacts:write (+ companies:read para validar).
list_companiesListar empresas (companies) con búsqueda, rango de fechas y filtros por propiedades (permiso companies:read). Filtros de propiedades (se combinan con AND, case-insensitive por defecto): - Campos canónicos: name, industry, city, cityCode (código DANE de 5 dígitos, usar matchMode exact), stateCode (departamento, 2 dígitos), country, idNumber (NIT). - extraFilters: objeto libre { key: value } para filtrar por CUALQUIER propiedad custom no listada (ej. { size: '50-100' }). - matchMode: 'contains' (default, regex case-insensitive con escape) o 'exact' (igualdad exacta). search: texto libre sobre name, industry, email, phone, website, city, idNumber. Rango de fechas (createdAt) vía createdFrom/createdTo en ISO (YYYY-MM-DD). Ordenable por createdAt/updatedAt asc|desc. Paginación con page/limit.
list_company_contactsListar los contactos vinculados a una empresa (GET /api/companies/:id/contacts). Permiso companies:read.
update_companyActualizar una empresa por ID (merge de properties por key: los campos enviados se actualizan o agregan, el resto se conserva). Si cambia el name se sincroniza en los contactos vinculados. Permiso companies:write. CIUDAD (catálogo DANE, modo estricto): cityCode (5 dígitos) o city inequívoco; ambiguo/desconocido → 422 CITY_UNRESOLVED con candidates (reintenta con cityCode; si candidates viene vacío, búscalo con search_municipalities); cityCode inexistente → 400 INVALID_CITY_CODE. state (texto) es pista para homónimos; stateCode siempre se deriva.

Negocios (7)

create_dealCreate a deal (cuerpo igual que POST /api/deals). Acepta companyId opcional para asociar una empresa (debe pertenecer a la organización); si no se envía y el contacto principal tiene empresa vinculada, el deal la HEREDA automáticamente.
deals_summarySales summary for a date range filtered by closingDate (fecha de cierre real: al ganar o perder, closingDate pasa a ser el día en que se cerró; wonAt/lostAt llevan la hora). Total deals, won deals, total sold, avg ticket, top 5 by amount. Use this for monthly/period sales reports.
delete_dealEliminar negocio por ID (permiso deals:delete).
get_dealGet a deal by ID.
list_dealsList deals with optional date range, search, and pagination. Dates YYYY-MM-DD. Use closingFrom/closingTo to filter by closing date (for sales reports: closingDate is the REAL close date once a deal is won/lost, the estimate while open). wonFrom/wonTo and lostFrom/lostTo filter by the exact won/lost timestamp (wonAt/lostAt). Use createdFrom/createdTo to filter by creation date. Each deal exposes wonAt, lostAt and expectedCloseDate (the original estimate).
list_deals_configDevuelve todos los pipelines de deals con sus etapas embebidas en una sola llamada. Úsalo para conocer los stageId disponibles antes de crear o mover deals. Evita llamadas separadas.
update_dealUpdate a deal by ID. Campos editables: title, amount, pipeline, status, closingDate, customFields, contact y companyId. Para asociar un contacto a un deal existente enviar `contact`: - { "id": "<contactId>" } para usar un contacto ya existente de la organización - { "mobile": "573001234567", "name": "...", "company": "..." } para buscarlo por teléfono y crearlo si no existe - `null` para desasociar el contacto actual. Para asociar una empresa enviar `companyId` (debe pertenecer a la organización); `null` o "" desasocia la empresa actual.

Conversaciones y WhatsApp entrante (15)

add_conversation_messageAñadir mensaje a una conversación (POST .../messages).
change_conversation_pipelineCambia una conversación a otro pipeline, opcionalmente a una etapa inicial específica. Si no se pasa initialStageId, se usa el primer stage (por order) del pipeline destino. Escribe un log de cambio y dispara el webhook del stage destino si está habilitado.
conversations_summarySummary of conversations in a date range: counts and by source (sample up to 1000 per page).
create_conversationCrear conversación. Para descubrir los stageId disponibles usá list_pipeline_stages.
delete_conversationEliminar conversación por ID (permiso conversations:delete).
find_conversation_by_phoneFind conversation by contact phone. Includes messages.
get_conversationGet a conversation by ID with messages, lastMessage populated, and contact data. Returns the most recent `messagesLimit` messages. To read older history, pass `messagesBefore` = `messagesPagination.nextBefore` from the previous response (or the timestamp/_id of the oldest message you already have) and repeat while `messagesPagination.hasMore` is true.
get_conversation_stage_historyDevuelve el historial de cambios de etapa de una conversación (logs CONVERSATION_STAGE_CHANGE) ordenado por timestamp descendente. Cada item incluye fromStage, toStage, pipeline, user y triggeredBy (user/automation/system).
list_conversation_pipelinesLista los pipelines de conversación de la organización (id, nombre, isDefault). Úsalo antes de list_pipeline_stages si querés un pipeline específico que no sea el default.
list_conversationsList conversations with filters, messages included. Dates dd/mm/yyyy; timezone = UTC offset (e.g. -5 Colombia). dateField controls which date column is filtered (createdAt, updatedAt, lastMessageTimestamp).
list_conversations_configDevuelve todos los pipelines de conversación con sus stages embebidos en una sola llamada. Úsalo para conocer los stageId disponibles antes de crear o mover conversaciones. Evita tener que llamar list_conversation_pipelines y list_pipeline_stages por separado.
list_pipeline_stagesLista las etapas (stages) de un pipeline de conversación, ordenadas por order. Devuelve _id, name, order, color, autoAssign, webhookEnabled. Si no pasas pipelineId se usa el pipeline default de la organización. Necesario para conocer los stageId antes de mover o crear conversaciones.
move_conversation_stageMueve una conversación a otra etapa dentro del mismo pipeline. Escribe un log de cambio y dispara el webhook configurado en la etapa destino si está habilitado. El stageId debe pertenecer al pipeline actual de la conversación (usar list_pipeline_stages para obtenerlo).
search_conversationsBuscar conversaciones por título, nombre/apellido del contacto o teléfono (query q, substring sin distinguir mayúsculas). NO busca dentro del contenido de los mensajes — para localizar una conversación por lo que se dijo en ella, usar list_conversations y revisar mensajes, o buscar por el contacto.
update_conversationActualizar conversación por ID. Para mover entre etapas usá move_conversation_stage (no pases currentStage aquí).

Envío de WhatsApp (4)

list_whatsapp_campaignsListar las campañas masivas de WhatsApp de la organización (las de /mass-whatsapp), más recientes primero, con un resumen por contacto único según el ÚLTIMO estado que reportó Meta: accepted (Meta aceptó el envío — NO significa entregado), delivered, read, failed y lastSentAt. Para el detalle (quién respondió, motivo de cada fallo, excluidos) usa get_campaign_stats con el id.
list_whatsapp_templatesListar las plantillas de WhatsApp (Meta/Facebook Marketing) aprobadas para la organización. Devuelve nombre, idioma, categoría, estado y componentes (BODY/HEADER/FOOTER con su texto) para que la IA pueda elegir la plantilla adecuada antes de enviarla con send_whatsapp_template. Requiere permiso whatsapp:send en el token.
send_whatsapp_messageEnviar un mensaje de WhatsApp de sesión (texto o media) a un número. Solo llega si el cliente escribió en las últimas 24 h (ventana de Meta); fuera de la ventana usa send_whatsapp_template. organizationId se rellena automáticamente.
send_whatsapp_templateEnviar una plantilla de WhatsApp (Meta/Facebook Marketing) a un número. FLUJO EN 2 PASOS: (1) llama SIN confirm (o confirm=false) → NO envía nada, devuelve un PREVIEW del estado (si el contacto existe y qué campos de segmentación le faltan; si la conversación existe, su etapa y si un agente IA la atiende; las etapas disponibles del pipeline). Muestra ese plan al usuario y pregúntale: a qué etapa debe ir la conversación (stageId), si el agente IA debe atenderla (assignTo) y qué datos del contacto completar (bloque contact). (2) re-llama con confirm=true y los parámetros elegidos → envía de verdad. Usa list_whatsapp_templates primero para obtener templateName, language y components. 'to' sin '+'. 'components' en formato oficial de Meta. Requiere permiso whatsapp:send. Nota: la conversación nueva NO se asigna al agente IA por defecto (el primer entrante suele ser un autorespondedor del cliente y el bot conversaría con otra máquina).

Correo (1)

send_emailEnviar un correo por SMTP del usuario (mismo envío que la app web). Solo texto/HTML, sin adjuntos. Requiere que el usuario tenga SMTP configurado. Registra una actividad 'Correo' en los contactos cuyo email coincida con los destinatarios. Si un destinatario está desuscrito del email marketing, el resultado incluye un campo 'warning' (el envío 1:1 no se bloquea) — tenlo en cuenta antes de enviar contenido comercial.

Cotizaciones (6)

create_quotationCrear cotización (POST /api/quotations). El número lo asigna el sistema. Si el contacto no existe por mobile, se crea.
delete_quotationEliminar cotización por ID (permiso quotations:delete).
download_quotation_pdfDescargar PDF de cotización por número (quotationNumber). Devuelve base64; puede ser pesado.
get_quotationObtener cotización por ID (Mongo).
list_quotationsListar cotizaciones con filtros y paginación.
update_quotationActualizar cotización por ID (PUT /quotations/:id). El número de cotización no se puede cambiar.

Facturas (6)

apply_invoice_withholdingsRegistrar las retenciones que el cliente practicó al pagar una factura de venta (PUT /api/invoices/:id/withholdings o, con prefix+number, PUT /api/invoices/withholdings). Requiere invoices:write. No emite nada ante la DIAN. Es lo mismo que el modal 'Aplicar retenciones' de la app: queda pegado a la factura, netea la cartera (saldo = total − retenciones − abonos) y sale en el Excel del contador y en el informe mensual. Cada ítem lleva key (concepto) y amount O pct. Con pct el servidor calcula sobre la base sin IVA (tax_exclusive_amount); para reteIva calcula sobre el IVA de la factura. Si no mandas ni amount ni pct usa el porcentaje sugerido del concepto. El monto que manda es el que el cliente retuvo de verdad (el comprobante): si difiere del cálculo, manda amount. Conceptos y % sugerido: compras25 (2.5), compras35 (3.5), serviciosGeneral4 (4), serviciosGeneral6 (6), honorarios10 (10), honorarios11 (11), arrendamientoBienesMuebles (4), arrendamientoBienesRaices (3.5), serviciosAseoVigilancia (2), serviciosHotelesRestaurantes (3.5), transporteCarga (1), reteIva (15, sobre el IVA), reteIca (sin % sugerido: manda amount). REEMPLAZA el set completo de la factura (idempotente): para editar, manda todos los ítems otra vez. Con dryRun: true devuelve el cálculo sin guardar; úsalo para mostrarle a la persona el total y el neto antes de aplicar. Solo facturas de venta (direction outgoing, tipo factura): notas crédito/débito y facturas de proveedor responden 422 NOT_A_SALES_INVOICE.
create_invoiceCrear factura electrónica (POST /api/invoices, formato DIAN). Requiere customer con identification_number, invoice_lines no vacío y legal_monetary_totals.payable_amount > 0.
get_invoiceObtener factura por ID.
get_invoice_withholdingsRetenciones que el cliente le practicó a una factura de venta emitida (GET /api/invoices/:id/withholdings). Requiere invoices:read. Devuelve withholdings (null si no tiene): base, iva, items[{key, kind, label, pct, amount}], byKind{rteFuente, rteIva, rteIca}, total, netPayable, appliedAt, source; y netPayable (= total de la factura si no hay retenciones). Úsala antes de apply_invoice_withholdings para saber si la factura ya las tiene. El id se obtiene con list_invoices (search por prefijo/número).
list_invoicesListar facturas (direction outgoing/incoming, etc.).
remove_invoice_withholdingsQuitar las retenciones registradas en una factura de venta (DELETE /api/invoices/:id/withholdings). Requiere invoices:write. Idempotente: si no tenía, responde igual con removed: false. La cartera vuelve a cobrar el total completo.

Productos (10)

create_productCrear producto (POST /api/products).
create_product_variantCrea una variante de un producto de la organización. El sku debe ser único en TODA la organización: uno repetido responde 409. No marca el producto como 'con variantes' (hasVariants); si hace falta, usa update_product. Requiere products:write.
delete_productEliminar un producto del catálogo por su ID (DELETE /products/:id, permiso products:delete). BORRA el producto Y TODAS sus variantes (tallas, colores, SKUs de ProductVariant). Es IRREVERSIBLE: no hay papelera ni forma de recuperarlo. Si el producto aparece en una cotización abierta (borrador o enviada) responde 409 PRODUCT_IN_OPEN_QUOTATIONS y NO borra nada: cierra o edita esas cotizaciones primero. No toca cotizaciones ni facturas ya emitidas (guardan su propia copia del ítem). Confirma con el usuario el nombre del producto (get_product) antes de llamarla. Para solo cambiar precio o datos usa update_product.
delete_product_variantELIMINA una variante de producto. Es IRREVERSIBLE y libera su sku. Si solo quieres dejar de ofrecerla usa update_product_variant con isActive=false. Confirma con el usuario antes de llamarlo. Requiere products:delete.
get_productObtener producto por ID.
get_product_variantDevuelve una variante de producto por id. Un id de otra organización o inexistente responde 404.
list_product_variantsLista las variantes de producto de la organización (SKU, precio, stock, atributos como talla o color, activa o no). Filtra por productId para ver las de un producto (404 si el producto no es de la organización). Para el catálogo de productos usa list_products.
list_productsListar productos con filtros.
update_productActualizar producto por ID (PUT /products/:id). Solo se tocan los campos enviados.
update_product_variantEdita una variante de producto: sku (único en la organización, 409 si se repite), price, stock, imageUrl, isActive (false la oculta sin borrarla) o attributeValues (REEMPLAZA la lista completa). Parcial: solo cambia lo que envíes; null en stock o imageUrl los limpia. No cambia de producto padre. Requiere products:write.

Formularios web (5)

get_formObtener un formulario por ID, incluyendo el link público, el snippet de embed y la URL del webhook (panel Compartir).
get_form_responsesRespuestas (envíos) de un formulario, más recientes primero. Cada una trae el contacto creado/actualizado, el negocio si se creó, las respuestas y el origen (UTMs, página). Los sospechosos (honeypot) se excluyen salvo includeSuspicious.
list_formsListar los formularios de captación de la organización (nombre, slug público, campos, si crea negocio, total de envíos).
submit_formEnviar un formulario en nombre de un contacto (server-to-server). Pasa por el mismo camino que la página pública: busca el contacto por email/celular y lo crea o actualiza sin duplicar, registra el origen, crea el negocio si el form lo tiene configurado y avisa al dueño. `values` = { claveDeCampo: valor }; opcionales utm_source/utm_medium/utm_campaign/page_url. Solo cuentan las claves que son campos del formulario (get_form). CIUDAD (catálogo DANE, modo estricto): si el envío trae ciudad y no resuelve sin ambigüedad, responde 400 con code CITY_UNRESOLVED (ambigua, mal escrita o desconocida) o INVALID_CITY_CODE (cityCode inexistente), más input y candidates [{code, shortName, stateName, stateCode}], y NO guarda nada. Reintenta con cityCode (si el formulario tiene ese campo) o con la ciudad + el departamento (campo state/departamento/depto/dpto) para homónimos como Rionegro; con candidates vacío, búscala con search_municipalities. Nunca elijas un candidato por el usuario.
update_formEditar la configuración de un formulario de captación (PUT /forms/:id): campos, tema, qué pasa después de enviar, negocio automático, avisos, consentimiento y dominios permitidos. Solo cambian las claves enviadas; nunca toca slug, publicKey ni estadísticas. Los campos (fields[].key) son claves de propiedades de contacto de la organización. active: false pausa el formulario (los envíos responden 410).

Proyectos (7)

create_projectCrear proyecto (POST /api/projects).
create_project_taskCrear tarea dentro de un proyecto (POST /projects/:id/tasks).
delete_projectEliminar proyecto por ID (permiso projects:delete).
get_projectObtener proyecto por ID.
list_project_tasksListar tareas de un proyecto (GET /projects/:id/tasks).
list_projectsListar proyectos.
update_projectActualizar proyecto por ID (PUT /projects/:id). Solo se tocan los campos enviados.

Tareas (5)

create_taskCrear tarea (POST /api/tasks). Incluir projectId en el cuerpo si aplica.
delete_taskEliminar tarea por ID (permiso tasks:delete).
get_taskObtener tarea por ID.
list_tasksListar tareas (opcional projectId en query para filtrar).
update_taskActualizar tarea por ID (PUT /tasks/:id). Solo se tocan los campos enviados.

Respuestas rápidas (5)

create_fragmentCrear un fragment. El 'atajo' debe ser único globalmente (índice único en BD).
delete_fragmentEliminar un fragment por ID (permiso fragments:delete).
get_fragmentObtener un fragment por ID. La respuesta incluye su "format" ("text" o "html").
list_fragmentsListar fragments (snippets de texto reutilizables) de la organización. Soporta paginación y búsqueda por nombre, texto o atajo. Cada fragment incluye su "format" ("text" o "html").
update_fragmentActualizar un fragment por ID. Campos editables: name, text, atajo, format, asunto.

Biblioteca (4)

get_library_fileObtener un archivo de la Biblioteca por ID, con su mediaURL, tags y metadatos.
list_library_filesListar archivos de la Biblioteca (library) de la organización: imágenes, PDFs, videos, etc. ya subidos. Devuelve id, name, fileType, mediaURL (URL pública del archivo), tags y fechas. IMPORTANTE: esta tool NO sube archivos. El MCP solo puede consultar y editar metadatos de archivos que ya existen; para subir uno nuevo hay que usar la app o la web de FusionCRM.
list_library_tagsListar los tags únicos en uso en la Biblioteca de la organización. Útil para saber qué tags existen antes de reetiquetar un archivo con update_library_file.
update_library_fileEditar los METADATOS de un archivo YA subido a la Biblioteca. Campos editables: name y tags. NO sube archivos, NO reemplaza el contenido del archivo y NO cambia su mediaURL: únicamente renombra y reetiqueta algo que ya está en la Biblioteca. Ojo: 'tags' REEMPLAZA la lista completa de tags (no agrega). Para añadir uno, lee los tags actuales con get_library_file y envía la lista completa ya con el nuevo.

Base de conocimiento (4)

delete_knowledge_base_documentElimina un documento de la base de conocimiento (RAG) por ID, incluyendo sus chunks/embeddings. IRREVERSIBLE. Usa list_knowledge_base_documents para obtener el ID. Requiere permiso rag:delete.
list_knowledge_base_documentsLista los documentos de la base de conocimiento (RAG) de la organización: título, estado de indexación (processing/ready/failed), tags y cantidad de chunks. Útil para saber qué información hay disponible antes de buscar con search_knowledge_base.
search_knowledge_baseBusca en la base de conocimiento PROPIA de la organización (RAG multi-tenant): documentos que la organización subió a FusionCRM (catálogos, información de productos, políticas, precios propios, manuales internos, etc.). Devuelve los fragmentos más relevantes con score de similitud y documento de origen. Si no hay resultados, la organización quizá no ha subido documentos aún (la subida se hace desde la app en /api/v1/rag/documents).
upload_knowledge_base_textSube un documento de TEXTO PLANO a la base de conocimiento (RAG) de la organización. La indexación (chunking + embeddings) corre en segundo plano: el documento queda en status 'processing' y pasa a 'ready' en segundos (verificable con list_knowledge_base_documents). Solo texto plano; archivos binarios (PDF, DOCX) se suben desde la app. Requiere permiso rag:write. Para REEMPLAZAR un documento existente (p. ej. una lista de precios), sube primero la versión nueva y luego elimina la vieja con delete_knowledge_base_document — si quedan las dos, la búsqueda puede mezclar información desactualizada.

Tu cuenta (1)

get_current_organization¿A qué organización apunta esta conexión? Devuelve la organización (id y nombre), el usuario a cuyo nombre se emitió el token, sus permisos, su vencimiento y el plan efectivo con sus módulos. Llámalo al empezar y ANTES de cualquier acción de salida (WhatsApp, correo, campañas) si el usuario tiene varias cuentas conectadas: equivocarse de conexión es enviar desde el número de otra empresa.

Configuración: pipelines y etapas (17)

create_conversation_pipelineCrea el pipeline de conversaciones de la organización con sus etapas. La app trabaja con UN solo pipeline de conversaciones por organización: si ya existe uno, NO crea otro y devuelve el existente con alreadyExists: true (status 200). Para cambiar las etapas de un pipeline existente usa create_conversation_stage / update_conversation_stage / delete_conversation_stage. webhookUrl debe ser https. Requiere pipelines:write.
create_conversation_stageAgrega una etapa a un pipeline de conversaciones (por defecto al final). Campos opcionales: color, autoAssign + assignToTeam (userId de un empleado de la org), webhookEnabled + webhookUrl (https obligatorio), reopenOnNewMessage. No mueve conversaciones. Requiere pipelines:write.
create_deal_pipelineCrea un pipeline de negocios nuevo con sus etapas en el orden dado (la primera queda a la izquierda del tablero). Máximo 50 etapas. Marca con isWon la etapa de negocio ganado y con isLost la de perdido (una etapa no puede ser las dos). No mueve ni crea negocios. Requiere pipelines:write.
create_deal_stageAgrega una etapa al FINAL de un pipeline de negocios (para cambiar su posición llama luego reorder_deal_stages). Máximo 50 etapas por pipeline. No mueve negocios a la etapa nueva. Requiere pipelines:write.
delete_conversation_pipelineBORRA un pipeline de conversaciones y TODAS sus etapas (incluida la configuración de webhooks y asignación). Es IRREVERSIBLE. Se niega (409) si es el único pipeline de la organización o si todavía tiene conversaciones: primero hay que moverlas con change_conversation_pipeline. Si era el predeterminado, otro pasa a serlo. No borra conversaciones. Confirma con el usuario antes de llamarlo. Requiere pipelines:write.
delete_conversation_stageBORRA una etapa de un pipeline de conversaciones, con su configuración (webhook, asignación). Es IRREVERSIBLE. Si la etapa tiene conversaciones, hay que indicar targetStageId (otra etapa del MISMO pipeline): las conversaciones se mueven ahí antes de borrar; sin él responde 409 con el número de conversaciones afectadas. No borra conversaciones ni registra historial de cambio de etapa. La última etapa del pipeline no se puede borrar. Confirma con el usuario antes de llamarlo. Requiere pipelines:write.
delete_deal_pipelineELIMINA un pipeline de negocios y TODAS sus etapas. Es IRREVERSIBLE (no hay papelera). Solo funciona si el pipeline no tiene negocios: si tiene, responde 409 con dealsCount y no borra nada; en ese caso mueve o elimina esos negocios primero. Confirma con el usuario antes de llamarlo. Requiere pipelines:write.
delete_deal_stageELIMINA una etapa de un pipeline de negocios. Es IRREVERSIBLE. Solo funciona si la etapa no tiene negocios: si tiene, responde 409 con dealsCount y no borra nada; mueve antes esos negocios a otra etapa con update_deal. Nunca borra negocios. Confirma con el usuario antes de llamarlo. Requiere pipelines:write.
get_conversation_pipelineDevuelve UN pipeline de conversaciones con todas sus etapas ordenadas (id, name, order, color, autoAssign, assignToTeam, webhookEnabled, webhookUrl, último resultado del webhook, reopenOnNewMessage). Úsalo antes de editar etapas para conocer sus ids. No lista pipelines (para eso list_conversation_pipelines) ni devuelve conversaciones. Requiere pipelines:read.
get_deal_pipelineDevuelve un pipeline de negocios por id con sus etapas ordenadas, la configuración de cada etapa y el número de negocios por etapa. Un id de otra organización o inexistente responde 404. No devuelve los negocios (para eso list_deals con el pipeline).
list_deal_pipelinesLista los pipelines de NEGOCIOS (deals) de la organización con todas sus etapas en orden: id, nombre, color, probabilidad de cierre, marcas de ganada/perdida (null = se infiere del nombre), días máximos en la etapa (maxDaysInStage, null = sin límite), requisitos por etapa (stageGateEnabled/stageGateCumulative) y cuántos negocios hay en cada etapa. Úsalo antes de crear, mover o configurar etapas para tener los ids. NO sirve para los pipelines de conversaciones de WhatsApp (esos son list_conversation_pipelines) ni lista los negocios en sí (list_deals).
reorder_conversation_stagesReordena las etapas de un pipeline de conversaciones: stageIds debe traer TODAS las etapas del pipeline, sin repetir, en el orden nuevo (la posición en el arreglo pasa a ser su order). No crea ni borra etapas ni mueve conversaciones. Requiere pipelines:write.
reorder_deal_stagesCambia el orden de las etapas (columnas del tablero) de un pipeline de negocios. stageIds debe traer TODAS las etapas del pipeline, cada una una sola vez, en el orden deseado; si falta, sobra o se repite alguna responde 400 y no cambia nada. Obtén los ids con get_deal_pipeline. No mueve negocios. Requiere pipelines:write.
update_conversation_pipelineRenombra un pipeline de conversaciones o lo marca como predeterminado (isDefault: true apaga el predeterminado anterior). NO edita etapas: para eso create_/update_/delete_conversation_stage y reorder_conversation_stages. isDefault solo acepta true (para quitarlo, marca otro). Requiere pipelines:write.
update_conversation_stageEdita una etapa de un pipeline de conversaciones: solo cambia los campos enviados. webhookUrl null la quita; cambiar la URL o apagar el webhook limpia su último resultado. assignToTeam debe ser un empleado de la organización (null lo quita). Para cambiar el orden de varias usa reorder_conversation_stages. No mueve conversaciones. Requiere pipelines:write.
update_deal_pipelineRenombra un pipeline de negocios o cambia sus requisitos por etapa: stageGateEnabled activa/desactiva los campos obligatorios y el checklist al mover negocios; stageGateCumulative exige también los requisitos de las etapas anteriores (apágalo en pipelines con etapas paralelas). NO edita etapas (usa update_deal_stage, create_deal_stage, reorder_deal_stages). Envía al menos un campo. Requiere pipelines:write.
update_deal_stageEdita una etapa de un pipeline de negocios: nombre, color, ganada/perdida, probabilidad de cierre y días máximos. Parcial: solo cambia lo que envíes. null en isWon/isLost vuelve a 'automático por nombre'; null en probability o maxDaysInStage deja el campo sin configurar. La etapa debe pertenecer a ese pipeline. NO cambia el orden (usa reorder_deal_stages) ni mueve negocios. Requiere pipelines:write.

Configuración: campos personalizados (11)

create_company_propertyCrea una propiedad nueva para las empresas. key: letra inicial y luego letras, dígitos o _ (máx. 64), única y no puede ser una clave núcleo (409). type default 'input'; un 'select' exige options. groupId opcional (default: el primer grupo); ver los grupos con list_company_properties. NO llena el valor en empresas existentes. Las propiedades NO se pueden borrar: solo ocultar con update_company_property isVisible=false. Requiere fields:write.
create_contact_propertyCrea una propiedad nueva para los contactos. key: letra inicial y luego letras, dígitos o _ (máx. 64), única y no puede ser una clave núcleo (409). type default 'input'; un 'select' exige options. groupId opcional (default: el primer grupo); ver los grupos con list_contact_properties. NO llena el valor en contactos existentes. Las propiedades NO se pueden borrar: solo ocultar con update_contact_property isVisible=false. Requiere fields:write.
create_deal_fieldCrea un campo personalizado en un pipeline de negocios. La clave (key) debe ser única dentro del pipeline (si ya existe responde 400 DUPLICATE_KEY). Un campo select necesita al menos una opción. requiredForStage (opcional) hace el campo obligatorio para LLEGAR a esa etapa, que debe ser del mismo pipeline; enforcement 'block' (default) impide mover el negocio y 'warn' solo avisa. required=true lo exige además al cerrar ganado. NO llena valores en negocios existentes. Requiere fields:write.
delete_deal_fieldBORRA un campo personalizado de negocios Y TODOS los valores que ese campo tenga guardados en TODOS los negocios de la organización. Es IRREVERSIBLE: no hay papelera ni forma de recuperar los datos. Confirma con el usuario antes de llamarla. Devuelve cuántos valores se borraron (deletedValues). Requiere fields:write.
get_deal_fieldDevuelve un campo personalizado de negocios por su _id (definición del campo, no sus valores). 404 si no existe o es de otra organización. Requiere fields:read.
list_company_propertiesLista las propiedades (campos) de las empresas de la organización con sus grupos: key, label, type, isVisible, options, groupId y reserved (true = propiedad núcleo del CRM como name, idNumber, email o phone). type: catalog (con catalog: co_municipalities | co_states) = cityCode y stateCode, códigos DANE del catálogo nacional: cityCode se escribe con un código de search_municipalities y stateCode se deriva solo; no se pueden crear propiedades catalog. Las keys son las que usan create_company/update_company. NO devuelve empresas. Requiere fields:read.
list_contact_propertiesLista las propiedades (campos) de los contactos de la organización con sus grupos: key, label, type, isVisible, options, groupId y reserved (true = propiedad núcleo del CRM como email, mobile, firstName o companyType). type: catalog (con catalog: co_municipalities | co_states) = cityCode y stateCode, códigos DANE del catálogo nacional: cityCode se escribe con un código de search_municipalities y stateCode se deriva solo; no se pueden crear propiedades catalog. Las keys son las que usan create_contact/update_contact y los filtros de list_contacts. NO devuelve contactos. Requiere fields:read.
list_deal_fieldsLista los campos personalizados de negocios (deals) de la organización: nombre, clave (key), tipo, opciones, si es obligatorio y si es requisito para llegar a una etapa (requiredForStage + enforcement block|warn). Con pipelineId devuelve solo los de ese pipeline; sin él, los de todos los pipelines. NO devuelve los valores que cada negocio tiene guardados (eso viene en get_deal). Requiere fields:read.
update_company_propertyActualiza una propiedad de empresa por su key: label, isVisible (false = ocultarla, true = mostrarla), options, groupId y type. La key NUNCA cambia. En propiedades núcleo (reserved) el type no se puede cambiar, pero sí label, visibilidad y opciones. No existe borrado de propiedades: ocultar es la única forma de retirarla, y los valores guardados en las empresas se conservan. Requiere fields:write.
update_contact_propertyActualiza una propiedad de contacto por su key: label, isVisible (false = ocultarla, true = mostrarla), options, groupId y type. La key NUNCA cambia. En propiedades núcleo (reserved) el type no se puede cambiar, pero sí label, visibilidad y opciones (ej. las de companyType). No existe borrado de propiedades: ocultar es la única forma de retirarla, y los valores guardados en los contactos se conservan. Requiere fields:write.
update_deal_fieldActualiza un campo personalizado de negocios. Solo cambia lo que se envía. Cambiar la key no migra los valores ya guardados (siguen ligados al campo por su _id, no por la key). Si se cambia pipelineId, la etapa requisito debe existir en el nuevo pipeline (o enviar requiredForStage: null). NO borra valores. Requiere fields:write.

Plantillas de correo y documentos (8)

create_email_templateCrea una plantilla de correo. Manda emailJson (preferido) o emailHtml. Si mandas emailJson, el servidor genera el HTML desde los bloques con el logo de la organización y emailHtml se IGNORA. Si solo mandas emailHtml, se guarda sanitizado (sin scripts ni javascript:, máx 500 KB) y el editor visual de la app no podrá mostrar sus bloques. Bloques admitidos (type): header {text}, text {content}, image {src, alt}, button {label, url}, divider, spacer {height}, logo, social {networks[]}, footer {text} (enlace de baja {{unsubscribe_url}}), twoCols {leftBlocks, rightBlocks}. Solo crea la plantilla: NO envía correos ni crea campañas.
delete_email_templateBORRA una plantilla de correo de la organización. Es IRREVERSIBLE: no hay papelera ni forma de recuperarla. Las campañas y automatizaciones que la referencian se quedan sin plantilla. No borra correos ya enviados. Confirma con el usuario antes de llamarla.
generate_document_template_pdfGenera el PDF de una plantilla rellenado con los valores que mandes y lo devuelve en base64 (body.base64, contentType application/pdf; puede ser pesado). values es un mapa plano { "etapa.campo": valor } con las claves pdfFieldName de get_document_template; una clave que termina en "?" es opcional (si el PDF no tiene ese campo no falla). Si falta un campo requerido responde 400 con missingFields. No guarda nada en el CRM, no envía el documento a nadie ni lo adjunta al contacto.
get_document_templateDevuelve una plantilla PDF con sus etapas y campos. Cada campo trae pdfFieldName ("etapa.campo"), que es la clave que espera generate_document_template_pdf en values, además de label, type, si es requerido y valueTemplate (cómo se compone desde el contacto, p. ej. '{firstName} {lastName}'). Con includePdfFields=true también lista los campos reales del PDF (más lento: descarga el archivo). Solo lectura.
get_email_templateDevuelve una plantilla de correo completa: nombre, bloques del editor visual (emailJson) y el HTML final (emailHtml) que se envía. Úsalo antes de update_email_template para no perder bloques. Un id de otra organización o inexistente responde 404. No envía nada.
list_document_templatesLista las plantillas de documentos PDF de la organización (PDF con campos de formulario que el CRM rellena: contratos, cartas, remisiones). Devuelve id, nombre, categoría, archivo, versión y cuántas etapas/campos tiene. Por defecto solo las activas. Solo lectura: no crea ni sube plantillas (eso se hace desde la app).
list_email_templatesLista las plantillas de correo de la organización (las que se usan en campañas de email y en la acción 'Enviar correo' de las automatizaciones), más recientes primero. Devuelve id, nombre, si está guardada (saved), número de bloques y fechas; NO incluye el HTML ni los bloques (pídelos con get_email_template). No envía ningún correo.
update_email_templateEdita una plantilla de correo existente. Solo cambia lo que mandes: name, saved, y el contenido con emailJson (preferido; regenera el HTML y emailHtml se ignora) o emailHtml (se sanitiza y deja emailJson vacío, así que el editor visual ya no podrá editarla por bloques). emailJson REEMPLAZA todos los bloques: lee primero la plantilla con get_email_template. No envía correos. Las campañas o automatizaciones que usan esta plantilla enviarán la versión nueva.

Configuración de la cuenta (17)

create_scoring_ruleCrea una regla de puntuación de leads (queda activa salvo isActive=false). Condiciones de CONTACTO (propertyName obligatorio = key de una propiedad de contacto; consulta list_contact_properties): exists (sin value), equals / not_equals (texto, número o booleano), contains (texto), greater_than / less_than (número), in_list (arreglo). Condiciones de NEGOCIOS (propertyName no se usa): last_deal_older_than / last_deal_newer_than / purchase_frequency_less_than (días), deal_amount_greater_than / deal_amount_less_than / total_deals_amount_greater_than (monto), total_deals_count_greater_than (cantidad), deal_status_is (id de etapa), deal_pipeline_is (id de pipeline), has_purchased_product (id de producto), deal_field_value_is ({ fieldId, value }). points puede ser negativo. NO recalcula los puntajes existentes: para aplicarla a todos los contactos llama después recalculate_scoring_rules. Requiere settings:write.
delete_scoring_ruleELIMINA una regla de puntuación de leads. Es IRREVERSIBLE. Los puntajes ya calculados de los contactos NO cambian hasta que se llame recalculate_scoring_rules. Si solo quieres dejar de aplicarla, usa update_scoring_rule con isActive=false. Confirma con el usuario antes de llamarlo. Requiere settings:write.
get_deal_checklistDevuelve el checklist de cierre de un pipeline de negocios (items vacío e id null si aún no tiene). Un pipeline de otra organización o inexistente responde 404. Los ids de pipeline salen de list_deal_pipelines.
get_integrationDevuelve una integración por id (de list_integrations): servicio, nombre, si está activa, fechas y configuredKeys (solo los nombres de las credenciales guardadas). Nunca devuelve valores de credenciales ni settings. Un id de otra organización o inexistente responde 404.
get_invoice_configDevuelve la configuración de FACTURACIÓN ELECTRÓNICA (DIAN) de la organización, en solo lectura: empresa emisora (razón social, NIT, DV, correo, teléfono, dirección, municipio y códigos DIAN), resolución de facturación (prefijo, número, rango desde/hasta, vigencia y siguiente consecutivo), numeración de notas crédito, notas débito y documento soporte, ambiente (environment: 'production' o 'test'), si el certificado digital está cargado y cuándo vence, correo SMTP de envío y buzón IMAP de recepción (host, puerto, usuario y si hay contraseña guardada) y los textos por defecto de la factura. NUNCA devuelve contraseñas, la clave técnica, el token del proveedor, el PIN del software ni el certificado: solo booleanos que dicen si existen (hasPassword, hasTechnicalKey, provider.hasToken, certificate.uploaded). Útil para saber qué prefijo y consecutivo saldrá en la próxima factura o si la resolución está por vencer. 404 si la organización no ha configurado facturación. No emite nada ni cambia la configuración (eso se hace desde la app).
get_mail_accountDevuelve una cuenta de correo IMAP por id (de list_mail_accounts): correo, proveedor, estado, último error, última sincronización y servidor IMAP (host, puerto, usuario, TLS). Nunca devuelve la contraseña. Un id de otra organización o inexistente responde 404.
get_organization_settingsDevuelve los datos de la EMPRESA dueña de la cuenta y sus ajustes generales: nombre, tipo y número de identificación (NIT), correo, teléfono, WhatsApp, dirección (con cityCode = código DANE del municipio, vacío si la ciudad no está confirmada), logo e icono; y en settings la zona horaria, idioma/región (locale BCP-47), moneda (ISO-4217), la ventana de envío de automatizaciones (automationSendWindow: días 0=domingo..6=sábado y franja HH:mm en que SÍ se escribe a clientes; null = sin restricción), si se exige 2FA, los textos por defecto de cotizaciones (condiciones de pago y envío, notas, pie, días de validez, prefijo del número, rótulo del impuesto) y de órdenes de compra, y el campo de negocios que alimenta el KPI de productos vendidos. NO incluye usuarios (list_users), plan, configuración de facturación electrónica, integraciones ni credenciales (tienen sus propias tools). Requiere settings:read.
get_scoring_ruleDevuelve una regla de puntuación de leads por id con todas sus condiciones. Un id de otra organización o inexistente responde 404.
get_scoring_rules_recalculationEstado de un recálculo de puntajes lanzado con recalculate_scoring_rules: processing, completed o failed (con el error), más inicio, fin y duración. Los jobs viven en memoria del servidor: tras un reinicio o una hora después de terminar responden 404; en ese caso lanza otro si hace falta.
list_deal_checklistsLista los checklists de cierre configurados en los pipelines de NEGOCIOS de la organización (uno por pipeline): cada ítem con nombre, peso (el % de avance de un negocio es sobre la suma de pesos), activityType (registrar una actividad de ese tipo en el negocio marca el ítem sola) y requiredForStage (etapa a la que el negocio no puede llegar sin el ítem marcado; enforcement block = impide, warn = solo avisa). Los pipelines sin checklist no aparecen.
list_integrationsLista las integraciones que la organización tiene conectadas (WhatsApp, Messenger, Instagram, OpenAI, Claude, Brevo, n8n, Google Analytics…): id, servicio, nombre, si está activa, fechas y configuredKeys (solo los NOMBRES de las credenciales guardadas, p. ej. ['accessToken','phoneNumberId']). Solo lectura: NUNCA devuelve valores de credenciales ni settings. Sirve para responder '¿está conectado WhatsApp?' o diagnosticar por qué algo no envía (integración inactiva o sin la llave esperada). Conectar, desconectar o cambiar llaves se hace desde la app.
list_mail_accountsLista las cuentas de correo IMAP que la organización conectó a la bandeja compartida del CRM: id, correo, proveedor, estado (status 'active' | 'error' | 'inactive', isActive), último error de conexión y su código (lastError, lastErrorCode: auth_failed, oauth_required, timeout…), última verificación y última sincronización, si está sincronizando, y el servidor IMAP (host, puerto, usuario, TLS). Solo lectura: NUNCA devuelve la contraseña. Útil para diagnosticar por qué no llegan correos a la bandeja. No sirve para leer correos ni para el correo de facturación (ese es get_invoice_config). Crear, probar o sincronizar cuentas se hace desde la app.
list_scoring_rulesLista las reglas de puntuación de leads (lead scoring) de la organización. Cada regla tiene un nombre, si está activa y sus condiciones: propertyName (propiedad del contacto), condition, value y points. El puntaje de un contacto (leadScore) es la suma de los points de TODAS las condiciones que cumple en las reglas activas. Úsalo antes de crear o editar reglas para no duplicarlas.
recalculate_scoring_rulesRecalcula en segundo plano el puntaje (leadScore) de TODOS los contactos de la organización con las reglas activas actuales, SOBRESCRIBIENDO el puntaje que tengan (si no hay reglas activas no cambia nada). Responde enseguida con un jobId; consulta el avance con get_scoring_rules_recalculation. Si ya hay un recálculo en curso devuelve ese mismo job (alreadyRunning=true) en vez de lanzar otro. Úsalo después de crear, editar o borrar reglas. Requiere settings:write.
update_deal_checklistREEMPLAZA el checklist de cierre completo de un pipeline de negocios (lo crea si no existe). Envía TODOS los ítems en el orden deseado: los que no envíes se eliminan, y un items vacío deja el pipeline sin checklist. Para conservar un ítem existente (y las marcas que ya tienen los negocios) incluye su id; un ítem sin id es nuevo y ningún negocio lo tiene marcado. requiredForStage debe ser una etapa de ESE pipeline: con enforcement block los negocios no podrán pasar a esa etapa sin el ítem marcado (afecta a todo el equipo). Máximo 50 ítems. Requiere pipelines:write.
update_organization_settingsEdita los datos de la empresa y sus ajustes generales. PARCIAL: envía SOLO los campos a cambiar; lo que no mandes queda igual (null borra un campo opcional). Cambia lo que ven los clientes: nombre, NIT, dirección y los textos por defecto salen en las cotizaciones y órdenes de compra NUEVAS, y la moneda/idioma cambian el formato de dinero y fechas en toda la cuenta. automationSendWindow se reemplaza COMPLETA y con enabled=true retiene los envíos automáticos de WhatsApp/correo fuera de la franja (null la quita). Solo funciona si el token es del PROPIETARIO (si no, 403 OWNER_REQUIRED). Campos no editables por aquí (employees, plan, consecutivos de cotización/compra, logo, 2FA, credenciales) responden 400. Confirma los cambios con el usuario antes de llamarlo. Requiere settings:write.
update_scoring_ruleEdita una regla de puntuación de leads: nombre, descripción (null la borra), isActive (false la desactiva sin borrarla) o rules. OJO: rules REEMPLAZA todas las condiciones de la regla (envía la lista completa, no solo la nueva). Mismas condiciones y validación que create_scoring_rule. NO recalcula los puntajes existentes: llama después recalculate_scoring_rules. Envía al menos un campo. Requiere settings:write.

Usuarios (2)

get_userDevuelve un usuario de la organización por id (nombre, correo, celular, rol, avatar, lastSeenAt). Un id de otra organización o inexistente responde 404. Solo lectura. Requiere users:read.
list_usersLista los usuarios (miembros del equipo) de la organización: id, nombre, correo, celular, rol (propietario, empleado o lector = solo lectura), avatar y lastSeenAt (última actividad en la app; null si no hay sesión reciente). Úsalo para obtener el id de un asesor antes de asignarle negocios, conversaciones o tareas. Solo lectura: NO crea, invita, edita ni borra usuarios (eso cambia asientos cobrados y se hace desde la app). No devuelve contraseñas ni credenciales. Requiere users:read.

Automatizaciones (borrador) (9)

create_automationCrea una automatización NUEVA que nace SIEMPRE en BORRADOR (status draft) con sus disparadores inactivos: NO se dispara, NO envía mensajes ni ejecuta nada. Un humano la revisa y la publica desde la web (Automatizaciones → Publicar); por MCP no se puede publicar, activar, pausar ni probar. Valida el grafo y responde 400 con la lista de errores si no es válido (usa validate_automation y list_automation_variables antes). Efecto externo: ninguno hasta que alguien la publique. Requiere automations:write.
get_automationDevuelve una automatización por id con lo PUBLICADO (pasos y disparadores reales, lo que corre hoy) y el BORRADOR (pasos y disparadores pendientes de publicar), más la última ejecución. Los secretos de las acciones http_request (headers de autorización, apiKey, tokens) salen como ***REDACTED***: si editas el borrador con update_automation puedes dejar ese marcador tal cual y se conserva el valor guardado. Un id de otra organización o inexistente responde 404. Solo lectura.
get_automation_triggerDevuelve un disparador real por id: tipo, activo, filtros, programación, estadísticas y su automatización. Un id de otra organización o inexistente responde 404. Solo lectura.
list_automation_runsHistorial paginado de ejecuciones de una automatización (más recientes primero): estado de la ejecución, fechas, conversación/contacto y, por paso, tipo, estado, duración, intentos y error. NO incluye los datos de entrada/salida de los pasos ni el evento que la disparó (traen datos del cliente y secretos). No reintenta ni ejecuta nada. Los registros se conservan ~30 días.
list_automation_triggersLista los disparadores REALES (publicados) de las automatizaciones de la organización: tipo, si está activo, filtros, programación (cron) y estadísticas de ejecución, con el nombre y estado de su automatización. Filtros opcionales por automatización, tipo y activo. Solo lectura: por MCP no se crean, editan ni activan disparadores (los de un borrador se editan con update_automation).
list_automation_variablesCatálogo para armar automatizaciones: las variables {{...}} disponibles según los tipos de disparador dados (con ejemplo), los tipos de disparador válidos y los tipos de acción que existen (send_whatsapp, send_whatsapp_template, send_email, create_task, http_request…). Úsalo antes de create_automation. Solo lectura.
list_automationsLista las automatizaciones de la organización: nombre, estado (draft = borrador nunca publicado, published = activa, paused = pausada), si tiene cambios sin publicar, sus disparadores reales (tipo y si están activos), los disparadores del borrador, la última ejecución y cuántas ejecuciones hubo por estado en los últimos ~30 días. Solo lectura. Para ver los pasos usa get_automation.
update_automationEdita SOLO el BORRADOR de una automatización (nombre, disparadores y/o pasos; lo que no envíes se conserva del borrador actual o, si no hay, de lo publicado). Si la automatización está publicada, lo que corre hoy NO cambia ni su estado: los cambios quedan pendientes hasta que un humano pulse Publicar en la web. Por MCP no se publica, pausa, activa ni prueba. Los disparadores del borrador quedan inactivos. Un ***REDACTED*** de get_automation conserva el secreto guardado en ese mismo paso. Valida el grafo (400 con errores). reEntry solo se acepta en automatizaciones nunca publicadas. Requiere automations:write.
validate_automationValida un grafo de automatización (nombre, disparadores y pasos) SIN guardarlo: devuelve valid, errors (bloquean) y warnings. Usa las mismas reglas que create_automation y que el botón Publicar de la web (tipos de acción, variables disponibles para los disparadores, cron, filtros que el disparador sí guarda). No crea ni ejecuta nada.

Campañas (4)

create_campaign_draftCrea el BORRADOR de una campaña masiva de WhatsApp (la de /mass-whatsapp): nombre, lista de contactos de la organización y plantilla aprobada en Meta (nombre, idioma, de dónde sale cada variable {{n}} y la imagen/video de cabecera si la plantilla la lleva). Queda en estado 'planeacion' y SIN fecha de envío: NO envía ningún mensaje, NO la programa y NO la ejecuta — eso lo hace una persona desde la app con 'Enviar ahora'. No verifica que la plantilla exista en Meta (un nombre mal escrito falla recién al ejecutar): toma el nombre de list_whatsapp_templates. Después usa get_campaign_preflight para ver a cuántos les llegaría. Requiere automations:write.
get_campaign_preflightAntes de enviar: calcula, con la lista TAL COMO ESTÁ HOY, a cuántos contactos les llegaría la campaña de WhatsApp y cuántos quedan fuera por motivo (no_mobile = sin celular, landline = número fijo, invalid_phone = formato inválido, opted_out = pidió no ser contactado, whatsapp_invalid = marcado sin WhatsApp), con hasta 10 ejemplos (teléfono enmascarado). Es la misma selección que usa el envío real. Solo lectura: NO envía nada ni modifica contactos. Responde 409 si la campaña no tiene lista o la lista ya no existe. Requiere contacts:read.
get_campaign_statsResultados reales de una campaña masiva de WhatsApp: embudo (listSize → attempted → accepted → delivered → read → replied, y failed/skipped), tasas (entrega, lectura, respuesta, fallo), motivos de fallo agrupados (número sin WhatsApp, plantilla pausada…), tabla por contacto con estado, fechas y conversación del CRM, y las últimas ejecuciones de la cola. 'accepted' es lo que Meta aceptó (HTTP 200); delivered/read llegan por webhook de Meta; 'replied' = contacto con mensaje entrante después del envío; 'skipped' = lo que la lista dejaría fuera hoy (sin celular, fijo, opt-out, marcado sin WhatsApp). Úsalo para responder '¿se enviaron todos?', '¿cuántos respondieron?' o '¿a quién no le llegó y por qué?' sin mirar la base de datos.
update_campaign_draftEdita el borrador de una campaña masiva de WhatsApp: nombre, descripción, lista de contactos o la plantilla (nombre, idioma, variables, cabecera). Solo funciona si la campaña NUNCA se ha ejecutado y no se está ejecutando: si ya salió, está en cola o su fecha programada ya llegó responde 409 y no cambia nada (crea otra con create_campaign_draft). NO envía, NO programa y NO cambia el estado ni la fecha de la campaña. En template solo se cambian los campos enviados; variableMapping o headerMediaUrl en null los quitan. Requiere automations:write.

Landing pages (8)

create_landing_pageCrea una landing page nueva SIEMPRE EN BORRADOR (no queda pública hasta publish_landing_page). Parte de una plantilla (templateId; default blank) o de las secciones que envíes (sections reemplaza las de la plantilla). El slug se genera del nombre y es único global; se cambia después con update_landing_page. Si la página tiene bloque form, asocia formId (un formulario de la organización, de list_forms). Las imágenes van como URLs https (no se suben archivos). Cuenta contra el límite de páginas del plan: si está lleno responde 403 PLAN_LIMIT_REACHED. Requiere landings:write.
duplicate_landing_pageDuplica una landing page: crea una copia EN BORRADOR con '(copia)' en el nombre, slug nuevo terminado en -copia, las mismas secciones y configuración, y estadísticas en cero. La original no cambia. Cuenta contra el límite de páginas del plan (403 PLAN_LIMIT_REACHED si está lleno). Requiere landings:write.
get_landing_page_statsEstadísticas de una landing page en un rango de fechas (default: últimos 30 días): totales (vistas, únicos, sesiones, clics CTA, clics WhatsApp, envíos, negocios ganados y monto, tiempo medio), las dos conversiones (envíos/únicos y WhatsApp/únicos), serie diaria, embudo por sesión (vista → scroll 50% → interacción → conversión → ganado), top utm_source, clics por sección, dispositivo y últimas respuestas.
list_landing_page_templatesLista las plantillas de landing page (captacion, cotizacion, evento, descarga, blank) con su objetivo (whatsapp/form), si necesitan un formulario y qué secciones traen, más el catálogo de tipos de sección (hero, benefits, text, gallery, testimonials, faq, whatsapp_cta, form) con un ejemplo del objeto data de cada uno y las claves de ícono permitidas. Úsalo ANTES de create_landing_page o update_landing_page para armar secciones válidas. Solo lectura.
list_landing_pagesListar las landing pages (páginas de aterrizaje) de la organización: nombre, slug, estado (draft/published), plantilla, URL pública, formulario asociado y stats acumuladas (vistas, únicos, envíos, clics a WhatsApp). Cada página se sirve en forms.fusioncol.com/p/<slug> y convierte a WhatsApp o a un formulario de captación.
publish_landing_pagePUBLICA una landing page: queda PÚBLICA EN INTERNET en su publicUrl (forms.fusioncol.com/p/<slug>) para cualquiera que tenga el enlace, indexable por buscadores salvo seo.indexable=false, y empieza a recibir visitas y leads reales. Confirma con el usuario antes de publicar. Exige al menos una sección activa y, si hay bloque form, un formulario activo de la organización; si no cumple responde 400 con el motivo. Requiere landings:write.
unpublish_landing_pageDespublica una landing page: vuelve a borrador y su URL pública deja de responder (404) de inmediato; los anuncios, QR o enlaces que apunten a ella quedan rotos hasta volver a publicarla. No borra la página, sus secciones ni sus estadísticas. Requiere landings:write.
update_landing_pageEdita una landing page: nombre, slug (cambia la URL pública; 409 si ya existe), formulario, secciones, tema, SEO, tracking (Pixel de Meta / GA4 por ID) y WhatsApp. Solo cambia lo que envías; sections REEMPLAZA la lista completa (lee antes el detalle con fusion_request GET /landing-pages/{id} para conservar las demás). OJO: si la página está PUBLICADA los cambios salen EN VIVO en internet de inmediato (no hace falta republicar); si el cambio la deja sin poder publicarse (sin secciones activas o con bloque form sin formulario activo) vuelve a borrador y la respuesta trae unpublished: true con el motivo. No cambia el estado: usa publish_landing_page / unpublish_landing_page. Requiere landings:write.

Actividades (5)

create_activityRegistra un seguimiento en la ficha de un contacto y/o empresa (debe venir al menos contactId o companyId, de esta organización). Queda a nombre del dueño del token. Si pones 'reminder' (fecha-hora), el asesor (advisorId) o, sin asesor, quien lo registró recibe un aviso por push y correo a esa hora. Aparece también en el calendario como seguimiento. No envía nada al cliente. Requiere activities:write.
delete_activityELIMINA un seguimiento de la ficha del contacto/empresa. Es IRREVERSIBLE (no hay papelera); si era el recordatorio ligado a un paso de un negocio, el negocio conserva su paso pero sin recordatorio. Confirma con el usuario antes de llamarlo. Requiere activities:write.
get_activityDevuelve un seguimiento (actividad de contacto/empresa) por id: tipo, título, fecha, notas, estado, recordatorio, contacto, empresa, asesor y quién lo registró. Un id de otra organización o inexistente responde 404.
list_activitiesLista los SEGUIMIENTOS (actividades) de contactos y empresas: llamadas, reuniones, correos, notas y WhatsApp registrados en la ficha, con su fecha, estado (incomplete = pendiente, complete = hecho), recordatorio y asesor. Filtra por contacto, empresa, asesor, estado, tipo o rango de fechas (sobre 'date'). Más recientes primero, paginado. NO lista las actividades del timeline de un NEGOCIO (esas son de list_deals/get_deal) ni las citas del calendario (list_calendar_events).
update_activityEdita un seguimiento; parcial: solo cambia lo que envíes. status 'complete' lo marca hecho (y cierra el próximo paso del negocio ligado, si lo hay); 'incomplete' lo reabre. reminder null quita el recordatorio; cambiarlo re-arma el aviso. contactId/companyId/advisorId null desvincula (siempre debe quedar contacto o empresa). Requiere activities:write.

Calendario (5)

create_calendar_eventCrea un evento en el calendario de la organización. Para repetirlo usa recurrenceRule (RRULE RFC 5545 sin prefijo, p. ej. 'FREQ=WEEKLY;BYDAY=TU'; mínimo diario). ⚠️ CORREOS: si hay attendees y sendInvitations es true, cada invitado RECIBE UN CORREO con la invitación (.ics) enviado a nombre de la organización; por defecto (false) NO se envía nada. Pide confirmación al usuario antes de poner sendInvitations: true. reminderEnabled avisa al asesor (push y correo interno) antes del inicio. Requiere calendar:write.
delete_calendar_eventELIMINA un evento del calendario; IRREVERSIBLE. Si es recurrente y NO pasas occurrenceStart se borra la SERIE ENTERA con todas sus ocurrencias (incluidas las editadas aparte); con occurrenceStart se quita solo esa fecha. ⚠️ Con sendCancellations true los invitados que ya recibieron la invitación reciben un CORREO de cancelación; por defecto no se envía nada. Confirma con el usuario antes de llamarlo. No sirve para ids 'activity:' (usa delete_activity). Requiere calendar:write.
get_calendar_eventDevuelve un evento del calendario por id (la serie completa si es recurrente: recurrenceRule, excludedDates) con invitados, contacto, asesor y recordatorios. Acepta también 'activity:<id>' para ver un seguimiento como evento. Un id de otra organización o inexistente responde 404.
list_calendar_eventsLista los eventos del calendario (citas, reuniones, tareas, recordatorios) entre 'from' y 'to' (ambos obligatorios, ISO 8601; rango máximo ~3 años, se recorta si es mayor). Las series recurrentes salen expandidas: cada ocurrencia trae el id de la serie y 'occurrenceStart' (úsalo para editar o borrar SOLO esa ocurrencia). Con includeActivities=true suma los seguimientos de contactos como eventos de solo lectura (id 'activity:<id>'). Filtros: asesor (assignedTo, con includeCreatedBy para sumar los que creó) y contacto. Paginado, en orden cronológico.
update_calendar_eventEdita un evento del calendario; parcial. Sin occurrenceStart edita la SERIE completa si es recurrente; con occurrenceStart (el valor que trae list_calendar_events) edita SOLO esa ocurrencia, que se separa de la serie como evento propio. null en assignedTo/contactId los quita; recurrenceRule '' o null deja de repetir. ⚠️ CORREOS: con sendInvitations true los invitados reciben la invitación actualizada y los que quitaste reciben una cancelación; por defecto (false) no se envía nada. Confirma con el usuario antes de enviar. No sirve para ids 'activity:' (usa update_activity). Requiere calendar:write.

Notificaciones (2)

list_notificationsListar las notificaciones de la campana (menciones, asignaciones, recordatorios, formularios recibidos, cotización vista, errores de automatización, avisos de cuenta) del USUARIO a cuyo nombre está emitido el token — no las de otros empleados. Más recientes primero. Devuelve data[] (id, type, title, body, targetUrl, entity, actor, createdAt, readAt: null = sin leer), unreadCount y pagination. Solo lectura: NO las marca como leídas (para eso mark_notifications_read).
mark_notifications_readMarcar como leídas notificaciones del usuario del token. Pasa ids (lista de ids de list_notifications, máximo 50) O all: true para marcar TODAS las no leídas. Solo cambia el estado leído (baja el contador de la campana en la web y la app); no borra nada. Una ya leída cuenta como éxito (alreadyRead); un id inexistente o de otro usuario responde 404 en su fila.

Importar y exportar (6)

create_exportPedir una copia COMPLETA de los datos de la organización (ZIP con CSV de contactos, empresas, negocios, conversaciones y mensajes, cotizaciones, facturas, tareas, eventos y usuarios, más organization.json sin secretos). Solo funciona si el token es del PROPIETARIO (si no, 403 OWNER_REQUIRED). Se genera en segundo plano (202): el enlace llega por correo al propietario y se consulta con get_export. Si ya hay uno en curso no arranca otro (alreadyRunning: true). No borra ni modifica datos. Es pesado: no la llames en bucle; para leer registros sueltos usa las tools de cada módulo.
get_exportConsultar el ÚLTIMO export de datos de la organización (solo el propietario). status: none (no hay o ya caducó el ZIP), queued (generándose) o ready, con downloadUrl = URL firmada que caduca en 24 h (expiresAt) y createdAt del ZIP. Cada llamada firma una URL nueva; no existe URL pública permanente. El enlace da acceso a TODOS los datos: no lo compartas fuera de la conversación con el propietario. No genera un export nuevo (para eso create_export).
get_importDevuelve una importación del historial por id con sus conteos, avisos y las filas rechazadas (rejectedRows: número de fila, motivo y valores) para corregirlas y reimportarlas. Un id de otra organización o inexistente responde 404. Solo lectura.
import_contactsImporta contactos EN LOTE (hasta 500 filas por llamada) con el mismo motor que el importador de Excel de la app. Cada fila es un objeto { clave: valor } con las claves de las propiedades de contacto de la organización (firstName, lastName, email, mobile, phone, company, city… — consulta las claves reales con la configuración de propiedades de contacto) más 'owner' (correo del asesor dueño). DEDUPE: busca cada fila contra los contactos existentes por email (sin distinguir mayúsculas), luego mobile y luego phone (comparando los últimos 10 dígitos, con o sin indicativo), y también fusiona filas repetidas dentro del mismo lote. Si el contacto YA EXISTE lo ACTUALIZA: escribe los valores no vacíos de la fila (una celda vacía nunca borra un dato existente) y conserva lo demás; si no existe lo crea. Una clave que no es propiedad de contacto de la organización RECHAZA esa fila (sale en rejectedRows, no se inventan propiedades). CIUDADES (catálogo DANE, modo estricto): 'cityCode' (código DANE de 5 dígitos, ej. 05001 = Medellín) es la fuente de verdad y deriva city/state/stateCode; si solo mandas 'city' debe ser un nombre inequívoco del catálogo (opcionalmente con 'state' para desambiguar). Una fila con ciudad ambigua (ej. 'Rionegro': Antioquia 05615 o Santander 68615), mal escrita o desconocida se RECHAZA y sale en rejectedRows con los candidatos (sin candidatos = mal escrita o desconocida: busca el código con search_municipalities): corrige con cityCode y reimporta. stateCode siempre se deriva del cityCode. Nunca se asigna un 'mejor candidato'. Un contacto de otro país (country distinto de Colombia) guarda city como texto libre. 'company' enlaza la empresa por nombre; con createMissingCompanies=true crea las que falten. Respeta el límite de contactos del plan (los que no caben salen en skippedByPlan). NO dispara automatizaciones ni eventos de 'contacto creado', NO envía mensajes ni correos. RECOMENDADO: llama primero con dryRun=true para ver cuántos crearía, actualizaría y rechazaría sin escribir nada, y confirma con el usuario antes de importar de verdad. La importación real queda en el historial (list_imports) y se puede deshacer con import_rollback.
import_rollbackREVIERTE una importación (contactos, empresas o negocios): BORRA los registros que esa importación creó y RESTAURA los que actualizó a como estaban antes (en contactos también borra las empresas creadas por createMissingCompanies y restaura el enlace a empresa). Los cambios hechos a mano después sobre esos registros se pierden. Solo puede el propietario de la organización o quien hizo la importación (si no, 403). Una importación ya revertida responde 409. No emite eventos ni envía nada. Confirma con el usuario antes de llamarlo.
list_importsLista el historial de importaciones de la organización (más reciente primero): tipo (contacts, companies, deals, purchases), origen (web = Excel de la app, api = JSON por API/MCP), estado (completed, failed, rolled_back), autor y conteos (importados, actualizados, rechazados, omitidos por plan, empresas creadas). No incluye el detalle de filas rechazadas (usa get_import). Solo lectura.

Compras (5)

create_purchaseRegistrar una compra a un proveedor (POST /api/purchases). Requiere purchases:write. kind=invoice (default): factura del proveedor. Obligatorios: orderNumber (el número de la factura del proveedor), total y paymentMethod. Si cubre una OC abierta, manda orderRef con el id de la OC (ver list_open_purchase_orders): la OC queda completed con invoiceRef. 409 si la OC ya está facturada o cancelada. kind=order: orden de compra. El servidor genera el consecutivo OC-n (settings.purchases.numberPrefix + contador): NO mandes orderNumber, se ignora. Obligatorios: total y paymentMethod. Sus retenciones son estimadas y no entran en informes ni certificados. El proveedor puede ir como supplierId (contacto de la MISMA organización) o simplemente como supplierName + supplierNIT. En cada ítem, si no se envía total se calcula como quantity × unitPrice, y tax vacío se toma como 0.
get_purchaseObtener una orden de compra por ID, con el proveedor resuelto. Requiere purchases:read.
list_open_purchase_ordersÓrdenes de compra ABIERTAS (kind=order, pending, sin factura enlazada) de un proveedor (GET /api/purchases/open-orders). Sirve para conciliar: antes de registrar la factura de un proveedor, buscar si ya había una OC que la cubra y pasar su id como orderRef en create_purchase. El NIT se compara normalizado (900.480.268-1 = 900480268). Con total, la lista viene ordenada por cercanía y cada OC trae totalDiff (|OC.total − total|). Sin nit devuelve todas las abiertas. Requiere purchases:read.
list_purchasesListar compras a proveedores (GET /api/purchases): facturas de proveedor (kind=invoice) y órdenes de compra (kind=order). Sin `kind` devuelve ambas. Filtros por estado, proveedor, texto y rango de fechas de compra. Requiere purchases:read y el módulo de facturación en el plan. Cada compra trae kind, orderRef (en la factura: la OC que cubre) e invoiceRef (en la OC: la factura que la cerró). En una OC, status pending = abierta y completed = facturada.
update_purchaseActualizar una compra por ID (PUT /api/purchases/:id). Solo se tocan los campos enviados; enviar items los REEMPLAZA. supplierId vacío o null desvincula al proveedor. kind no se cambia después de crear (400 si difiere); orderNumber de una OC lo asigna el servidor (400 si difiere); cancelar una OC ya facturada responde 409. Requiere purchases:write.

Agentes IA (7)

create_agentCrear un agente IA. Nace en status draft (no responde a nadie hasta activarlo con set_agent_status). name y systemPrompt son obligatorios (systemPrompt ≤ 30000 caracteres); el resto toma valores por defecto (openai / gpt-4o-mini, temperature 0.4, maxTokens 400). Confirma el contenido del prompt con el usuario antes de crearlo.
delete_agentBorrar un agente IA (no se puede deshacer). Las conversaciones que tenía asignadas quedan sin agente. Confirma con el usuario antes de llamarlo.
get_agentDetalle completo de un agente IA: systemPrompt, modelo, temperatura, tools habilitadas, palabras de traspaso a humano, triggers, contextSettings y esquema de lead. Úsalo antes de update_agent: triggers, contextSettings y leadStateSchema se reemplazan completos al actualizar.
list_agent_toolsCatálogo de tools que un agente IA puede tener habilitadas (valores válidos para enabledTools en create_agent / update_agent).
list_agentsListar los agentes IA de WhatsApp/Messenger de la organización (nombre, estado draft/active/paused, proveedor y modelo, estadísticas de uso) y los límites del plan. Sin paginación.
set_agent_statusActivar (active), pausar (paused) o volver a borrador (draft) un agente IA. Activar hace que empiece a responder a clientes reales según sus triggers: confírmalo con el usuario.
update_agentActualizar un agente IA (solo cambian las claves enviadas). OJO: triggers, contextSettings y leadStateSchema se reemplazan COMPLETOS — lee el agente con get_agent y envía la lista entera. Si el agente está activo, el cambio afecta de inmediato las respuestas a clientes: confirma con el usuario.

Reportes (3)

get_purchases_overviewResumen de compras de un período: total bruto, retenciones practicadas, neto pagado y desglose. Requiere el módulo de facturación electrónica del plan y el permiso purchases:read.
get_sales_forecastPrevisión ponderada de ventas: suma de (monto × probabilidad de la etapa) de los negocios abiertos con cierre esperado en el rango, por mes, por etapa y por vendedor. Avisa aparte lo que no entra al número: etapas sin probabilidad configurada, abiertos sin fecha de cierre y abiertos con la fecha vencida.
get_sales_overviewResumen de ventas de un período (los mismos números de la pantalla Informes): total vendido, negocios ganados, ticket promedio, comparación con el mismo rango del año anterior, serie de 13 meses y mejores clientes. Fechas YYYY-MM-DD en la zona horaria de la organización.

General (1)

fusion_requestPetición cruda a rutas FusionCRM bajo /api (quotations, tasks, products, etc.).

Otros (4)

create_shipmentGenerar un envío (guía) en Envia.com (POST /api/shipments). 🚨 COBRA SALDO REAL de la cuenta de Envia de la organización en esta misma llamada: confirma con el usuario la transportadora, el servicio y el precio antes de llamarla. Requiere shipments:write y el módulo envios. Primero cotiza con quote_shipment y pasa aquí el carrier y el service de la tarifa elegida, con los MISMOS paquetes y destino. Pasa SIEMPRE idempotencyKey, una por INTENTO de envío y no por pedido (ej. "pedido-4521-1"; 1-100 caracteres, única por organización). Si reintentas con la misma clave y el envío ya tiene guía, responde 200 con reused:true sin volver a cobrar: en ese caso el body se IGNORA (no se aplican cambios de destino, paquetes ni transportadora) y hay que revisar data.status, que puede ser cualquier estado, incluso canceled. Para generar otra guía del mismo pedido (p. ej. tras cancelar la anterior) usa una clave NUEVA ("pedido-4521-2"). Si el intento anterior falló (draft), la misma clave reintenta generar sobre el mismo envío (201 reused:false, o 422 si falla otra vez). Mientras el generate de esa clave sigue en curso (candado de hasta 120 s), la misma clave responde 409 SHIPMENT_IN_PROGRESS sin volver a cobrar: espera unos segundos y reintenta. La misma clave con OTRO contactId → 400 IDEMPOTENCY_KEY_REUSED. NO reintentes en bucle; consulta con list_contact_shipments. 🚨 409 SHIPMENT_NEEDS_REVIEW (con shipmentId): Envia no respondió a tiempo, falló la red o respondió sin guía, así que NO sabemos si generó (y cobró) la guía. Ese envío NUNCA se regenera solo, ni con la misma clave. Dile al usuario que revise su panel de Envia; solo si allí no aparece la guía, reintenta con una clave NUEVA. El destino sale del contacto y destination lo pisa (ciudad ambigua o desconocida → 422 INVALID_CITY con candidates; el cityCode se obtiene con search_municipalities). dealId/quotationId/invoiceId y presetId deben ser de la organización (400 VALIDATION_ERROR con field si no). 201 = guía generada: data.status created con trackingNumber, trackUrl, labelUrl (etiqueta PDF/ZPL), pricing.total y balanceAfter. Si Envia la rechaza (saldo insuficiente, transportadora caída…) → 422 ENVIA_GENERATE_FAILED con error y shipmentId del borrador (queda en draft; se puede reintentar con la misma clave). Si error.code es CARRIER_ERROR, pide al usuario revisar su panel de Envia antes de reintentar: la transportadora pudo haber creado la guía. Estados posibles de un envío: draft (falló la generación o está en revisión si generateOutcome=unknown), created (guía generada; único cancelable — cancelar se hace desde la app: la API pública expone POST /api/shipments/{id}/cancel, pero este MCP no tiene tool de cancelar hasta verificar con una guía real cómo responde Envia y si devuelve el saldo), picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception. Terminales: delivered, returned, lost, canceled.
list_contact_shipmentsListar los envíos de UN contacto (GET /api/shipments?contactId=…), más recientes primero, con el último estado guardado (no consulta a Envia; para refrescar usa track_shipment). Requiere shipments:read y el módulo envios. Cada envío trae id, carrier, service, trackingNumber, trackUrl, labelUrl, status, events, destination, packages y pricing. Paginación: limit 20 por defecto y máximo 100 (un valor mayor da error de validación); la respuesta trae pagination { total, page, limit, pages }. status filtra por estado: draft, created, picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception.
quote_shipmentCotizar un envío con Envia.com (POST /api/shipments/rates). Es una CONSULTA: no crea nada ni cobra saldo. Requiere shipments:read y el módulo envios en el plan. Destino: manda contactId (se usa la dirección del contacto) o destination; con ambos, lo que venga en destination pisa lo del contacto. Sin ninguno → 400. La ciudad se resuelve contra el catálogo DANE: usa cityCode (DANE de 5 dígitos) o city + state inequívocos; si es ambigua o no se reconoce → 422 INVALID_CITY con candidates (pregúntale al usuario cuál es y reintenta con su cityCode; si no hay candidatos, búscalo con search_municipalities). Ojo: este código es INVALID_CITY, no el CITY_UNRESOLVED de contactos. Solo Colombia. El origen es el de Ajustes → Envíos de la app (no se manda; 422 ORIGIN_NOT_CONFIGURED si falta). Sin la API key de Envia → 422 ENVIA_NOT_CONFIGURED. Respuesta: data.rates ordenadas por totalPrice ascendente (COP), cada una con carrier + service (lo que pide create_shipment), deliveryEstimate y desglose; data.failed = transportadoras que no respondieron (no tumban la cotización). Consulta cada transportadora con timeout de 12 s: puede tardar.
track_shipmentRefrescar el rastreo de un envío consultando a Envia.com (POST /api/shipments/:id/track): agrega los eventos nuevos sin duplicar y actualiza status/statusRaw. Devuelve el envío actualizado (events con at, status, raw, description, location). No cobra. Requiere shipments:write y el módulo envios. Un envío sin guía (draft) → 409 SHIPMENT_NOT_TRACKABLE; inexistente o de otra organización → 404; Envia caído → 502 ENVIA_TRACK_FAILED. Para ver el último estado guardado sin consultar a Envia usa list_contact_shipments. Estados: draft, created, picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception. Terminales: delivered, returned, lost, canceled.