Как написать плагин
Пошаговый сценарий доработки любого MCP-сервера, поддерживающего плагины. Команды даны для PowerShell на Windows.
Шаг 1. Прочитать контракт из образа
Единственный источник истины — читаемые файлы внутри образа. Их три:
/app/plugin_api.py
Полный справочник: каждый хук и таблица продукта, форма payload, какие поля применяются, рабочий пример
/app/plugins/AGENTS.md
Короткая версия для агента: модель, правила, чем платят за правку
/app/plugins/example.py
Пример, объявляющий все хуки и таблицы продукта; безвреден, копируется целиком
Исключения по пути справочника: CodeMetadataSearchServer — /app/src/plugin_api.py, 1CCodeChecker — /app/MCP_1copilot/plugin_api.py.
Достать их можно, не запуская сервер:
# Из работающего контейнера
docker cp 1c_help_mcp:/app/plugin_api.py .
docker cp 1c_help_mcp:/app/plugins/AGENTS.md .
docker cp 1c_help_mcp:/app/plugins/example.py .
# Или из образа, без контейнера
docker run --rm comol/1c_help_mcp:latest cat /app/plugin_api.py > plugin_api.pyШаг 2. Написать один файл
Файл E:\plugins\mcp_docs\10-terminy.py:
Три вещи, которые чаще всего делают неправильно:
Забывают
return. Хук получает копию payload. Изменить её и не вернуть — это не частичный эффект, это отсутствие эффекта.Пишут хук, которого у продукта нет. Имя вне объявленного набора отклоняет весь файл. Набор — в
plugin_api.pyэтого сервера, а не другого.Правят derived-state хук ради call-scoped задачи. Это стоит пересборки индекса. Сначала проверьте, нет ли подходящей декларативной таблицы.
Имя файла задаёт порядок. Файлы выполняются в отсортированном порядке имён, каждый хук — конвейер: что вернул 10-terminy.py, то получит 20-filtr.py. Называйте файлы так, чтобы читаемый порядок совпадал с фактическим.
Шаг 3. Прогнать dry-run
Dry-run запускает объявленные в файле хуки на фикстурах, зашитых в образ, и печатает payload до и после. Ему не нужны ни смонтированные данные, ни построенный индекс, ни запущенный сервер, ни лицензионный ключ. Ошибку загрузки он сообщает теми же словами, что и сервер.
HelpSearchServer
python3 plugin_host.py --dry-run /tmp/my_plugin.py
SSLSearchServer
python launcher.py --dry-run /tmp/my_plugin.py
SyntaxCheckServer
python mcp_server.py --dry-run /tmp/my_plugin.py
TemplatesSearchServer
python main.py --dry-run /tmp/my_plugin.py
Graph Metadata Search
python run.py plugin-dry-run /tmp/my_plugin.py
CodeMetadataSearchServer
python src/plugin_dry_run.py /tmp/my_plugin.py
Полностью, одной строкой и без запущенного сервера:
Шаг 4. Смонтировать каталог
Каталог плагинов подключается томом. Один каталог — один сервер: плагин, написанный для другого продукта, отклоняется по PRODUCT, если объявил REQUIRES, и по неизвестным именам хуков, если не объявил. Именно поэтому REQUIRES стоит писать: ошибка получается внятной с первого раза.
Для Graph Metadata Search каталог мало смонтировать — подсистему нужно включить:
Переменные, которые относятся к плагинам:
PLUGIN_DIR
Help, SSL, Templates, Code, 1CCodeChecker
Каталог плагинов внутри контейнера
/app/plugins
PLUGINS_DIR
Syntax
То же; пустое значение означает /app/plugins
(пусто)
PLUGIN_STRICT_DERIVED_STATE
Help, SSL, Templates, Code
true — упавший derived-state хук роняет сборку вместо пропуска единицы
false
PLUGIN_HOOK_TIMEOUT_SECONDS
Code
Бюджет времени одного хука; превысивший его хук считается упавшим
5.0
GRAPH_PLUGINS_ENABLED
Graph
Читать каталог плагинов вообще
false
GRAPH_PLUGINS_DIRECTORY
Graph
Каталог плагинов; относительный путь считается от /app
plugins
GRAPH_PLUGIN_STRICT_BUILD
Graph
Ронять построение поколения при ошибке derived-state хука
false
GRAPH_PLUGIN_HOOK_TIMEOUT_SECONDS
Graph
Бюджет времени одного хука; 0 отключает контроль
5.0
Шаг 5. Перезагрузить каталог без перезапуска
Перезагрузка перечитывает каждый файл каталога, атомарно заменяет действующий набор хуков и начинает новую эпоху плагинов. Если хотя бы один файл больше не загружается, не заменяется ничего: прежний набор остаётся в силе, а ответ называет проблемный файл и причину.
HelpSearchServer
GET /plugins
POST /plugins/reload (HTTP 409, если перезагрузка отклонена)
SSLSearchServer
MCP-инструмент plugin_state
MCP-инструмент plugin_reload
SyntaxCheckServer
MCP-инструмент plugin_state
MCP-инструмент plugin_reload
TemplatesSearchServer
MCP-инструмент plugin_state
MCP-инструмент plugin_reload — только если включены изменяющие инструменты
Graph Metadata Search
MCP-инструмент list_plugins
MCP-инструмент reload_plugins(operation_id) — только профиль admin
CodeMetadataSearchServer
MCP-инструмент plugin_state
MCP-инструмент plugin_reload
1CCodeChecker
GET /plugins
POST /plugins/reload (HTTP 409, если новый набор не загружается)
TemplatesSearchServer. plugin_reload относится к изменяющим инструментам (вместе с add_template; remember в эту группу не входит и доступен всегда). По умолчанию они не регистрируются вообще: нужно задать MCP_ENABLE_WRITE_TOOLS, и вместе с ним — стойкий MCP_OPERATOR_TOKEN, иначе сервер откажется стартовать. Токен передаётся в заголовке Authorization, а не аргументом инструмента. Без них перечитать каталог можно перезапуском контейнера, а plugin_state доступен всегда.
Что когда начинает действовать: call-scoped хук и таблица — со следующего вызова после перезагрузки; derived-state хук — со следующего построения индекса, не задним числом.
Шаг 6. Проверить состояние
Ответ plugin_state / list_plugins / GET /plugins показывает по каждому файлу: какие хуки и таблицы он объявил, классификацию каждого хука, включён он или отключён, ошибку, которая его отключила, счётчик сбоев, накопленное в хуке время, порядок выполнения файлов и текущую эпоху плагинов.
Три вопроса, на которые он отвечает быстрее, чем журнал:
Плагин загрузился? Файла нет в списке — он не в каталоге или каталог не смонтирован (у Graph — ещё и
GRAPH_PLUGINS_ENABLED).Почему хук не срабатывает? Отключён после исключения — рядом будет ошибка и счётчик сбоев.
Почему стало медленно? У каждого хука показано накопленное время.
Типичные ошибки загрузки
Имя хука названо и перечислены разрешённые
Опечатка или хук другого продукта
Взять имя из plugin_api.py этого сервера
Назван неизвестный параметр и предложены доступные
Опечатка в имени аргумента хука
Хук получает аргументы по именам; берите только нужные
Названы продукт плагина и продукт сервера
REQUIRES из другого сервера семейства
Исправить REQUIRES или переложить файл в правильный каталог
Названы требуемая и предлагаемая версии
HOOKS_VERSION/HOST_CONTRACT_VERSION не совпали
Сверить payload с plugin_api.py новой версии
Названы файл и правило кодировки
Файл не в UTF-8
Пересохранить в UTF-8 (BOM допустим)
Названы запись таблицы и что с ней не так
Некорректная декларативная таблица
Исправить запись; форму смотреть в plugin_api.py
Хук отмечен отключённым, рядом трассировка
Исключение в хуке
Исправить и перезагрузить: до перезагрузки хук не срабатывает
Результат хука отброшен, в журнале имя файла
Возврат нарушил контракт ответа
Вернуть ту же форму; не добавлять записей, которых сервер не производил
Полный разбор правил, которые за этим стоят: Правила хоста.
Last updated