使用 Claude Agent SDK(TypeScript)构建一个类似 Claude Code 的 AI Agent
Updated on
如果你用过 Claude Code 或其他 AI 编码助手,你一定熟悉那种“有点魔法”的体验:你在侧边栏里提问,AI 会在同一个会话里“记住”上下文,帮你分析代码、调用工具、解决卡壳问题。
借助 Claude Agent SDK,你可以在 Node.js 中用很少的代码搭建一个你自己的 Claude Code 风格 Agent。
本文会带你完成以下内容:
- 搭建 TypeScript 项目
- 用
query()发送第一个请求 - 让对话“有记忆”的会话机制
- 构建命令行聊天体验
- 通过 MCP 添加一个数学工具,让 Agent 会“用计算器”
所有示例都使用 TypeScript + Node.js,并且在结构和命名上尽量清晰,方便你直接迁移到自己的项目中。
1. 项目初始化
创建新项目:
mkdir dev-assistant-agent
cd dev-assistant-agent
npm init -y安装依赖:
npm install @anthropic-ai/claude-agent-sdk
npm install typescript tsx @types/node
npx tsc --init我们还会用 dotenv 来加载环境变量:
npm install dotenv在项目根目录创建 .env 文件:
ANTHROPIC_API_KEY=your_api_key_here
ANTHROPIC_BASE_URL=https://api.anthropic.com再准备一个简单的配置模块:
// 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. 第一次调用:一个最小可用的 Agent
Claude Agent SDK 的核心是 query() 函数。它不是返回一整个字符串,而是返回一个 异步迭代器,输出系统消息、助手消息等。你可以像流式响应一样一边接收一边处理。
先写一个简单的“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);
}
}
}
}
}入口文件:
// src/main.ts
import { runHelloAgent } from './agent/helloAgent';
async function main() {
console.log('Launching hello agent...');
await runHelloAgent();
}
main().catch(console.error);运行:
npx tsx src/main.ts你应该能看到 Claude 描述它能做什么 —— 说明 SDK 已经接好了。
3. 会话机制:让 Agent 记住对话
单轮回复可以用,但一个 有记忆 的助手显然更好用。
Claude Agent SDK 支持 会话(session)。每个对话都有一个 session_id。再次调用 query() 时,通过 resume 参数传入这个 ID,就可以继续之前的会话。
先写一个小封装:发送消息,并返回本次使用的 session_id:
// 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 };
}可以这样测试:
// src/examples/sessionDemo.ts
import { sendMessageWithSession } from '../agent/sessionClient';
export async function runSessionDemo() {
let sessionId: string | undefined;
// 第一个问题
const first = await sendMessageWithSession('Hello, who are you?', sessionId);
sessionId = first.sessionId;
// 第二个问题,继续同一个对话
const second = await sendMessageWithSession(
'What did I just ask you?',
sessionId
);
sessionId = second.sessionId;
}现在 Claude 就可以回答“我刚刚问了你什么”,因为你在复用同一个 session。
4. 搭建命令行聊天体验(CLI)
接下来把它变成一个简单的 命令行聊天应用。
我们会:
- 使用 Node 的
readline模块 - 在多轮对话中维护
sessionId - 每条用户消息都通过
sendMessageWithSession发送
创建一个只负责 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);
});
}修改入口文件使用 CLI:
// src/main.ts
import { startCliConversation } from './cli/chatLoop';
startCliConversation().catch(console.error);运行:
npx tsx src/main.ts现在你就有了一个“没有 UI 的 Claude Code”:一个在终端里持续保持上下文、实时回答问题的助手。
5. 通过 MCP 给 Agent 加工具(示例:数学工具)
Claude Code 最强大的特点之一,就是“会用工具”——模型可以在需要时:
- 调用计算器
- 搜索文件
- 运行命令
- 查询 API
在 Claude Agent SDK 中,你可以通过 Model Context Protocol(MCP) 添加工具。下面我们实现一个数学工具,让 Agent 在需要精确计算时可以调用真实的“计算器”,而不是自己估算。
5.1 安装额外依赖
我们会用 mathjs (opens in a new tab) 和 zod 做入参校验:
npm install mathjs zod@3.25.76(使用 Zod 3.x 是为了避免与新版本的兼容性问题。)
5.2 一个简单的数学辅助函数
// 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 为 Agent 定义一个工具
SDK 提供了 tool 辅助函数。我们定义一个叫 numeric_calculator 的工具,它接收一个字符串参数 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 通过 MCP server 暴露工具
接下来把一个或多个工具打包成一个 MCP server:
// 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 让 CLI 支持工具调用
我们再写一个支持工具调用的消息发送函数,并把它接到新的 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,
},
// 工具命名规范: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':
// 工具结果会以一个特殊的 user 消息返回
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 };
}再写一个使用工具版 client 的 CLI 循环:
// 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);
});
}修改 src/main.ts 使用这个带工具的版本:
// src/main.ts
import { startCliWithTools } from './cli/chatWithTools';
startCliWithTools().catch(console.error);这时当你输入:
You: (2454 + 23546)^2 / 32你应该能看到:
- 一条日志提示已调用工具
- 工具返回的数值结果
- Claude 给出的自然语言解释与总结
到这里,你已经拥有了一个可以聊天、记忆上下文、还能 调用工具 的“类 Claude Code” Agent。
6. 扩展你的 Agent 的更多想法
在这个骨架跑通之后,你就可以开始添加更多 Claude Code 风格的能力了,比如:
-
代码库相关工具
实现读取仓库文件、全文搜索、汇总大文件内容等工具。 -
执行类工具
工具可以在受控环境中运行测试、脚本或小段代码。 -
面向项目的 systemPrompt
把项目元数据、架构文档、代码规范等注入systemPrompt,让 Agent 更“懂这个项目”。 -
按用户持久化会话
在数据库中以用户为键保存session_id,为每个用户提供长生命周期的专属助手。
Claude Agent SDK 提供的是基础积木;你的 Agent 要做到多“像 Claude Code”,则取决于你如何设计工具、提示词和 UI。