Capítulo 4: Herramientas Integradas — Guía Completa

Por: Artiko
claudeagent-sdktoolsherramientaspythontypescript

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

HerramientaCategoríaRequiere permisos especiales
ReadFilesystemNo
WriteFilesystemSí (acceptEdits o bypass)
EditFilesystemSí (acceptEdits o bypass)
BashSistemaSí (bypass recomendado)
GlobFilesystemNo
GrepFilesystemNo
WebSearchInternetNo
WebFetchInternetNo
AgentOrquestaciónNo
TaskOrquestaciónNo
AskUserQuestionInteracciónNo
TodoWriteEstadoNo
TodoReadEstadoNo

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

Cuándo usar Write vs Edit

SituaciónHerramienta recomendada
Crear archivo nuevoWrite
Generar contenido desde ceroWrite
Modificar parte de un archivo existenteEdit
Reescribir completamente un archivo existenteWrite

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:

  1. No reescribe el archivo completo (menor riesgo)
  2. Preserva contexto alrededor del cambio
  3. 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:

  1. Incluir más contexto alrededor del texto a cambiar
  2. O usar replace_all: true si 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ónSignificado
*.pyArchivos .py en el directorio actual
**/*.pyArchivos .py en cualquier subdirectorio
src/**/*.tsArchivos .ts bajo src/
**/*.{js,ts}Archivos .js o .ts
tests/test_*.pyTests 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

ModoDescripciónCuándo usar
files_with_matchesLista rutas de archivos que tienen coincidenciasPor defecto; para saber qué archivos revisar
contentMuestra las líneas que coincidenPara ver el contexto del match
countCuenta de matches por archivoPara 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ónHerramienta
Buscar documentación de una libreríaWebSearch
Obtener el contenido exacto de una URL conocidaWebFetch
Encontrar soluciones a un errorWebSearch
Descargar un archivo de configuraciónWebFetch
Investigar una tecnología nuevaWebSearch → WebFetch

Limitaciones de WebSearch

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 contenidoComportamiento
HTMLConvierte a Markdown limpio
JSONRetorna JSON formateado
Texto planoRetorna tal cual
PDFExtrae texto (soporte limitado)
ImágenesNo 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

AspectoAgentTask
AislamientoComparte cwdPuede tener worktree propio
Caso de usoDelegación simpleCambios riesgosos en código
Git isolationNoSí (con isolation: "worktree")
OverheadBajoMayor (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

HerramientadefaultacceptEditsbypassPermissionsplan
ReadAutoAutoAutoMuestra plan
GlobAutoAutoAutoMuestra plan
GrepAutoAutoAutoMuestra plan
WritePide OKAutoAutoMuestra plan
EditPide OKAutoAutoMuestra plan
BashPide OKPide OKAutoMuestra plan
WebSearchAutoAutoAutoMuestra plan
WebFetchAutoAutoAutoMuestra plan
AgentAutoAutoAutoMuestra 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:

La clave para usar herramientas efectivamente:

  1. Principio de menor privilegio: solo habilita lo que necesitas
  2. permission_mode adecuado al contexto (interactive vs automated)
  3. cwd específico y restrictivo
  4. 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.