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-code";
import { z } from "zod";

// Buat server MCP SDK dengan alat kustom
const customServer = createSdkMcpServer({
  name: "my-custom-tools",
  version: "1.0.0",
  tools: [
    tool(
      "get_weather",
      "Dapatkan cuaca saat ini untuk suatu lokasi",
      {
        location: z.string().describe("Nama kota atau koordinat"),
        units: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("Satuan suhu")
      },
      async (args) => {
        // Panggil API cuaca
        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: `Suhu: ${data.temp}°\nKondisi: ${data.conditions}\nKelembaban: ${data.humidity}%`
          }]
        };
      }
    )
  ]
});

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-code";

// 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
}

Type Safety dengan Python

Dekorator @tool mendukung berbagai pendekatan definisi skema untuk type safety:
import { z } from "zod";

tool(
  "process_data",
  "Proses data terstruktur dengan type safety",
  {
    // 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",
      "Eksekusi query database",
      {
        query: z.string().describe("Query SQL untuk dieksekusi"),
        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 terotentikasi 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 pemajemukan 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` +
                  `Pemajemukan: ${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