Перейти к содержимому
MCP-сервер

Срезы каталога

Как отдавать ассистенту только нужную часть инструментов и не тратить контекст на остальное.

Полный каталог — 73 инструмента и около 25800 токенов. Они уезжают в контекст ассистента при каждом подключении, ещё до первого вопроса. Человеку, который ведёт задачи, инструменты форм не нужны никогда; тому, кто собирает формы, не нужны спринты.

Срез оставляет в каталоге одно семейство и всё, без чего оно не работает.

Срез Инструментов Токенов Экономия
весь каталог 73 25 936 —
core (ядро) 16 6 690 74 %
projects (проекты) 25 9 391 64 %
folders (папки) 29 10 436 60 %
sprints (спринты) 30 11 465 56 %
structure (структура) 34 12 510 52 %
forms (формы) 27 11 780 55 %
tasks (задачи) 34 13 706 47 %
pages (страницы) 38 13 420 48 %
disk (диск) 20 8 126 69 %
readonly 34 10 445 60 %

Числа сняты с живого сервера запросом tools/list, а не посчитаны по коду, и пересчитываются одной командой — python3 scripts/slices.py. Способ счёта держится один и тот же намеренно: пересчитать таблицу иначе значит молча подменить смысл её чисел. Считается сумма по каждому описанию инструмента вместе со схемой аргументов, кодировкой cl100k_base. Служебная обёртка ответа в счёт не входит — поэтому цифра меньше той, что покажет счётчик всего тела ответа целиком.

Инструмент layers_toolsets тоже называет цену наборов, но считает иначе — по длине описаний, без токенизатора, с подобранным коэффициентом. Расхождение с таблицей до девяти процентов, и в его ответе цена честно названа приблизительной. Точные числа здесь.

Свойство при запуске:

Окно терминала
java -jar layers-mcp-server.jar --mcp.tools.profile=forms

Готовые срезы:

Срез Что входит
full всё; значение по умолчанию, если ничего не задано
core только общее: кто вы, какие пространства и проекты, люди, поиск, переход по ссылке
forms формы
pages страницы, папки
tasks задачи, комментарии, пакетные операции над ними, списки задач
projects проекты
sprints списки задач и проекты, в которых они живут
folders папки и проекты — папка внутри проекта устроена как вложенный проект
structure перекладывание структуры: проекты, списки задач, папки. Задачи не входят: перекладывают контейнеры, а не содержимое
readonly только читающие инструменты из всех разделов; задаётся заголовком

Ядро — user, workspace, search — остаётся в любом срезе. Без него ассистент не сможет ни понять, в каком пространстве работает, ни найти сущность по имени.

Состав срезов не произволен, и правило одно: в срез входит семейство и только то, без чего оно физически не работает. Не «то, что рядом по смыслу» — иначе срезы расползутся обратно в полный каталог.

tasks включает списки задач потому, что задача обязана лежать в списке: бэкенд не примет её без него. А проекты и теги — не включает: список проектов с именами отдаёт инструмент из ядра, а имена тегов инструменты задач разрешают сами. А projects не включает ничего: проект заводится сам, и спринт по умолчанию создаётся при этом внутри бэкенда, а не чужим инструментом; участников отдаёт инструмент из того же семейства. У pages в составе папки, но не теги — инструменты страниц про теги ничего не знают, теги живут у задач.

core — набор, с которого стоит открывать разговор, когда заранее неизвестно, чем человек займётся. Четырнадцать инструментов и три тысячи токенов вместо тридцати: понять, в каком пространстве работаем, какие есть проекты и люди, найти нужное поиском, перейти по ссылке.

Всё остальное подключается, когда стало ясно, о чём речь.

Что бывает дальше, ассистент узнаёт у самого сервера — инструмент layers_toolsets входит в ядро и отвечает тремя вещами: какой набор действует прямо сейчас (читается из заголовка живого запроса, а не предполагается), список наборов с описанием на языке дел, и цена каждого в инструментах и токенах.

Описания в нём намеренно обещают только то, что человек может сделать руками в интерфейсе. У форм, например, сказано про сборку, правку полей и статистику ответов — но не про публикацию, хотя инструмент публикации существует.

Переключает набор человек, в настройках подключения. Ассистент может понять, чего ему не хватает, и назвать это — подключить себе сам он не может, и layers_toolsets говорит об этом прямо, а не делает вид, что умеет.

Когда готовый срез не подходит, семейства перечисляются вручную:

Окно терминала
java -jar layers-mcp-server.jar --mcp.tools.families=pages,tags

Список семейств сильнее имени среза: если заданы оба, действует список.

Семейства: forms, pages, tasks, projects, sprints, folders, tags, membership, bulk.

Тот же список принимает и заголовок X-Mcp-Profile — см. ниже.

Неизвестное имя среза — отказ на старте, а не молчаливый откат к полному каталогу:

Неизвестный срез тулов «form». Есть: full, forms, pages, tasks, projects.
Либо перечисли семейства в mcp.tools.families.

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

Каждый класс инструментов помечен семейством:

@ToolFamily("forms")
public class FormTools { … }

Аннотация несёт условие регистрации бина. Семейство не входит в срез — бин не создаётся, а tools/list собирается из бинов. Значит каталог физически не может разойтись с тем, что работает: инструмента нет ни в списке, ни в вызове.

Фильтровать выдачу отдельно не нужно, и это важнее, чем кажется. Фильтр на выдаче рано или поздно расходится с фильтром на вызове, и появляется инструмент, которого в каталоге нет, но вызвать его можно — или наоборот.

Человек работает фазами: сегодня страницы, завтра формы — параллельно не бывает. Поэтому срез под занятие ему и подходит. Но посреди фазы регулярно случается другое: «нашёл ошибку — заведи задачу». Чистый срез в этот момент ответил бы отказом, и прерываться на смену подключения пришлось бы ровно тогда, когда это хуже всего.

Поэтому в каждом срезе, кроме его собственных инструментов, всегда есть короткий список того, что делают мимоходом из любого места:

  • завести задачу и убедиться, что завелась;
  • завести страницу и открыть её;
  • оставить комментарий — замечание часто оставляют им;
  • найти по названию, понять, где вы сейчас, пройти по дереву;
  • назвать проект один раз на разговор.

Это имена инструментов, а не семейства. В семействе задач их два десятка; втянув его целиком в каждый срез, мы вернулись бы к полному каталогу, ради ухода от которого всё и затевалось. Мимоходом нужно завести — вести задачу дальше человек будет в своей фазе.

Срез можно назвать словом занятия: ?profile=pages, ?profile=forms, ?profile=projects, ?profile=tasks. Русские имена тоже приняты (страницы, формы, проекты, задачи), но в адресе надёжнее английские: клиент, подставляющий адрес без преобразования, получит от сервера «Bad Request» на русское слово, и выглядеть это будет как поломка подключения.

Свойство при запуске годится, когда у поверхности свой процесс. Общий адрес, куда приходят разные люди, так не разделить: всем достанется один срез. Для этого есть заголовки — они выбирают срез на конкретный запрос.

Окно терминала
curl -H 'X-Mcp-Profile: forms' ...
curl -H 'X-Mcp-Profile: forms,tags' ...
curl -H 'X-Mcp-Tools: tool_name,tool_name' ...

X-Mcp-Profile принимает имя готового среза или перечисление семейств через запятую. X-Mcp-Tools перечисляет инструменты поимённо, через запятую — самый узкий вариант; имена берутся из ответа tools/list.

Срез адресом, когда заголовок задать негде

Заголовок раздела «Срез адресом, когда заголовок задать негде»

Заголовок доступен там, где конфигурацию клиента правят руками. Но подключение, описанное одним адресом, — обычный случай, и заголовку там взяться неоткуда. Поэтому то же самое задаётся строкой запроса:

https://ваш-сервер/mcp?profile=structure
https://ваш-сервер/mcp?tools=layers_task_list_smart,layers_task_update

Заголовок сильнее адреса: он задан под конкретный запуск, а адрес живёт в настройках долго и переживает то, ради чего его когда-то выбрали.

Обнаружению OAuth строка запроса не мешает: метаданные ресурса называют канонический /mcp без неё, а запросы клиент шлёт по своему адресу — со срезом.

Клиент подгружает схемы инструментов по мере надобности и вытесняет уже загруженные, когда подтягивает другие. Тогда вызов инструмента, который в этой же сессии уже дважды отработал, отвечает «схема ещё не загружена». Проверено: между успехом и отказом прошли шесть чужих вызовов.

Чем больше каталог, тем чаще. Срез уменьшает давление на этот механизм — не косвенно, а прямо: вытеснять становится нечего.

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

Побеждает первое заданное, сверху вниз:

  1. X-Mcp-Tools — поимённый список;
  2. X-Mcp-Profile — срез или список семейств;
  3. свойство mcp.tools.families;
  4. свойство mcp.tools.profile;
  5. иначе весь каталог процесса.

Заголовок не дополняет свойство, а заменяет его целиком — иначе объяснить заранее, что придёт в ответе, было бы нельзя.

Инструмент, которого при запуске не создали, заголовком не вернуть: фильтр при старте убирает его из приложения, и возвращать нечего. Запрос несуществующего инструмента или раздела — ошибка с именем в тексте, а не молча укороченный каталог.

Инструмент вне среза нельзя и вызвать: иначе список обещал бы одно, а сервер позволял другое.

Переключения среза посреди разговора. Протокол работает без сессии, и канала, по которому сервер сообщил бы клиенту «каталог изменился», в нём нет: при рукопожатии сервер честно отвечает tools.listChanged: false. Значит клиент перечитает каталог только при новом подключении, и смена среза на лету пока ничего не сэкономила бы — старый список остался бы у него в контексте.