Комментарии 14
К разделу про зависшие скрипты: если внутри Python-инструмента тоже запускаются процессы, у Popen.communicate(timeout=...) есть неочевидная особенность. По таймауту он выбрасывает TimeoutExpired, но оставляет процесс работать. В документации для этого случая показаны proc.kill() и повторный proc.communicate(), чтобы дочитать вывод и дождаться завершения. У subprocess.run(timeout=...) завершение дочернего процесса уже встроено. Это полезно учитывать при переносе консольной утилиты в инструмент: иначе агент уже получил ошибку, а запущенная утилита ещё выполняет свою работу. Рецепт: https://docs.python.org/3/library/subprocess.html#subprocess.Popen.communicate
Ценное предостережение, спасибо!
Если внутри самого Python-скрипта запускаются вложенные сабпроцессы через голый Popen без subprocess.run или обвязки try/finally с proc.kill(), они могут остаться зомби в ОС даже после падения основного скрипта.
Со стороны рантайма ToolHub внешний вызов глушится по жесткому таймауту на уровне ОС (через PID родительского процесса и последующую зачистку воркспейса), но чистоплотность внутренних форков внутри самого скрипта - это зона ответственности автора конкретного тула, и об этом важно помнить.
Подождём ещё подобных edge case’ов, и в ридми их в дальнейшем добавлю обязательно.
И чем это сильно отличается от skill?
Примерно тем же, чем идея пожарить картошку отличается от плиты и сковородки.
Покажите ваш конкретный "skill" - подробно на примере и рассмотрим чем отличается.
Да хоть такой
brave-search-skills/skills/web-search/SKILL.md at main · brave/brave-search-skills
Вообще на curl можно много скилов сделать. Т.е. такие же скилы есть для запуска из командной строки приложений служебных.
Или такое
1c-oData-skill/skills/odata/SKILL.md at main · gybson63/1c-oData-skill
Здесь обертка mcp нужна только потому, что в винде проблема с русским языком бывает в скриптах
Супер примеры! Это как раз идеальная демонстрация разницы между Prompt-driven подходом и Runtime-driven. Смотрите, оба ваших скилла - это гигантские инструкции на несколько тыщ токенов каждая.
В вашем подходе:
Вы заставляете LLM держать в контексте огромную простыню документации (разницу между OData v3/v4, правила substringof, необходимость собирать Base64 для Basic Auth, URL-encoding кириллицы
%D0%...) и тд.Модель на каждом шаге сама пытается собрать длинную команду для универсального fetch/curl. Она может ошибиться в кавычках, забыть закодировать строку или сгенерировать невалидный заголовок.
Если подключить 5–10 таких интеграций (1С, Brave, GitLab, Jira, Postgres) через скиллы - агент выжрет 20-30k токенов контекста еще до начала диалога.
А сейчас мы возьмём и сделаем, к примеру, второй (потому что он посложнее и поинтереснее как по мне) ваш скилл в виде тула для тулхаба:
Код
import { readFileSync, writeFileSync } from 'fs';
interface Input {
entityType: 'Catalog' | 'Document' | 'InformationRegister' | 'AccumulationRegister';
name: string;
filter?: string;
select?: string;
top?: number;
}
async function run() {
try {
const input: Input = JSON.parse(readFileSync('input.json', 'utf-8'));
const { entityType = 'Catalog', name, filter, select, top = 10 } = input;
const baseUrl = process.env.ODATA_URL || 'http://localhost/base/odata/standard.odata';
const user = process.env.ODATA_USER || 'Администратор';
const pass = process.env.ODATA_PASS || 'пароль';
// 1. Авто-кодирование кириллических сущностей 1С (Catalog_Сотрудники -> Catalog_%D0%A1...)
const resource = `${entityType}_${encodeURIComponent(name)}`;
// 2. Сборка OData v3 Query-параметров
const params = new URLSearchParams();
params.append('$format', 'json');
if (top) params.append('$top', top.toString());
if (select) params.append('$select', select);
if (filter) params.append('$filter', filter);
const auth = 'Basic ' + Buffer.from(`${user}:${pass}`, 'utf-8').toString('base64');
const res = await fetch(`${baseUrl}/${resource}?${params.toString()}`, {
headers: {
'Authorization': auth,
'Accept': 'application/json'
}
});
if (!res.ok) {
throw new Error(`1C OData Error ${res.status}: ${await res.text()}`);
}
const data = await res.json();
// 1C отдает массив записей в поле .value
writeFileSync('output.json', JSON.stringify(data.value || data));
} catch (err: any) {
writeFileSync('output.json', JSON.stringify({ error: err.message }));
}
}
run();
Это псевдокод, с 1C конкретно дел не имею, может потребовать адаптации под реалии, но суть - сколько тысяч токенов и минут сэкономили?)
Итог простой: В случае со SKILL.md мы перекладываем на дорогую и недетерминированную LLM работу парсера, кодировщика строк и генератора curl-команд, попутно сжигая контекст на чтение инструкций. В случае с toolhub - мы отдаём эту рутину обычному коду за 5 миллисек, а модели оставляем только высокоуровневое намерение (“дай сотрудников с фамилией Иванов”), экономя неимоверное количество токенов и своего времени в процессе самой работы, тратя лишь часть его на разработку тула/тулпака.
Вот в этом и есть фундаментальная разница: скилл учит модель, как костылить запрос в терминале, а toolhub даёт готовый атомарный микросервис, знать модели детали реализации которого для эффективной работы досконально не обязательно.
Бонусом: все ваши скиллы model-specific, а тулхабом одинаково не смогут пользоваться разве что уж совсем маленькие и глупые модельки.
Прям рекомендую попробовать в отдельном проекте, если понравится - переносить постепенно все .md на него. Разница в удобстве колоссальная.
Так в этом и смысл скила и отличие от MCP. Позволяем LLM самому написать, даже, например, запрос T-SQL адекватный именно текущему запросу. Ваш инструмент как обертка над скриптами может быть хорошей идеей. Но это надо доказать. И надо найти границу за которой ваш инструмент не станет тем же Курсором или Кодекс. Вы думаете они там ничего такого не придумали?
Я больше скажу, mcp без соответствующих ему скилов может вообще быть бесполезен полностью, его не смогут позвать.
Т.е. если человек не умеет свистеть, то ему можно дать свисток. Но ему все-равно надо будет объяснить когда, зачем и как свистеть, этого не избежать.
Позволяем LLM самому написать, даже, например, запрос T-SQL адекватный именно текущему запросу.
Так toolhub ровно это и позволяет! Никто не заставляет хардкодить логику. Вы можете сделать тул callTool("/db/execute_sql", { "query": "SELECT ... FROM ..." }), где модель сама на лету генерирует любой T-SQL под текущий контекст. Но сам коннект к СУБД, пул соединений, таймауты и отлов ошибок берет на себя рантайм, а не LLM через консольные костыли.
И надо найти границу за которой ваш инструмент не станет тем же Курсором или Кодекс. Вы думаете они там ничего такого не придумали?
Именно что придумали! Но они зашили это внутрь своих закрытых, платных и проприетарных экосистем. Та же фича у Claude Code, когда модель сама пишет скрипт и крутит его во временном окружении - это ровно тот же паттерн. Смысл toolhub - дать этот же уровень удобства в виде открытого, self-hosted и ЯП-агностичного Function-as-a-service движка для любой модели (включая локальные через Ollama или в связке со своим UI).
mcp без соответствующих ему скилов может вообще быть бесполезен полностью, его не смогут позвать. Т.е. если человек не умеет свистеть, то ему можно дать свисток. Но ему все-равно надо будет объяснить когда, зачем и как свистеть...
Тут вы на 100% правы, инструмент без контекста применения бесполезен. Возможно, я недостаточно явно подсветил это в статье: в toolhub именно что уже заложен этот механизм, причем иерархически:
На уровне папки (у каждой папки есть appendPrompt - добавочный промпт, его можно как подмешивать в системный так и просто кормить им модель): Когда агент делает
listTools("/1c"), он вместе со списком тулов получает мета-инструкцию: правила предметной области, когда какой тул звать и в каком порядке.На уровне тула (описание конкретного тула + примеры few shots): У каждого инструмента есть не просто типы параметров, а текстовое описание поведения и few-shot примеры входных/выходных данных.
Разница лишь в доставке: мы не вываливаем на модель инструкции ко всем 50 "свисткам" на старте диалога (сжигая все бабки и лимиты), а отдаем объяснение к конкретному "свистку" ровно в тот момент, когда агент зашел в соответствующую категорию.
Отличная идея - продолжайте развивать! Хотя инструменты мсп и можно (и нужно) отправлять в кеш, экономия токена, а так же ограничивать аллоу списками отсекая лишние и ненужные, итогом будет понимание, что гораздо дешевле, быстрее и качественнее вызвать короткий точечный скрипт в нужное и заранее известное место.
Сейчас все новое и все имеет шанс. Вот например https://github.com/refly-ai/refly
Согласен! да, на рефли тоже натыкался какое-то время назад. Кстати, они тоже пришли к той же мысли что и мы с вами выше - Skills are not prompts. They are durable infrastructure. Выходит, тоже что-то знают)) правда, рефли это больше про тяжелый энтерпрайз, n8n like графы, очень много интеграций под капотом. Тулхаб таки что-то вроде минималистичный unix-way рантайм, но общая суть безусловно схожа - отделить слой тулинга от слоя мышления.

Хорош хардкодить кривые MCP: превращаем любой скрипт в инструмент LLM за 2 секунды