Пользовательские инструменты позволяют расширить возможности Claude Code собственной функциональностью через внутрипроцессные MCP-серверы, позволяя Claude взаимодействовать с внешними сервисами, API или выполнять специализированные операции.

Создание пользовательских инструментов

Используйте вспомогательные функции createSdkMcpServer и tool для определения типобезопасных пользовательских инструментов:
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}%`
          }]
        };
      }
    )
  ]
});

Использование пользовательских инструментов

Передайте пользовательский сервер в функцию 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-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 это 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)}%`
          }]
        };
      }
    )
  ]
});

Связанная документация