Un análisis profundo de cómo Claude Code descubre, despacha y ejecuta herramientas
Introducción
En un artículo anterior, realizamos ingeniería inversa del sistema de memoria del Anthropic Harness a partir de su código fuente filtrado. La memoria resultó ser sorprendentemente simple: archivos markdown, metadatos frontmatter e ingeniería de prompts. El sistema de herramientas es todo lo contrario. Es el subsistema más elaborado de toda la base de código: más de 43 herramientas, un pipeline de ejecución en streaming, un sistema de permisos en capas, un framework de hooks y un planificador de concurrencia, todo interconectado para convertir un modelo de lenguaje sin estado en algo capaz de leer archivos, ejecutar comandos de shell, buscar en la web y generar subagentes.
Este artículo recorre el ciclo de vida de una herramienta: desde cómo se define, hasta cómo se despachan las llamadas a herramientas del modelo y cómo los resultados regresan a la conversación. En términos generales, el sistema se compone de cuatro capas: una interfaz de herramienta que implementa cada herramienta, un registro que arma el conjunto de herramientas, un pipeline de despacho que valida, verifica permisos y ejecuta cada llamada, y un planificador de concurrencia que decide qué se ejecuta en paralelo.
La Arquitectura de un Vistazo

La Interfaz de la Herramienta
Cada herramienta en Claude Code implementa la misma interfaz, definida en Tool.ts. El tipo es genérico sobre tres parámetros: Input (un esquema de Zod), Output (el tipo de resultado) y P (datos de progreso). En la práctica, una herramienta es un objeto con aproximadamente 30 métodos, pero solo unos pocos importan para entender el sistema.
La forma central:
1type Tool<Input, Output, P> = {2 name: string3 inputSchema: ZodType // Esquema Zod para validación de entrada4 call(input, context, canUseTool,5 parentMessage, onProgress): Promise<ToolResult>67 // Declaraciones de comportamiento8 isConcurrencySafe(input): boolean // ¿Puede ejecutarse en paralelo?9 isReadOnly(input): boolean // ¿Operación de solo lectura?10 isDestructive(input): boolean // ¿Acción destructiva?1112 // Permisos y validación13 checkPermissions(input, context): Promise<PermissionResult>14 validateInput(input, context): Promise<ValidationResult>1516 // Integración con API17 description(input, options): Promise<string>18 prompt(options): Promise<string> // Texto del prompt del sistema para esta herramienta19 mapToolResultToToolResultBlockParam(result, toolUseId): ToolResultBlockParam2021 // Renderizado de UI (React)22 renderToolUseMessage(input, options): ReactNode23 renderToolResultMessage(content, ...): ReactNode24}
Ninguna herramienta implementa esto desde cero. Una función de fábrica llamada buildTool() completa con valores predeterminados seguros:

Los valores predeterminados son deliberadamente conservadores. Un autor de herramienta que olvide declarar seguridad de concurrencia obtendrá ejecución en serie. Un autor que olvide implementar verificaciones de permisos obtendrá el flujo de permisos predeterminado. El sistema falla de forma segura.
El tipo ToolResult es digno de mención:
1type ToolResult<T> = {2 data: T // La salida real3 newMessages?: Message[] // Mensajes de seguimiento opcionales4 contextModifier?: (ctx) => ToolUseContext // Modifica el contexto para la siguiente herramienta5 mcpMeta?: { ... } // Metadatos del protocolo MCP6}
El contextModifier es importante: permite que una herramienta cambie el contexto de ejecución para las herramientas siguientes en el mismo turno. Así es como herramientas como EnterWorktree cambian el directorio de trabajo para todo lo que sigue. Críticamente, los modificadores de contexto solo están permitidos para herramientas que no son seguras para concurrencia. Si una herramienta se ejecuta en paralelo, no puede modificar el estado compartido.
El Registro de Herramientas
Todas las herramientas se registran en una sola función: getAllBaseTools() en tools.ts. Devuelve un arreglo plano. Algunas herramientas están siempre presentes; otras están controladas por banderas de funcionalidad, variables de entorno o verificaciones de plataforma.
Siempre disponibles (16 herramientas)

Herramientas con Banderas de Funcionalidad (~27 herramientas)
Las herramientas restantes se incluyen condicionalmente. Algunas están controladas por variables de entorno (USER_TYPE=ant para herramientas internas de Anthropic como config y tungsten). Algunas están controladas por banderas de funcionalidad a través de Statsig (web_browser, sleep, monitor). Algunas son específicas de plataforma (powershell en Windows). Otras están controladas por condiciones compuestas: la herramienta repl requiere tanto USER_TYPE=ant como una bandera de funcionalidad REPL.
Lista completa de herramientas con banderas de funcionalidad
Solo Ant: config, tungsten, suggest_background_pr, repl (también necesita bandera REPL)
Banderas de funcionalidad: web_browser, web_search, sleep, monitor, overflow_test, ctx_inspect, terminal_capture, list_peers, workflow, snip
Disparadores de agente: cron_create, cron_delete, cron_list, remote_trigger
Kairos (agente proactivo): sleep, send_user_file, push_notification, subscribe_pr
Enjambres multiagente: team_create, team_delete, send_message
Todo v2: task_create, task_get, task_update, task_list
Entorno: lsp (ENABLE_LSP_TOOL), enter_worktree / exit_worktree (modo worktree), powershell (Windows)
Descubrimiento de herramientas: tool_search (cuando el conjunto de herramientas es grande)
Solo prueba: testing_permission (NODE_ENV=test)
Herramientas MCP
Más allá de las herramientas integradas, Claude Code admite servidores Model Context Protocol (MCP) — procesos externos que exponen sus propias herramientas a través de un protocolo estandarizado. Las herramientas MCP se registran dinámicamente en tiempo de ejecución desde los servidores conectados y se envuelven en la misma interfaz Tool. Desde la perspectiva del pipeline de despacho, una herramienta MCP es indistinguible de una herramienta integrada.
Cada herramienta MCP lleva metadatos sobre su servidor de origen (mcpInfo: { serverName, toolName }), que se utilizan para reglas de permisos, manejo de errores y autenticación. Cuando una herramienta MCP falla con un error de autenticación, el sistema actualiza automáticamente el estado del servidor a needs-auth y muestra el problema al usuario.
Ensamblaje del Conjunto de Herramientas
Tres funciones ensamblan el conjunto final de herramientas:
- getAllBaseTools() — devuelve la lista sin procesar de más de 43 herramientas integradas con las banderas de funcionalidad aplicadas
- getTools(permissionContext) — filtra por reglas de denegación y isEnabled()
- assembleToolPool(permissionContext, mcpTools) — fusiona herramientas integradas y MCP
La estrategia de fusión en assembleToolPool() es deliberada:
Las herramientas integradas van primero, por lo que en caso de colisión de nombres, gana la integrada. La ordenación alfabética dentro de cada partición mantiene el orden estable entre sesiones, lo que importa para el almacenamiento en caché de prompts — el arreglo de herramientas es parte de la solicitud a la API, y reordenarlo invalidaría el caché.
La estrategia de fusión en assembleToolPool() es deliberada:
1// Ordenar cada partición alfabéticamente, concatenar, deduplicar2const byName = (a, b) => a.name.localeCompare(b.name)3return uniqBy(4 [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),5 'name',6)
Serialización de la API
Antes de que las herramientas lleguen a la API de Claude, toolToAPISchema() convierte el esquema Zod de cada herramienta al formato JSON Schema de la API de Anthropic.
El Pipeline de Despacho
Cuando Claude responde, su mensaje puede contener bloques tool_use — solicitudes estructuradas para invocar herramientas. El pipeline de despacho procesa estos bloques a través de siete fases. Cada llamada a herramienta pasa por todas las fases, en orden.
Fase 1: Extracción
En el bucle principal de consulta (query.ts), los bloques tool_use se filtran del mensaje del asistente:
1const msgToolUseBlocks = message.message.content.filter(2 content => content.type === 'tool_use',3) as ToolUseBlock[]
Cada bloque tiene un name, un objeto input y un id único. El id es fundamental — el resultado de la herramienta debe hacer referencia al mismo id cuando se envía de vuelta a la API, o la conversación se rompe.
Fase 2: Validación de Entrada
El esquema Zod de la herramienta valida la entrada sin procesar usando safeParse() — una variante que no lanza excepciones y devuelve datos válidos o un error estructurado. Si la validación falla, el modelo recibe un mensaje de error formateado con pistas del esquema y la ejecución se detiene para esa herramienta. No se ejecuta código con entrada no válida.
1const parsedInput = tool.inputSchema.safeParse(input)2if (!parsedInput.success) {3 let errorContent = formatZodValidationError(tool.name, parsedInput.error)4 // Devolver error al modelo, omitir ejecución5}
Después de la validación de Zod, algunas herramientas ejecutan una segunda verificación validateInput() para validación semántica que no se puede expresar en un esquema — por ejemplo, verificar que una ruta de archivo sea absoluta, no relativa.
Fase 3: Hooks Previos a la Herramienta
Antes de las verificaciones de permisos, se ejecutan los hooks configurados por el usuario. Estos son comandos o scripts de shell externos que se activan en las invocaciones de herramientas. Un hook previo puede:
- Permitir la llamada a la herramienta, omitiendo el aviso de permiso interactivo
- Denegar la llamada a la herramienta directamente
- Modificar la entrada antes de la ejecución
- Bloquear la ejecución con un mensaje de error
- Proporcionar contexto adicional al usuario
Una invariante crítica: la permisión de un hook no omite las reglas de denegación de la configuración. El código fuente tiene un comentario explícito al respecto: "Hook 'allow' does NOT bypass settings.json deny/ask rules". La intención es que los hooks pueden abrir puertas, pero no anular candados.
Fase 4: Verificación de Permisos
El sistema de permisos es la parte más intrincada del pipeline. Se resuelve a través de múltiples capas, en orden:
- Reglas de denegación — se verifican primero. Si alguna regla de denegación coincide, la ejecución se detiene inmediatamente. Las reglas de denegación son definitivas y no pueden ser anuladas por ninguna otra capa.
- Reglas de consulta — si coinciden, se solicita la aprobación del usuario (a menos que se aplique la aprobación automática de sandbox para Bash).
- Permisos específicos de la herramienta — se ejecuta el método checkPermissions() de la propia herramienta. BashTool, por ejemplo, analiza el comando para verificar reglas a nivel de subcomando.
- Verificaciones de seguridad — protecciones codificadas para rutas sensibles (.git/, .claude/, configuraciones de shell). Estas son inmunes a la omisión — incluso en modo de omisión total, requieren aprobación interactiva.
- Modo de permiso — el modo configurado del usuario determina el comportamiento predeterminado.
- Reglas de permisión — se verifican al final. Si una regla de permisión coincide y no se activó ninguna regla de denegación/consulta, la herramienta procede.
Modos de permiso
default — Siempre solicitar al usuario en decisiones de "consulta".
acceptEdits — Permitir automáticamente operaciones de archivo seguras (lecturas, ediciones), solicitar para todo lo demás.
bypassPermissions — Permitir automáticamente todo excepto reglas de denegación y verificaciones de seguridad.
plan — Aprobar un plan primero, luego seguir el modo anterior para la ejecución.
auto — Usar un clasificador de IA para decidir si permitir o solicitar.
dontAsk — Convertir todas las decisiones de "consulta" en "denegación" — nunca solicitar, simplemente rechazar.
Las reglas de permisos provienen de múltiples fuentes, resueltas en orden de prioridad: policySettings, localSettings, projectSettings, userSettings, flagSettings, cliArg, command, session. Esto permite que las políticas de la organización anulen las preferencias del usuario, y que los argumentos de la CLI anulen ambas.
Fase 5: Ejecución
Si se concede el permiso, se invoca el método call() de la herramienta:
1const result = await tool.call(2 callInput,3 { ...toolUseContext, toolUseId: toolUseID },4 canUseTool,5 assistantMessage,6 progress => onToolProgress({ toolUseID: progress.toolUseID, data: progress.data })7)
Cinco argumentos: la entrada validada, un contexto de ejecución (directorio de trabajo, controlador de cancelación, estado de la aplicación), un callback de permiso (para herramientas que necesitan solicitar permisos adicionales durante la ejecución), el mensaje del asistente padre y un callback de progreso para actualizaciones en tiempo real. La duración se rastrea globalmente.
Un detalle sutil: la entrada que se pasa a call() es la entrada original del modelo, no la versión completada que vieron los hooks y los permisos. Esto preserva la consistencia de la transcripción — la llamada a la herramienta registrada en la conversación coincide exactamente con lo que generó el modelo.
Fase 6: Hooks Posteriores a la Herramienta
Después de la ejecución, se activan los hooks posteriores a la herramienta. Estos pueden modificar la salida de la herramienta MCP, proporcionar contexto adicional o bloquear la continuación de la conversación. También hay un hook PostToolUseFailure separado que se activa solo en errores, dando a los sistemas externos la oportunidad de registrar fallos o sugerir soluciones.
Fase 7: Mapeo de Resultados
Cada herramienta implementa mapToolResultToToolResultBlockParam() para convertir su salida al formato ToolResultBlockParam de la API de Anthropic — un bloque tool_result con una referencia tool_use_id y contenido estructurado o de cadena.
Si el resultado supera un umbral de tamaño, se persiste en disco en sessionDir/tool-results/{toolUseId}.txt y se envía a la API una vista previa con una referencia al archivo. Esto evita que las salidas grandes (un archivo de 10,000 líneas leído, la salida de un comando verboso) inflen el contexto de la conversación.
El Planificador de Concurrencia
Cuando el modelo emite múltiples llamadas a herramientas en un solo mensaje, no todas se ejecutan a la vez. Un planificador las particiona en lotes basándose en la seguridad de concurrencia.
El algoritmo es simple. Recorre las llamadas a herramientas en orden. Para cada una, verifica isConcurrencySafe(input). Si es segura y el lote anterior también era seguro, la agrega al lote. De lo contrario, inicia un nuevo lote.
1// Simplificado de toolOrchestration.ts2for (const toolUse of toolUseMessages) {3 const isSafe = tool.isConcurrencySafe(parsedInput)4 if (isSafe && lastBatch.isConcurrencySafe) {5 lastBatch.blocks.push(toolUse) // Fusionar en lote concurrente6 } else {7 batches.push({ isConcurrencySafe: isSafe, blocks: [toolUse] })8 }9}
Los lotes seguros se ejecutan concurrentemente (hasta un límite de 10, configurable mediante CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY). Los lotes no seguros se ejecutan en serie, una herramienta a la vez. Los modificadores de contexto solo se aplican entre lotes, no dentro de ellos.
En la práctica, esto significa que un mensaje como "lee estos 5 archivos" produce un lote concurrente, mientras que "lee este archivo, luego edítalo" produce dos lotes en serie. El modelo puede incluso activar ambos patrones en un solo turno — las llamadas de solo lectura consecutivas se agrupan, y la primera escritura rompe el lote.
El Ejecutor en Streaming
Hay una segunda ruta de ejecución: el StreamingToolExecutor. Cuando el streaming está habilitado, las herramientas comienzan a ejecutarse mientras el modelo todavía está generando su respuesta. A medida que cada bloque tool_use se completa en el flujo, se encola inmediatamente para su ejecución en lugar de esperar la respuesta completa.
El ejecutor en streaming usa las mismas reglas de concurrencia pero agrega un comportamiento: cascada de errores de Bash. Si un comando de Bash falla mientras las herramientas hermanas se ejecutan en paralelo, el ejecutor cancela todas las herramientas hermanas. La lógica es que un comando de Bash fallido probablemente invalida el contexto en el que están operando otras herramientas — continuar con ellas pierde tiempo y puede causar errores confusos.
1if (isErrorResult && tool.block.name === BASH_TOOL_NAME) {2 this.hasErrored = true3 this.siblingAbortController.abort('sibling_error')4}
Un Ejemplo Práctico
Para hacerlo concreto, rastreemos lo que sucede cuando el modelo decide leer un archivo. El modelo emite:
1{2 "type": "tool_use",3 "id": "toolu_01XYZ",4 "name": "read",5 "input": { "file_path": "/src/index.ts" }6}
Extracción: query.ts filtra esto del contenido del mensaje del asistente.
- Búsqueda de herramienta: findToolByName(tools, "read") encuentra FileReadTool.
- Validación de entrada: Zod analiza { file_path: "/src/index.ts" } contra z.object({ file_path: z.string(), offset: z.number().optional(), limit: z.number().optional(), pages: z.string().optional() }). Pasa la validación.
- Hooks previos: Cualquier hook configurado por el usuario se activa. Ninguno modifica la entrada.
- Verificación de permisos: checkPermissions() de FileReadTool llama a checkReadPermissionForTool(). Las herramientas de lectura generalmente están permitidas en la mayoría de los modos de permiso.
- Ejecución: FileReadTool.call() lee el archivo, aplica numeración de líneas (formato cat -n), maneja PDFs/imágenes/cuadernos como casos especiales.
- Mapeo de resultados: El contenido del archivo se convierte en un bloque tool_result que referencia a "toolu_01XYZ".
- Retorno: El resultado se añade a la conversación como un mensaje de usuario y se envía en la siguiente llamada a la API.
Debido a que FileReadTool declara isConcurrencySafe: () => true e isReadOnly: () => true, si el modelo hubiera emitido cinco llamadas de lectura en el mismo mensaje, las cinco se ejecutarían en paralelo.
Resumen
El sistema de herramientas es la columna vertebral de ejecución de Claude Code. Toma la intención del modelo — expresada como bloques tool_use estructurados — y la convierte en acciones reales en tu máquina, con validación, permisos y control de concurrencia en cada paso.
El diseño está en capas: una fábrica buildTool() conservadora garantiza valores predeterminados seguros, un registro con banderas de funcionalidad controla lo que está disponible, un pipeline de despacho de siete fases valida y verifica permisos de cada llamada, y un planificador de concurrencia maximiza el paralelismo mientras preserva la corrección. El ejecutor en streaming añade una optimización de rendimiento adicional: las herramientas comienzan a ejecutarse antes de que el modelo termine de pensar.
En comparación con el sistema de memoria (5 rutas, un directorio de archivos markdown e ingeniería de prompts), el sistema de herramientas es un runtime propiamente dicho. Es la diferencia entre un archivador y un sistema operativo.
Lo Interesante
El Modelo como Planificador
El planificador de concurrencia es reactivo — agrupa en lotes lo que sea que el modelo emita. Pero el modelo mismo es el verdadero planificador. El prompt del sistema le dice que "haga todas las llamadas a herramientas independientes en paralelo" y que "use una sola llamada a Bash con && para encadenar comandos dependientes." El runtime confía en esto. Si el modelo emite cinco lecturas seguidas de una escritura, el planificador paralelizará las lecturas y serializará la escritura. Pero el modelo decidió ese orden. El planificador está ejecutando el plan del modelo, no creando el suyo propio.
Fallar de Forma Segura por Defecto
El principio de diseño más consistente: todo falla de forma segura. ¿Herramienta desconocida? Error. ¿Entrada no válida? Error. ¿Sin declaración de concurrencia? Ejecución en serie. ¿Sin declaración de permisos? Preguntar al usuario. ¿Sin bandera de funcionalidad? La herramienta no existe. Esto es inusual para un sistema donde el usuario principal es un modelo de IA que podría alucinar nombres de herramientas o malformar entradas. El sistema está diseñado para contener los errores del modelo, no para acomodarlos.
Los Hooks como Punto de Extensión
El sistema de hooks — previo a la herramienta, posterior a la herramienta y posterior a la falla — es el principal punto de extensión. Así es como las organizaciones aplican políticas (reglas de denegación en hooks previos), cómo los sistemas de registro capturan el uso de herramientas (hooks posteriores) y cómo se integran los pipelines de CI/CD (hooks de falla). Es importante destacar que los hooks solo pueden endurecer las restricciones, no flexibilizarlas. Un hook puede denegar una herramienta que la configuración permite, pero no puede permitir una herramienta que la configuración deniega.
43 Herramientas, 1 Interfaz
Quizás lo más llamativo es la uniformidad. Un comando bash, un web_fetch, la generación de un subagente, la creación de un trabajo cron y una notificación push implementan todos la misma interfaz de 30 métodos, pasan por el mismo pipeline de siete fases y respetan el mismo sistema de permisos. No hay casos especiales en el despachador. La complejidad está en las implementaciones individuales de las herramientas y en las reglas de permisos, no en el enrutamiento.





