Пользовательские инструменты
Пользовательские инструменты позволяют расширить возможности Claude Code с помощью собственной функциональности через внутрипроцессные MCP серверы, позволяя Claude взаимодействовать с внешними сервисами, API или выполнять специализированные операции.
Создание пользовательских инструментов
Используйте вспомогательные функции createSdkMcpServer и tool для определения типобезопасных пользовательских инструментов:
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
// Создание SDK MCP сервера с пользовательскими инструментами
const customServer = createSdkMcpServer({
name: "my-custom-tools",
version: "1.0.0",
tools: [
tool(
"get_weather",
"Получить текущую температуру для местоположения по координатам",
{
latitude: z.number().describe("Координата широты"),
longitude: z.number().describe("Координата долготы")
},
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: `Температура: ${data.current.temperature_2m}°F`
}]
};
}
)
]
});from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeSDKClient, ClaudeAgentOptions
from typing import Any
import aiohttp
# Определение пользовательского инструмента с использованием декоратора @tool
@tool("get_weather", "Получить текущую температуру для местоположения по координатам", {"latitude": float, "longitude": float})
async def get_weather(args: dict[str, Any]) -> dict[str, Any]:
# Вызов API погоды
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"Температура: {data['current']['temperature_2m']}°F"
}]
}
# Создание SDK MCP сервера с пользовательским инструментом
custom_server = create_sdk_mcp_server(
name="my-custom-tools",
version="1.0.0",
tools=[get_weather] # Передача декорированной функции
)Использование пользовательских инструментов
Передайте пользовательский сервер в функцию query через опцию mcpServers в виде словаря/объекта.
Важно: Пользовательские MCP инструменты требуют режима потокового ввода. Вы должны использовать асинхронный генератор/итерируемый объект для параметра prompt - простая строка не будет работать с MCP серверами.
Формат имени инструмента
Когда MCP инструменты предоставляются Claude, их имена следуют определенному формату:
- Шаблон:
mcp__{server_name}__{tool_name} - Пример: Инструмент с именем
get_weatherна сервереmy-custom-toolsстановитсяmcp__my-custom-tools__get_weather
Настройка разрешенных инструментов
Вы можете контролировать, какие инструменты может использовать Claude, через опцию allowedTools:
import { query } from "@anthropic-ai/claude-agent-sdk";
// Использование пользовательских инструментов в запросе с потоковым вводом
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 это string, args.data.age это number, и т.д.
console.log(`Обработка данных ${args.data.name} как ${args.format}`);
// Ваша логика обработки здесь
return {
content: [{
type: "text",
text: `Обработаны данные для ${args.data.name}`
}]
};
}
)Обработка ошибок
Обрабатывайте ошибки корректно, чтобы предоставить осмысленную обратную связь:
tool(
"fetch_data",
"Получить данные из API",
{
endpoint: z.string().url().describe("URL конечной точки API")
},
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)}%`
}]
};
}
)
]
});