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

Теперь, когда мы разобрались с тем, как описывать и интегрировать функции-инструменты в MCP-сервер, настало время перейти к самому интересному — созданию реальных, полезных инструментов, которые позволят вашему ИИ-агенту не просто рассуждать, а действовать.

Мы уже знаем: LLM может вызывать функции только тогда, когда они чётко описаны в спецификации MCP и доступны через /capabilities. Но описание — это только начало. Настоящая сила агента раскрывается, когда он может взаимодействовать с внешними системами, обрабатывать данные и генерировать контент. Именно это мы и будем реализовывать.

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

Давайте рассмотрим два типовых, но мощных сценария: получение данных из API и создание PDF-отчётов. Оба примера написаны на TypeScript, используют асинхронные функции и включают валидацию и обработку ошибок — ключевые элементы надёжного агента.

Инструмент API-запроса: получение данных с GigaChat

Один из самых распространённых сценариев — интеграция с внешними сервисами. В России особенно актуальна работа с локальными LLM, такими как GigaChat от Сбербанка. Давайте создадим инструмент, который будет получать краткое резюме текста через её API.

Сначала опишем инструмент в формате, понятном LLM:

{
  "name": "summarizeTextWithGigaChat",
  "description": "Анализирует текст и возвращает краткое резюме с помощью GigaChat API-сервиса",
  "parameters": {
    "type": "object",
    "properties": {
      "text": {
        "type": "string",
        "description": "Текст для анализа и резюмирования"
      }
    },
    "required": ["text"]
  }
}

Теперь реализуем функцию:

import axios from 'axios';

const GIGACHAT_URL = 'https://gigachat.api.cloud.sber.ru/v1/chat/completions';
const API_KEY = process.env.GIGACHAT_API_KEY;

interface GigaChatResponse {
  choices: { message: { content: string } }[];
}

async function summarizeTextWithGigaChat(text: string): Promise<string> {
  // Валидация входных данных
  if (!text || text.length < 10) {
    throw new Error('Текст слишком короткий для резюмирования');
  }

  try {
    const response = await axios.post<GigaChat游戏副本>(
      GIGACHAT_URL,
      {
        model: 'GigaChat',
        messages: [
          {
            role: 'system',
            content: 'Ты — помощник, который создаёт краткие, ёмкие резюме текстов.'
          },
          { role: 'user', content: `Сделай резюме: ${text}` }
        ]
      },
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.data.choices[0].message.content.trim();
  } catch (error: any) {
    console.error('Ошибка при вызове GigaChat:', error.message);
    throw new Error('Не удалось получить резюме. Проверьте API-ключ и подключение.');
  }
}

Важно!
Обратите внимание на использование async/await, валидацию входных данных и обработку ошибок. Без этого инструмент может "падать" при первом же некорректном запросе, что сделает агента ненадёжным.

Теперь LLM сможет вызывать эту функцию, передавая текст, а MCP-сервер — выполнять запрос и возвращать результат. Это и есть инструмент API-запроса — функция, которая взаимодействует с внешним сервисом и возвращает структурированный ответ.

Инструмент генерации PDF: создание отчётов

Ещё один полезный инструмент — генерация файлов. Представим, что наш агент должен создавать PDF-отчёты по запросу пользователя. Мы используем библиотеку pdf-lib, которая отлично работает в Node.js.

Описание инструмента:

{
  "name": "generateUserReportPDF",
  "description": "Создаёт PDF-отчёт с информацией о пользователе: имя, email, статус подписки",
  "parameters": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "email": { "type": "string", "format": "email" },
      "subscription": { "type": "string", "enum": ["free", "pro", "enterprise"] }
    },
    "required": ["name", "email", "subscription"]
  }
}

Реализация:

import { PDFDocument, StandardFonts } from 'pdf-lib';

async function generateUserReportPDF(
  name: string,
  email: string,
  subscription: string
): Promise<Uint8Array> {
  // Валидация
  if (!name || !email || !subscription) {
    throw new Error('Все поля обязательны для заполнения');
  }

  if (!email.includes('@')) {
    throw new Error('Некорректный email');
  }

  try {
    const pdfDoc = await PDFDocument.create();
    const page = pdfDoc.addPage([400, 200]);
    const font = await pdfDoc.embedFont(StandardFonts.Helvetica);

    const fontSize = 12;
    page.drawText(`Отчёт о пользователе`, { x: 50, y: 170, size: fontSize, font });
    page.drawText(`Имя: ${name}`, { x: 50, y: 140, size: fontSize, font });
    page.drawText(`Email: ${email}`, { x: 50, y: 120, size: fontSize, font });
    page.drawText(`Подписка: ${subscription}`, { x: 50, y: 100, size: fontSize, font });

    return await pdfDoc.save(); // Возвращает бинарные данные PDF
  } catch (error) {
    console.error('Ошибка при генерации PDF:', error);
    throw new Error('Не удалось создать PDF-отчёт');
  }
}

Теперь ваш агент может не просто говорить о пользователе, а создавать официальный документ — настоящий шаг к автоматизации бизнес-процессов.

Совет: безопасность
Всегда проверяйте входные данные в инструментах. Особенно если они используются для генерации файлов или запросов к API. Это защитит ваш сервер от инъекций и перегрузок.

Практическое упражнение: инструмент для работы с данными

Теперь пришло время применить знания на практике. Создайте простой инструмент для чтения данных из JSON-файла — например, списка задач.

Задача:
Реализуйте инструмент getTasksFromJSON, который:

  • Читает файл tasks.json
  • Возвращает массив задач (с полями id, title, status)
  • Обрабатывает ошибки (файл не найден, повреждён и т.д.)
import fs from 'fs/promises';

async function getTasksFromJSON(): Promise<any[]> {
  try {
    const data = await fs.readFile('tasks.json', 'utf-8');
    const tasks = JSON.parse(data);
    
    if (!Array.isArray(tasks)) {
      throw new Error('Формат данных неверен: ожидается массив');
    }

    return tasks;
  } catch (error: any) {
    if (error.code === 'ENOENT') {
      throw new Error('Файл tasks.json не найден');
    }
    throw new Error(`Ошибка чтения данных: ${error.message}`);
  }
}

Проверьте себя:

  • Есть ли описание инструмента для LLM?
  • Используется ли асинхронность?
  • Обрабатываются ли ошибки?
  • Проверяется ли структура данных?

Если да — вы создали полноценный пользовательский инструмент.

Подводим итоги

Мы создали три типа инструментов:

  • Инструмент API-запроса — для взаимодействия с внешними сервисами (GigaChat)
  • Инструмент генерации PDF — для создания контента
  • Инструмент для работы с файлами — для доступа к локальным данным

Каждый из них расширяет возможности агента, превращая его из "говорящего" в действующего агента.

Помните:
Без описания в /capabilities LLM не увидит ваш инструмент. Он должен быть явно объявлен — как мы это делали в предыдущей теме.

Теперь ваш агент может:

  • Получать информацию извне
  • Генерировать документы
  • Работать с данными

Это уже не просто чат-бот. Это автономный помощник, способный выполнять реальные задачи.

Что дальше?

Теперь, когда у нас есть набор полезных инструментов, пришло время объединить их в полноценный сервер. В следующей теме мы начнём создавать MCP-сервер на TypeScript с использованием Express.js — одного из самых популярных фреймворков для Node.js.

Там мы:

  • Определим эндпоинты /capabilities, /prompt, /tool
  • Подключим наши инструменты
  • Сделаем сервер готовым к интеграции с LLM

Вы уже почти у цели: через несколько шагов вы сможете запустить своего первого рабочего ИИ-агента.

Готовы превратить инструменты в реальный сервер? Тогда — вперёд! 🚀