Claude Code SDK 与程序化集成
Claude Code 没有传统意义上的“SDK npm 包“,但它提供了多层程序化集成方式:MCP 服务器(外部工具)、Hooks(生命周期脚本)、CLI 程序化调用(子进程集成)。本章介绍这些程序化集成方式,并以天气预报智能体为例展示完整实现。
SDK 总览
Claude Code 的“SDK“由三个层次组成:
| 层次 | 方式 | 灵活度 | 配置方式 | 适用场景 |
|---|---|---|---|---|
| MCP 服务器 | JSON-RPC 外部进程协议 | ⭐⭐⭐⭐ | .claude/settings.json | 外部工具集成、API 调用 |
| Hooks | Shell/LLM/Agent(智能体) 脚本 | ⭐⭐⭐ | .claude/settings.json | 事件驱动自动化 |
| CLI 程序化 | 子进程执行 | ⭐⭐ | Shell 脚本/CI | CI/CD 流水线 |
与 OpenCode 的区别:Claude Code 没有代码级 Plugin(插件) API(如
definePlugin),所有扩展通过配置文件和外部进程实现。但 MCP(模型上下文协议) 协议是 Anhtropic 主导的开放标准,生态最为成熟。
方式一:MCP 服务器集成
MCP(Model Context(上下文) Protocol)是 Claude Code 推荐的程序化扩展方式。通过编写 MCP 服务器,你可以为 Claude Code 添加任意自定义工具。
MCP 服务器基本结构
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{ name: "weather-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// 注册工具
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "get_weather",
description: "查询天气",
inputSchema: {
type: "object",
properties: {
city: { type: "string" },
},
},
},
],
}));
server.setRequestHandler("tools/call", async (request) => {
// 工具执行逻辑
return {
content: [{ type: "text", text: "天气结果" }],
};
});
const transport = new StdioServerTransport();
await server.connect(transport);
配置 MCP 服务器
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["path/to/mcp-weather-server/index.js"],
"env": {
"WEATHER_API_KEY": "${WEATHER_API_KEY}"
}
}
}
}
方式二:Hooks 脚本集成
Hooks 允许在 Claude Code 生命周期事件中执行 Shell 脚本:
{
"hooks": {
"PostToolUse": {
"command": "node",
"args": [".claude/hooks/validate-output.js"],
"timeout": 10000
},
"PreToolUse": {
"command": "node",
"args": [".claude/hooks/normalize-input.js"],
"timeout": 5000
}
}
}
Hook 脚本通过环境变量 $TOOL_NAME、$TOOL_INPUT、$TOOL_OUTPUT 等获取上下文。
方式三:CLI 程序化调用
# 非交互模式
claude -p "东京的天气如何?" --print
# 输出 JSON 格式
claude -p "东京天气" --json
# 管道输入
echo "查询东京、伦敦、纽约的天气" | claude --print
# 指定 CLAUDE.md 配置
claude -p "天气查询" --claude-md ./weather-config.md
案例:全球天气预报智能体
以下案例演示如何用 Claude Code 的 MCP 服务器和 Hooks 系统实现全球天气预报智能体。
案例架构
用户输入 "东京今天天气如何?"
│
▼
┌───────────────────┐
│ Claude Code Agent │
│ (MCP weather tool) │
└───────┬───────────┘
│ MCP 协议调用
▼
┌───────────────────────┐
│ MCP Weather Server │
│ (stdio 传输协议) │
└───────┬───────────────┘
│ 调用外部 API
▼
┌──────────────────┐
│ 外部天气 API │
│ (OpenWeatherMap)│
└───────┬──────────┘
│ 原始数据
▼
┌──────────────────┐
│ normalize.js │ 规范化 → 统一格式
└───────┬──────────┘
│ 规范化数据
▼
┌──────────────────┐
│ validate.js │ 验证 → 结果检查
└───────┬──────────┘
│ 返回 Claude Code
▼
┌──────────────────┐
│ 格式化回复给用户 │
└──────────────────┘
1. 数据模型与规范化
// 统一的天气预报数据规范
const WEATHER_SCHEMA = {
city: "", // 城市名
country: "", // 国家代码
temperature: {
current: 0, // 当前温度 (°C)
feels_like: 0, // 体感温度
min: 0, // 当日最低
max: 0, // 当日最高
},
humidity: 0, // 湿度 (%)
pressure: 0, // 气压 (hPa)
wind: {
speed: 0, // 风速 (m/s)
direction: "", // 风向
},
conditions: "", // 天气状况
description: "", // 详细描述
visibility: 0, // 能见度 (km)
timestamp: "", // ISO 8601
source: "", // 数据来源
};
2. MCP Weather Server(含规范化和验证)
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// ─── 步骤 1: 外部 API 调用 ───
async function fetchWeatherFromApi(city, apiKey) {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}&units=metric`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`API 请求失败: ${response.status}`);
}
return response.json();
}
// ─── 步骤 2: 数据规范化 ───
function normalizeWeatherData(raw) {
const directions = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"];
const windDir = directions[Math.round((raw.wind.deg || 0) / 45) % 8];
return {
city: raw.name,
country: raw.sys.country,
temperature: {
current: Math.round(raw.main.temp * 10) / 10,
feels_like: Math.round(raw.main.feels_like * 10) / 10,
min: Math.round(raw.main.temp_min * 10) / 10,
max: Math.round(raw.main.temp_max * 10) / 10,
},
humidity: raw.main.humidity,
pressure: raw.main.pressure,
wind: { speed: Math.round(raw.wind.speed * 10) / 10, direction: windDir },
conditions: raw.weather[0]?.main || "未知",
description: raw.weather[0]?.description || "",
visibility: Math.round((raw.visibility || 0) / 1000),
timestamp: new Date(raw.dt * 1000).toISOString(),
source: "OpenWeatherMap",
};
}
// ─── 步骤 3: 数据验证 ───
function validateWeatherData(data) {
const checks = [];
const addCheck = (name, passed, message) => {
checks.push({ name, passed, message });
};
addCheck("城市名称", data.city.length > 0, `城市: ${data.city}`);
addCheck("温度范围",
data.temperature.current >= -89 && data.temperature.current <= 57,
`温度 ${data.temperature.current}°C ${data.temperature.current >= -89 && data.temperature.current <= 57 ? "合理" : "异常"}`
);
addCheck("湿度",
data.humidity >= 0 && data.humidity <= 100,
`湿度 ${data.humidity}%`
);
addCheck("气压",
data.pressure >= 870 && data.pressure <= 1085,
`气压 ${data.pressure}hPa`
);
addCheck("风速",
data.wind.speed >= 0 && data.wind.speed <= 120,
`风速 ${data.wind.speed}m/s`
);
addCheck("能见度",
data.visibility >= 0 && data.visibility <= 100,
`能见度 ${data.visibility}km`
);
const allPassed = checks.every((c) => c.passed);
return { passed: allPassed, checks };
}
// ─── 步骤 4: MCP 服务器 ───
const server = new Server(
{ name: "global-weather-agent", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// 支持的全球城市
const MAJOR_CITIES = [
"Tokyo", "Beijing", "Shanghai", "Singapore", "Dubai",
"London", "Paris", "Berlin", "Moscow", "New York",
"Los Angeles", "Sydney", "Mumbai", "Seoul", "Bangkok",
"São Paulo", "Cairo", "Cape Town", "Toronto", "Mexico City",
];
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "get_weather",
description: "查询指定城市的当前天气,包含数据规范化和验证",
inputSchema: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名称(支持中英文)",
},
},
required: ["city"],
},
},
{
name: "list_supported_cities",
description: "列出支持的全球主要城市",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "batch_weather",
description: "批量查询多个城市天气并验证",
inputSchema: {
type: "object",
properties: {
cities: {
type: "array",
items: { type: "string" },
description: "城市名称数组",
},
},
required: ["cities"],
},
},
],
}));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name === "list_supported_cities") {
const list = MAJOR_CITIES.map((c, i) => `${i + 1}. ${c}`).join("\n");
return {
content: [{ type: "text", text: `支持以下城市:\n${list}\n共 ${MAJOR_CITIES.length} 个城市。` }],
};
}
if (name === "get_weather" || name === "batch_weather") {
const apiKey = process.env.WEATHER_API_KEY;
if (!apiKey) {
return { content: [{ type: "text", text: "错误: 未设置 WEATHER_API_KEY" }] };
}
const citiesToQuery = name === "batch_weather" ? args.cities : [args.city];
const results = [];
for (const city of citiesToQuery) {
try {
// 调用 API → 规范化 → 验证
const raw = await fetchWeatherFromApi(city, apiKey);
const normalized = normalizeWeatherData(raw);
const validation = validateWeatherData(normalized);
// 格式化输出
const lines = [
`## ${normalized.city}, ${normalized.country}`,
`**天气**: ${normalized.conditions} - ${normalized.description}`,
`**温度**: ${normalized.temperature.current}°C (体感 ${normalized.temperature.feels_like}°C)`,
` 最低 ${normalized.temperature.min}°C / 最高 ${normalized.temperature.max}°C`,
`**湿度**: ${normalized.humidity}% | **气压**: ${normalized.pressure}hPa`,
`**风速**: ${normalized.wind.speed}m/s (${normalized.wind.direction}风)`,
`**能见度**: ${normalized.visibility}km`,
];
// 验证结果
const failedChecks = validation.checks.filter((c) => !c.passed);
if (failedChecks.length > 0) {
lines.push(`**数据验证**: ❌ ${failedChecks.length} 项异常`);
failedChecks.forEach((c) => lines.push(` - ${c.name}: ${c.message}`));
} else {
lines.push("**数据验证**: ✅ 全部通过");
}
results.push(lines.join("\n"));
} catch (err) {
results.push(`## ${city}\n**错误**: ${err.message}`);
}
}
return {
content: [{ type: "text", text: results.join("\n\n---\n\n") }],
};
}
throw new Error(`未知工具: ${name}`);
});
const transport = new StdioServerTransport();
await server.connect(transport);
3. MCP 服务器 Hook 验证脚本
以下 Hook 脚本可配置为在每次工具调用后运行,验证 MCP 返回的数据质量:
#!/usr/bin/env node
// Claude Code Hook 脚本:验证天气数据
// 在 PostToolUse 事件中触发
const toolName = process.env.TOOL_NAME;
const toolOutput = process.env.TOOL_OUTPUT;
if (toolName === "get_weather" || toolName === "batch_weather") {
if (toolOutput.includes("错误")) {
console.error("⚠️ 天气查询返回了错误");
process.exit(1);
}
if (toolOutput.includes("数据验证: ✅")) {
console.log("✅ 天气数据已验证通过");
} else if (toolOutput.includes("数据验证: ❌")) {
console.warn("⚠️ 部分天气数据验证未通过");
}
}
4. CLAUDE.md 配置
# 天气智能体项目
## 可用工具
- `get_weather`: 查询城市天气(含数据验证)
- `list_supported_cities`: 列出支持的城市
- `batch_weather`: 批量查询
## 使用示例
- "东京今天天气如何?"
- "对比一下伦敦和纽约的天气"
- "查询东京、新加坡、悉尼三个城市的天气"
## 数据验证说明
每次天气查询会自动执行 6 项验证:
温度(-89~57°C)、湿度(0~100%)、气压(870~1085hPa)、
风速(0~120m/s)、能见度(0~100km)、时间戳格式
5. 配置与运行
{
"mcpServers": {
"global-weather": {
"command": "node",
"args": [".claude/mcp/weather-server.mjs"],
"env": {
"WEATHER_API_KEY": "${WEATHER_API_KEY}"
}
}
},
"hooks": {
"PostToolUse": {
"command": "node",
"args": [".claude/hooks/weather-validator.js"],
"timeout": 5000
}
}
}
# 安装依赖
npm install @modelcontextprotocol/sdk
# 设置 API Key
export WEATHER_API_KEY="your_openweathermap_api_key"
# 启动 Claude Code
claude
6. 使用示例
用户: 帮我查一下东京、伦敦和悉尼今天的天气,并验证数据质量。
Claude: 正在通过天气 MCP 服务器查询三个城市...
🌍 Tokyo, JP
天气: Clear - 晴空万里
温度: 24.5°C (体感 22.8°C) 最低 20.1°C / 最高 27.3°C
湿度: 65% | 气压: 1013hPa
风速: 3.1m/s (南风) | 能见度: 10km
数据验证: ✅ 全部通过
🌍 London, GB
天气: Clouds - 多云
温度: 15.2°C (体感 14.1°C) 最低 12.8°C / 最高 17.6°C
湿度: 78% | 气压: 1008hPa
风速: 5.6m/s (西风) | 能见度: 8km
数据验证: ✅ 全部通过
🌍 Sydney, AU
天气: Rain - 小雨
温度: 18.9°C (体感 17.5°C) 最低 16.2°C / 最高 21.4°C
湿度: 82% | 气压: 1018hPa
风速: 4.2m/s (东南风) | 能见度: 6km
数据验证: ✅ 全部通过
三城市数据均通过完整性验证,无异常值。
三种集成方式的对比
| 维度 | MCP 服务器 | Hooks | CLI 程序化 |
|---|---|---|---|
| 可添加自定义工具 | ✅ | ❌(仅验证/脚本) | ❌ |
| 支持编程逻辑 | ✅(任意 Node.js) | ✅(Shell/Node) | ❌ |
| 事件驱动 | ❌(按需调用) | ✅(生命周期事件) | ❌ |
| 外部进程隔离 | ✅ | ✅ | ❌(同进程) |
| 调试难度 | 中 | 低 | 低 |
| MCP 生态互通 | ✅(可复用社区 MCP) | ❌ | ❌ |
相关资源
- Claude Agent(智能体) SDK:编程式 Agent 开发 —
@anthropic-ai/claude-agent-sdk深入参考(生产级配置、上下文管理、错误重试) - 扩展机制参考 — Claude Code 六层扩展体系详解
- MCP 服务器 — MCP 协议在 OpenCode 中的配置和实践(跨工具参考)
- Claude Code 生态参考 — 社区扩展和最佳实践
常见反模式
MCP 服务器中不做输入验证就透传外部数据
天气预报智能体案例中展示了数据规范化的完整流程(API 调用 → normalize → validate),但许多开发者在自己的 MCP 服务器中跳过了验证步骤,直接将外部 API 的原始数据返回给 Claude。外部 API 可能返回异常值(如温度 -200°C)、格式变更或恶意注入的数据。未经验证的数据直接传递给 LLM 可能导致错误的推理结果,甚至通过 Prompt 注入影响 Agent 行为。
每个 MCP 服务器工具都应该实现输入验证和输出规范化。对外部 API 的响应执行字段完整性检查、数值范围检查和格式验证。将验证失败的信息明确返回给 Claude,让它能判断数据质量并调整策略。
Hooks 脚本中执行耗时操作而不设超时
Hook 脚本(PreToolUse/PostToolUse)在 Agent 的工具调用流程中同步执行。如果你的 Hook 脚本调用了一个响应缓慢的外部 API(如代码质量检查服务),整个 Agent 会话会阻塞等待 Hook 完成。在最坏情况下,一个超时的 Hook 可能导致 Agent 会话挂起。
所有 Hook 脚本都应该设置超时。在 Hook 配置中使用 timeout 参数(毫秒),建议设为 5-10 秒。如果 Hook 逻辑需要更长时间完成,改为异步模式:Hook 脚本只做轻量级检查并立即返回,将耗时操作放到后台进程,通过文件或消息队列与主流程同步。
CLI 程序化调用中不处理 stderr 输出
使用 claude -p "query" 进行脚本集成时,许多开发者只读取 stdout 而忽略 stderr。Claude Code 的诊断信息、警告和错误日志都输出到 stderr。忽略 stderr 意味着你无法知道 MCP 服务器连接失败、权限被拒绝或模型降级等问题,直到最终结果出现异常才发现。
在 CI/CD 脚本中,将 stderr 重定向到日志文件或 Sentry 等监控系统。至少检查 Claude CLI 的退出码(0 = 成功,非 0 = 失败),并在失败时捕获 stderr 输出用于调试。
适用场景与限制
MCP 服务器只适合工具级别的扩展
MCP 服务器是 Claude Code 推荐的程序化扩展方式,但它只能提供“工具“——被 Claude 按需调用的函数。MCP 无法实现“行为注入“(在 Agent 推理过程中自动触发)或“流程控制“(决定 Agent 是否应该继续执行)。如果你的需求是后者,MCP 服务器不是正确的抽象层。
对于需要行为注入的场景,使用 Hooks(Shell 脚本在生命周期事件中执行)。对于需要深度控制 Agent 行为的场景,使用 Agent SDK 的编程式 Hook。MCP 服务器最适合的场景是:提供外部数据源(数据库查询、API 调用)和外部操作能力(文件系统、版本控制)。
CLI 程序化调用的输出格式不稳定
claude -p "query" 的输出是自然语言文本,Claude 可能在回答中包含格式化符号、代码块标记或解释性文字。将这种输出作为脚本的输入时,格式不稳定会导致解析错误。即使使用 --output-format json,JSON 的 schema 也可能在 Claude Code 版本升级后发生变化。
对于需要稳定结构化输出的场景,优先使用 MCP 服务器(返回 JSON 格式的工具结果)或 Agent SDK(编程式消费消息流)。CLI 调用适合人类消费的场景(CI 日志、PR 评论),不适合作为自动化管道的结构化数据源。
天气预报案例无法直接用于生产
本章的天气预报智能体是一个教学示例,展示了 MCP 服务器 + Hooks 的集成模式,但缺少生产环境必需的元素:没有 API Key 的安全注入机制(示例中使用环境变量但没有讨论密钥轮换),没有速率限制(外部 API 可能有调用频率限制),没有缓存层(相同城市的重复查询每次都调用外部 API)。
将案例改造为生产部署时,需要补充:使用 HashiCorp Vault 或 AWS Secrets Manager 管理 API Key,实现 Token 桶速率限制器,添加内存或 Redis 缓存层(天气数据短期有效),以及错误重试和降级策略。
常见失败与陷阱
MCP 服务器配置路径错误导致静默失败
MCP 服务器配置文件的路径查找有优先级顺序:项目级 .mcp.json → 用户级 ~/.claude.json → 插件内 .mcp.json。如果在错误的位置配置了 MCP 服务器(比如在 .claude/settings.json 中而不是 .mcp.json 中),Claude Code 不会报错,只是不加载该服务器。
配置 MCP 服务器后,运行 claude mcp list 确认服务器出现在列表中。如果服务器未显示,检查配置文件的位置和格式是否正确。项目级配置必须在项目根目录的 .mcp.json 中,格式必须是 {"mcpServers": {...}}。
Hook 环境变量在不同平台上行为不一致
Hook 脚本通过环境变量($TOOL_NAME、$TOOL_INPUT、$TOOL_OUTPUT)获取上下文信息。这些环境变量的内容在不同操作系统(macOS/Linux/Windows)上的格式可能不同,特别是当输出包含特殊字符或路径分隔符时。一个在 macOS 上正常工作的 Hook 脚本在 Windows 上可能因为路径分隔符差异而失败。
跨平台团队应该在 Hook 脚本中使用跨平台兼容的解析逻辑。避免硬编码路径分隔符,使用 path 模块处理路径。对于 JSON 格式的环境变量,使用平台无关的 JSON 解析器而非正则表达式匹配。
CLI 调用的管道输入在 Windows 上的编码问题
在 Windows 上使用 echo "query" | claude --print 进行管道输入时,PowerShell 和 cmd.exe 的默认编码(GBK/UTF-16)可能导致中文内容传递给 Claude 时出现乱码。Claude Code 内部使用 UTF-8,管道输入的编码不匹配会导致 prompt 被错误解析。
Windows 环境中使用管道输入时,确保内容以 UTF-8 编码传递。PowerShell 中使用 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 设置输出编码,或者将 prompt 写入 UTF-8 编码的文件后用 Get-Content 读取。
关联章节
- ← Claude Agent(智能体) SDK:编程式 Agent 开发 —
@anthropic-ai/claude-agent-sdk深入参考 - → Claude Code 生态参考 — 社区扩展和最佳实践