自訂工具允許您透過程序內 MCP 伺服器以自己的功能擴展 Claude Code 的能力,使 Claude 能夠與外部服務、API 互動,或執行專門的操作。

建立自訂工具

使用 createSdkMcpServertool 輔助函數來定義類型安全的自訂工具:
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-code";
import { z } from "zod";

// 建立具有自訂工具的 SDK MCP 伺服器
const customServer = createSdkMcpServer({
  name: "my-custom-tools",
  version: "1.0.0",
  tools: [
    tool(
      "get_weather",
      "取得位置的當前天氣",
      {
        location: z.string().describe("城市名稱或座標"),
        units: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("溫度單位")
      },
      async (args) => {
        // 呼叫天氣 API
        const response = await fetch(
          `https://api.weather.com/v1/current?q=${args.location}&units=${args.units}`
        );
        const data = await response.json();
        
        return {
          content: [{
            type: "text",
            text: `溫度:${data.temp}°\n狀況:${data.conditions}\n濕度:${data.humidity}%`
          }]
        };
      }
    )
  ]
});

使用自訂工具

透過 mcpServers 選項將自訂伺服器作為字典/物件傳遞給 query 函數。
重要: 自訂 MCP 工具需要串流輸入模式。您必須為 prompt 參數使用非同步生成器/可迭代物件 - 簡單的字串無法與 MCP 伺服器一起使用。

工具名稱格式

當 MCP 工具暴露給 Claude 時,它們的名稱遵循特定格式:
  • 模式:mcp__{server_name}__{tool_name}
  • 範例:在伺服器 my-custom-tools 中名為 get_weather 的工具變成 mcp__my-custom-tools__get_weather

設定允許的工具

您可以透過 allowedTools 選項控制 Claude 可以使用哪些工具:
import { query } from "@anthropic-ai/claude-code";

// 在查詢中使用自訂工具與串流輸入
async function* generateMessages() {
  yield {
    type: "user" as const,
    message: {
      role: "user" as const,
      content: "舊金山的天氣如何?"
    }
  };
}

for await (const message of query({
  prompt: generateMessages(),  // 使用非同步生成器進行串流輸入
  options: {
    mcpServers: {
      "my-custom-tools": customServer  // 作為物件/字典傳遞,而非陣列
    },
    // 可選擇性地指定 Claude 可以使用哪些工具
    allowedTools: [
      "mcp__my-custom-tools__get_weather",  // 允許天氣工具
      // 根據需要新增其他工具
    ],
    maxTurns: 3
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

多工具範例

當您的 MCP 伺服器有多個工具時,您可以選擇性地允許它們:
const multiToolServer = createSdkMcpServer({
  name: "utilities",
  version: "1.0.0",
  tools: [
    tool("calculate", "執行計算", { /* ... */ }, async (args) => { /* ... */ }),
    tool("translate", "翻譯文字", { /* ... */ }, async (args) => { /* ... */ }),
    tool("search_web", "搜尋網路", { /* ... */ }, async (args) => { /* ... */ })
  ]
});

// 僅允許特定工具與串流輸入
async function* generateMessages() {
  yield {
    type: "user" as const,
    message: {
      role: "user" as const,
      content: "計算 5 + 3 並將 'hello' 翻譯成西班牙文"
    }
  };
}

for await (const message of query({
  prompt: generateMessages(),  // 使用非同步生成器進行串流輸入
  options: {
    mcpServers: {
      utilities: multiToolServer
    },
    allowedTools: [
      "mcp__utilities__calculate",   // 允許計算器
      "mcp__utilities__translate",   // 允許翻譯器
      // "mcp__utilities__search_web" 不被允許
    ]
  }
})) {
  // 處理訊息
}

Python 的類型安全

@tool 裝飾器支援各種模式定義方法以實現類型安全:
import { z } from "zod";

tool(
  "process_data",
  "使用類型安全處理結構化資料",
  {
    // Zod 模式定義執行時驗證和 TypeScript 類型
    data: z.object({
      name: z.string(),
      age: z.number().min(0).max(150),
      email: z.string().email(),
      preferences: z.array(z.string()).optional()
    }),
    format: z.enum(["json", "csv", "xml"]).default("json")
  },
  async (args) => {
    // args 基於模式完全類型化
    // TypeScript 知道:args.data.name 是字串,args.data.age 是數字等
    console.log(`正在將 ${args.data.name} 的資料處理為 ${args.format}`);
    
    // 您的處理邏輯在此
    return {
      content: [{
        type: "text",
        text: `已處理 ${args.data.name} 的資料`
      }]
    };
  }
)

錯誤處理

優雅地處理錯誤以提供有意義的回饋:
tool(
  "fetch_data",
  "從 API 取得資料",
  {
    endpoint: z.string().url().describe("API 端點 URL")
  },
  async (args) => {
    try {
      const response = await fetch(args.endpoint);
      
      if (!response.ok) {
        return {
          content: [{
            type: "text",
            text: `API 錯誤:${response.status} ${response.statusText}`
          }]
        };
      }
      
      const data = await response.json();
      return {
        content: [{
          type: "text",
          text: JSON.stringify(data, null, 2)
        }]
      };
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: `取得資料失敗:${error.message}`
        }]
      };
    }
  }
)

範例工具

資料庫查詢工具

const databaseServer = createSdkMcpServer({
  name: "database-tools",
  version: "1.0.0",
  tools: [
    tool(
      "query_database",
      "執行資料庫查詢",
      {
        query: z.string().describe("要執行的 SQL 查詢"),
        params: z.array(z.any()).optional().describe("查詢參數")
      },
      async (args) => {
        const results = await db.query(args.query, args.params || []);
        return {
          content: [{
            type: "text",
            text: `找到 ${results.length} 行:\n${JSON.stringify(results, null, 2)}`
          }]
        };
      }
    )
  ]
});

API 閘道工具

const apiGatewayServer = createSdkMcpServer({
  name: "api-gateway",
  version: "1.0.0",
  tools: [
    tool(
      "api_request",
      "對外部服務進行已驗證的 API 請求",
      {
        service: z.enum(["stripe", "github", "openai", "slack"]).describe("要呼叫的服務"),
        endpoint: z.string().describe("API 端點路徑"),
        method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe("HTTP 方法"),
        body: z.record(z.any()).optional().describe("請求主體"),
        query: z.record(z.string()).optional().describe("查詢參數")
      },
      async (args) => {
        const config = {
          stripe: { baseUrl: "https://api.stripe.com/v1", key: process.env.STRIPE_KEY },
          github: { baseUrl: "https://api.github.com", key: process.env.GITHUB_TOKEN },
          openai: { baseUrl: "https://api.openai.com/v1", key: process.env.OPENAI_KEY },
          slack: { baseUrl: "https://slack.com/api", key: process.env.SLACK_TOKEN }
        };
        
        const { baseUrl, key } = config[args.service];
        const url = new URL(`${baseUrl}${args.endpoint}`);
        
        if (args.query) {
          Object.entries(args.query).forEach(([k, v]) => url.searchParams.set(k, v));
        }
        
        const response = await fetch(url, {
          method: args.method,
          headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
          body: args.body ? JSON.stringify(args.body) : undefined
        });
        
        const data = await response.json();
        return {
          content: [{
            type: "text",
            text: JSON.stringify(data, null, 2)
          }]
        };
      }
    )
  ]
});

計算器工具

const calculatorServer = createSdkMcpServer({
  name: "calculator",
  version: "1.0.0",
  tools: [
    tool(
      "calculate",
      "執行數學計算",
      {
        expression: z.string().describe("要評估的數學表達式"),
        precision: z.number().optional().default(2).describe("小數精度")
      },
      async (args) => {
        try {
          // 在生產環境中使用安全的數學評估庫
          const result = eval(args.expression); // 僅供範例!
          const formatted = Number(result).toFixed(args.precision);
          
          return {
            content: [{
              type: "text",
              text: `${args.expression} = ${formatted}`
            }]
          };
        } catch (error) {
          return {
            content: [{
              type: "text",
              text: `錯誤:無效表達式 - ${error.message}`
            }]
          };
        }
      }
    ),
    tool(
      "compound_interest",
      "計算投資的複利",
      {
        principal: z.number().positive().describe("初始投資金額"),
        rate: z.number().describe("年利率(以小數表示,例如 0.05 代表 5%)"),
        time: z.number().positive().describe("投資期間(年)"),
        n: z.number().positive().default(12).describe("每年複利頻率")
      },
      async (args) => {
        const amount = args.principal * Math.pow(1 + args.rate / args.n, args.n * args.time);
        const interest = amount - args.principal;
        
        return {
          content: [{
            type: "text",
            text: `投資分析:\n` +
                  `本金:$${args.principal.toFixed(2)}\n` +
                  `利率:${(args.rate * 100).toFixed(2)}%\n` +
                  `時間:${args.time}\n` +
                  `複利:每年 ${args.n}\n\n` +
                  `最終金額:$${amount.toFixed(2)}\n` +
                  `賺取利息:$${interest.toFixed(2)}\n` +
                  `回報:${((interest / args.principal) * 100).toFixed(2)}%`
          }]
        };
      }
    )
  ]
});

相關文件