Metadata-Version: 2.5
Name: odata1c-gate
Version: 0.1.1
Summary: Локальный MCP-шлюз к OData 1С:Предприятие с гейтом псевдонимизации
Project-URL: Homepage, https://github.com/Romandredan/odata1c-gate
Project-URL: Repository, https://github.com/Romandredan/odata1c-gate
Author: Roman Danilov
License: MIT
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: ahocorasick-rs>=0.22
Requires-Dist: httpx2>=2.12
Requires-Dist: httpx>=0.27
Requires-Dist: lxml>=5.2
Requires-Dist: mcp>=2.2
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: ruamel-yaml>=0.18
Requires-Dist: snowballstemmer>=2.2
Requires-Dist: uvicorn>=0.30
Provides-Extra: keyring
Requires-Dist: keyring>=25.0; extra == 'keyring'
Description-Content-Type: text/markdown

# odata1c-gate

Локальный MCP-шлюз между Claude Code и стандартным OData-интерфейсом 1С:Предприятие 8.3.
Модель получает доступ к данным базы — справочникам, документам, регистрам, — но между ней и 1С
стоит **гейт псевдонимизации**: реквизиты (ИНН, счета, паспорта, телефоны) и, на выбранном уровне,
названия организаций и ФИО заменяются токенами вида `[[type:tail]]` до того, как данные увидит
модель. Обратная подмена происходит только внутри шлюза, перед отправкой запроса в 1С.

Один пользователь, одна машина, несколько сессий агентов одновременно, несколько баз 1С.
Клиент — Claude Code (другие клиенты MCP работают, но подтверждение записи у них устроено иначе,
см. [docs/install.md](docs/install.md)).

**Статус: выпуск `0.1.0` (чтение M1, запись M2 и поставка M3 закрыты 2026-09-14).** Работают
демон, лаунчер, индекс метаданных, гейт всех уровней, девять тулов чтения и семь тулов записи;
приёмка на живой базе 1С пройдена для чтения, записи и установки из PyPI и маркетплейса
([docs/probes/M1d-live-check.md](docs/probes/M1d-live-check.md),
[docs/probes/M2-live-check.md](docs/probes/M2-live-check.md),
[docs/probes/M3-clean-install.md](docs/probes/M3-clean-install.md)); перечень изменений —
[CHANGELOG.md](CHANGELOG.md).

## Зачем это

Дать модели читать рабочую базу 1С — значит отдать ей персональные данные и коммерческую тайну:
ИНН контрагентов, расчётные счета, телефоны и адреса физических лиц, названия клиентов.
Обезличивать выгрузку заранее неудобно (модель должна видеть свежие данные и уметь дозапрашивать),
а инструктировать модель «не показывай ИНН» бессмысленно — инструкция не механизм.

Шлюз решает это подменой на границе: модель работает с живой базой, но защищаемые значения
заменяются токенами до того, как попадут в её контекст. Токен детерминирован (одно значение — один
токен), поэтому по нему можно отбирать, связывать записи и вести разговор, не зная исходного
значения. Разработчик, который читает ответы модели, видит `[[inn:M4T2Q9XZ7K]]`, а не ИНН — и при
необходимости раскрывает его сам, командой в терминале, мимо модели.

## Что видит модель, а что нет

**Что уходит модели.** Структура базы (сущности, поля, ключи, навигация) и данные, прошедшие
гейт. Номера и даты документов, суммы, количества, коды, GUID, значения перечислений не
защищаются ни на одном уровне — без них работа с базой теряет смысл. Названия организаций и ФИО
(классы `org` и `person`) и защищаемые реквизиты — ИНН, КПП, ОГРН, счета, БИК, карты, СНИЛС,
документы, телефоны, почта, даты рождения, адреса — приходят токенами `[[type:tail]]` по правилам
политики базы. Уровень задаётся на базу: `off` — гейт выключен, `identifiers` — реквизиты,
`identifiers+names` — реквизиты плюс названия и ФИО.

**Что не уходит никогда.** Реальные значения защищаемых классов не выходят через MCP ни в одном
ответе: ни в данных, ни в текстах ошибок 1С, ни в превью записи, ни в журнале, ни в
`odata1c_raw_get`, ни в вопросах подтверждения. Это инвариант, а не тест: последний проход по
готовому ответу делает страж утечек — он ищет в сериализованном ответе известные словарю значения
и заменяет их токенами, если что-то прошло мимо гейта. Учётные данные 1С модель не получает:
пароль не покидает домашнего каталога вовсе, имя пользователя 1С не попадает ни в один ответ, а
адрес публикации базы не возвращает ни один тул. О самой базе модель узнаёт ровно то, что
перечисляет `odata1c_bases`: имя, подпись, роль, уровень гейта, разрешена ли запись, состояние
индекса (собран ли, когда, сколько сущностей) и конфигурацию 1С, к которой база отнесена.
Единственное исключение по адресу — текст сетевого сбоя, в который его может вписать HTTP-клиент.
Хук плагина вдобавок запрещает модели читать файлы домашнего каталога шлюза. Раскрыть токен может
только владелец машины, командой `odata1c reveal` в своём терминале.

**Кто подтверждает запись.** Тулы записи ничего не пишут в 1С: они готовят операцию, показывают
превью в токенах и возвращают `pending_id`. Выполняет её отдельный вызов `odata1c_commit`, и
только после подтверждения человека механизмом клиента — в Claude Code это диалог разрешения,
у клиентов с elicitation — вопрос шлюза. Реплика «да» в чате подтверждением не считается: модель
не может подтвердить запись сама себе. Каждая выполненная запись попадает в локальный журнал и
откатывается тулом `odata1c_undo`. Записи в базах с ролью `prod` по умолчанию нет вовсе, а состав
разрешённого сужается флагами разрешений в настройках базы.

## Установка

Нужен [`uv`](https://docs.astral.sh/uv/) — он сам поставит подходящий Python, отдельно ставить
интерпретатор не нужно. Дальше две команды в терминале ставят плагин Claude Code вместе со шлюзом:

```text
claude plugin marketplace add Romandredan/odata1c-gate
claude plugin install odata1c@odata1c-gate
```

Плагин приносит MCP-сервер шлюза, три навыка, хук подтверждения записи и агента-следователя.
Версия пакета закреплена в `plugin/.mcp.json`, поэтому `claude plugin update odata1c` обновляет и
плагин, и шлюз.

**Две оговорки.** Первая: обе команды заработают начиная с выпуска `0.1.0` — пока тег не
опубликован, пакета `odata1c-gate` на PyPI нет, и `uvx` при первом запуске шлюза ответит отказом
(`.mcp.json` плагина закреплён на версии из репозитория). Вторая: репозиторий пока приватный,
`claude plugin marketplace add` клонирует его через git, поэтому на машине нужны учётные данные
git с доступом к нему. Открытие репозитория — отдельное решение владельца.

Отдельно ставится командная строка — она нужна владельцу базы, а не модели (описать базу, собрать
индекс, раскрыть токен, править политику гейта):

```text
uv tool install odata1c-gate
```

После этого команда `odata1c` доступна в терминале. Без установки то же самое запускается как
`uvx --from odata1c-gate odata1c <команда>`. Подробности, Linux, обновление и разбор типовых
сбоев — [docs/install.md](docs/install.md).

## Первые пять минут

```text
odata1c init                          # ~/.claude/odata1c/ с шаблонами настроек, права владельца
odata1c base add ut_test --role test --recipes ut   # спросит адрес, подпись, пользователя, пароль
odata1c base test ut_test             # проверить соединение с 1С
odata1c reindex ut_test               # разобрать $metadata и построить индекс метаданных
odata1c doctor                        # проверить окружение: uv, дом, базы, демон, Claude Code
```

Начинать с `init` обязательно: плагин домашнего каталога не создаёт — его делают либо эта команда,
либо лаунчер при первой сессии Claude Code. `doctor` на пустом месте честно ответит `FAIL` в
строке домашнего каталога, поэтому он и стоит последним; запускать его можно в любой момент.

Адрес базы — это адрес публикации OData 1С, он оканчивается на `/odata/standard.odata/`
(например `https://1c.example.local/ut/odata/standard.odata/`). Пользователь 1С заводится
отдельный, с правами только на то, что нужно читать, — например `odata_claude`. Пароль вводится в
терминале и на экране не отображается; он ложится в `bases.yaml` домашнего каталога открытым
текстом, файл закрывается правами владельца (SPEC §3.4, ADR-0014) — осознанное решение: файл не
покидает диск владельца, это не сетевой секрет. Роль базы задаёт умолчания: `prod` — уровень гейта
`identifiers+names` и только чтение, `test` — уровень `identifiers`, `dev` — гейт выключен и запись
разрешена; любое поле переопределяется правкой файла вручную — готовая запись выглядит так (адрес
и пароль — плейсхолдеры; шаблон со всеми полями — `src/odata1c/templates/bases.example.yaml`):

```yaml
bases:
  ut_test:
    label: УТ 11, тестовая
    url: https://server/base/odata/standard.odata/
    user: odata_claude
    password: "…"
    role: test
    config: ut
```

Чаще всего руками правят `label`, `role`, `write` (разрешить запись), `gate.mode` (уровень гейта),
`permissions` (что именно разрешено писать) и `config` (библиотека рецептов); демон перечитывает
`bases.yaml` по изменению файла без перезапуска. Другой домашний каталог — переменная
`ODATA1C_HOME` или ключ `--home <путь>` у любой команды.

**Файл правит владелец, не модель:** модель его штатно не читает и не правит — хук плагина
`PreToolUse` перехватывает такие обращения к домашнему каталогу шлюза и требует решения человека;
это защита в глубину, а не абсолютный барьер (что он не отсекает и почему главная защита в другом
— раздел «Безопасность» в [AGENTS.md](AGENTS.md)). Реальные значения не выходят через MCP ни при
каком обращении, а пароль 1С модели попросту не нужен — этого достаточно, даже если бы хука не
было. Базу заводит владелец сам, в своём терминале — просить модель «пропиши базу» бессмысленно, у
неё нет для этого инструмента. Подробнее — [docs/install.md](docs/install.md).

Первый реиндекс долгий: у типовой УТ `$metadata` — это около 17 МБ описания и больше семи тысяч
сущностей. Дальше индекс пересобирается, только если у публикации изменилась контрольная сумма
`$metadata`.

Теперь можно спрашивать в Claude Code:

- «какие базы 1С мне доступны?» — модель вызовет `odata1c_bases`;
- «найди справочник контрагентов и покажи состав его полей» — `odata1c_find_entity`,
  затем `odata1c_describe_entity` с классами гейта у каждого поля;
- «возьми любого контрагента и покажи его пять последних заказов клиента» — `odata1c_query`;
  название контрагента придёт токеном, номера и суммы документов — как есть.

Что модель видит и в каком порядке ходит — навык `odata1c` из плагина; справочные темы об
устройстве OData 1С, токенах, политике и протоколе записи — тул `odata1c_info`.

## Что внутри

Два процесса из одного пакета: **демон** (`odata1c daemon`) — единственный на машину, держит
соединения с базами, индекс, словарь и журнал, отвечает по MCP Streamable HTTP на
`127.0.0.1:7171`; **лаунчер** (`odata1c mcp`) — тонкий stdio-процесс на сессию, который поднимает
демон при необходимости и проксирует ему вызовы. Собственной логики у лаунчера нет.

Тулы чтения: `odata1c_bases`, `odata1c_find_entity`, `odata1c_describe_entity`, `odata1c_query`,
`odata1c_get`, `odata1c_info`, `odata1c_reindex`, `odata1c_raw_get`, `odata1c_recipe`.
Тулы записи: `odata1c_create`, `odata1c_update`, `odata1c_mark_for_deletion`, `odata1c_action`
(проведение и отмена), `odata1c_undo`, `odata1c_commit`, `odata1c_journal`.

Рецепт — именованный параметризованный запрос к одной сущности («остатки на складе на дату»,
«задолженность контрагента»): модель подставляет параметры, шлюз строит запрос сам. Рецепты
копятся по конфигурации 1С, а не по отдельной базе.

## Ограничения

- **Физического удаления объектов нет.** «Удаление» для объектов и подчинённых регистров — только
  пометка удаления (`DeletionMark = true`). `PUT` не используется.
- **Сокращённое название в свободном тексте проходит открытым.** Словарь знает полные написания
  названия; если в комментарии документа человек написал узнаваемое сокращение, которого нет в
  справочнике, гейт его не заменит. Ловить «ядро» названия отвергнуто: ядра — обычные слова, их
  замена портила бы данные ложными срабатываниями.
- **Аутентификации у демона нет.** Он слушает только `127.0.0.1`; модель угроз — диск и машина
  владельца. Публикация по сети с TLS и токенами — этап M4.
- **Записи в регистры, подчинённые регистратору, нет** ни при каком флаге разрешений: их пишет
  проведение документа, а не прямая запись.
- **Шаблоны рецептов заполнены только для УТ.** Для БП и ЗУП шаблоны пока пустые: базы для сверки
  имён нет, а невыверенный рецепт хуже пустого.
- **Клиент — Claude Code.** Claude Desktop и Cowork не поддерживаются (ADR-0012).

## Документы

| Файл | Что внутри |
|---|---|
| [docs/install.md](docs/install.md) | установка на Windows и Linux, обновление, типовые сбои, раздел сопровождающего |
| [CHANGELOG.md](CHANGELOG.md) | что вошло в выпуск |
| [SPEC.md](SPEC.md) | спецификация v0.2, 16 разделов — главный артефакт проекта |
| [CONTEXT.md](CONTEXT.md) | глоссарий: термины и запрещённые синонимы |
| [docs/adr/](docs/adr/) | 15 архитектурных решений (0001–0015), статус — во frontmatter |
| [AGENTS.md](AGENTS.md) | вводная для AI-агентов: архитектура, стек, инварианты, процесс |
| [CLAUDE.md](CLAUDE.md) | указатель для Claude Code поверх AGENTS.md |

## Структура репозитория

```text
src/odata1c/          пакет PyPI odata1c-gate: демон, лаунчер, CLI (SPEC §2.2, §11.1)
  config/             bases.yaml и daemon.yaml, роли, валидация, права файлов
  registry/           реестр баз, статус индекса, видимость по сессии
  client1c/           httpx-пул на базу, IBSession, семафор, маппинг ошибок 1С
  index/              парсер EDMX → metadata.sqlite, реиндекс, нечёткий поиск
  gate/               детекторы реквизитов, словарь, подмена в обе стороны, страж
  write/              разрешения, pending-операции, commit, журнал, undo
  recipes/            загрузка рецептов и рендеринг параметров в OData-литералы
  tools/              регистрация тулов, ресурсов, промптов, server instructions
  templates/          файлы-шаблоны в поставке: конфигурация и рецепты УТ/БП/ЗУП
  cli.py              команды odata1c: init, base, reindex, policy, recipe, doctor, reveal, daemon, mcp
  daemon.py           демон: тулы, ресурсы, промпт, MCP Streamable HTTP на 127.0.0.1
  launcher.py         лаунчер: stdio-прокси демону, подъём демона, проброс elicitation
plugin/               плагин Claude Code: манифест, .mcp.json, навыки, хук, агент, evals (SPEC §11.2)
.claude-plugin/       маркетплейс плагина для claude plugin marketplace add
tests/                unit / property / integration + образцы EDMX (SPEC §12)
tools/                bump_version.py, plugin_dev_copy.py, probes/ — скрипты проверок и приёмки
docs/adr/             архитектурные решения
docs/plans/           планы реализации по этапам
docs/probes/          отчёты технических проверок P1–P8 и приёмок на живой базе
```

## Что не попадает в репозиторий

`bases.yaml` хранит пароли 1С открытым текстом (SPEC §3.4), поэтому в git не коммитятся
конфигурация рабочей машины, env-файлы, базы SQLite (словарь, индекс, журнал), выгрузки
`$metadata` и логи — см. [.gitignore](.gitignore). В поставке живут только файлы-шаблоны
в `src/odata1c/templates/`, в тестах — урезанные образцы `$metadata`, не полные дампы.

## Лицензия

MIT.
