> For the complete documentation index, see [llms.txt](https://docs.onerpa.ru/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.onerpa.ru/mcp-servery-1c/sistema-pluginov.md).

# Доработка MCP: система плагинов

MCP-серверы поставляются готовыми Docker-образами, внутри которых почти всё скомпилировано. Чтобы их можно было адаптировать под конкретную конфигурацию, компанию и терминологию **без пересборки образа**, в новых beta-сборках прикладных серверов встроена единая система плагинов.

**Плагин — это один Python-файл в каталоге плагинов сервера.** Ни базового класса, ни декоратора, ни регистрации, ни манифеста, ни шага сборки. Пустой файл — валидный плагин, который ничего не меняет.

{% hint style="info" %}
**Эта статья и её подстатьи написаны в первую очередь для ИИ-агента** (Cursor, Claude Code, e-agent), которому поручили доработать MCP-сервер. Всё, что нужно для написания плагина, лежит внутри образа в читаемом виде: `/app/plugin_api.py` (полный справочник; у CodeMetadataSearchServer — `/app/src/plugin_api.py`, у 1CCodeChecker — `/app/MCP_1copilot/plugin_api.py`), `/app/plugins/AGENTS.md` (короткая версия) и `/app/plugins/example.py` (пример со всеми хуками). Документация ниже — карта этих файлов и различий между серверами.
{% endhint %}

## Модель в пяти строках

* **Один плагин — один `.py` файл** в каталоге плагинов. Импорты не нужны, чтобы написать рабочий плагин.
* **Пустой файл — валидный плагин.** Хука нет — hook не срабатывает.
* **Применяется только возвращённое значение.** Хук получает *копию* payload: изменить её и забыть `return` — значит не изменить ничего.
* **Опечатка в имени хука — громкая ошибка, а не тишина.** Имя вне объявленного набора отклоняет загрузку файла и печатает список разрешённых имён.
* **Упавший хук не роняет сервер.** Исключение перехватывается, пишется в журнал с именем файла, хуком и трассировкой, вызов отвечает так, будто плагина нет.

## Какие серверы поддерживают плагины

| Сервер                                                                      | Плагины   | Идентификатор продукта  | Каталог плагинов                           | Включение                    |
| --------------------------------------------------------------------------- | --------- | ----------------------- | ------------------------------------------ | ---------------------------- |
| [HelpSearchServer](/mcp-servery-1c/servery/help-search-server.md)           | Да        | `1c-help`               | `/app/plugins` (`PLUGIN_DIR`)              | Всегда включены              |
| [SSLSearchServer](/mcp-servery-1c/servery/ssl-search-server.md)             | Да        | `ssl-search`            | `/app/plugins` (`PLUGIN_DIR`)              | Всегда включены              |
| [SyntaxCheckServer](/mcp-servery-1c/servery/syntax-check-server.md)         | Да        | `bsl-syntax-check`      | `/app/plugins` (`PLUGINS_DIR`)             | Всегда включены              |
| [TemplatesSearchServer](/mcp-servery-1c/servery/templates-search-server.md) | Да        | `template-search`       | `/app/plugins` (`PLUGIN_DIR`)              | Всегда включены              |
| [Graph Metadata Search](/mcp-servery-1c/servery/graph-metadata-search.md)   | Да        | `graph-metadata-search` | `/app/plugins` (`GRAPH_PLUGINS_DIRECTORY`) | `GRAPH_PLUGINS_ENABLED=true` |
| [CodeMetadataSearchServer](/mcp-servery-1c/servery/code-metadata-search.md) | Да        | `code-metadata-search`  | `/app/plugins` (`PLUGIN_DIR`)              | Всегда включены              |
| [1CCodeChecker](/mcp-servery-1c/servery/code-checker.md)                    | Да (beta) | `onec-code-checker`     | `/app/plugins` (`PLUGIN_DIR`)              | Всегда включены              |

{% hint style="info" %}
У Graph Metadata Search подсистема плагинов **включена по умолчанию**. При `GRAPH_PLUGINS_ENABLED=false` каталог не читается вообще, а `reload_plugins` отвечает, что подсистема отключена.
{% endhint %}

{% hint style="info" %}
У CodeMetadataSearchServer справочник лежит в `/app/src/plugin_api.py`, у 1CCodeChecker — в `/app/MCP_1copilot/plugin_api.py`. Проверка только `/app/plugin_api.py` даст для них ложное «нет плагинов».
{% endhint %}

## Два слоя: контракт хоста и набор хуков

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

| Слой               | Что в нём                                                                                                                                                      | Где описан                                                                                                     |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **Контракт хоста** | Обнаружение и загрузка файлов, передача аргументов, семантика возврата, изоляция сбоев, порядок выполнения, перезагрузка, эпохи, интроспекция, версии, dry-run | [Правила хоста](/mcp-servery-1c/sistema-pluginov/pravila-hosta.md) — одинаковы во всех серверах семейства      |
| **Набор хуков**    | Какие хуки существуют, что им передаётся, когда они срабатывают, какие декларативные таблицы принимаются                                                       | [Справочник хуков по серверам](/mcp-servery-1c/sistema-pluginov/spravochnik-hukov.md) — свой у каждого сервера |

Практический вывод: правила («только возврат применяется», «опечатка отклоняет файл», «падение изолируется») выучиваются один раз и работают везде. Различаются только имена хуков и форма payload — их и нужно смотреть в `plugin_api.py` конкретного образа.

Версионируются слои раздельно: `HOST_CONTRACT_VERSION` — общий контракт, `HOOKS_VERSION` — набор хуков конкретного продукта, `PRODUCT` — сам продукт. Плагин может закрепить их через `REQUIRES`, и тогда при несовпадении он не загрузится с явным сообщением, где названы обе стороны:

```python
REQUIRES = {"host": 1, "product": "ssl-search", "hooks": 1}
```

## Классификация хуков: чем платят за правку

Каждый хук объявлен одним из двух классов, и это не косметика.

| Класс             | Что делает                                                                                   | Цена правки                                                                                                                              |
| ----------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **call-scoped**   | Влияет на один вызов и ни на что, что его переживает                                         | Правка + перезагрузка каталога → действует со следующего вызова. Ничего не пересобирается                                                |
| **derived-state** | Формирует то, что сервер строит один раз и переиспользует: индекс, векторную коллекцию, граф | Исходник файла входит в отпечаток годности состояния. Правка делает сохранённое состояние устаревшим, и следующий старт пересобирает его |

Что именно пересобирается при правке derived-state хука:

| Сервер                   | derived-state хуки                                                            | Что пересобирается                                                                                                                   |
| ------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| HelpSearchServer         | `on_document`                                                                 | Индекс справки: сборка в staging-поколение, текущее продолжает отвечать                                                              |
| CodeMetadataSearchServer | `on_source_file`, `on_chunk`, `on_metadata_object`                            | Векторные коллекции, SQLite-под-индексы и хранилище объектов метаданных: сборка нового поколения, опубликованное продолжает отвечать |
| SSLSearchServer          | `on_entry`                                                                    | Полное переэмбеддирование выбранной базы БСП `bases/<версия>.db`                                                                     |
| TemplatesSearchServer    | `on_template`, `on_memory`                                                    | Векторные коллекции шаблонов и заметок (`index_meta.json` хранит отпечаток)                                                          |
| Graph Metadata Search    | `on_source_unit`, `on_metadata_object`, `on_routine`, `on_embedding_document` | Проект пересобирается: граф, полнотекстовый и векторный маршруты                                                                     |
| SyntaxCheckServer        | нет                                                                           | Ничего: сервер не хранит состояния между вызовами, все хуки call-scoped                                                              |
| 1CCodeChecker            | нет                                                                           | Ничего: все хуки call-scoped, анализ выполняется в 1С:Напарнике                                                                      |

{% hint style="danger" %}
Пересборка индекса на полном корпусе — это десятки минут, а на конфигурации — часы. Прежде чем править derived-state хук, убедитесь, что нужного эффекта нельзя добиться call-scoped хуком или декларативной таблицей.
{% endhint %}

## Декларативные таблицы вместо кода

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

| Таблица                  | Серверы                                                                          | Что делает                                                                    |
| ------------------------ | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `ALIASES`                | HelpSearchServer, Graph Metadata Search                                          | Дополнительные имена, которые разрешаются в существующие сущности             |
| `QUERY_ALIASES`          | SSLSearchServer, TemplatesSearchServer, CodeMetadataSearchServer                 | Замены терминов в нормализованном запросе до поиска                           |
| `TOOL_PRESETS`           | HelpSearchServer, Graph Metadata Search, CodeMetadataSearchServer, 1CCodeChecker | Новые MCP-инструменты как пресеты существующих с зафиксированными аргументами |
| `CYPHER_TEMPLATES`       | Graph Metadata Search                                                            | Дополнительные read-only Cypher-шаблоны для `run_graph_cypher_template`       |
| `SUPPRESSED_DIAGNOSTICS` | SyntaxCheckServer                                                                | Код диагностики → причина, по которой она не попадает в отчёт                 |

## Жизненный цикл доработки

1. **Прочитать контракт.** `/app/plugin_api.py` в образе нужного сервера (у CodeMetadataSearchServer — `/app/src/plugin_api.py`, у 1CCodeChecker — `/app/MCP_1copilot/plugin_api.py`) — полный справочник: payload каждого хука, какие поля применяются, рабочий пример.
2. **Скопировать `example.py`.** В `/app/plugins/example.py` объявлены все хуки и таблицы продукта, закомментированные и безвредные.
3. **Написать один файл.** Имя файла задаёт порядок: файлы выполняются в отсортированном порядке имён, каждый хук — конвейер.
4. **Прогнать dry-run.** Одна команда в пустом контейнере: без смонтированных данных, без построенного индекса, без запущенного сервера.
5. **Смонтировать каталог** плагинов томом в контейнер.
6. **Перезагрузить и проверить** состояние: что загружено, что отключено и почему.

Подробно, с командами для каждого сервера: [Как написать плагин](/mcp-servery-1c/sistema-pluginov/kak-napisat-plugin.md).

## Что система гарантирует

Плагин выполняется внутри процесса сервера, поэтому система **не** обещает защитить реализацию от автора плагина. Она обещает другое — что плагин не сможет:

* **уронить сервер** — исключение перехватывается, call-scoped хук отключается до следующей перезагрузки;
* **испортить производное состояние** — упавший derived-state хук по умолчанию оставляет свою единицу необработанной и считается в сводке сборки, а в строгом режиме роняет сборку, и обслуживающее состояние продолжает отвечать;
* **нарушить опубликованный контракт ответа** — пределы измеряются после последнего хука, счётчики пересчитываются из фактически возвращённого, ответ проверяется по схеме, а результат, который её не проходит, отбрасывается в пользу неизменённого;
* **скрыть подмену от вызывающей стороны** — если хук изменил аргументы, ответ сообщает и исходные аргументы, и фактически использованные, и имя файла плагина;
* **обойти правила журналирования** — секреты (лицензионный ключ, ключ embedding-API, операторский токен, пароль Neo4j) приходят в `on_startup` заменёнными или в виде булева «задано / не задано»;
* **добраться до внутренностей** — в хук передаются только простые данные: примитивы, списки и словари, без ссылок на хранилище, модель, соединение или объект сервера.

## Куда дальше

{% content-ref url="/pages/ZzGi8GJnqKburYrX3WMh" %}
[Как написать плагин](/mcp-servery-1c/sistema-pluginov/kak-napisat-plugin.md)
{% endcontent-ref %}

{% content-ref url="/pages/7yioLV4rf1N4Bp8VW8cQ" %}
[Правила хоста](/mcp-servery-1c/sistema-pluginov/pravila-hosta.md)
{% endcontent-ref %}

{% content-ref url="/pages/XBkeoDP2KkGx3RE40ptB" %}
[Справочник хуков по серверам](/mcp-servery-1c/sistema-pluginov/spravochnik-hukov.md)
{% endcontent-ref %}

{% content-ref url="/pages/Yt545KwPGbYOgReDJnNi" %}
[Рецепты](/mcp-servery-1c/sistema-pluginov/recepty.md)
{% endcontent-ref %}
