Alat kustom memungkinkan Anda memperluas kemampuan Claude Code dengan fungsionalitas Anda sendiri melalui server MCP dalam proses, memungkinkan Claude berinteraksi dengan layanan eksternal, API, atau melakukan operasi khusus.

Membuat Alat Kustom

Gunakan fungsi helper createSdkMcpServer dan tool untuk mendefinisikan alat kustom yang type-safe:
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// Buat server SDK MCP dengan alat kustom
const customServer = createSdkMcpServer({
  name: "my-custom-tools",
  version: "1.0.0",
  tools: [
    tool(
      "get_weather",
      "Dapatkan suhu saat ini untuk suatu lokasi menggunakan koordinat",
      {
        latitude: z.number().describe("Koordinat lintang"),
        longitude: z.number().describe("Koordinat bujur")
      },
      async (args) => {
        const response = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&current=temperature_2m&temperature_unit=fahrenheit`);
        const data = await response.json();

        return {
          content: [{
            type: "text",
            text: `Suhu: ${data.current.temperature_2m}°F`
          }]
        };
      }
    )
  ]
});

Menggunakan Alat Kustom

Berikan server kustom ke fungsi query melalui opsi mcpServers sebagai dictionary/object.
Penting: Alat MCP kustom memerlukan mode input streaming. Anda harus menggunakan async generator/iterable untuk parameter prompt - string sederhana tidak akan bekerja dengan server MCP.

Format Nama Alat

Ketika alat MCP diekspos ke Claude, nama mereka mengikuti format tertentu:
  • Pola: mcp__{server_name}__{tool_name}
  • Contoh: Alat bernama get_weather di server my-custom-tools menjadi mcp__my-custom-tools__get_weather

Mengkonfigurasi Alat yang Diizinkan

Anda dapat mengontrol alat mana yang dapat digunakan Claude melalui opsi allowedTools:
import { query } from "@anthropic-ai/claude-agent-sdk";

// Gunakan alat kustom dalam query Anda dengan input streaming
async function* generateMessages() {
  yield {
    type: "user" as const,
    message: {
      role: "user" as const,
      content: "Bagaimana cuaca di San Francisco?"
    }
  };
}

for await (const message of query({
  prompt: generateMessages(),  // Gunakan async generator untuk input streaming
  options: {
    mcpServers: {
      "my-custom-tools": customServer  // Berikan sebagai object/dictionary, bukan array
    },
    // Secara opsional tentukan alat mana yang dapat digunakan Claude
    allowedTools: [
      "mcp__my-custom-tools__get_weather",  // Izinkan alat cuaca
      // Tambahkan alat lain sesuai kebutuhan
    ],
    maxTurns: 3
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

Contoh Beberapa Alat

Ketika server MCP Anda memiliki beberapa alat, Anda dapat secara selektif mengizinkannya:
const multiToolServer = createSdkMcpServer({
  name: "utilities",
  version: "1.0.0",
  tools: [
    tool("calculate", "Lakukan perhitungan", { /* ... */ }, async (args) => { /* ... */ }),
    tool("translate", "Terjemahkan teks", { /* ... */ }, async (args) => { /* ... */ }),
    tool("search_web", "Cari di web", { /* ... */ }, async (args) => { /* ... */ })
  ]
});

// Izinkan hanya alat tertentu dengan input streaming
async function* generateMessages() {
  yield {
    type: "user" as const,
    message: {
      role: "user" as const,
      content: "Hitung 5 + 3 dan terjemahkan 'hello' ke bahasa Spanyol"
    }
  };
}

for await (const message of query({
  prompt: generateMessages(),  // Gunakan async generator untuk input streaming
  options: {
    mcpServers: {
      utilities: multiToolServer
    },
    allowedTools: [
      "mcp__utilities__calculate",   // Izinkan kalkulator
      "mcp__utilities__translate",   // Izinkan penerjemah
      // "mcp__utilities__search_web" TIDAK diizinkan
    ]
  }
})) {
  // Proses pesan
}

Keamanan Tipe dengan Python

Decorator @tool mendukung berbagai pendekatan definisi skema untuk keamanan tipe:
import { z } from "zod";

tool(
  "process_data",
  "Proses data terstruktur dengan keamanan tipe",
  {
    // Skema Zod mendefinisikan validasi runtime dan tipe 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 sepenuhnya diketik berdasarkan skema
    // TypeScript tahu: args.data.name adalah string, args.data.age adalah number, dll.
    console.log(`Memproses data ${args.data.name} sebagai ${args.format}`);
    
    // Logika pemrosesan Anda di sini
    return {
      content: [{
        type: "text",
        text: `Data diproses untuk ${args.data.name}`
      }]
    };
  }
)

Penanganan Error

Tangani error dengan baik untuk memberikan umpan balik yang bermakna:
tool(
  "fetch_data",
  "Ambil data dari API",
  {
    endpoint: z.string().url().describe("URL endpoint API")
  },
  async (args) => {
    try {
      const response = await fetch(args.endpoint);
      
      if (!response.ok) {
        return {
          content: [{
            type: "text",
            text: `Error 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: `Gagal mengambil data: ${error.message}`
        }]
      };
    }
  }
)

Contoh Alat

Alat Query Database

const databaseServer = createSdkMcpServer({
  name: "database-tools",
  version: "1.0.0",
  tools: [
    tool(
      "query_database",
      "Jalankan query database",
      {
        query: z.string().describe("Query SQL untuk dijalankan"),
        params: z.array(z.any()).optional().describe("Parameter query")
      },
      async (args) => {
        const results = await db.query(args.query, args.params || []);
        return {
          content: [{
            type: "text",
            text: `Ditemukan ${results.length} baris:\n${JSON.stringify(results, null, 2)}`
          }]
        };
      }
    )
  ]
});

Alat API Gateway

const apiGatewayServer = createSdkMcpServer({
  name: "api-gateway",
  version: "1.0.0",
  tools: [
    tool(
      "api_request",
      "Buat permintaan API yang diautentikasi ke layanan eksternal",
      {
        service: z.enum(["stripe", "github", "openai", "slack"]).describe("Layanan yang akan dipanggil"),
        endpoint: z.string().describe("Path endpoint API"),
        method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe("Metode HTTP"),
        body: z.record(z.any()).optional().describe("Body permintaan"),
        query: z.record(z.string()).optional().describe("Parameter query")
      },
      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)
          }]
        };
      }
    )
  ]
});

Alat Kalkulator

const calculatorServer = createSdkMcpServer({
  name: "calculator",
  version: "1.0.0",
  tools: [
    tool(
      "calculate",
      "Lakukan perhitungan matematika",
      {
        expression: z.string().describe("Ekspresi matematika untuk dievaluasi"),
        precision: z.number().optional().default(2).describe("Presisi desimal")
      },
      async (args) => {
        try {
          // Gunakan library evaluasi matematika yang aman di produksi
          const result = eval(args.expression); // Hanya contoh!
          const formatted = Number(result).toFixed(args.precision);
          
          return {
            content: [{
              type: "text",
              text: `${args.expression} = ${formatted}`
            }]
          };
        } catch (error) {
          return {
            content: [{
              type: "text",
              text: `Error: Ekspresi tidak valid - ${error.message}`
            }]
          };
        }
      }
    ),
    tool(
      "compound_interest",
      "Hitung bunga majemuk untuk investasi",
      {
        principal: z.number().positive().describe("Jumlah investasi awal"),
        rate: z.number().describe("Tingkat bunga tahunan (sebagai desimal, mis. 0.05 untuk 5%)"),
        time: z.number().positive().describe("Periode investasi dalam tahun"),
        n: z.number().positive().default(12).describe("Frekuensi penggabungan per tahun")
      },
      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: `Analisis Investasi:\n` +
                  `Pokok: $${args.principal.toFixed(2)}\n` +
                  `Tingkat: ${(args.rate * 100).toFixed(2)}%\n` +
                  `Waktu: ${args.time} tahun\n` +
                  `Penggabungan: ${args.n} kali per tahun\n\n` +
                  `Jumlah Akhir: $${amount.toFixed(2)}\n` +
                  `Bunga yang Diperoleh: $${interest.toFixed(2)}\n` +
                  `Pengembalian: ${((interest / args.principal) * 100).toFixed(2)}%`
          }]
        };
      }
    )
  ]
});

Dokumentasi Terkait