Alat Kustom
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}¤t=temperature_2m&temperature_unit=fahrenheit`);
const data = await response.json();
return {
content: [{
type: "text",
text: `Suhu: ${data.current.temperature_2m}°F`
}]
};
}
)
]
});from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeSDKClient, ClaudeAgentOptions
from typing import Any
import aiohttp
# Definisikan alat kustom menggunakan decorator @tool
@tool("get_weather", "Dapatkan suhu saat ini untuk suatu lokasi menggunakan koordinat", {"latitude": float, "longitude": float})
async def get_weather(args: dict[str, Any]) -> dict[str, Any]:
# Panggil API cuaca
async with aiohttp.ClientSession() as session:
async with session.get(
f"https://api.open-meteo.com/v1/forecast?latitude={args['latitude']}&longitude={args['longitude']}¤t=temperature_2m&temperature_unit=fahrenheit"
) as response:
data = await response.json()
return {
"content": [{
"type": "text",
"text": f"Suhu: {data['current']['temperature_2m']}°F"
}]
}
# Buat server SDK MCP dengan alat kustom
custom_server = create_sdk_mcp_server(
name="my-custom-tools",
version="1.0.0",
tools=[get_weather] # Berikan fungsi yang telah didekorasi
)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_weatherdi servermy-custom-toolsmenjadimcp__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)}%`
}]
};
}
)
]
});