Construa um Agente de IA Estilo Claude Code com Claude Agent SDK (TypeScript)
Updated on
Se você já usou Claude Code ou outros copilotos de código com IA, sabe como é mágico: você digita perguntas em um painel, e uma IA que “lembra” da sessão ajuda a raciocinar sobre o código, executar ferramentas e destravar bloqueios.
Com o Claude Agent SDK, você pode criar seu próprio agente ao estilo Claude Code em Node.js com surpreendentemente pouco código.
Neste guia, vamos passar por:
- Como configurar um projeto em TypeScript
- Enviar sua primeira requisição com
query() - Manter uma sessão de conversa de longa duração
- Construir uma experiência de chat em linha de comando
- Adicionar uma ferramenta de matemática via MCP para o agente “usar uma calculadora”
Todos os exemplos usam TypeScript + Node.js e são intencionalmente estruturados e nomeados para serem fáceis de adaptar ao seu próprio projeto.
1. Configuração do Projeto
Crie um novo projeto:
mkdir dev-assistant-agent
cd dev-assistant-agent
npm init -yInstale as dependências:
npm install @anthropic-ai/claude-agent-sdk
npm install typescript tsx @types/node
npx tsc --initTambém usaremos dotenv para carregar variáveis de ambiente:
npm install dotenvCrie um arquivo .env na raiz do projeto:
ANTHROPIC_API_KEY=your_api_key_here
ANTHROPIC_BASE_URL=https://api.anthropic.comE um pequeno módulo de configuração:
// 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. Primeiro Contato: uma Chamada de Agente Minimalista
O núcleo do Claude Agent SDK é a função query(). Em vez de retornar uma única string grande, ela retorna um iterador assíncrono de mensagens (mensagens de sistema, do assistente etc.). Você pode fazer streaming e tratá-las conforme chegam.
Crie um exemplo simples de “hello”:
// 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);
}
}
}
}
}Ponto de entrada:
// src/main.ts
import { runHelloAgent } from './agent/helloAgent';
async function main() {
console.log('Launching hello agent...');
await runHelloAgent();
}
main().catch(console.error);Execute:
npx tsx src/main.tsVocê deve ver o Claude descrevendo o que ele pode fazer — isso confirma que o SDK está conectado corretamente.
3. Sessões de Conversa: Deixando o Agente “Lembrar”
Uma resposta única é legal; um assistente que lembra é melhor ainda.
O Claude Agent SDK oferece suporte a sessions. Cada conversa tem um session_id. Quando você inicia um novo query(), pode passar a opção resume para continuar uma sessão existente.
Aqui está uma pequena abstração que envia um prompt e retorna qualquer session ID que o SDK fornecer:
// 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 };
}Você pode testar assim:
// src/examples/sessionDemo.ts
import { sendMessageWithSession } from '../agent/sessionClient';
export async function runSessionDemo() {
let sessionId: string | undefined;
// Primeira pergunta
const first = await sendMessageWithSession('Hello, who are you?', sessionId);
sessionId = first.sessionId;
// Segunda pergunta, mesma conversa
const second = await sendMessageWithSession(
'What did I just ask you?',
sessionId
);
sessionId = second.sessionId;
}Agora o Claude pode responder perguntas sobre turnos anteriores, porque você está reutilizando a mesma sessão.
4. Construindo uma Experiência de Chat em CLI
Vamos transformar isso em um pequeno app de chat em linha de comando.
Nós vamos:
- Usar o módulo
readlinedo Node - Manter um
sessionIdentre os turnos - Enviar cada mensagem do usuário via
sendMessageWithSession
Crie um arquivo apenas para o loop da 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);
});
}Altere o ponto de entrada para usar a CLI:
// src/main.ts
import { startCliConversation } from './cli/chatLoop';
startCliConversation().catch(console.error);Execute:
npx tsx src/main.tsAgora você tem um “Claude Code sem UI”: um terminal que mantém contexto entre os turnos e responde em tempo real.
5. Dando Ferramentas ao Agente via MCP (Exemplo: Ferramenta de Matemática)
Uma das partes mais legais do Claude Code é o uso de ferramentas — o modelo pode decidir quando:
- Chamar uma calculadora
- Buscar arquivos
- Executar comandos
- Consultar APIs
Com o Claude Agent SDK, você pode adicionar ferramentas via Model Context Protocol (MCP). Vamos implementar uma ferramenta de matemática para que o agente possa avaliar expressões com precisão em vez de fazer “conta de cabeça”.
5.1 Instalar dependências extras
Usaremos mathjs (opens in a new tab) e zod para validação de schema:
npm install mathjs zod@3.25.76(Usar Zod 3.x evita problemas de compatibilidade com versões mais novas do Zod.)
5.2 Um pequeno helper de matemática
// 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 Definindo uma ferramenta para o agente
O SDK expõe um helper tool. Vamos definir uma ferramenta chamada numeric_calculator que recebe uma 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 Expondo ferramentas via um servidor MCP
Agora empacotamos uma ou mais ferramentas em um 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 Atualizando a CLI para suportar ferramentas
Vamos criar uma versão do handler de mensagens com suporte a ferramentas e depois trocá-la no nosso loop 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':
// Resultados de ferramentas retornam como uma mensagem especial de usuário
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 };
}Agora um loop de CLI separado que usa o client com ferramentas:
// 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);
});
}Atualize src/main.ts para rodar essa versão com ferramentas:
// src/main.ts
import { startCliWithTools } from './cli/chatWithTools';
startCliWithTools().catch(console.error);Agora, quando você perguntar:
You: (2454 + 23546)^2 / 32Você deve ver:
- Um log indicando que a ferramenta foi chamada
- O resultado numérico impresso como resultado da ferramenta
- Uma explicação em linguagem natural do Claude resumindo a resposta
Parabéns — agora você tem um agente estilo Claude que consegue conversar, lembrar e chamar ferramentas.
6. Ideias para Estender seu Agente
Depois que esse esqueleto estiver funcionando, você pode começar a adicionar mais recursos ao estilo Claude Code:
-
Ferramentas para codebase
Ferramentas que leem arquivos do seu repositório, fazem busca textual ou resumem arquivos grandes. -
Ferramentas de execução
Ferramentas que rodam testes, scripts ou pequenos trechos de código em um ambiente controlado. -
System prompts cientes do projeto
Injete metadados do projeto, documentos de arquitetura ou guias de estilo nosystemPromptpara fazer o agente parecer “nativo do projeto”. -
Sessions persistentes por usuário
Armazene osession_idem um banco de dados associado às contas de usuário, para que cada pessoa tenha um assistente de longa duração.
O Claude Agent SDK fornece os blocos de construção. O quão “Claude Code–like” seu agente será depende inteiramente das ferramentas, prompts e UI que você construir em volta dele.