02Contrato de herramientas

Sección 02

Contrato de herramientas

Referencia de lo que el Gateway envía a cada herramienta y de lo que espera de vuelta.

Métodos del protocolo

MétodoExigenciaUso desde el Gateway
server/discoverDEBEPrimera llamada. Anuncia versiones soportadas, capacidades e identidad. Con el SDK oficial, este método solo lo instala la entrada HTTP moderna: si monta el transporte a mano, su servidor responde -32601 y no pasa la certificación. El servidor de referencia usa el camino correcto y lo documenta en el código.
tools/listDEBECatálogo con esquemas. Al registrar, al notificar cambio y al expirar la caché (15 min).
tools/callDEBEEjecución de una herramienta.
subscriptions/listenPUEDEAviso toolsListChanged cuando cambia el catálogo. Sin esto, el refresco es por caducidad.

El Gateway solo acepta resultados con resultType: "complete", que es lo que el SDK emite por defecto. El patrón de varias vueltas (input_required) no se consume: toda herramienta se resuelve en una sola llamada.

Transporte

  • Un único endpoint HTTPS POST, certificado válido.
  • Sin estado: la revisión 2026-07-28 eliminó las sesiones de protocolo y el saludo de inicialización. Cada petición es autónoma.
  • Cabeceras estándar Mcp-Method y Mcp-Name en cada POST.
  • Sin reanudación de flujo: si la conexión se corta antes de recibir la respuesta, el Gateway reintenta como petición nueva, con un identificador de petición nuevo y la misma clave de idempotencia. De ahí la exigencia de la garantía 1. Esto es distinto de un tiempo de espera agotado: ahí el Gateway no reintenta, informa al agente y la conversación sigue sin la herramienta.
  • tools/list DEBERÍA devolver las herramientas en orden estable. El propio estándar lo recomienda: un orden cambiante invalida la caché del modelo y degrada la latencia.

Definición de una herramienta

JSON
{
  "name": "abrir_caso_soporte",
  "title": "Abrir caso de soporte",
  "description": "Registra un caso de soporte para un cliente identificado y devuelve su número de radicado. Úsala solo cuando el cliente ya describió el problema. No la uses para consultar el estado de un caso existente.",
  "inputSchema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["documento", "tipo", "descripcion"],
    "properties": {
      "documento":   { "type": "string", "maxLength": 20 },
      "tipo":        { "enum": ["falla", "instalacion", "cobro"] },
      "descripcion": { "type": "string", "maxLength": 400 },
      "prioridad":   { "type": ["string", "null"], "enum": ["alta", "normal", null] }
    }
  },
  "outputSchema": {
    "type": "object",
    "required": ["radicado", "estado"],
    "properties": {
      "radicado":  { "type": "string" },
      "estado":    { "enum": ["abierto", "en_cola"] },
      "sla_horas": { "type": "integer" }
    }
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": false,
    "idempotentHint": true
  }
}

Reglas del esquema

ReglaExigenciaMotivo
Todo parámetro opcional acepta nullDEBESirven las dos formas: "type": ["string","null"] o anyOf con una rama {"type":"null"} (es lo que genera zod con .nullable()). Ver el aviso de abajo: es el requisito que más veces se pasa por alto y el que más rompe.
required con solo lo imprescindibleDEBEUn required largo hace que el modelo invente valores para poder llamar a la herramienta. Deje fuera todo lo que su sistema pueda resolver por defecto.
additionalProperties: falseDEBEUn esquema abierto invita al modelo a inventar campos. Es seguro: los metadatos del Gateway viajan en _meta, no en arguments.
Valores cerrados como enumDEBELo que no esté enumerado, el modelo lo adivina en lenguaje natural.
maxLength en todo texto libreDEBEProtege su backend y acota el coste del turno.
Sin identificadores internos como entrada obligatoriaDEBEEl modelo no puede construir un identificador suyo. Si hace falta, exponga primero una herramienta que lo resuelva.
Una operación por herramientaDEBEUn parámetro accion que multiplexa varias operaciones degrada la elección del modelo y no pasa la certificación.
Sin anidamiento profundo ni oneOf/anyOf en la raízDEBERÍAObjetos planos de una capa se aciertan mucho más.

Por qué los opcionales DEBEN aceptar null. El proveedor del modelo valida el llamado contra su esquema antes de entregárnoslo. Si un parámetro opcional declara un tipo estricto y el modelo envía null (cosa que hace a menudo en vez de omitir el campo), la API responde 400 y se cae la generación completa: no es que falle esa herramienta, es que el agente se queda sin respuesta en medio de la conversación. Nos ocurrió en producción con nuestras propias herramientas; desde entonces todos los opcionales de nuestro catálogo admiten null. Los suyos también deben admitirlo.

Clasificación de la herramienta: _meta en su definición

Cada herramienta DEBE declarar en el _meta de su propia definición a qué escenario del agente pertenece y con qué verbo se le describe al modelo. No se manda en un archivo aparte ni se configura en un panel: viaja con el catálogo, así que cuando usted añade una herramienta, ya viene clasificada.

Clave en el _meta de la herramientaExigenciaValor
com.asixto/capabilityDEBEUna de las claves canónicas de escenario de la página 04. Determina en qué conversaciones se expone.
com.asixto/verbDEBEEl verbo de negocio en infinitivo con el que el agente la nombra ante el cliente: "agendar una cita", "consultar el estado de un caso".
com.asixto/capabilitiesPUEDELista, si la misma herramienta sirve a varios escenarios (["pqr","soporte"]). Sustituye a la clave singular.
JSON
{
  "name": "abrir_caso_soporte",
  "description": "…",
  "inputSchema": { "…": "…" },
  "annotations": { "readOnlyHint": false, "destructiveHint": false, "idempotentHint": true },
  "_meta": {
    "com.asixto/capabilities": ["pqr", "soporte", "garantia"],
    "com.asixto/verb": "abrir un caso de soporte"
  }
}

Por qué es obligatorio y no un detalle. El agente no expone todo su catálogo en cada conversación: filtra por escenario, y el texto que le dice al modelo qué puede hacer se construye con esos verbos. Una herramienta sin capability no se expone nunca, y una sin verb queda disponible pero el modelo no sabe que resuelve ese caso, así que casi no la usa. Los dos campos son tres palabras de trabajo y son la diferencia entre una herramienta que se usa y una que no.

Una clave de escenario que no exista se rechaza en la certificación. No las invente: están todas en la página 04.

Nombres y longitudes

ElementoLímiteMotivo
Nombre de herramienta≤ 40 caracteres, ^[a-z0-9_]+$, único en su catálogoEl proveedor del modelo acepta ^[a-zA-Z0-9_-]{1,64}$. El Gateway prefija con ext__{empresa}__ para no colisionar con las herramientas nativas, y ese prefijo reserva 24 caracteres del presupuesto. Un nombre más largo no se puede exponer.
description de la herramienta≤ 700 caracteresEl proveedor rechaza descripciones de más de 1.024. El margen es para el prefijo de contexto que añade el agente.
description de cada parámetro≤ 200 caracteresCada carácter se paga en cada turno de la conversación.
  • snake_case, verbo + entidad: consultar_cliente, crear_cita, mover_etapa.
  • Estables. Renombrar equivale a publicar otra herramienta y obliga a recertificar.
  • El prefijo lo pone el Gateway; no aparece en su servidor.

Descripciones

La descripción es el texto que decide si el agente acierta al elegir la herramienta. No es documentación interna.

  • Una o dos frases: qué hace y cuándo NO usarla.
  • Sin tono, sin marca, sin instrucciones dirigidas al modelo ("responde amablemente…"). Esa capa la pone el agente; si usted la duplica, entra en conflicto.
  • Sin datos volátiles (precios, horarios, inventario): eso va en la respuesta, no en la descripción, que se cachea.

Anotaciones y su efecto

AnotaciónEfecto en el Gateway
readOnlyHint: trueSe clasifica como nivel read. Se ejecuta sin confirmación.
destructiveHint: trueSe clasifica como nivel admin: queda fuera de los agentes con nivel read o write.
idempotentHint: trueDeclara que repetir la llamada es seguro. Hoy el runtime no reintenta por su cuenta: ante timeout degrada y sigue la conversación. La anotación describe su herramienta; no active un comportamiento nuestro que aún no existe.
AusentesSe asume el caso más restrictivo: escritura no idempotente, nivel admin.

Las anotaciones son pistas, no autoridad: el estándar las define como no verificables por el consumidor. El Gateway las usa para clasificar el nivel; la decisión de exponer o no es del control de acceso de Asixto y de la configuración del tenant. Declarar de solo lectura una herramienta que escribe es un incumplimiento de contrato y bloquea la certificación.

No delegue la confirmación en el agente. Que el agente pida confirmación antes de una acción sensible es una instrucción al modelo, y las instrucciones al modelo no son deterministas: en nuestras mediciones se cumplen alrededor del 92% de las veces.

Para cualquier operación irreversible (cobrar, cancelar, borrar) la guarda que cuenta es la de su sistema: exija un campo de confirmación explícito en el esquema, valide estado y propiedad, y rechace lo que no cuadre. El porcentaje restante, cuando hay dinero o una cita de por medio, se convierte en un incidente.

Metadatos que envía el Gateway

Van en el campo _meta de los params de tools/call, no en arguments. Esto es deliberado: _meta es el punto de extensión que define el propio estándar, así que su inputSchema puede seguir cerrado sin rechazar nada. El SDK se los entrega sin que usted declare nada.

Clave en _metaTipoUso
com.asixto/idempotencyKeystringClave determinista por operación lógica, derivada de la conversación, la herramienta y la firma de los argumentos. En toda escritura: persistirla y no repetir el efecto.
com.asixto/conversationIdstringÁmbito de la conversación en curso. Dos conversaciones distintas nunca comparten clave de idempotencia.
com.asixto/requestIdstringCorrelación de traza. Regístrelo: es la referencia para cualquier incidente.
com.asixto/channel"chat" o "voice"Canal. Úselo: en voice la respuesta se lee en voz alta y aplican las restricciones de la página 07.
com.asixto/companyIdstringEmpresa del tenant. Relevante si opera varias marcas o unidades de negocio detrás del mismo servidor.
com.asixto/priceListIdstring o nullLista de precios vigente para esa conversación. Si su catálogo tiene varias, el precio debe salir de esta, no del precio por defecto.
com.asixto/currencystringMoneda del tenant en ISO 4217 (COP, USD…). Los importes que devuelva deben ser de esta moneda.
com.asixto/timezonestringZona horaria del tenant (America/Bogota). Toda fecha que devuelva debe llevar offset coherente con ella.
JSON
{
  "method": "tools/call",
  "params": {
    "name": "abrir_caso_soporte",
    "arguments": { "documento": "CC1032…", "tipo": "falla", "descripcion": "…" },
    "_meta": {
      "com.asixto/idempotencyKey": "9f2a7c…",
      "com.asixto/conversationId": "cnv_77b…",
      "com.asixto/requestId": "req_c41…",
      "com.asixto/channel": "voice",
      "com.asixto/companyId": "cmp_31a…",
      "com.asixto/priceListId": "pl_mayorista",
      "com.asixto/currency": "COP",
      "com.asixto/timezone": "America/Bogota"
    }
  }
}

Lo que el Gateway NO envía: una identidad autenticada del usuario final. La conversación ocurre en WhatsApp, en un chat web o por teléfono, y ahí no hay sesión. Quien dice ser el cliente lo afirma en la conversación. Consecuencia directa para usted: la validación de propiedad (garantía 2 de la página 05) se hace contra el identificador que su propia herramienta de consulta ya resolvió en esa conversación, no contra un token de usuario.

Forma de la respuesta

  • content: texto breve para el modelo. Es lo que puede terminar leído en voz alta.
  • structuredContent: los datos, conformes a su outputSchema.

Devuelva datos ya resueltos: el nombre en vez del identificador, el precio final en vez de sus componentes, la fecha completa en vez de un código interno. Una respuesta pensada para una tabla en pantalla no sirve en una llamada telefónica.

Tamaño: 3.000 caracteres. No es un consejo: el runtime sanea y recorta a 3.000 caracteres todo resultado antes de inyectarlo al modelo, dejando una marca de truncado. No es un error, así que su servidor no se entera: simplemente el agente responde con datos incompletos. Si su respuesta natural es más grande, pagine (página 07).

Una búsqueda sin resultados es un éxito, no un error. Devuelva la lista vacía y el total en 0. Si la reporta como error, el agente entra en la rama de fallo técnico y reintenta o cambia de paso en vez de decirle al cliente, con naturalidad, que no encontró nada.

Prohibido en la respuesta: identificadores internos, datos de terceros, campos administrativos, trazas, tokens. El resultado entra al contexto del modelo y queda en la traza de la conversación.

Un fallo de negocio usa la misma respuesta, marcada como error. No es un error de transporte: se devuelve con isError: true y el objeto de error en structuredContent. La forma exacta y los seis valores admitidos están en la página 06.

JSON
{
  "isError": true,
  "content": [{ "type": "text", "text": "{\"error\":{…}}" }],
  "structuredContent": { "error": { "error_type": "NOT_FOUND", "user_message": "No encontré una cita con esos datos." } }
}

El campo resultType que exige el estándar lo pone el SDK, no usted.

Media (imágenes, PDF, audio): no en la v1. El envío de archivos por el canal exige un identificador de media resuelto contra el backend de Asixto; una URL o un adjunto que devuelva su herramienta no se le entrega al cliente final, y en llamada se elimina del texto. Si su caso de uso lo necesita, escálelo antes de construirlo.