API existente como tools de agentes: fachada de intención, no 200 endpoints
Resumen
Un OpenAPI con 80 paths no es un toolset. Contrato para envolver una API que ya tienes: 5–12 tools de intención, un adapter que valida y recorta, auth fuera del contexto y el generador OpenAPI solo como borrador. Incluye matriz REST vs tool, checklist y el error de volcar el spec crudo al modelo.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Tu API de facturación, CRM o tickets ya existe. El error de primer deploy es pegarle al modelo los 80 paths del OpenAPI como si cada GET /v1/invoices/{id}/line_items fuera una tool. El modelo no es un cliente HTTP: no lee operationId, no entiende paginación cursor y no debe ver el bearer. La API se envuelve: 5–12 tools de intención, un adapter que valida y recorta, auth fuera del contexto. El generador OpenAPI sirve de borrador, no de contrato.
No es function calling: esa guía cubre el loop (schema → llamada → resultado). Aquí el problema es qué superficie expones cuando el backend ya está escrito. No es MCP: MCP es el cable; esto es el recorte de intención aunque hables HTTP plano. No es versionar schemas: versionar evita breaking; la fachada evita que existan 80 tools que nunca debieron nacer.
Contrato: el modelo elige una intención (“crear factura borrador”, “buscar cliente por NIT”). El adapter traduce a REST, inyecta auth y devuelve un recorte. Cero spec crudo en el prompt.
REST no es un toolset
Un humano con Postman navega 40 endpoints. Un modelo con 40 tools adivina, mezcla list con search y rellena query params que tu API ignora. OpenAI lo dice en las best practices de function calling (verificado 2026-09-06): nombres y descripciones explícitos, no hacer que el modelo rellene argumentos que ya conoces, y menos de 20 functions al inicio de un turno. Anthropic, en Define tools (mismo día): descripciones de 3–4 frases, consolidar operaciones relacionadas y devolver solo señal alta.
| Superficie REST | Tool de intención | Por qué |
|---|---|---|
GET /customers, GET /customers/{id}, GET /customers:search | buscar_cliente({ nit?, email?, nombre? }) | Un job; el adapter elige path y 404 vs lista vacía |
POST /invoices + 12 campos opcionales | crear_factura_borrador({ cliente_id, items[] }) | Defaults, moneda e impuestos viven en código |
GET /invoices?cursor=&limit= | listar_facturas({ cliente_id, estado, limite? }) | Cursor y page size no son decisión del modelo |
POST /invoices/{id}/void | anular_factura({ factura_id, motivo }) | Irreversible: un nombre, no un verbo HTTP genérico |
Header Authorization | ningún parámetro | El adapter lo inyecta; si el modelo lo ve, se filtra |
La columna izquierda es el OpenAPI. La derecha es lo que el modelo puede elegir sin improvisar transporte.
Fachada de 5 a 12, no un espejo
Empieza por los jobs reales del agente, no por el índice de paths. Un agente de cobros en Guatemala casi nunca necesita más que:
buscar_clientever_saldocrear_factura_borradorenviar_facturaregistrar_pagoanular_facturalistar_facturas
Siete tools. Si mañana aparece “nota de crédito”, agrega una octava; no abras POST /credit-notes más tres GETs de catálogo. OpenAI sugiere evaluar con distinto número de functions y dejar las raras detrás de tool search (gpt-5.4+). Anthropic pide namespacing (billing_crear_factura, no create) cuando hay más de un servicio.
Tope práctico: 12 tools visibles por turno. Por encima, el modelo elige mal y pagas tokens: las definitions cuentan como input. Si tu OpenAPI tiene 80 operations, el 90 % es infra para humanos (webhooks, admin, exports). Eso no entra al loop.

El adapter hace el trabajo sucio
La tool no es un fetch. Es un handler con tres obligaciones:
- Validar el JSON del modelo contra el schema antes de tocar la red. Con OpenAI,
strict: trueexigeadditionalProperties: falsey todos los campos enrequired(opcionales como["string", "null"]). Si el schema no cumple, Responses puede caer astrict: false; no lo dejes implícito. - Traducir a la llamada REST: path, query, body, idempotency key. El modelo no inventa
Idempotency-Keynilimit=500. - Recortar la respuesta. Un
GET /invoices/123puede devolver 40 KB de line items, metadata y URLs firmadas. El modelo necesita id, estado, total, moneda y fecha. El resto es truncar resultados: presupuesto por tool, preview + handletruncated, tijera muda prohibida.
Auth fuera del contexto. El token vive en el proceso, en un secret store o en /run/secrets. Si el schema tiene api_key o authorization, el modelo lo va a emitir o a pedir. Eso es fuga, no “flexibilidad”. Ver secretos de agentes.
async function crearFacturaBorrador(args: unknown, ctx: TenantCtx) {
const input = CrearFactura.parse(args); // Zod / JSON Schema
const res = await billing.fetch("/v1/invoices", {
method: "POST",
headers: {
authorization: `Bearer ${ctx.token}`, // nunca sale del adapter
"idempotency-key": ctx.idempotencyKey,
},
body: JSON.stringify(toBillingPayload(input, ctx.defaults)),
});
const full = await res.json();
return pick(full, ["id", "status", "total", "currency", "due_date"]);
}
El intern test de OpenAI aplica: un humano con solo el schema ¿sabría usarlo? Si pregunta “¿NIT o tax_id?”, el schema está mal.
OpenAPI es borrador, no contrato
Un generador (o un script que recorre paths) produce un espejo 1:1: un tool por operationId, parámetros de query crudos, securitySchemes colados y descripciones copiadas del YAML. Sirve para inventar la primera lista y tachar. No se commitea como toolset.
Flujo que sí escala:
- Exporta o copia el OpenAPI (la spec vive en swagger.io/specification, verificada 2026-09-06).
- Marca las 5–12 operations que cubren jobs del agente.
- Reescribe nombres y descripciones en idioma de intención. Anthropic pide ≥3–4 oraciones: qué hace, cuándo sí, cuándo no, qué no devuelve.
- Junta operaciones que siempre van en secuencia (
get+mark_seen→ unver_saldoque ya marca). - Escribe el adapter a mano. Tests sin LLM: JSON válido, JSON extra, 401, 404, payload enorme. Eso es tests de tools, no un eval del modelo.
Si el generador te deja 47 tools “por si acaso”, no las cargues. Tool search existe precisamente para no inyectar el catálogo entero en el system message.

Matriz de decisión
| Señal | Fachada (sí) | Espejo OpenAPI (no) |
|---|---|---|
| Jobs del agente ≤ 12 | Un tool por job | Un tool por path |
El humano ya tiene cliente_id en sesión | Cero parámetro; el código lo pone | customer_id en el schema |
| Respuesta > 2–4 KB | Recorte + truncated | JSON completo al LLM |
| Acción irreversible | Nombre explícito + confirmación en el handler | POST genérico con action libre |
| Token / cookie / HMAC | Header en el proceso | Campo del tool |
| Cambio breaking del backend | Versión de toolset, no 80 diffs | Regenerar todo el spec encima |
Claude acepta nombres ^[a-zA-Z0-9_-]{1,64}$. No uses puntos ni espacios copiados de operationId tipo Invoices_GetById. Prefijo de servicio + verbo de negocio.
Checklist de release
- ≤ 12 tools visibles por turno; el resto no existe o va a tool search.
- Cada tool tiene descripción de intención (cuándo sí / cuándo no) y parámetros que un intern usaría sin Slack.
-
strict: true(o equivalente) conadditionalProperties: false; opcionales como unión connull. - Cero
api_key, cookie o URL firmada en schema, logs o tool result. - Adapter con tests sin LLM: schema inválido, 4xx, recorte de payload.
- OpenAPI guardado como referencia; el toolset se reviewa como código, no como dump del generador.
- Acciones irreversibles con nombre propio, no un CRUD genérico.
FAQ
¿Y si el agente “necesita” explorar la API? No. Exploración es trabajo de un humano con docs. El agente ejecuta jobs. Si aparece un job nuevo, abres una tool nueva con PR, no le das request({ method, path, body }).
¿MCP no resuelve esto? MCP transporta tools. Si publicas 80 tools MCP clonadas del OpenAPI, el modelo sigue ahogado. La fachada es anterior al cable.
¿Puedo dejar GET libres y wrappear solo los POST? Los GET ruidosos también saturan contexto (listas sin recorte, PII, URLs). Envuelve lectura y escritura. La diferencia es el permiso del handler, no el verbo HTTP.
¿Qué hago con webhooks y admin? No son tools del agente de producto. Viven en el backend y en CI, no en el loop.
Siguiente paso: envuelve un job hoy (buscar + una escritura) con adapter y tests sin LLM. Luego la tercera tool. El curso gratis cubre el loop; esta guía evita que ese agente hable REST crudo.
Lecturas relacionadas
Sigue explorando Herramientas y otras piezas para builders.

Max turns y recursion_limit: cómo cortar un agente que no para

Truncar tool results en agentes: recortar el payload, no el contrato

Tool calling en paralelo: cuándo sí, cuándo no y cómo devolver resultados
