Capítulo 4: Herramientas Integradas — Guía Completa
Capítulo 4: Herramientas Integradas — Guía Completa
1. Arquitectura de herramientas
Las herramientas son la forma en que el agente interactúa con el mundo real: lee archivos, ejecuta comandos, navega la web. Sin herramientas, Claude es solo un modelo de lenguaje que responde preguntas. Con herramientas, es un agente capaz de actuar.
Cómo el agente decide qué herramienta usar
flowchart TD
A[Prompt del usuario] --> B[Claude analiza la tarea]
B --> C{¿Necesita información externa?}
C -->|No| D[Responde directamente]
C -->|Sí| E[Selecciona herramienta apropiada]
E --> F[Genera tool_use block]
F --> G[SDK ejecuta la herramienta]
G --> H[tool_result enviado a Claude]
H --> I{¿Tarea completa?}
I -->|No| C
I -->|Sí| J[Genera respuesta final]
Flujo interno de una herramienta
sequenceDiagram
participant Claude as Claude (LLM)
participant SDK as SDK
participant FS as Sistema de archivos
Claude->>SDK: AssistantMessage con tool_use block
Note over SDK: Deserializa parámetros
SDK->>FS: Ejecuta operación real (read/write/exec)
FS-->>SDK: Resultado o error
SDK-->>Claude: ToolResultMessage
Note over Claude: Procesa resultado y continúa
tool_use block en AssistantMessage
Cuando Claude decide usar una herramienta, el AssistantMessage incluye un bloque tool_use:
{
"type": "assistant",
"message": {
"content": [
{
"type": "text",
"text": "Primero leeré el archivo de configuración."
},
{
"type": "tool_use",
"id": "toolu_01ABC123",
"name": "Read",
"input": {
"file_path": "config/settings.py",
"limit": 100
}
}
],
"stop_reason": "tool_use"
}
}
El campo id es único y se usa para correlacionar el resultado (ToolResultMessage.tool_use_id).
Herramientas disponibles por defecto
| Herramienta | Categoría | Requiere permisos especiales |
|---|---|---|
Read | Filesystem | No |
Write | Filesystem | Sí (acceptEdits o bypass) |
Edit | Filesystem | Sí (acceptEdits o bypass) |
Bash | Sistema | Sí (bypass recomendado) |
Glob | Filesystem | No |
Grep | Filesystem | No |
WebSearch | Internet | No |
WebFetch | Internet | No |
Agent | Orquestación | No |
Task | Orquestación | No |
AskUserQuestion | Interacción | No |
TodoWrite | Estado | No |
TodoRead | Estado | No |
2. Read — Leer archivos
La herramienta más fundamental. Lee el contenido de cualquier archivo dentro del cwd.
Signature y parámetros
// TypeScript — interface de parámetros
interface ReadInput {
file_path: string; // Ruta al archivo (absoluta o relativa al cwd)
limit?: number; // Número máximo de líneas (default: 2000)
offset?: number; // Línea desde la que empezar (default: 1)
}
# Python — el agente usa estos parámetros automáticamente
# file_path: str — ruta del archivo
# limit: int — máximo de líneas (default 2000)
# offset: int — línea inicial (default 1)
Formato de salida
Read devuelve el contenido con números de línea prefijados:
1→import asyncio
2→from pathlib import Path
3→
4→async def main():
5→ pass
Este formato permite al agente referenciar líneas exactas en sus respuestas.
Límites de tamaño y cómo leer archivos grandes
El límite por defecto es 2000 líneas. Para archivos más grandes, el agente debe paginar usando offset y limit:
# Python — ejemplo de cómo el agente pagina archivos grandes
# (normalmente el agente hace esto automáticamente, pero puedes guiarlo en el prompt)
PAGINA_SYSTEM = """Cuando necesites leer un archivo grande:
1. Primero usa Read sin límite para ver cuántas líneas tiene
2. Si tiene más de 2000 líneas, usa offset+limit para leer por secciones
3. Lee las secciones relevantes según el contexto de la tarea"""
from claude_code_sdk import query, ClaudeCodeOptions
options = ClaudeCodeOptions(
system_prompt=PAGINA_SYSTEM,
allowed_tools=["Read"],
max_turns=10
)
async for message in query(
"Lee el archivo src/large_module.py (tiene 5000 líneas) y encuentra la función 'process_data'",
options
):
...
// TypeScript — guiar al agente a leer por secciones
const options: ClaudeCodeOptions = {
system_prompt: `Para archivos grandes (>2000 líneas), usa Read con offset y limit
para navegar. Primero lee las primeras 100 líneas para entender la estructura.`,
allowed_tools: ["Read"],
max_turns: 8,
};
Leer múltiples archivos
El agente puede leer múltiples archivos en un mismo contexto. Para guiar este comportamiento:
# Python
options = ClaudeCodeOptions(
allowed_tools=["Read", "Glob"],
max_turns=15
)
async for message in query(
"""Lee todos los archivos en src/models/ y src/services/
y explica cómo se relacionan entre sí.""",
options
):
...
Archivos binarios e imágenes
Read puede manejar imágenes (PNG, JPG, etc.) cuando el modelo soporta visión. El contenido se devuelve como imagen embebida.
# Python — analizar screenshot
options = ClaudeCodeOptions(
allowed_tools=["Read"],
cwd="/home/user/screenshots"
)
async for message in query(
"Lee el archivo error_screenshot.png y describe qué error se muestra",
options
):
if isinstance(message, ResultMessage):
print(message.result) # Descripción del error en la imagen
Ejemplo completo: agente que analiza múltiples archivos
# Python — multi_file_analyzer.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import AssistantMessage, ToolResultMessage, ResultMessage
async def analyze_dependencies(project_path: str) -> dict:
"""Mapea dependencias entre módulos de un proyecto Python."""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt="""Analiza las dependencias entre módulos Python.
Para cada archivo, identifica:
- Imports locales (de otros módulos del proyecto)
- Imports externos (librerías)
- Funciones/clases exportadas
Retorna JSON: {"modules": [{"file": str, "imports_local": [], "imports_external": [], "exports": []}]}""",
allowed_tools=["Read", "Glob"],
model="claude-sonnet-4-5",
max_turns=20
)
files_read = []
async for message in query(
"Lee todos los archivos .py y mapea sus dependencias", options
):
if isinstance(message, ToolResultMessage):
if message.tool_name == "Read":
files_read.append(message.tool_use_id)
elif isinstance(message, ResultMessage):
print(f"Módulos analizados: {len(files_read)}")
import json
return json.loads(message.result.strip().strip("```json").strip("```"))
return {}
asyncio.run(analyze_dependencies("/home/user/my-project"))
// TypeScript — fileAnalyzer.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function findTodoComments(projectPath: string): Promise<string[]> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Encuentra todos los comentarios TODO, FIXME, HACK, XXX.
Retorna un array JSON de strings con formato: "file:line - comentario"`,
allowed_tools: ["Read", "Grep", "Glob"],
max_turns: 10,
};
for await (const message of query({
prompt: "Lista todos los TODOs y FIXMEs del proyecto",
options,
})) {
if (message.type === "result" && !message.is_error) {
const json = message.result.replace(/^```json?\n?|\n?```$/g, "");
return JSON.parse(json) as string[];
}
}
return [];
}
3. Write — Crear archivos
Crea archivos nuevos o sobreescribe los existentes con contenido completo.
Comportamiento clave
- Si el archivo no existe: lo crea (incluidos directorios intermedios si es necesario)
- Si el archivo ya existe: lo sobreescribe completamente
- Requiere
permission_mode: "acceptEdits"o"bypassPermissions"para operar sin confirmación
Cuándo usar Write vs Edit
| Situación | Herramienta recomendada |
|---|---|
| Crear archivo nuevo | Write |
| Generar contenido desde cero | Write |
| Modificar parte de un archivo existente | Edit |
| Reescribir completamente un archivo existente | Write |
Buena práctica: verificar antes de escribir
# Python — agente que verifica antes de sobreescribir
system = """Antes de usar Write en un archivo existente:
1. Usa Read para leer el archivo actual
2. Decide si es mejor Edit (modificación parcial) o Write (reescritura total)
3. Si usas Write, preserva cualquier contenido importante del archivo original"""
options = ClaudeCodeOptions(
system_prompt=system,
allowed_tools=["Read", "Write", "Edit", "Glob"],
permission_mode="acceptEdits"
)
Ejemplo: agente generador de documentación
# Python — doc_generator.py
import asyncio
from pathlib import Path
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import ResultMessage
async def generate_module_docs(module_path: str) -> None:
"""Genera documentación Markdown para un módulo Python."""
project_root = str(Path(module_path).parent)
system = """Eres un technical writer experto en Python.
Generas documentación Markdown clara y útil con:
- Descripción del módulo
- Lista de clases y funciones públicas con sus signatures
- Ejemplos de uso reales (no inventados)
- Notas sobre edge cases importantes
FORMATO: Markdown válido, sin HTML, con code blocks."""
options = ClaudeCodeOptions(
cwd=project_root,
system_prompt=system,
allowed_tools=["Read", "Write"],
permission_mode="bypassPermissions",
model="claude-sonnet-4-5",
max_turns=8
)
module_name = Path(module_path).stem
doc_path = str(Path(module_path).parent / f"{module_name}_docs.md")
async for message in query(
f"""Lee el archivo {Path(module_path).name} y genera documentación completa.
Guarda la documentación en {doc_path}""",
options
):
if isinstance(message, ResultMessage):
if message.is_error:
print(f"Error: {message.result}")
else:
print(f"Documentación generada: {doc_path}")
print(f"Costo: ${message.cost_usd:.4f}")
asyncio.run(generate_module_docs("/home/user/project/src/auth.py"))
// TypeScript — configGenerator.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
import path from "path";
async function generateEslintConfig(projectPath: string): Promise<void> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Generas configuraciones ESLint modernas.
Lee package.json para entender las dependencias y tecnologías del proyecto.
Genera una configuración ESLint optimizada para el stack detectado.`,
allowed_tools: ["Read", "Write"],
permission_mode: "bypassPermissions",
max_turns: 5,
};
for await (const message of query({
prompt:
"Lee package.json y genera un .eslintrc.json apropiado para este proyecto",
options,
})) {
if (message.type === "result") {
console.log(message.is_error ? `Error: ${message.result}` : "ESLint config generado");
}
}
}
Anti-patrón: Write sin validación del path
# MAL — el agente podría escribir en cualquier lugar del sistema
options = ClaudeCodeOptions(
cwd="/", # cwd en raíz del sistema
allowed_tools=["Write"],
permission_mode="bypassPermissions"
)
# BIEN — restringir cwd al proyecto específico
options = ClaudeCodeOptions(
cwd="/home/user/project/output", # directorio específico de salida
allowed_tools=["Write"],
permission_mode="bypassPermissions"
)
4. Edit — Editar con precisión
Edit modifica archivos existentes usando un mecanismo de diff: reemplaza una cadena exacta con otra. Es la herramienta preferida para modificar código porque:
- No reescribe el archivo completo (menor riesgo)
- Preserva contexto alrededor del cambio
- Es más eficiente en tokens que Write para cambios pequeños
Parámetros
// TypeScript — interface completa
interface EditInput {
file_path: string; // Archivo a editar
old_string: string; // Texto exacto a reemplazar (debe ser único en el archivo)
new_string: string; // Texto nuevo
replace_all?: boolean; // Si es true, reemplaza todas las ocurrencias (default: false)
}
Cuándo Edit falla
Edit falla si old_string no es exactamente único en el archivo. En ese caso, debes:
- Incluir más contexto alrededor del texto a cambiar
- O usar
replace_all: truesi quieres cambiar todas las ocurrencias
# Python — guiar al agente para ediciones precisas
system = """Al usar Edit:
- Incluye suficiente contexto en old_string para que sea único
- Nunca uses old_string de una sola línea si esa línea aparece múltiples veces
- Para renombrar en todo el archivo, usa replace_all: true
- Verifica con Read antes de editar que old_string existe exactamente"""
options = ClaudeCodeOptions(
system_prompt=system,
allowed_tools=["Read", "Edit"],
permission_mode="acceptEdits"
)
Estrategia para ediciones en múltiples lugares
Cuando necesitas cambiar algo en múltiples lugares del mismo archivo:
# Python — el agente puede encadenar múltiples Edits
async for message in query(
"""En src/models.py:
1. Renombra la clase 'UserModel' a 'User' en todo el archivo
2. Cambia el campo 'created_at' por 'created_date' en todas las ocurrencias
3. Agrega @dataclass decorator a la clase User""",
options
):
...
Ejemplo completo: refactoring de una función
# Python — refactoring_agent.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import AssistantMessage, ToolResultMessage, ResultMessage
async def refactor_function(
project_path: str,
file_path: str,
function_name: str,
refactoring_goal: str
) -> dict:
"""Refactoriza una función específica en un archivo."""
system = """Eres un experto en refactoring Python.
Al refactorizar:
1. Lee primero el archivo completo para entender el contexto
2. Identifica la función objetivo y sus dependencias
3. Aplica el refactoring con Edit, preservando la interfaz pública
4. Verifica que los tests relacionados (si existen) siguen siendo válidos
Principios SOLID: SRP principalmente. Una función, una responsabilidad."""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt=system,
allowed_tools=["Read", "Edit", "Grep"],
permission_mode="acceptEdits",
model="claude-opus-4-5",
max_turns=15
)
edits_made = []
async for message in query(
f"""Refactoriza la función '{function_name}' en {file_path}.
Objetivo: {refactoring_goal}
Mantén la misma signatura pública.""",
options
):
if isinstance(message, ToolResultMessage) and message.tool_name == "Edit":
if not message.is_error:
edits_made.append({
"tool_use_id": message.tool_use_id,
"success": True
})
elif isinstance(message, ResultMessage):
return {
"success": not message.is_error,
"edits_count": len(edits_made),
"cost_usd": message.cost_usd,
"summary": message.result
}
return {"success": False, "edits_count": 0}
result = asyncio.run(refactor_function(
"/home/user/project",
"src/data_processor.py",
"process_batch",
"Extraer la validación en una función separada y reducir la complejidad ciclomática"
))
print(f"Ediciones realizadas: {result['edits_count']}")
print(f"Costo: ${result['cost_usd']:.4f}")
// TypeScript — refactorFile.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
interface RefactorResult {
editsCount: number;
costUsd: number;
summary: string;
}
async function extractConstants(
projectPath: string,
filePath: string
): Promise<RefactorResult> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Extrae números y strings mágicos como constantes nombradas.
Coloca las constantes al inicio del archivo con nombres en UPPER_SNAKE_CASE.
Actualiza todas las referencias en el archivo.`,
allowed_tools: ["Read", "Edit"],
permission_mode: "acceptEdits",
max_turns: 10,
};
let editsCount = 0;
for await (const message of query({
prompt: `Extrae todas las constantes mágicas de ${filePath}`,
options,
})) {
if (message.type === "tool_result" && message.tool_name === "Edit" && !message.is_error) {
editsCount++;
} else if (message.type === "result") {
return {
editsCount,
costUsd: message.cost_usd,
summary: message.result,
};
}
}
return { editsCount: 0, costUsd: 0, summary: "" };
}
5. Bash — Ejecutar comandos
La herramienta más poderosa y la más peligrosa. Ejecuta comandos arbitrarios en el sistema operativo.
Output de Bash
{
"stdout": "Tests: 42 passed, 0 failed\nCoverage: 87%",
"stderr": "",
"exit_code": 0,
"interrupted": false
}
Casos de uso legítimos
# Python — casos donde Bash es apropiado
BASH_CASES = [
"Ejecutar tests: pytest tests/",
"Instalar dependencias: pip install -r requirements.txt",
"Compilar TypeScript: npx tsc",
"Ejecutar linter: flake8 src/",
"Git operations: git log --oneline -20",
"Verificar entorno: python --version",
"Construir proyecto: npm run build",
"Docker: docker ps",
]
Comandos peligrosos y cómo bloquearlos
El agente podría potencialmente ejecutar comandos destructivos. Usa hooks para bloquearlos (ver capítulo 6):
# Python — hook para bloquear comandos peligrosos
from claude_code_sdk import query, ClaudeCodeOptions
BLOCKED_COMMANDS = [
"rm -rf",
"dd if=",
"mkfs",
"format",
":(){:|:&};:", # fork bomb
"chmod 777 /",
"curl | sh", # pipe to shell
"wget | sh",
]
def bash_safety_hook(tool_name: str, tool_input: dict) -> dict | None:
"""Hook PreToolUse para bloquear comandos peligrosos."""
if tool_name != "Bash":
return None # No intervenir en otras herramientas
command = tool_input.get("command", "")
for dangerous in BLOCKED_COMMANDS:
if dangerous in command:
return {
"decision": "block",
"reason": f"Comando bloqueado por política de seguridad: '{dangerous}'"
}
return None # Permitir el comando
options = ClaudeCodeOptions(
allowed_tools=["Read", "Bash"],
permission_mode="bypassPermissions",
hooks={
"PreToolUse": [bash_safety_hook]
}
)
// TypeScript — validación de comandos
const options: ClaudeCodeOptions = {
system_prompt: `Al usar Bash:
- NUNCA uses rm -rf sin verificar la ruta exacta
- NUNCA pipes a sh/bash (curl | sh, wget | bash)
- Para limpiar, mueve a /tmp en lugar de eliminar
- Siempre usa rutas absolutas o relativas al cwd`,
allowed_tools: ["Bash", "Read"],
permission_mode: "bypassPermissions",
};
Timeout de comandos
Bash tiene un timeout de 2 minutos por defecto. Para comandos largos:
# Python — guiar al agente para comandos de larga duración
system = """Para comandos que pueden tardar más de 2 minutos:
1. Usa el flag --timeout si el comando lo soporta
2. Divide la tarea en partes más pequeñas
3. Para builds grandes, usa make -j$(nproc) para paralelizar
4. Considera usar nohup y luego verificar el resultado"""
options = ClaudeCodeOptions(
system_prompt=system,
allowed_tools=["Bash", "Read"],
permission_mode="bypassPermissions"
)
Ejemplo: agente que ejecuta tests y analiza errores
# Python — test_runner_agent.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import AssistantMessage, ToolResultMessage, ResultMessage
async def run_and_fix_tests(project_path: str) -> dict:
"""Ejecuta tests, analiza fallas y las corrige."""
system = """Eres un experto en testing Python.
Tu flujo de trabajo:
1. Ejecuta 'pytest tests/ -v --tb=short' para ver qué falla
2. Analiza cada test fallido
3. Lee el código del test y el código que testea
4. Si el bug está en el código fuente (no en el test), usa Edit para corregirlo
5. Vuelve a ejecutar los tests para verificar la corrección
6. Repite hasta que todos los tests pasen o confirmes que no puedes corregirlos
Formato de reporte final:
- Tests que pasaron antes y ahora también
- Tests que fallaban y ahora pasan (tus correcciones)
- Tests que siguen fallando con explicación"""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt=system,
allowed_tools=["Bash", "Read", "Edit"],
permission_mode="bypassPermissions",
model="claude-sonnet-4-5",
max_turns=20
)
bash_calls = []
async for message in query(
"Ejecuta los tests y corrige cualquier falla que encuentres en el código fuente",
options
):
if isinstance(message, ToolResultMessage) and message.tool_name == "Bash":
bash_calls.append({
"success": not message.is_error,
"output_length": sum(len(b.text) for b in message.content)
})
elif isinstance(message, ResultMessage):
return {
"success": not message.is_error,
"bash_executions": len(bash_calls),
"cost_usd": message.cost_usd,
"report": message.result
}
return {}
result = asyncio.run(run_and_fix_tests("/home/user/project"))
print(result["report"])
// TypeScript — ciAgent.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
interface CIResult {
passed: boolean;
testsRun: number;
coveragePct: number;
errors: string[];
}
async function runCIChecks(projectPath: string): Promise<CIResult> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Ejecuta el pipeline de CI completo:
1. npm install (si hay package.json)
2. npx tsc --noEmit (verificar TypeScript)
3. npx eslint src/ (linting)
4. npm test (tests)
5. Reporta el resultado de cada paso
Retorna JSON: {passed: bool, testsRun: number, coveragePct: number, errors: string[]}`,
allowed_tools: ["Bash", "Read"],
permission_mode: "bypassPermissions",
max_turns: 15,
};
for await (const message of query({
prompt: "Ejecuta el pipeline CI completo y retorna el reporte JSON",
options,
})) {
if (message.type === "result" && !message.is_error) {
const json = message.result.replace(/^```json?\n?|\n?```$/g, "");
return JSON.parse(json) as CIResult;
}
}
return { passed: false, testsRun: 0, coveragePct: 0, errors: ["Sin resultado"] };
}
Comandos de larga duración con background
# Python — para procesos de larga duración
options = ClaudeCodeOptions(
system_prompt="""Para comandos que pueden tardar más de 2 minutos:
Usa este patrón:
1. Inicia el proceso en background: comando &> /tmp/output.log &
2. Guarda el PID: echo $! > /tmp/process.pid
3. Espera con: sleep 5 && cat /tmp/output.log
4. Verifica si terminó: kill -0 $(cat /tmp/process.pid) 2>/dev/null""",
allowed_tools=["Bash"],
permission_mode="bypassPermissions",
max_turns=10
)
6. Glob — Buscar archivos por patrón
Encuentra archivos usando patrones glob. El resultado está ordenado por fecha de modificación (más reciente primero).
Sintaxis de patrones glob
| Patrón | Significado |
|---|---|
*.py | Archivos .py en el directorio actual |
**/*.py | Archivos .py en cualquier subdirectorio |
src/**/*.ts | Archivos .ts bajo src/ |
**/*.{js,ts} | Archivos .js o .ts |
tests/test_*.py | Tests de pytest |
!node_modules/** | Excluir node_modules |
Patrones más útiles con ejemplos
# Python — el agente usa estos patrones automáticamente
PATRONES_COMUNES = {
"Python": ["**/*.py", "!**/__pycache__/**"],
"TypeScript": ["**/*.ts", "**/*.tsx", "!node_modules/**"],
"Config": ["**/*.json", "**/*.yaml", "**/*.toml", "!node_modules/**"],
"Tests Python": ["**/test_*.py", "**/*_test.py"],
"Tests TypeScript": ["**/*.test.ts", "**/*.spec.ts"],
"Markdown": ["**/*.md"],
"Dockerfile": ["**/Dockerfile", "**/docker-compose*.yml"],
}
# Ejemplo: agente que encuentra todos los tests
options = ClaudeCodeOptions(
allowed_tools=["Glob"],
max_turns=3
)
async for message in query(
"Lista todos los archivos de test en el proyecto (pytest y Jest)",
options
):
...
// TypeScript — buscar archivos con patrones específicos
const options: ClaudeCodeOptions = {
system_prompt: `Usa Glob para encontrar archivos antes de leerlos.
Patterns útiles:
- src/**/*.ts para TypeScript fuente
- **/*.test.ts para tests
- **/index.ts para entry points`,
allowed_tools: ["Glob", "Read"],
max_turns: 8,
};
Resultado ordenado por fecha
El resultado de Glob viene ordenado por fecha de modificación. Esto es útil para encontrar archivos recientemente modificados:
# Python — encontrar archivos recientes
options = ClaudeCodeOptions(
allowed_tools=["Glob"],
max_turns=3
)
async for message in query(
"¿Cuáles son los 5 archivos Python más recientemente modificados?",
options
):
# El agente puede usar el orden del resultado de Glob
...
Ejemplo: agente que encuentra archivos por tipo
# Python — file_inventory.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import ResultMessage
async def get_project_inventory(project_path: str) -> str:
"""Genera un inventario de archivos del proyecto."""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt="""Genera un inventario de archivos en formato Markdown.
Para cada tipo de archivo, lista cuántos hay y los más importantes.
Retorna Markdown con tablas.""",
allowed_tools=["Glob"],
model="claude-haiku-3-5", # Tarea simple, modelo ligero
max_turns=5
)
async for message in query(
"""Usa Glob para encontrar:
1. Archivos de código fuente (.py, .ts, .js)
2. Archivos de configuración (.json, .yaml, .toml)
3. Archivos de test
4. Documentación (.md)
Genera un resumen del inventario.""",
options
):
if isinstance(message, ResultMessage):
return message.result if not message.is_error else ""
return ""
inventory = asyncio.run(get_project_inventory("/home/user/project"))
print(inventory)
7. Grep — Buscar contenido
Busca contenido dentro de archivos usando expresiones regulares (implementado con ripgrep internamente).
Modos de output
| Modo | Descripción | Cuándo usar |
|---|---|---|
files_with_matches | Lista rutas de archivos que tienen coincidencias | Por defecto; para saber qué archivos revisar |
content | Muestra las líneas que coinciden | Para ver el contexto del match |
count | Cuenta de matches por archivo | Para estadísticas |
Parámetros importantes
// TypeScript — interface completa de Grep
interface GrepInput {
pattern: string; // Regex (ripgrep syntax)
path?: string; // Directorio o archivo donde buscar
glob?: string; // Filtrar por patrón de archivo
type?: string; // Tipo de archivo: "py", "ts", "js", etc.
output_mode?: "files_with_matches" | "content" | "count";
"-i"?: boolean; // Case insensitive
"-A"?: number; // Líneas después del match
"-B"?: number; // Líneas antes del match
"-C"?: number; // Líneas de contexto (antes y después)
multiline?: boolean; // Patrones que cruzan líneas
head_limit?: number; // Limitar número de resultados
}
Patrones regex útiles
# Python — ejemplos de patrones de búsqueda comunes
PATTERNS = {
# Encontrar TODOs y FIXMEs
"todos": r"(TODO|FIXME|HACK|XXX|BUG|OPTIMIZE).*",
# Contraseñas hardcodeadas (anti-pattern)
"credentials": r"(password|passwd|secret|api_key)\s*=\s*['\"][^'\"]+['\"]",
# Funciones async sin await
"async_no_await": r"async def \w+.*:\s*\n(?:(?!await).)*return",
# Prints de debug
"debug_prints": r"print\s*\(.*\)",
# Imports no usados (básico)
"unused_imports": r"^import \w+$",
# SQL injection risk
"sql_injection": r"execute\s*\(\s*['\"].*%s.*['\"]",
# URLs hardcodeadas
"hardcoded_urls": r"https?://(?!example\.com)\S+",
}
Ejemplo: encontrar todos los TODO/FIXME
# Python — todo_finder.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import ResultMessage
async def find_technical_debt(project_path: str) -> list[dict]:
"""Encuentra y categoriza deuda técnica en el código."""
system = """Busca problemas de calidad de código.
Para cada TODO/FIXME encontrado:
- Extrae el tipo (TODO, FIXME, HACK, etc.)
- El archivo y número de línea
- El mensaje completo
- Una categoría: bug-pendiente, feature-pendiente, refactoring, desconocido
Retorna JSON: [{"type": str, "file": str, "line": int, "message": str, "category": str}]"""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt=system,
allowed_tools=["Grep"],
model="claude-haiku-3-5",
max_turns=5
)
async for message in query(
"""Usa Grep con pattern='(TODO|FIXME|HACK|XXX|BUG).*' y output_mode='content'
para encontrar todos los comentarios de deuda técnica.
Retorna el JSON de resultados.""",
options
):
if isinstance(message, ResultMessage) and not message.is_error:
import json
try:
text = message.result.strip().strip("```json").strip("```")
return json.loads(text)
except json.JSONDecodeError:
return []
return []
debts = asyncio.run(find_technical_debt("/home/user/project"))
print(f"Deuda técnica encontrada: {len(debts)} items")
bugs = [d for d in debts if d["category"] == "bug-pendiente"]
print(f"Bugs pendientes: {len(bugs)}")
// TypeScript — securityScanner.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function scanForSecrets(projectPath: string): Promise<string[]> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Busca potenciales secretos o credenciales hardcodeadas.
Patrones a buscar:
- API keys: [a-zA-Z0-9]{32,}
- Passwords en código: password\s*=\s*["'][^"']+["']
- Tokens JWT: eyJ[a-zA-Z0-9]
Retorna lista de archivos con sus líneas problemáticas.`,
allowed_tools: ["Grep"],
model: "claude-haiku-3-5",
max_turns: 5,
};
const findings: string[] = [];
for await (const message of query({
prompt: "Escanea el proyecto en busca de secretos o credenciales hardcodeadas",
options,
})) {
if (message.type === "result" && !message.is_error) {
return message.result.split("\n").filter((l) => l.trim());
}
}
return findings;
}
8. WebSearch — Búsqueda web
Realiza búsquedas en internet. Útil cuando el agente necesita información actualizada que no está en el código.
Cuándo usar WebSearch vs WebFetch
| Situación | Herramienta |
|---|---|
| Buscar documentación de una librería | WebSearch |
| Obtener el contenido exacto de una URL conocida | WebFetch |
| Encontrar soluciones a un error | WebSearch |
| Descargar un archivo de configuración | WebFetch |
| Investigar una tecnología nueva | WebSearch → WebFetch |
Limitaciones de WebSearch
- Los resultados son snippets, no el contenido completo
- La calidad depende del motor de búsqueda configurado
- No garantiza resultados actualizados al momento exacto
Ejemplo: investigación técnica
# Python — research_agent.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import ResultMessage
async def research_migration(
from_lib: str,
to_lib: str,
language: str = "Python"
) -> str:
"""Investiga cómo migrar de una librería a otra."""
options = ClaudeCodeOptions(
allowed_tools=["WebSearch", "WebFetch"],
model="claude-sonnet-4-5",
max_turns=10
)
async for message in query(
f"""Investiga cómo migrar de {from_lib} a {to_lib} en {language}.
Busca:
1. Guía de migración oficial si existe
2. Principales diferencias de API
3. Breaking changes más comunes
4. Herramientas de migración automática si existen
Genera un reporte de migración en Markdown.""",
options
):
if isinstance(message, ResultMessage) and not message.is_error:
return message.result
return ""
guide = asyncio.run(research_migration("requests", "httpx", "Python"))
print(guide[:500]) # Primeros 500 chars del reporte
// TypeScript — dependencyChecker.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function checkDeprecations(
packageName: string,
currentVersion: string
): Promise<string> {
const options: ClaudeCodeOptions = {
allowed_tools: ["WebSearch"],
model: "claude-haiku-3-5",
max_turns: 3,
};
for await (const message of query({
prompt: `Busca si el paquete npm "${packageName}" versión ${currentVersion} tiene vulnerabilidades conocidas o está deprecado. Responde en máximo 3 oraciones.`,
options,
})) {
if (message.type === "result") return message.result;
}
return "Sin información disponible";
}
9. WebFetch — Obtener contenido web
Descarga y procesa el contenido de una URL específica. Convierte HTML a Markdown automáticamente.
Formatos soportados
| Tipo de contenido | Comportamiento |
|---|---|
| HTML | Convierte a Markdown limpio |
| JSON | Retorna JSON formateado |
| Texto plano | Retorna tal cual |
| Extrae texto (soporte limitado) | |
| Imágenes | No soportado |
Límites de respuesta
WebFetch tiene un límite de respuesta (aproximadamente 50k caracteres). Para páginas muy grandes, el contenido se trunca.
# Python — obtener documentación oficial
options = ClaudeCodeOptions(
allowed_tools=["WebFetch"],
max_turns=5
)
async for message in query(
"Obtén la documentación de la función `asyncio.gather` de https://docs.python.org/3/library/asyncio-task.html",
options
):
...
Ejemplo: obtener y resumir documentación
# Python — doc_fetcher.py
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import ResultMessage
async def get_api_docs(url: str, function_name: str) -> str:
"""Obtiene documentación de una URL y extrae info de una función."""
options = ClaudeCodeOptions(
allowed_tools=["WebFetch"],
model="claude-haiku-3-5",
max_turns=3
)
async for message in query(
f"""Obtén el contenido de {url}.
Extrae específicamente la documentación de '{function_name}':
- Descripción
- Parámetros
- Return value
- Ejemplo básico de uso""",
options
):
if isinstance(message, ResultMessage) and not message.is_error:
return message.result
return ""
docs = asyncio.run(get_api_docs(
"https://docs.python.org/3/library/asyncio-task.html",
"asyncio.create_task"
))
print(docs)
// TypeScript — fetchChangelog.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function fetchChangelogSummary(
packageName: string,
fromVersion: string,
toVersion: string
): Promise<string> {
const changelogUrl = `https://raw.githubusercontent.com/owner/${packageName}/main/CHANGELOG.md`;
const options: ClaudeCodeOptions = {
allowed_tools: ["WebFetch"],
max_turns: 3,
};
for await (const message of query({
prompt: `Obtén ${changelogUrl} y resume los cambios entre versión ${fromVersion} y ${toVersion}. Lista solo breaking changes y nuevas features.`,
options,
})) {
if (message.type === "result") return message.result;
}
return "";
}
10. Agent — Lanzar subagentes
La herramienta más poderosa para orquestación. Permite al agente principal delegar tareas a subagentes especializados.
Concepto de orquestación
flowchart TD
A[Agente Principal] --> B[Agent: Analizar arquitectura]
A --> C[Agent: Revisar seguridad]
A --> D[Agent: Verificar performance]
B --> B1[Lee src/]
B --> B2[Genera reporte arquitectura]
C --> C1[Grep patrones peligrosos]
C --> C2[Genera reporte seguridad]
D --> D1[Analiza queries DB]
D --> D2[Genera reporte performance]
B2 --> E[Agente Principal consolida]
C2 --> E
D2 --> E
E --> F[Reporte final integrado]
Cómo el agente principal delega
# Python — el sistema del agente principal para orquestar
orchestrator_system = """Eres un arquitecto de software que coordina análisis de proyectos.
Para proyectos grandes, delega tareas específicas a subagentes usando la herramienta Agent.
Cada subagente debe recibir:
1. Un prompt específico y acotado
2. Solo las herramientas que necesita
3. Un contexto claro de su tarea
Integra los resultados de todos los subagentes en un reporte cohesivo."""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt=orchestrator_system,
allowed_tools=["Agent", "Read"],
model="claude-opus-4-5",
max_turns=15
)
async for message in query(
"""Analiza este proyecto usando subagentes especializados:
1. Un subagente para arquitectura (solo Read, Glob)
2. Un subagente para seguridad (solo Grep, Read)
3. Consolida los resultados en un reporte ejecutivo""",
options
):
...
// TypeScript — orchestratorAgent.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function orchestrateCodeReview(projectPath: string): Promise<string> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Eres un orquestador de code review.
Lanza subagentes especializados:
- Agent para análisis de TypeScript (herramientas: Read, Glob, Grep)
- Agent para verificar tests (herramientas: Read, Glob, Bash)
Consolida los resultados en un reporte final.`,
allowed_tools: ["Agent", "Read", "Glob"],
model: "claude-sonnet-4-5",
max_turns: 20,
};
for await (const message of query({
prompt:
"Ejecuta un code review completo usando subagentes especializados",
options,
})) {
if (message.type === "result") return message.result;
}
return "";
}
11. Task — Subagentes con aislamiento
Task es similar a Agent pero con mayor aislamiento. El subagente Task puede tener su propio worktree Git separado.
Diferencias Task vs Agent
| Aspecto | Agent | Task |
|---|---|---|
| Aislamiento | Comparte cwd | Puede tener worktree propio |
| Caso de uso | Delegación simple | Cambios riesgosos en código |
| Git isolation | No | Sí (con isolation: "worktree") |
| Overhead | Bajo | Mayor (crea worktree) |
# Python — cuándo usar Task
TASK_USE_CASES = [
"Experimenting: probar un refactoring sin afectar el código original",
"Parallelismo: múltiples subagentes trabajando en diferentes features",
"Safety: cambios destructivos que necesitan aislamiento",
]
# El agente principal decide usar Task para cambios riesgosos
options = ClaudeCodeOptions(
allowed_tools=["Task", "Read"],
system_prompt="""Para cambios en código de producción, usa Task con isolation=worktree.
Esto crea un branch temporal donde el subagente trabaja de forma segura."""
)
// TypeScript — usar Task para operaciones aisladas
const options: ClaudeCodeOptions = {
allowed_tools: ["Task", "Read", "Glob"],
system_prompt: `Para refactorings grandes, usa Task con aislamiento:
- isolation: "worktree" crea un Git worktree temporal
- El subagente trabaja en su propia copia del código
- Los cambios se pueden revisar antes de mergear`,
};
12. AskUserQuestion — Interacción humana
Permite al agente interrumpir el loop para solicitar información al usuario.
Cómo funciona en el flujo
sequenceDiagram
participant App as Aplicación
participant Agent as Agente
participant User as Usuario
Agent->>App: AssistantMessage con tool_use AskUserQuestion
App->>User: Muestra la pregunta
User->>App: Respuesta del usuario
App->>Agent: ToolResultMessage con la respuesta
Agent->>Agent: Continúa con la información
Manejo del input en el código
# Python — HITL (Human-in-the-Loop) básico
import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import AssistantMessage
# Nota: AskUserQuestion es manejado automáticamente por Claude Code CLI
# Si quieres interceptar preguntas, usa hooks
async def interactive_agent(project_path: str) -> None:
"""Agente interactivo que puede hacer preguntas al usuario."""
system = """Eres un asistente de desarrollo interactivo.
Cuando necesites información del usuario (preferencias, confirmaciones, opciones),
usa AskUserQuestion con una pregunta clara y concisa.
Nunca asumas preferencias del usuario sin preguntar."""
options = ClaudeCodeOptions(
cwd=project_path,
system_prompt=system,
allowed_tools=["Read", "Write", "Edit", "AskUserQuestion"],
permission_mode="acceptEdits",
max_turns=20
)
async for message in query(
"Ayúdame a configurar este proyecto según mis preferencias",
options
):
if isinstance(message, AssistantMessage):
for block in message.message.content:
if block.type == "text":
print(block.text, end="", flush=True)
// TypeScript — agente interactivo para setup
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
async function interactiveSetup(projectPath: string): Promise<void> {
const options: ClaudeCodeOptions = {
cwd: projectPath,
system_prompt: `Configura proyectos según las preferencias del usuario.
Usa AskUserQuestion para preguntar:
- ¿Qué framework de testing prefiere? (pytest/unittest/nose)
- ¿Desea configurar pre-commit hooks?
- ¿Qué convención de nombres prefiere?`,
allowed_tools: ["AskUserQuestion", "Write", "Read"],
permission_mode: "acceptEdits",
max_turns: 15,
};
for await (const message of query({
prompt: "Configura el proyecto según mis preferencias de desarrollo",
options,
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") process.stdout.write(block.text);
}
}
}
}
13. Permisos y permission_mode
El sistema de permisos
flowchart TD
A[Agente quiere usar herramienta] --> B{¿permission_mode?}
B -->|default| C{¿Herramienta modifica sistema?}
C -->|No: Read, Glob, Grep| D[Ejecuta sin pedir permiso]
C -->|Sí: Write, Edit, Bash| E[Pide confirmación al usuario]
E --> F{¿Usuario acepta?}
F -->|Sí| G[Ejecuta herramienta]
F -->|No| H[Herramienta bloqueada]
B -->|acceptEdits| I{¿Es edición de archivo?}
I -->|Sí: Write, Edit| J[Ejecuta automáticamente]
I -->|No: Bash| K[Pide confirmación]
B -->|bypassPermissions| L[Ejecuta todo sin preguntar]
B -->|plan| M[Solo planifica, no ejecuta nada]
Tabla: herramientas vs permission_mode
| Herramienta | default | acceptEdits | bypassPermissions | plan |
|---|---|---|---|---|
| Read | Auto | Auto | Auto | Muestra plan |
| Glob | Auto | Auto | Auto | Muestra plan |
| Grep | Auto | Auto | Auto | Muestra plan |
| Write | Pide OK | Auto | Auto | Muestra plan |
| Edit | Pide OK | Auto | Auto | Muestra plan |
| Bash | Pide OK | Pide OK | Auto | Muestra plan |
| WebSearch | Auto | Auto | Auto | Muestra plan |
| WebFetch | Auto | Auto | Auto | Muestra plan |
| Agent | Auto | Auto | Auto | Muestra plan |
Cuándo usar cada modo
# Python — guía de selección de permission_mode
# default: exploración interactiva, desarrollo local manual
options_interactive = ClaudeCodeOptions(
permission_mode="default"
# El usuario ve y aprueba cada cambio
)
# acceptEdits: automatización de refactoring que confías
options_refactor = ClaudeCodeOptions(
permission_mode="acceptEdits",
allowed_tools=["Read", "Edit", "Write"]
# Auto-acepta ediciones, pero no comandos de shell
)
# bypassPermissions: CI/CD, entornos controlados
options_ci = ClaudeCodeOptions(
permission_mode="bypassPermissions",
cwd="/tmp/sandbox" # SIEMPRE con cwd restringido
# Acepta todo — usar SOLO en ambientes aislados
)
# plan: ver qué haría el agente sin ejecutarlo
options_preview = ClaudeCodeOptions(
permission_mode="plan"
# Perfecto para revisar antes de ejecutar
)
// TypeScript — contextos de uso
// CI/CD pipeline — entorno controlado
const ciOptions: ClaudeCodeOptions = {
permission_mode: "bypassPermissions",
cwd: process.env.WORKSPACE_DIR!, // Directorio específico del job
env: { CI: "true" },
};
// Code review tool — solo lectura + edición
const reviewOptions: ClaudeCodeOptions = {
permission_mode: "acceptEdits",
allowed_tools: ["Read", "Edit", "Glob", "Grep"],
};
// Preview antes de ejecutar
const previewOptions: ClaudeCodeOptions = {
permission_mode: "plan",
// Ver el plan completo sin side effects
};
14. Combinaciones de herramientas
Las combinaciones correctas de herramientas definen el perfil del agente.
Receta: análisis de código (solo lectura)
Objetivo: entender código sin riesgo de modificarlo.
# Python
READ_ONLY_ANALYSIS = ClaudeCodeOptions(
allowed_tools=["Read", "Glob", "Grep"],
permission_mode="default", # No puede modificar nada de todas formas
model="claude-sonnet-4-5",
max_turns=15
)
# Uso: code review, documentación, análisis de dependencias
// TypeScript
const READ_ONLY: ClaudeCodeOptions = {
allowed_tools: ["Read", "Glob", "Grep"],
model: "claude-sonnet-4-5",
max_turns: 15,
};
Receta: desarrollo activo completo
Objetivo: modificar código con plenas capacidades.
# Python
FULL_DEV = ClaudeCodeOptions(
allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
permission_mode="acceptEdits",
model="claude-opus-4-5",
max_turns=30
)
# Uso: feature development, refactoring, bug fixing
// TypeScript
const FULL_DEV: ClaudeCodeOptions = {
allowed_tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
permission_mode: "acceptEdits",
model: "claude-opus-4-5",
max_turns: 30,
};
Receta: investigación web + documentación
Objetivo: investigar en internet y documentar hallazgos.
# Python
WEB_RESEARCH = ClaudeCodeOptions(
allowed_tools=["WebSearch", "WebFetch", "Write"],
permission_mode="acceptEdits",
model="claude-sonnet-4-5",
max_turns=15
)
# Uso: research de librerías, changelog analysis, best practices
// TypeScript
const WEB_RESEARCH: ClaudeCodeOptions = {
allowed_tools: ["WebSearch", "WebFetch", "Write"],
permission_mode: "acceptEdits",
model: "claude-sonnet-4-5",
max_turns: 15,
};
Receta: DevOps y automatización
Objetivo: ejecutar comandos, gestionar infraestructura.
# Python — SOLO en entornos controlados/aislados
DEVOPS = ClaudeCodeOptions(
allowed_tools=["Bash", "Read", "Write"],
permission_mode="bypassPermissions",
cwd="/home/deploy/workspace", # Directorio de deploy aislado
env={
"KUBECONFIG": "/home/deploy/.kube/config",
"AWS_PROFILE": "deploy"
},
model="claude-sonnet-4-5",
max_turns=20
)
# Uso: deployment scripts, CI/CD, infrastructure management
# IMPORTANTE: siempre en sandbox o con hooks de seguridad
// TypeScript
const DEVOPS: ClaudeCodeOptions = {
allowed_tools: ["Bash", "Read", "Write"],
permission_mode: "bypassPermissions",
cwd: process.env.DEPLOY_WORKSPACE!,
max_turns: 20,
};
Receta: agente interactivo con usuario
Objetivo: colaboración humano-agente con confirmaciones.
# Python
INTERACTIVE = ClaudeCodeOptions(
allowed_tools=["Read", "Write", "Edit", "Bash", "AskUserQuestion", "Glob"],
permission_mode="default", # Pide confirmación en cambios importantes
model="claude-sonnet-4-5",
max_turns=25
)
# Uso: pair programming asistido, scaffolding interactivo
// TypeScript
const INTERACTIVE: ClaudeCodeOptions = {
allowed_tools: [
"Read", "Write", "Edit", "Bash", "AskUserQuestion", "Glob"
],
permission_mode: "default",
model: "claude-sonnet-4-5",
max_turns: 25,
};
Anti-patrón: dar todas las herramientas siempre
# MAL — exceso de privilegios
BAD_OPTIONS = ClaudeCodeOptions(
allowed_tools=[
"Read", "Write", "Edit", "Bash", "Glob", "Grep",
"WebSearch", "WebFetch", "Agent", "Task", "AskUserQuestion"
],
permission_mode="bypassPermissions",
cwd="/" # Peor aún: cwd en raíz
)
# BIEN — principio de menor privilegio
GOOD_OPTIONS = ClaudeCodeOptions(
allowed_tools=["Read", "Grep"], # Solo lo necesario para la tarea
cwd="/home/user/specific-project"
)
15. Herramientas custom via MCP
Las herramientas MCP extienden las capacidades del agente con herramientas personalizadas. Ver capítulo 5 para la guía completa.
Nomenclatura de herramientas MCP
Las herramientas MCP siguen el patrón: mcp__{server_name}__{tool_name}
# Python — ejemplos de nombres de herramientas MCP
MCP_TOOL_EXAMPLES = [
"mcp__database__query", # servidor "database", herramienta "query"
"mcp__database__insert",
"mcp__github__create_pr", # servidor "github"
"mcp__github__list_issues",
"mcp__slack__send_message", # servidor "slack"
"mcp__jira__create_ticket",
"mcp__postgres__execute_sql",
]
Habilitar herramientas MCP en allowed_tools
# Python
options = ClaudeCodeOptions(
mcp_servers=[
{
"type": "stdio",
"command": "node",
"args": ["/path/to/database-mcp-server.js"],
"env": {"DATABASE_URL": "postgres://localhost/mydb"}
}
],
allowed_tools=[
"Read", # herramienta built-in
"Grep", # herramienta built-in
"mcp__database__query", # herramienta MCP
"mcp__database__schema", # herramienta MCP
]
)
// TypeScript
const options: ClaudeCodeOptions = {
mcp_servers: [
{
type: "stdio",
command: "node",
args: ["/path/to/my-mcp-server.js"],
},
],
allowed_tools: [
"Read",
"mcp__myserver__custom_tool",
],
};
16. Auditoría de uso de herramientas
Monitorear qué herramientas usa el agente es esencial para optimizar costos, detectar comportamientos inesperados y mantener compliance.
Tracking básico de tool_use
# Python — tool_tracker.py
from dataclasses import dataclass, field
from datetime import datetime
from collections import Counter
from claude_code_sdk import query, ClaudeCodeOptions
from claude_code_sdk.types import AssistantMessage, ToolResultMessage, ResultMessage
@dataclass
class ToolUsage:
name: str
input_summary: str
timestamp: datetime
success: bool
duration_ms: int = 0 # tiempo entre tool_use y tool_result
@dataclass
class SessionMetrics:
tool_calls: list[ToolUsage] = field(default_factory=list)
total_cost_usd: float = 0.0
total_input_tokens: int = 0
total_output_tokens: int = 0
num_turns: int = 0
@property
def tool_frequency(self) -> dict[str, int]:
return dict(Counter(t.name for t in self.tool_calls))
@property
def error_rate(self) -> float:
if not self.tool_calls:
return 0.0
errors = sum(1 for t in self.tool_calls if not t.success)
return errors / len(self.tool_calls)
@property
def cost_per_tool_call(self) -> float:
if not self.tool_calls:
return 0.0
return self.total_cost_usd / len(self.tool_calls)
async def tracked_query(
prompt: str,
options: ClaudeCodeOptions | None = None
) -> tuple[str, SessionMetrics]:
metrics = SessionMetrics()
pending: dict[str, tuple[str, str, datetime]] = {} # id -> (name, summary, timestamp)
async for message in query(prompt, options):
if isinstance(message, AssistantMessage):
for block in message.message.content:
if block.type == "tool_use":
# Resumir el input para el log (sin datos sensibles)
summary = str(block.input)[:100]
pending[block.id] = (block.name, summary, datetime.now())
elif block.type == "text":
# Acumular tokens de salida
pass
if message.message.usage:
metrics.total_input_tokens += message.message.usage.input_tokens
metrics.total_output_tokens += message.message.usage.output_tokens
elif isinstance(message, ToolResultMessage):
if message.tool_use_id in pending:
name, summary, start_time = pending.pop(message.tool_use_id)
duration = int((datetime.now() - start_time).total_seconds() * 1000)
metrics.tool_calls.append(ToolUsage(
name=name,
input_summary=summary,
timestamp=start_time,
success=not message.is_error,
duration_ms=duration
))
elif isinstance(message, ResultMessage):
metrics.total_cost_usd = message.cost_usd
metrics.num_turns = message.num_turns
result = message.result
return result, metrics
# Uso y reporte
async def main():
options = ClaudeCodeOptions(
cwd="/home/user/project",
allowed_tools=["Read", "Grep", "Glob", "Bash"]
)
result, metrics = await tracked_query(
"Analiza el proyecto y ejecuta los tests",
options
)
print("=== MÉTRICAS DE USO ===")
print(f"Herramientas usadas: {sum(metrics.tool_frequency.values())} llamadas")
print(f"Distribución:")
for tool, count in sorted(metrics.tool_frequency.items(), key=lambda x: -x[1]):
pct = count / sum(metrics.tool_frequency.values()) * 100
print(f" {tool}: {count} ({pct:.0f}%)")
print(f"Tasa de error: {metrics.error_rate:.1%}")
print(f"Costo total: ${metrics.total_cost_usd:.4f}")
print(f"Costo por llamada: ${metrics.cost_per_tool_call:.5f}")
print(f"Tokens input: {metrics.total_input_tokens:,}")
print(f"Tokens output: {metrics.total_output_tokens:,}")
print(f"Turns: {metrics.num_turns}")
asyncio.run(main())
// TypeScript — toolMetrics.ts
import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code-sdk";
interface ToolMetrics {
callCount: Record<string, number>;
errorCount: Record<string, number>;
totalCost: number;
numTurns: number;
}
async function queryWithMetrics(
prompt: string,
options?: ClaudeCodeOptions
): Promise<{ result: string; metrics: ToolMetrics }> {
const metrics: ToolMetrics = {
callCount: {},
errorCount: {},
totalCost: 0,
numTurns: 0,
};
for await (const message of query({ prompt, options })) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "tool_use") {
metrics.callCount[block.name] = (metrics.callCount[block.name] ?? 0) + 1;
}
}
} else if (message.type === "tool_result" && message.is_error) {
metrics.errorCount[message.tool_name] =
(metrics.errorCount[message.tool_name] ?? 0) + 1;
} else if (message.type === "result") {
metrics.totalCost = message.cost_usd;
metrics.numTurns = message.num_turns;
return { result: message.result, metrics };
}
}
return { result: "", metrics };
}
// Reporte de métricas
function printMetricsReport(metrics: ToolMetrics): void {
const totalCalls = Object.values(metrics.callCount).reduce((a, b) => a + b, 0);
console.log("\n=== TOOL METRICS ===");
console.log(`Total calls: ${totalCalls}`);
for (const [tool, count] of Object.entries(metrics.callCount).sort(
(a, b) => b[1] - a[1]
)) {
const errors = metrics.errorCount[tool] ?? 0;
const pct = ((count / totalCalls) * 100).toFixed(0);
console.log(` ${tool}: ${count} (${pct}%)${errors > 0 ? ` ⚠ ${errors} errors` : ""}`);
}
console.log(`Cost: $${metrics.totalCost.toFixed(4)}`);
console.log(`Cost/call: $${(metrics.totalCost / totalCalls).toFixed(5)}`);
}
Métricas para optimizar costos
# Python — identificar herramientas costosas
async def cost_breakdown_query(prompt: str, options=None) -> dict:
"""Calcula el costo estimado por herramienta."""
# Estimación de tokens por tipo de herramienta
TOOL_TOKEN_ESTIMATE = {
"Read": 500, # ~500 tokens de output por llamada
"Grep": 200, # resultado filtrado
"Glob": 100, # lista de paths
"Bash": 300, # stdout variable
"WebSearch": 400, # snippets de resultados
"WebFetch": 1000, # página completa
"Write": 100, # confirmación
"Edit": 150, # confirmación con diff
}
tool_calls = {}
total_cost = 0.0
async for message in query(prompt, options):
if isinstance(message, AssistantMessage):
for block in message.message.content:
if block.type == "tool_use":
tool_calls[block.name] = tool_calls.get(block.name, 0) + 1
elif isinstance(message, ResultMessage):
total_cost = message.cost_usd
# Calcular proporción de costo por herramienta
total_estimated_tokens = sum(
TOOL_TOKEN_ESTIMATE.get(tool, 200) * count
for tool, count in tool_calls.items()
)
breakdown = {}
for tool, count in tool_calls.items():
tool_tokens = TOOL_TOKEN_ESTIMATE.get(tool, 200) * count
proportion = tool_tokens / total_estimated_tokens if total_estimated_tokens > 0 else 0
breakdown[tool] = {
"calls": count,
"estimated_cost": total_cost * proportion
}
return breakdown
Resumen del capítulo
Las herramientas integradas del SDK cubren todas las necesidades básicas de un agente de desarrollo:
- Read/Write/Edit para manipulación de archivos con diferentes niveles de granularidad
- Bash para ejecutar comandos del sistema (con cuidado de seguridad)
- Glob/Grep para navegar y buscar en el codebase eficientemente
- WebSearch/WebFetch para acceso a información externa
- Agent/Task para orquestación y paralelismo
- AskUserQuestion para interacción humana en el loop
La clave para usar herramientas efectivamente:
- Principio de menor privilegio: solo habilita lo que necesitas
- permission_mode adecuado al contexto (interactive vs automated)
- cwd específico y restrictivo
- Auditoría para optimizar costos y detectar anomalías
En el siguiente capítulo aprenderás a crear herramientas personalizadas con MCP para extender las capacidades del agente.