Skip to content

Construye un agente de IA tipo Claude Code con Claude Agent SDK (TypeScript)

Updated on

Si alguna vez has usado Claude Code u otros copilotos de código con IA, sabes lo mágico que se siente: escribes preguntas en un panel, y una IA que “recuerda” la sesión te ayuda a razonar sobre el código, ejecutar herramientas y desbloquearte cuando te quedas atascado.

Con Claude Agent SDK, puedes crear tu propio agente al estilo Claude Code en Node.js con sorprendentemente pocas líneas de código.

En esta guía veremos:

  1. Cómo configurar un proyecto TypeScript
  2. Cómo enviar tu primera solicitud con query()
  3. Cómo mantener una sesión de conversación de larga duración
  4. Cómo construir una experiencia de chat en la línea de comandos
  5. Cómo añadir una herramienta de matemáticas vía MCP para que el agente pueda “usar una calculadora”

Todos los ejemplos usan TypeScript + Node.js y están estructurados y nombrados de forma intencional para que sean fáciles de adaptar a tu propio proyecto.


1. Configuración del proyecto

Crea un nuevo proyecto:

mkdir dev-assistant-agent
cd dev-assistant-agent
npm init -y

Instala las dependencias:

npm install @anthropic-ai/claude-agent-sdk
npm install typescript tsx @types/node
npx tsc --init

También usaremos dotenv para cargar variables de entorno:

npm install dotenv

Crea un archivo .env en la raíz del proyecto:

ANTHROPIC_API_KEY=your_api_key_here
ANTHROPIC_BASE_URL=https://api.anthropic.com

Y un pequeño módulo de configuración:

// src/config/runtime.ts
import 'dotenv/config';
 
export const API_KEY = process.env.ANTHROPIC_API_KEY;
export const BASE_URL = process.env.ANTHROPIC_BASE_URL;
 
if (!API_KEY) {
  throw new Error('ANTHROPIC_API_KEY is not set.');
}

2. Primer contacto: una llamada mínima al agente

El núcleo de Claude Agent SDK es la función query(). En lugar de devolver un único string grande, devuelve un iterador asíncrono de mensajes (mensajes de sistema, del asistente, etc.). Puedes hacer streaming y manejarlos a medida que vayan llegando.

Crea un ejemplo sencillo de “hola”:

// src/agent/helloAgent.ts
import { query, type Query } from '@anthropic-ai/claude-agent-sdk';
 
export async function runHelloAgent() {
  const stream: Query = query({
    prompt: 'Hi Claude, what can you do?',
  });
 
  for await (const item of stream) {
    if (item.type === 'assistant') {
      for (const chunk of item.message.content) {
        if (chunk.type === 'text') {
          console.log(chunk.text);
        }
      }
    }
  }
}

Punto de entrada:

// src/main.ts
import { runHelloAgent } from './agent/helloAgent';
 
async function main() {
  console.log('Launching hello agent...');
  await runHelloAgent();
}
 
main().catch(console.error);

Ejecuta:

npx tsx src/main.ts

Deberías ver a Claude describir lo que puede hacer; esto confirma que el SDK está correctamente conectado.


3. Sesiones de conversación: permitiendo que el agente recuerde

Una respuesta aislada está bien; un asistente que recuerda es mucho mejor.

Claude Agent SDK soporta sessions. Cada conversación tiene un session_id. Cuando inicias un nuevo query(), puedes pasar una opción resume para continuar una sesión existente.

Aquí tienes una pequeña abstracción que envía un prompt y devuelve cualquier session ID que nos proporcione el SDK:

// src/agent/sessionClient.ts
import { query, type Query } from '@anthropic-ai/claude-agent-sdk';
 
export async function sendMessageWithSession(
  userText: string,
  previousSessionId?: string
): Promise<{ sessionId?: string }> {
  let activeSessionId = previousSessionId;
 
  const stream: Query = query({
    prompt: userText,
    options: {
      resume: previousSessionId,
    },
  });
 
  for await (const item of stream) {
    switch (item.type) {
      case 'system':
        if (item.subtype === 'init') {
          activeSessionId = item.session_id;
          console.log(`(session started: ${activeSessionId})`);
        }
        break;
      case 'assistant':
        for (const piece of item.message.content) {
          if (piece.type === 'text') {
            console.log(`Claude: ${piece.text}`);
          }
        }
        break;
    }
  }
 
  return { sessionId: activeSessionId };
}

Puedes probarlo así:

// src/examples/sessionDemo.ts
import { sendMessageWithSession } from '../agent/sessionClient';
 
export async function runSessionDemo() {
  let sessionId: string | undefined;
 
  // Primera pregunta
  const first = await sendMessageWithSession('Hello, who are you?', sessionId);
  sessionId = first.sessionId;
 
  // Segunda pregunta, misma conversación
  const second = await sendMessageWithSession(
    'What did I just ask you?',
    sessionId
  );
  sessionId = second.sessionId;
}

Ahora Claude puede responder preguntas sobre los turnos anteriores, porque estás reutilizando la misma sesión.


4. Construyendo una experiencia de chat por CLI

Convirtamos esto en una pequeña aplicación de chat en la línea de comandos.

Haremos lo siguiente:

  • Usar el módulo readline de Node
  • Mantener un sessionId entre turnos
  • Enviar cada mensaje del usuario mediante sendMessageWithSession

Crea un archivo solo para el bucle de CLI:

// src/cli/chatLoop.ts
import readline from 'readline';
import { sendMessageWithSession } from '../agent/sessionClient';
 
export async function startCliConversation() {
  let sessionId: string | undefined;
 
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: 'You: ',
  });
 
  console.log('Interactive Claude Agent CLI');
  console.log('Type your message and press Enter. Ctrl+C to quit.\n');
  rl.prompt();
 
  rl.on('line', async (line) => {
    const trimmed = line.trim();
    if (!trimmed) {
      rl.prompt();
      return;
    }
 
    try {
      const { sessionId: newSessionId } = await sendMessageWithSession(
        trimmed,
        sessionId
      );
      sessionId = newSessionId;
    } catch (err) {
      console.error('Error talking to agent:', err);
    }
 
    rl.prompt();
  });
 
  rl.on('close', () => {
    console.log('\nGoodbye!');
    process.exit(0);
  });
}

Cambia el punto de entrada para usar la CLI:

// src/main.ts
import { startCliConversation } from './cli/chatLoop';
 
startCliConversation().catch(console.error);

Ejecuta:

npx tsx src/main.ts

Ahora tienes un sencillo “Claude Code sin interfaz gráfica”: una terminal que mantiene contexto entre turnos y responde en tiempo real.


5. Dotar al agente de herramientas vía MCP (ejemplo: herramienta de matemáticas)

Una de las partes más interesantes de Claude Code es el uso de herramientas: el modelo puede decidir cuándo:

  • Llamar a una calculadora
  • Buscar en archivos
  • Ejecutar comandos
  • Consultar APIs

Con Claude Agent SDK, puedes añadir herramientas mediante Model Context Protocol (MCP). Implementaremos una herramienta de matemáticas para que el agente pueda evaluar expresiones con precisión en lugar de hacer cálculos “de cabeza”.

5.1 Instalar dependencias adicionales

Usaremos mathjs (opens in a new tab) y zod para validación de esquemas:

npm install mathjs zod@3.25.76

(Usar Zod 3.x evita problemas de compatibilidad con versiones más nuevas.)

5.2 Un pequeño helper de matemáticas

// src/tools/mathEvaluator.ts
import * as math from 'mathjs';
 
export function evaluateMathExpression(expression: string): string {
  const result = math.evaluate(expression);
  return result.toString();
}

5.3 Definir una herramienta para el agente

El SDK expone un helper tool. Definiremos una herramienta llamada numeric_calculator que recibe un string expression.

// src/tools/mathTool.ts
import { tool } from '@anthropic-ai/claude-agent-sdk';
import { z } from 'zod';
import { evaluateMathExpression } from './mathEvaluator';
 
export const numericCalculatorTool = tool(
  'numeric_calculator',
  'Evaluate a mathematical expression using mathjs, e.g. "(2 + 3) * 4".',
  {
    expression: z.string().describe('A valid math expression.'),
  },
  async (args) => {
    const output = evaluateMathExpression(args.expression);
 
    return {
      content: [
        {
          type: 'text',
          text: output,
        },
      ],
    };
  }
);

5.4 Exponer herramientas mediante un servidor MCP

Ahora agrupamos una o más herramientas en un servidor MCP:

// src/mcp/toolkitServer.ts
import { createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk';
import { numericCalculatorTool } from '../tools/mathTool';
 
export const toolsetServer = createSdkMcpServer({
  name: 'toolset',
  version: '1.0.0',
  tools: [numericCalculatorTool],
});

5.5 Actualizar la CLI para soportar herramientas

Crearemos una versión del cliente de mensajes que entienda herramientas y luego la conectaremos a nuestro bucle de CLI.

// src/agent/toolEnabledClient.ts
import { query, type Query } from '@anthropic-ai/claude-agent-sdk';
import { toolsetServer } from '../mcp/toolkitServer';
 
export async function sendMessageWithTools(
  userText: string,
  previousSessionId?: string
): Promise<{ sessionId?: string }> {
  let activeSessionId = previousSessionId;
 
  const stream: Query = query({
    prompt: userText,
    options: {
      resume: previousSessionId,
      systemPrompt:
        'You are a helpful assistant. When you need to do precise math, use the numeric_calculator tool instead of guessing.',
      mcpServers: {
        toolset: toolsetServer,
      },
      // Tool names follow: mcp__{server_name}__{tool_name}
      allowedTools: ['mcp__toolset__numeric_calculator'],
    },
  });
 
  for await (const item of stream) {
    switch (item.type) {
      case 'system':
        if (item.subtype === 'init') {
          activeSessionId = item.session_id;
          console.log(`(session: ${activeSessionId})`);
        }
        break;
 
      case 'assistant':
        for (const piece of item.message.content) {
          if (piece.type === 'text') {
            console.log(`Claude: ${piece.text}`);
          } else if (piece.type === 'tool_use') {
            console.log(
              `[tool call] ${piece.name} with input: ${JSON.stringify(
                piece.input
              )}`
            );
          }
        }
        break;
 
      case 'user':
        // Los resultados de herramientas vuelven como un mensaje especial de usuario
        for (const piece of item.message.content) {
          if (piece.type === 'tool_result') {
            process.stdout.write('[tool result] ');
            for (const inner of piece.content) {
              if (inner.type === 'text') {
                process.stdout.write(inner.text);
              }
            }
            process.stdout.write('\n');
          }
        }
        break;
    }
  }
 
  return { sessionId: activeSessionId };
}

Ahora un bucle de CLI separado que usa este cliente con herramientas:

// src/cli/chatWithTools.ts
import readline from 'readline';
import { sendMessageWithTools } from '../agent/toolEnabledClient';
 
export async function startCliWithTools() {
  let sessionId: string | undefined;
 
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: 'You: ',
  });
 
  console.log('Claude Agent CLI (with math tool)');
  console.log('Ask me something like: (2454 + 23546)^2 / 32');
  rl.prompt();
 
  rl.on('line', async (line) => {
    const text = line.trim();
    if (!text) {
      rl.prompt();
      return;
    }
 
    try {
      const { sessionId: nextSessionId } = await sendMessageWithTools(
        text,
        sessionId
      );
      sessionId = nextSessionId;
    } catch (err) {
      console.error('Error:', err);
    }
 
    rl.prompt();
  });
 
  rl.on('close', () => {
    console.log('\nSession ended.');
    process.exit(0);
  });
}

Actualiza src/main.ts para ejecutar esta versión con herramientas:

// src/main.ts
import { startCliWithTools } from './cli/chatWithTools';
 
startCliWithTools().catch(console.error);

Ahora, cuando preguntes:

You: (2454 + 23546)^2 / 32

Deberías ver:

  • Un log indicando que se llamó a la herramienta
  • El resultado numérico impreso como resultado de la herramienta
  • Una explicación en lenguaje natural de Claude resumiendo la respuesta

Enhorabuena: ahora tienes un agente tipo Claude que puede chatear, recordar y llamar herramientas.


6. Ideas para extender tu agente

Una vez que este esqueleto funciona, puedes empezar a añadir más características al estilo Claude Code:

  • Herramientas para el codebase
    Herramientas que lean archivos de tu repositorio, busquen texto o resuman archivos grandes.

  • Herramientas de ejecución
    Herramientas que ejecuten tests, scripts o pequeños fragmentos de código en un entorno controlado.

  • System prompts conscientes del proyecto
    Inyecta metadatos del proyecto, documentos de arquitectura o guías de estilo en el systemPrompt para que el agente se sienta “nativo” del proyecto.

  • Sesiones persistentes por usuario
    Almacena el session_id en una base de datos asociada a las cuentas de tus usuarios, de modo que cada uno tenga un asistente de larga duración.

Claude Agent SDK te da los bloques de construcción. Qué tan “Claude Code–like” quieras que sea tu agente depende totalmente de las herramientas, prompts y la interfaz de usuario que construyas a su alrededor.