# mcp Specification ## Purpose MCP-серверы модулей хаба (`sdlc-hub mcp <модуль>`, stdio) для Claude: инструменты чтения зеркала и заметок, постановки и отслеживания пакетов команд, обновления зеркала, статуса агента и настроек — поверх HTTP API хаба с клиентским токеном. ## Requirements ### Requirement: Сервер и регистрация `sdlc-hub mcp <модуль> [--project <метка>]` SHALL запускать MCP-сервер модуля хаба по stdio (пакет `mcp`), ходящий в HTTP API хаба клиентским токеном из настроек; сервер называется `sdlc-<модуль>` (`sdlc-jira`, `sdlc-wiki`). Модуль обязателен: без него — код выхода 2 и список модулей в stderr, сервер не запускается; неизвестный модуль — так же. Общая регистрация — в user scope Claude Code как `sdlc-jira` (`uv run --project <путь> sdlc-hub mcp jira`), без проекта. Репозиторий проекта SHALL регистрировать серверы модулей в своём `.mcp.json` (project scope) с `--project <метка>`; эта запись перекрывает общую. Если хаб недоступен, каждый инструмент SHALL вернуть текст «хаб не запущен: sdlc-hub serve», а не исключение. #### Scenario: Хаб выключен - **WHEN** вызван `issues` при остановленном хабе - **THEN** ответ инструмента — одна строка с подсказкой, сервер MCP продолжает работать #### Scenario: Сервер модуля jira - **WHEN** запущен `sdlc-hub mcp jira --project AWPCA` - **THEN** сервер называется `sdlc-jira`, список инструментов и их описания совпадают с прежним сервером `jira-hub`, сессия привязана к `AWPCA` #### Scenario: Модуль по умолчанию - **WHEN** запущен `sdlc-hub mcp --project AWPCA` - **THEN** модуля по умолчанию больше нет: код выхода 2, в stderr — подсказка указать модуль и список модулей (`jira`, `wiki`) #### Scenario: Неизвестный модуль - **WHEN** запущен `sdlc-hub mcp nope` - **THEN** код выхода 2, в stderr — «неизвестный модуль nope» и список модулей (`jira`, `wiki`) ### Requirement: Инструменты | Инструмент | Параметры | Результат | |---|---|---| | `projects` | — | таблица: метка, название, доска, тип доски («неизвестен», пока агент не определил), число своих задач; в привязанной сессии текущий проект отмечен | | `project_create` | `label`, `name`, `board_id` (необязательно) | `POST /projects` с автором `claude`: проект, id пакета обновления и фрагмент `.mcp.json` для репозитория проекта (см. ниже); 409/422 — текст ошибки | | `issues` | фильтры как у `GET /issues` (кроме `project` — его задаёт привязка), `limit` (по умолчанию 50) | таблица: ключ, тема (до 80 символов), статус, срок, исполнитель, спринт | | `issue` | `key` | поля, связи (обе стороны, с типом и направлением), заметки, последние команды, комментарии: «комментарии (N):» и 5 последних (номер «#», автор, время, текст до 300 символов с «…»), «ещё M», если их больше 5; без комментариев — «комментариев нет»; у комментария со ссылками на вложения — строка «файлы: # <имя>, …»; затем вложения (см. «Вложения в MCP») | | `comment` | `key`, `comment_id` (номер из ответа `issue`, «#» в начале допускается) | один комментарий целиком по `GET /issues/{key}`: строка « # <автор>, создан » (и «, изменён », если `updated` есть и отличается от `created`), под ней полный текст с переносами строк; нет такого номера — «у нет комментария #»; есть ссылки на вложения — после текста строка «вложения:» и по строке на каждое: «# <имя> (<размер>)» | | `attachment` | `key`, `attachment` (номер вложения «#» или «», иначе — точное имя файла), `wait_seconds` (по умолчанию 120, не больше 600) | скачивает вложение и отдаёт путь к локальной копии (см. «Вложения в MCP») | | `notes_set` | `key`, подмножество полей заметок | обновлённые заметки | | `batch_create` | `title`, `commands` (список объектов по контракту `commands`; у `ensure_attachment` вместо `file` можно `path`) | `id`, `estimate_seconds`, число команд; 422 — текст ошибки валидации | | `batch_status` | `id` | статусы команд, ошибки, оценка остатка | | `batch_wait` | `id`, `timeout_minutes` (≤ 60) | как `batch_status`, возвращается по завершении или по таймауту | | `batches` | `limit` | последние пакеты со статусом | | `command_retry` | `id` | новый пакет | | `batch_cancel` | `id` | вид пакета; для команд пакета, уже взятых агентом, — строка «команда #N уже у агента и будет выполнена; остановить — `POST /commands/N/cancel` с `force: true`» | | `refresh` | `keys` или `full`, `wait` (bool) | без `wait` — строка с id поставленного пакета; с `wait` — итог пакета, как у `batch_wait` | | `agent_status` | — | онлайн/офлайн, версия, текущая команда, довыданные команды (id, тип, число выдач), 429 за сутки, версия настроек; публикация агента («публикация: v от <дата>» или «публикации нет»), состояние обновления («обновление: v → v»), запускатель («запускатель: v», «без запускателя» или, пока `agent_reported = false`, «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска хаба)»), `update_hint`, если он есть | | `sync_log` | `limit` | последние `sync_run` | | `settings_get`, `settings_set` | ключ, значение | текущее значение / новая `settings_version` | Разрез по проекту и поведение без привязки — требование «Привязка сессии к проекту». Описание инструмента `batch_create` SHALL перечислять параметры каждого типа команды (обязательные, необязательные в `[ ]`, формат значения) и собираться из описания параметров в коде хаба (см. `commands`), а не вручную. `ensure_attachment` в `batch_create` SHALL принимать вместо `file` параметр `path` — абсолютный путь к локальному файлу на машине MCP-сервера. До постановки пакета MCP SHALL прочитать файл, загрузить его `POST /files` и заменить `path` на `file`; `filename` по умолчанию — имя файла из `path`. Нет файла, нет прав, не файл, больше 10 МБ — ответ инструмента с причиной и путём, пакет не ставится. Передан и `path`, и `file` — ответ «у ensure_attachment либо path, либо file». Описание инструмента `batch_create` SHALL сообщать, что метку проекта сессии хаб дописывает в `ensure_issue` сам, метки других проектов запрещены, а команды над задачами вне проекта отклоняются; `board_id` для `ensure_sprint` выставляет хаб. Ответ `project_create` SHALL содержать фрагмент `.mcp.json` `{"mcpServers": {"sdlc-jira": {"command": "uv", "args": ["run", "--project", "<корень хаба>", "sdlc-hub", "mcp", "jira", "--project", "<метка>"]}}}`, где корень хаба — корень репозитория, из которого загружен пакет текущего процесса MCP (вычисляется от расположения пакета, без зашитого пути), и указание: положить файл в корень репозитория проекта (слить с имеющимся `.mcp.json`) и переподключить MCP (`/mcp` → reconnect, одобрить сервер проекта). Автор команд из MCP SHALL быть `claude`; из API без автора — `api`. #### Scenario: Пакет из чата - **WHEN** `batch_create {title: "1538 в спринт 2", commands: [ensure_fields{…}, ensure_link{…}, ensure_sprint{…}]}` - **THEN** пакет создан с четырьмя командами (три + `refresh`), ответ содержит оценку в минутах #### Scenario: Проект из чата - **WHEN** сессия без привязки, `project_create {label: "STOCKS_DRRK", name: "Запасы ДРРК", board_id: 5120}` - **THEN** проект создан, ответ содержит id пакета обновления и фрагмент `.mcp.json` с `"--project", "STOCKS_DRRK"` #### Scenario: Ошибка с файлом из чата - **WHEN** `batch_create {title: "Ошибка ОСВ 60", commands: [ensure_issue{temp_key: new:1, issue_type: bug, …}, ensure_attachment{key: new:1, path: "/Users/…/ОСВ сч. 60 за 2 кв. 2026.xlsx"}]}` - **THEN** файл загружен в хаб; пакет создан с `ensure_attachment {key: new:1, file: , filename: "ОСВ сч. 60 за 2 кв. 2026.xlsx"}` и хвостовым `refresh` #### Scenario: Файла нет - **WHEN** тот же вызов, файла по `path` нет - **THEN** ответ «файл не найден: », пакет не создан, в хаб ничего не загружено #### Scenario: Ожидание результата - **WHEN** `batch_wait {id, timeout_minutes: 10}` и пакет завершился через 6 минут - **THEN** ответ через 6 минут со статусами всех команд #### Scenario: Статус агента с довыдачей - **WHEN** агент на связи, команда 40 выдана дважды и ещё без отчёта - **THEN** ответ `agent_status` содержит «агент онлайн» и строку о команде 40 с числом выдач 2 #### Scenario: Отмена пакета с командой у агента - **WHEN** `batch_cancel {id: 9}`, команда 30 пакета в `claimed` - **THEN** ответ содержит вид пакета и строку «команда #30 уже у агента и будет выполнена; остановить — …» #### Scenario: Статус агента после отката - **WHEN** опубликована v7, агент v6 под запускателем v1 прислал `update_state = rolled_back` для v7 - **THEN** ответ `agent_status` содержит «публикация: v7 от …», «обновление: rolled_back v6 → v7», «запускатель: v1» и «откат с v7 на v6: …» #### Scenario: Статус агента сразу после перезапуска хаба - **WHEN** хаб перезапущен, агент ещё не прислал `claim` - **THEN** ответ `agent_status` содержит «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска хаба)» и не содержит «без запускателя» и «агент запущен без run.py» #### Scenario: Задача с семью комментариями - **WHEN** `issue {key: "AURORA-9001"}`, у задачи 7 комментариев, у самого нового текст 500 символов - **THEN** ответ содержит «комментарии (7):», 5 последних комментариев с номерами «#» (самый новый — первым, обрезан до 300 символов с «…») и «ещё 2» #### Scenario: Комментарий целиком - **WHEN** у задачи `AURORA-9001` комментарий `#101` длиной 800 символов с переносами строк, вызван `comment {key: "AURORA-9001", comment_id: "101"}` - **THEN** ответ — «AURORA-9001 #101 <автор>, создан » и полный текст комментария без обрезки, переносы строк сохранены #### Scenario: Нет такого комментария - **WHEN** `comment {key: "AURORA-9001", comment_id: "999"}`, у задачи нет комментария 999 - **THEN** ответ «у AURORA-9001 нет комментария #999» ### Requirement: Привязка сессии к проекту `sdlc-hub mcp <модуль> --project <метка>` SHALL привязывать сервер MCP к проекту. Привязка SHALL проверяться при каждом вызове инструмента по `GET /projects`: метки нет — ответ «проект <метка> не заведён в хабе: project_create или исправьте --project в .mcp.json» (кроме `projects`, `project_create`). В привязанной к проекту P сессии: - `issues` — `GET /issues?project=P` с остальными фильтрами; - `issue`, `comment` — любая задача по ключу; - `notes_set` — только своя задача P (метка P; соседи — нет) или временный ключ `new:` (его задача создаётся пакетом проекта), иначе ответ « не в проекте P»; - `batch_create`, `command_retry` — с `project = P` (проверки пакета проекта, см. `commands`; повтор команды другого проекта или пакета без проекта — отказ хаба); - `batches` — `GET /batches?project=P`; `batch_status`, `batch_wait`, `batch_cancel` — по id, без ограничения; - `refresh` — `full` — общий полный прогон всех проектов, `keys` — как раньше. Без привязки инструменты `issues`, `issue`, `comment`, `notes_set`, `batch_create`, `command_retry`, `refresh` SHALL отвечать «сессия не привязана к проекту: запустите MCP с --project <метка> (список — projects)» и в хаб не ходить; `projects`, `project_create`, `agent_status`, `sync_log`, `settings_get`, `settings_set`, `batches`, `batch_status`, `batch_wait`, `batch_cancel` работают. #### Scenario: Сессия без проекта - **WHEN** MCP запущен без `--project`, вызван `issues` - **THEN** ответ «сессия не привязана к проекту: …», запросов к `GET /issues` нет #### Scenario: Задачи своего проекта - **WHEN** MCP запущен с `--project STOCKS_DRRK`, вызван `issues` - **THEN** в таблице только свои задачи `STOCKS_DRRK` и их соседи #### Scenario: Создание задачи без метки - **WHEN** сессия `STOCKS_DRRK`, `batch_create {commands: [ensure_issue {temp_key: new:1, summary: "…"}]}` без `labels` - **THEN** пакет поставлен с `project = STOCKS_DRRK`, у `ensure_issue` `labels = ["STOCKS_DRRK"]` #### Scenario: Опечатка в проекте - **WHEN** MCP запущен с `--project STOKS`, такого проекта нет; вызван `issues` - **THEN** ответ «проект STOKS не заведён в хабе: …» ### Requirement: Лента изменений в MCP Инструмент `changes` SHALL отдавать ленту изменений (`GET /changes`) с параметрами `hours` (число > 0), `since`, `until`, `kinds`, `key`. `hours` и `since` вместе — ответ «либо hours, либо since». Без `hours` и `since` — с начала текущих суток по местному времени машины MCP-сервера; с `hours` — `since = сейчас − hours`. `since`/`until` без смещения считаются местным временем машины MCP-сервера (хаб время без смещения отвергает). В сессии, привязанной к проекту P, без `key` — `project = P`; с `key` — без проекта. Без привязки и без `key` — ответ «сессия не привязана к проекту: запустите MCP с --project <метка> (список — projects)», в хаб не ходить; с `key` — работает. Вид ответа: первая строка — период по местному времени («изменения с 22.10 00:00 по 22.10 14:05»); затем события, сгруппированные по задаче; задачи — по времени последнего события, новые сверху; у задачи строка «КЛЮЧ тема (до 80 символов) — текущий статус», под ней события по времени, новые сверху, каждое — «ЧЧ:ММ автор: <что>», с датой «ДД.ММ» перед временем, если событие не в текущих сутках. `<что>`: статус — «статус «было» → «стало»»; описание — «описание изменено»; срок и плановые даты — «срок / плановое начало / плановое окончание ДД.ММ → ДД.ММ» (дата — из начала `from_value`/`to_value` вида `ГГГГ-ММ-ДД`, иначе `from`/`to` как есть; пустое значение — «—»); комментарий — «комментарий: » и первые 200 символов текста с «…»; прочие поля — «<поле>: «было» → «стало»» (значения до 80 символов с «…»). Событий нет — «за период изменений нет». `truncated` — последняя строка «показаны последние N событий, сузьте период». Ошибка 422 хаба — её текст. Инструмент `issue` SHALL после комментариев выводить «изменения (N):» и 5 последних элементов `changes` задачи в том же виде событий (с датой), «ещё M», если их больше 5; при `changes = []` — строки нет. Инструмент `sync_log` SHALL выводить у каждого прогона «журнал: принят ДД.ММ ЧЧ:ММ» (по `history_at`, местное время) или «журнал: нет». #### Scenario: Что закрылось сегодня - **WHEN** сессия `AWPCA`, `changes {kinds: "status"}` в 14:05; сегодня X переведена в «Готово» в 11:15 - **THEN** ответ содержит строку X с темой и статусом «Готово» и «11:15 Петров Пётр: статус «В работе» → «Готово»» #### Scenario: За два часа - **WHEN** сессия `AWPCA`, `changes {hours: 2}` - **THEN** запрос `GET /changes?project=AWPCA&since=<сейчас − 2 ч>`; в ответе события статусов, описаний, сроков и комментариев #### Scenario: Без привязки - **WHEN** MCP без `--project`, `changes {}` - **THEN** ответ «сессия не привязана к проекту: …», запросов к `GET /changes` нет #### Scenario: Пустой период - **WHEN** `changes {hours: 1}`, событий нет - **THEN** ответ — строка периода и «за период изменений нет» #### Scenario: История в карточке - **WHEN** `issue {key: X}`, у X 7 элементов журнала - **THEN** ответ содержит «изменения (7):», 5 последних событий и «ещё 2» ### Requirement: Вложения в MCP `issue` SHALL после комментариев выводить «вложения (N):» и по строке на каждое вложение (новые сверху): «# <имя> (<размер>) — <автор>, », дальше через запятую: «в описании», если на вложение ссылается описание, «в #» для каждого ссылающегося комментария, «скачано», если `state = ready`, «больше 10 МБ — только в Jira» для таких файлов; без вложений — «вложений нет». Размер — в байтах, КБ или МБ с одним знаком после запятой («512 Б», «234,4 КБ», «2,3 МБ»); неизвестный — «размер неизвестен». `attachment` SHALL: 1. найти вложение по `GET /issues/{key}`: по номеру, иначе по точному имени (несколько с одним именем — самое новое, в ответ строка «у N вложений «<имя>», взято самое новое #»); не найдено — «у нет вложения «<значение>»; список — issue », в хаб больше не ходить; 2. `POST /attachments/{id}/fetch` с автором `claude`; 413 — «файл больше 10 МБ (<размер>) — скачайте в Jira: задача , вложение «<имя>»»; 409 и прочие ошибки хаба — их текст; 3. пока `state = fetching` — опрашивать `GET /attachments/{id}` раз в 2 с, не дольше `wait_seconds`; не дождался — «скачивание ещё идёт (команда #, агент <онлайн/офлайн>); вызовите attachment ещё раз — вторая команда не поставится»; `failed` — «не удалось скачать: »; 4. при `ready` — `GET /attachments/{id}/content` и записать файл в `<каталог данных хаба>/downloads//<имя>`, где имя — `filename` с заменой `/`, `\`, `:` и управляющих символов на `_` (пустое — `attachment-`); каталог с правами `0700`; запись через временный файл и переименование; если там уже лежит файл того же размера — не перезаписывать; 5. ответить «<абсолютный путь>» отдельной строкой и под ним «<имя> (<размер>), , задача ». #### Scenario: Скриншот из комментария - **WHEN** `comment {key: "AURORA-1650", comment_id: "101"}` показал «вложения: #31337 скрин 1.png (234,4 КБ)»; вызван `attachment {key: "AURORA-1650", attachment: "#31337"}`, агент онлайн, файла у хаба нет - **THEN** поставлена `fetch_attachment`; через несколько секунд ответ — путь `…/sdlc-hub/downloads/31337/скрин 1.png` и строка «скрин 1.png (234,4 КБ), image/png, задача AURORA-1650»; файл по пути читается #### Scenario: Повторный вызов - **WHEN** тот же вызов второй раз, файл у хаба (`ready`) и в `downloads/31337/` - **THEN** ответ сразу, команд в очереди не прибавилось #### Scenario: Агент не успел - **WHEN** `attachment {…, wait_seconds: 30}`, агент занят долгим `refresh` - **THEN** через 30 с ответ «скачивание ещё идёт (команда #N, агент онлайн); вызовите attachment ещё раз — вторая команда не поставится» #### Scenario: По имени - **WHEN** `attachment {key: "AURORA-1650", attachment: "ОСВ 60.xlsx"}`, вложение с таким именем одно - **THEN** скачивается оно; путь оканчивается на `/ОСВ 60.xlsx` #### Scenario: Вложения в карточке - **WHEN** `issue {key: "AURORA-1650"}`, у задачи вложения #31337 «скрин 1.png» (на него ссылается комментарий 101, скачано) и #31338 «запись.mp4» на 25 МБ - **THEN** в ответе «вложения (2):», строка #31338 с «больше 10 МБ — только в Jira», строка #31337 с «в #101, скачано»; у комментария 101 строка «файлы: #31337 скрин 1.png» ### Requirement: MCP вики `sdlc-hub mcp wiki [--project <метка>]` SHALL запускать по stdio MCP-сервер модуля `wiki` с именем `sdlc-wiki`, ходящий в HTTP API хаба клиентским токеном; хаб недоступен — каждый инструмент отвечает «хаб не запущен: sdlc-hub serve». Привязка к проекту проверяется так же, как у сервера jira («Привязка сессии к проекту»). Инструменты: | Инструмент | Параметры | Ответ | |---|---|---| | `projects` | — | таблица: метка, название, корень вики, страниц, последняя синхронизация; текущий проект отмечен | | `project_create` | `label`, `name`, `wiki_root` (необязательно) | `POST /projects` с автором `claude`; проект и фрагмент `.mcp.json` с записью `sdlc-wiki` | | `root_set` | `root` | `PATCH /projects/<текущий> {wiki_root}`; новый корень и id пакета синхронизации | | `tree` | `code` (необязательно), `depth` (по умолчанию 2) | дерево от корня или от `code`: отступ, код, название, размер | | `page` | `code` или `title`, `offset` (по умолчанию 0) | шапка (код, путь в дереве, адрес в Eva, дата синхронизации, вложения с `id`) и Markdown с `offset` до 20 000 символов, в конце — «продолжение: offset=», если текст длиннее; `title` ищется точно, иначе — до 10 кандидатов с кодами | | `search` | `query`, `limit` (по умолчанию 10) | страницы: код, название, путь, фрагмент | | `attachment` | `id` | ставит скачивание, ждёт до 60 с; готово — путь к копии `downloads/wiki-/<имя>`; иначе состояние и подсказка повторить | | `sync` | `wait` (по умолчанию `false`) | ставит синхронизацию (или сообщает о текущей) и id пакета; с `wait` — ждёт до 300 с и отдаёт итог | | `status` | — | корень, последняя синхронизация, ошибка, пакет в работе, агент `wiki`: онлайн/офлайн, версия, публикация, обновление, запускатель (как `agent_status` сервера jira) | Инструменты, кроме `projects` и `project_create`, SHALL требовать привязки к проекту. В привязанной сессии без корня (кроме `root_set`, `status`) ответ SHALL быть «у проекта <метка> нет корня вики: root_set DOC-…»; с корнем, но без синхронизаций — «вики проекта ещё не синхронизирована: sync». Набор инструментов и их описания у сервера `sdlc-jira` (модуль jira) SHALL остаться прежним. #### Scenario: Поиск из сессии awp-ca - **WHEN** сервер запущен с `--project AWPCA`, копия синхронизирована, вызван `search {query: "статус согласован"}` - **THEN** ответ — страницы с кодами и фрагментами; запрос в хаб — `GET /wiki/search?project=AWPCA&q=…` #### Scenario: Длинная страница - **WHEN** `page {code: DOC-3}`, Markdown 45 000 символов - **THEN** первые 20 000 символов и «продолжение: offset=20000»; `page {code: DOC-3, offset: 40000}` — остаток без строки продолжения #### Scenario: Корня нет - **WHEN** сессия `sfm_kedr` без корня, вызван `tree` - **THEN** ответ «у проекта sfm_kedr нет корня вики: root_set DOC-…», запроса `GET /wiki/tree` нет #### Scenario: Сервер jira не изменился - **WHEN** запущен `sdlc-hub mcp jira --project AWPCA` - **THEN** имя сервера `sdlc-jira`, список инструментов и описания совпадают с эталоном до change ### Requirement: Спринты и бэклог в MCP MCP `sdlc-jira` SHALL давать инструменты чтения по `GET /agile` проекта сессии (сессия без проекта — тот же отказ, что у прочих инструментов с проектом): - `sprints` — таблица спринтов доски: имя, состояние, даты, задач (выполнено/всего), SP (выполнено/всего), без оценки; закрытые — 10 последних и строка «ещё N закрытых», если их больше; без доски — «у проекта нет доски»; - `sprint` (`name`, по умолчанию — активный) — шапка спринта (имя, состояние, даты, цель, суммы) и задачи по колонкам доски: ключ, тема (до 80 символов), исполнитель, SP; - `backlog` (`limit`, по умолчанию 50) — бэклог по рангу: позиция, ключ, тема, статус, исполнитель, SP; в конце «ещё N», если задач больше `limit`. Таблица `issues` SHALL получить колонку «SP». Описание `batch_create` SHALL перечислять `ensure_sprint_meta`, `ensure_sprint_state`, `ensure_rank` и `story_points` в `ensure_fields` с параметрами; хвостовой `refresh` и `board_id` (у `ensure_sprint`, команд спринта и `ensure_fields` с `story_points`) хаб дописывает сам (см. `commands`). #### Scenario: Спринты проекта - **WHEN** сессия привязана к `AWPCA`, вызван `sprints` - **THEN** строки: активный `AWPCA_Sprint_3_Corrections`, будущие, затем закрытые; у каждой — задачи и SP «выполнено/всего» #### Scenario: Бэклог с лимитом - **WHEN** `backlog {limit: 2}`, в бэклоге 188 задач - **THEN** две строки по рангу и «ещё 186»