Cap 8: Errores Estructurados y tool_choice
MCP isError Flag
Cuando un MCP tool falla, debe comunicar el fallo usando el flag isError: true en la respuesta. Esto le indica al agente que la operación no fue exitosa y debe tomar una decisión de recovery.
{
"content": [
{
"type": "text",
"text": "Customer not found: ID 'cust_999' does not exist in the active customer database."
}
],
"isError": true
}
Sin isError, el agente puede interpretar un mensaje de error como un resultado válido y continuar el flujo incorrectamente.
Categorías de errores
No todos los errores son iguales. Clasificarlos permite al agente tomar decisiones de recovery apropiadas:
Transient (reintentar)
Fallos temporales que pueden resolverse con un retry:
- Network timeouts
- Rate limiting (429)
- Service temporarily unavailable (503)
Validation (corregir input)
El input proporcionado no cumple los requisitos:
- Formato inválido (email sin @, fecha mal formateada)
- Campo requerido faltante
- Valor fuera de rango permitido
Business (regla de negocio)
La operación viola una política o regla del dominio:
- “No se puede cancelar un pedido ya enviado”
- “El descuento máximo permitido es 30%”
- “Usuario no tiene suscripción activa”
Permission (acceso denegado)
El caller no tiene permisos para la operación:
- Token expirado o inválido
- Rol insuficiente
- Recurso fuera del scope del usuario
El problema de errores uniformes
Cuando todos los errores retornan el mismo formato genérico:
{
"content": [{ "type": "text", "text": "Operation failed" }],
"isError": true
}
El agente no puede decidir si debe reintentar, corregir el input, informar al usuario, o escalar. Esto causa loops de retry infinitos o abandonos prematuros.
Structured Error Metadata
Incluye metadata estructurada que permita al agente tomar decisiones:
{
"content": [
{
"type": "text",
"text": "Cannot apply 50% discount. Maximum allowed discount is 30% per company policy."
}
],
"isError": true,
"_meta": {
"errorCategory": "business",
"isRetryable": false,
"errorCode": "DISCOUNT_LIMIT_EXCEEDED",
"maxAllowed": 30,
"requested": 50
}
}
Campos recomendados
| Campo | Tipo | Propósito |
|---|---|---|
errorCategory | string | transient, validation, business, permission |
isRetryable | boolean | ¿Tiene sentido reintentar? |
errorCode | string | Código máquina-legible para lógica condicional |
description | string | Explicación human-readable para el usuario |
Business rules: retriable false + explicación
Para errores de reglas de negocio, siempre marca isRetryable: false e incluye una explicación que el agente pueda transmitir al usuario:
{
"isError": true,
"_meta": {
"errorCategory": "business",
"isRetryable": false,
"description": "Las reservaciones deben hacerse con al menos 24 horas de anticipación."
}
}
El agente sabe que no debe reintentar y tiene un mensaje claro para el usuario.
Recovery patterns
Local recovery en subagentes
Cada subagente debe manejar sus propios errores transient y de validación localmente:
Subagente de búsqueda:
1. Intenta search_products(query)
2. Si timeout → retry con backoff (hasta 3 intentos)
3. Si validation error → corregir query y reintentar
4. Si business/permission error → propagar al coordinator
Propagar solo errores irresolubles al agente coordinador. No escales timeouts que se pueden resolver con un retry.
Distinguir access failures de empty results
Dos situaciones muy diferentes:
// Access failure — algo salió mal, reintentar
{
"isError": true,
"_meta": { "errorCategory": "transient", "isRetryable": true }
}
// Valid empty result — la búsqueda funcionó, no hay matches
{
"content": [{ "type": "text", "text": "No results found for query 'xyz'" }],
"isError": false,
"_meta": { "resultCount": 0 }
}
Un isError: false con 0 resultados no es un error — es una respuesta válida que el agente debe aceptar sin reintentar.
tool_choice: controlando la selección
El parámetro tool_choice en la API controla cómo Claude selecciona tools:
auto (default)
Claude decide libremente si usar un tool o responder con texto:
{ "tool_choice": { "type": "auto" } }
Apropiado para: la mayoría de conversaciones donde el modelo debe decidir cuándo es relevante usar herramientas.
any (forzar alguna herramienta)
Claude debe usar algún tool — no puede responder solo con texto:
{ "tool_choice": { "type": "any" } }
Apropiado para: pasos de un pipeline donde siempre se necesita una acción.
Forced (tool específico)
Claude debe usar un tool particular:
{ "tool_choice": { "type": "tool", "name": "search_database" } }
Apropiado para: pasos predeterminados donde sabemos exactamente qué tool se necesita.
Límites de tools por agente
El problema de demasiados tools
La calidad de selección se degrada conforme aumenta el número de tools disponibles:
| Tools disponibles | Calidad de selección |
|---|---|
| 4-5 | Excelente — selección precisa |
| 8-10 | Buena — errores ocasionales |
| 15+ | Degradada — misrouting frecuente |
Scoped tool access
En lugar de dar 18 tools a un solo agente, distribuye por rol:
Agente de búsqueda: search_products, filter_results, sort_results, get_details
Agente de compra: create_order, apply_discount, process_payment, send_confirmation
Agente de soporte: get_ticket, update_status, search_knowledge_base, escalate
Cada agente tiene 4-5 tools de su dominio + acceso limitado a tools cross-role de alta frecuencia (ej: todos pueden usar get_customer_info).
Regla práctica
Si un agente tiene más de 5-6 tools, pregúntate si debería ser dos agentes.
← Cap 7: MCP Servers · Índice · Cap 9: CLAUDE.md Jerarquía →