Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Claude Code SDK 与程序化集成

Claude Code 没有传统意义上的“SDK npm 包“,但它提供了多层程序化集成方式:MCP 服务器(外部工具)、Hooks(生命周期脚本)、CLI 程序化调用(子进程集成)。本章介绍这些程序化集成方式,并以天气预报智能体为例展示完整实现。


SDK 总览

Claude Code 的“SDK“由三个层次组成:

层次方式灵活度配置方式适用场景
MCP 服务器JSON-RPC 外部进程协议⭐⭐⭐⭐.claude/settings.json外部工具集成、API 调用
HooksShell/LLM/Agent(智能体) 脚本⭐⭐⭐.claude/settings.json事件驱动自动化
CLI 程序化子进程执行⭐⭐Shell 脚本/CICI/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 服务器HooksCLI 程序化
可添加自定义工具❌(仅验证/脚本)
支持编程逻辑✅(任意 Node.js)✅(Shell/Node)
事件驱动❌(按需调用)✅(生命周期事件)
外部进程隔离❌(同进程)
调试难度
MCP 生态互通✅(可复用社区 MCP)

相关资源


常见反模式

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 读取。

关联章节