Определение и интеграция функций-инструментов
На прошлой странице мы научились, как сохранять и восстанавливать состояние агента — его память. Это позволило нашему ИИ-агенту помнить предыдущие взаимодействия и вести более осмысленный диалог.
Теперь пришло время наделить агента действием.
Пока агент может только рассуждать и отвечать. Но настоящая сила MCP — в способности агента взаимодействовать с внешним миром: получать данные, обновлять системы, запускать процессы.
Для этого используются инструменты (tools) — функции, которые агент может вызывать по решению LLM, когда это необходимо для выполнения задачи.
Что такое инструмент в MCP?
Инструмент (tool) — это функция на сервере, которая может быть вызвана LLM в процессе выполнения запроса.
Чтобы LLM мог её использовать, он должен понимать, что делает инструмент, какие у него параметры и что он возвращает.
Для этого инструмент описывается в специальном формате — JSON-схеме, доступной через эндпоинт /capabilities.
Когда LLM видит, что для решения задачи нужно выполнить действие (например, получить погоду), он формирует вызов инструмента и отправляет его на MCP-сервер.
Сервер обрабатывает вызов, выполняет функцию и возвращает результат обратно — и LLM продолжает рассуждать уже с новыми данными.
🔍 Важно: Инструменты — это не просто API-вызовы. Это структурированные возможности, которые LLM может осознанно выбирать, как человек выбирает отвёртку или молоток в зависимости от задачи.
Формат описания инструмента
Каждый инструмент описывается объектом в формате JSON. Вот обязательные поля:
| Поле | Описание |
|---|---|
name | Уникальное имя инструмента (латиница, без пробелов) |
description | Понятное описание того, что делает инструмент (LLM читает это!) |
parameters | JSON-схема параметров, которую понимает LLM |
Пример описания инструмента для получения погоды:
{
"name": "getWeather",
"description": "Возвращает текущую погоду в указанном городе",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Название города, например, Москва"
}
},
"required": ["city"]
}
}
⚠️ Ошибка, которую часто допускают:
Неточное описаниеdescription— например, "получает погоду".
Это недостаточно! LLM может не понять, нужен ли город, в каком формате и обязателен ли он.✅ Правильно: "Возвращает текущую погоду в указанном городе. Параметр city — обязательный, строка, например 'Санкт-Петербург'."
Реализация функции-инструмента на TypeScript
Теперь создадим саму функцию. Она должна:
- Принимать параметры в формате, соответствующем схеме
- Быть асинхронной (используем
async) - Выполнять действие (например, запрос к API)
- Возвращать результат в стандартизированном формате
import axios from 'axios';
interface GetWeatherParams {
city: string;
}
// Асинхронная функция-инструмент
export const getWeather = async (params: GetWeatherParams) => {
try {
// Валидация параметров
if (!params.city || typeof params.city !== 'string') {
return {
result: null,
error: 'Параметр "city" обязателен и должен быть строкой'
};
}
// Вызов внешнего API
const response = await axios.get('https://api.openweathermap.org/data/2.5/weather', {
params: {
q: params.city,
appid: process.env.OPENWEATHER_API_KEY,
lang: 'ru',
units: 'metric'
}
});
const weather = response.data;
return {
result: {
city: weather.name,
temp: weather.main.temp,
description: weather.weather[0].description
},
error: null
};
} catch (error: any) {
return {
result: null,
error: `Не удалось получить погоду: ${error.message}`
};
}
};
💡 Зачем нужна обёртка с
resultиerror?
Такой формат позволяет MCP-серверу предсказуемо обрабатывать ответы:
- Если
errorнеnull— LLM получит сообщение об ошибке и сможет попробовать снова или сообщить пользователю.- Если
resultнеnull— LLM продолжит работу с данными.
Регистрация инструмента в MCP-сервере
Чтобы LLM узнал о нашем инструменте, нужно добавить его описание в ответ эндпоинта /capabilities.
app.get('/capabilities', (req, res) => {
res.json({
tools: [
{
name: "getWeather",
description: "Возвращает текущую погоду в указанном городе",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "Название города" }
},
required: ["city"]
}
}
]
});
});
Теперь LLM "знает" об этом инструменте и может решить, когда его вызвать.
Обработка вызова инструмента: эндпоинт /tool
Когда LLM решает использовать инструмент, он отправляет запрос на /tool с телом:
{
"tool": "getWeather",
"parameters": {
"city": "Екатеринбург"
}
}
Наш сервер должен:
- Получить запрос
- Найти нужную функцию по имени
- Вызвать её с параметрами
- Вернуть результат
app.post('/tool', async (req, res) => {
const { tool, parameters } = req.body;
// Проверка, что инструмент существует
if (tool !== 'getWeather') {
return res.status(404).json({
error: `Инструмент "${tool}" не найден`
});
}
try {
// Вызов функции
const result = await getWeather(parameters);
res.json(result);
} catch (error) {
res.status(500).json({
result: null,
error: 'Внутренняя ошибка сервера при вызове инструмента'
});
}
});
🔐 Безопасность: В реальном проекте используйте реестр инструментов (например,
const tools = { getWeather, getTime, ... }) и проверяйте имя черезinилиhasOwnProperty.
Практический пример: интеграция от начала до конца
Представим, пользователь спрашивает:
"Какая погода в Казани? Нужно ли брать зонт?"
- LLM анализирует запрос → понимает, что нужно получить погоду
- Находит зарегистрированный инструмент
getWeather - Формирует вызов:
{ "tool": "getWeather", "parameters": { "city": "Казань" } } - Отправляет на
/tool - Сервер вызывает функцию, получает данные
- Возвращает:
{ "result": { "city": "Казань", "temp": 14, "description": "небольшой дождь" }, "error": null } - LLM получает результат и отвечает:
"В Казани 14°C и небольшой дождь. Возьмите зонт."
Упражнение: создайте свой первый инструмент
Добавьте в свой MCP-сервер новый инструмент — getTime, который возвращает текущее время в заданном часовом поясе.
Шаги:
- Опишите инструмент в
/capabilities:name:getTimedescription: "Возвращает текущее время в указанном часовом поясе, например 'Europe/Moscow'"parameters: объект с обязательным полемtimezone(строка)
- Создайте асинхронную функцию
getTime(params):- Используйте
Intl.DateTimeFormatдля получения времени - Возвращайте объект
{ result: { time: "15:30", timezone: "Europe/Moscow" }, error: null } - Обработайте ошибку, если часовой пояс не валиден
- Используйте
- Добавьте обработку в
/tool
Проверьте: отправьте POST-запрос на /tool с телом:
{ "tool": "getTime", "parameters": { "timezone": "Asia/Novosibirsk" } }
Подводим итоги
Сегодня мы:
- Познакомились с понятием инструмента (tool) — функции, которую может вызвать LLM
- Изучили формат описания инструмента в JSON для
/capabilities - Реализовали асинхронную функцию с валидацией и обработкой ошибок
- Настроили обработку вызова через эндпоинт
/tool - Вернули результат в стандартизированном формате:
{ result, error }
Теперь ваш агент может не только думать — он может действовать.
А в следующей теме мы расширим эти навыки: создадим инструменты для работы с базой данных, генерации PDF и вызова внутренних API.
Вы уже видите, как из простых кирпичиков строится мощный ИИ-агент?
Давайте продолжим — впереди ещё больше возможностей. 🚀