Михаил mikhail
  • Joined on 2025-03-15

zyfra-jira-mcp (0.3.1)

Published 2026-10-27 12:26:43 +03:00 by mikhail

Installation

pip install --index-url https://git.shahovalov.ru/api/packages/mikhail/pypi/simple/ --extra-index-url https://pypi.org/simple zyfra-jira-mcp

About this package

MCP-сервер для Jira и Confluence Data Center инсталляции Zyfra

zyfra-jira-mcp

MCP-сервер для Jira и Confluence Data Center инсталляции jira.zyfra.com: 26 инструментов поверх REST API, ревизия протокола 2026-07-28.

Сервер заточен под одну инсталляцию: адреса, проект IDPPA, пространство ZPA и набор полей зашиты в код. Настраивается только то, что зависит от машины.

Установка

Сервер ставится пакетом из реестра Gitea и даёт команду zyfra-jira в PATH:

uv tool install zyfra-jira-mcp --index https://git.shahovalov.ru/api/packages/mikhail/pypi/simple

Пакет в реестр кладёт CI (release.yml) по тегу vX.Y.Z, совпадающему с версией в pyproject.toml; тесты гоняет ci.yml на каждый push в main и pull request. Из чекаута пакет ставят копией в собственный venv инструмента:

uv tool install --reinstall W:/AiProjects/zyfra-jira-mcp

Для разработки — из чекаута: uv sync, команды через uv run zyfra-jira …. Клиенты запускают установленную копию, а не чекаут, поэтому новая версия доходит до них только переустановкой (см. «Подключение»).

Токены

Хранятся в Windows Credential Manager (zyfra-jira/jira_token, zyfra-jira/conf_token). Логин и пароль Zyfra сервер не хранит и не запрашивает: PAT выпускает человек в браузере, сервер только подсказывает ссылку.

zyfra-jira creds set jira --prompt

То же для conf. Источник токена задаётся явным флагом:

Флаг Когда
--prompt живая консоль; скрытый ввод с клавиатуры
--from-file PATH токен положен в файл; файл удаляется после чтения (--keep оставляет)
--stdin токен приходит по конвейеру

Без флага команда печатает справку и выходит. Угадывать терминал нельзя: getpass на Windows читает прямо с консоли мимо stdin, а sys.stdin.isatty() под Git Bash возвращает True даже при перенаправлении — команда молча повисла бы.

Токен не попадает ни в аргументы процесса, ни в переписку с агентом. Переопределить можно переменными ZYFRA_JIRA_TOKEN и ZYFRA_CONF_TOKEN — они имеют приоритет над Credential Manager.

Confluence-PAT живёт около 90 дней. Когда он протухнет, любой инструмент вернёт ссылку на страницу выпуска; после сохранения нового токена вызывается auth_reload.

Подключение

Сервер умеет оба транспорта спецификации: stdio и Streamable HTTP. Настройки машины в обоих режимах читаются из одного файла %LOCALAPPDATA%\zyfra-jira\config.toml, поэтому в .mcp.json их нет. Переменные окружения с теми же именами перекрывают файл.

# socks = "192.168.250.2:1080"   # VPN в VM; без ключа — напрямую, VPN на этой машине

stdio — основной режим

Клиент запускает сервер сам, в каждой сессии свой процесс; упавший перезапускает тоже он. Фоновый процесс и автозапуск не нужны.

"zyfra-jira": {
  "type": "stdio",
  "command": "C:/Users/mikhail/.local/bin/zyfra-jira.exe",
  "args": [],
  "env": {}
}

Команда — из установки uv tool, а не uv run в чекауте: сессии держат venv, из которого запущены, и uv sync при разработке иначе падал бы на занятых файлах. Переустановка (uv tool install --reinstall …) по той же причине идёт, когда сессии Claude закрыты; новая версия доходит до них с новой сессией.

Streamable HTTP — по желанию

Один общий процесс на loopback для всех клиентов. Его запускают вручную, автозапуска нет.

zyfra-jira creds new mcp

Команда создаёт токен самого сервера, кладёт его в Credential Manager (zyfra-jira/mcp_token) и печатает один раз. Claude Code берёт его оттуда сам: headersHelper вызывает zyfra-jira creds header при подключении, и токен не лежит ни в .mcp.json, ни в переменных окружения. Клиенту без headersHelper его дают переменной ZYFRA_JIRA_MCP_TOKEN. Новый токен отменяет старый после перезапуска сервера.

zyfra-jira http

Сервер слушает http://127.0.0.1:8765/mcp (--host, --port меняют адрес; адрес не из loopback отвергается). Без токена сервер не запускается.

"zyfra-jira": {
  "type": "http",
  "url": "http://127.0.0.1:8765/mcp",
  "headersHelper": "C:/Users/mikhail/.local/bin/zyfra-jira.exe creds header"
}

Путь в headersHelper полный: команду запускает оболочка клиента, и PATH у неё может не совпадать с PATH терминала.

Запуск без окна консоли — pythonw.exe из venv инструмента с %LOCALAPPDATA%\zyfra-jira\zyfra-jira-http.pyw: тот пишет журнал в zyfra-jira-http.log рядом. В журнал попадает каждая новая пара «клиент — ревизия протокола», один раз за запуск: клиент claude-code 2.1.308, протокол 2026-07-28. Когда в нём не останется ревизий 2025-11-25 и раньше, сессии можно выключить (stateless_http=True).

Защита Что закрывает
только loopback Jira-токены человека не уходят в сеть
Authorization: Bearer другие локальные процессы и страницы браузера: без токена — 401
проверка Host/Origin (SDK) DNS rebinding: чужое имя в Host — отказ

⚠️ Подтверждение записи держится на доверии к клиенту с токеном сервера. Клиента claude-code сервер узнаёт по имени, которое тот сообщает о себе, и не задаёт ему вопрос: окно разрешения показывает сам Claude Code. Процесс с ZYFRA_JIRA_MCP_TOKEN мог бы назваться так же — или просто ответить «да» на elicitation. Поэтому токен сервера даётся только клиентам, которые сами спрашивают человека.

Сессии HTTP включены: на 2026-07-28 сервер и так работает без состояния, а клиенту 2025-11-25 сессия нужна, чтобы ответ на elicitation вернулся в вызов инструмента. tools/list отдаёт ttlMs 10 минут: набор инструментов меняется только с новой версией.

Инструменты

Чтение Запись
jira_issue, jira_comments, jira_search jira_comment_add
jira_parent, jira_transitions jira_update
conf_images jira_build_fields, jira_batch (пакет разнотипных записей)
jira_worklogs, jira_worklog_check jira_worklog_add, jira_worklog_batch, jira_worklog_update
conf_page, conf_search, conf_page_by_jira jira_transition, jira_worklog_delete (destructive)
jira_attachments, conf_attachments (пишут файлы)
auth_status auth_reload, jira_export, conf_export (выгрузка в файл)

conf_page(format=…) отдаёт тело в виде html (view с раскрытыми макросами), markdown (тот же view общим переводом, без чистки под заметку) или storage (исходный XHTML с ac:-макросами и шириной картинок).

jira_export и conf_export кладут сырые ответы REST в <dest>/<KEY>.json и <dest>/<pageId>.json и возвращают только справку: путь, версию источника, размер и список того, чего в выгрузке не хватает. Файл читает скрипт навыка, а не модель, поэтому большой JSON не проходит через контекст.

Каталог dest у выгрузок и скачивания вложений выбирает агент: абсолютный путь — как есть, относительный — от каталога проекта сессии (из него клиент запускает stdio-сервер; auth_status показывает его в relative_base). Общий HTTP-процесс проекта сессии не знает и принимает только абсолютный путь. Имена файлов приходят из Jira, поэтому чистятся и не выходят за пределы dest.

Ключ задачи принимается в любом виде: IDPPA-8352, 8352, feat/IDPPA-8352.

Списание времени и Tempo

На jira.zyfra.com (Jira Server 9.13) ворклог Jira и есть ворклог Tempo: у записи Tempo jiraWorklogId совпадает с её id, рабочих атрибутов Tempo не заведено (/rest/tempo-core/1/work-attribute пуст). Поэтому инструменты ходят в /rest/api/2/issue/{key}/worklog, а не в tempo-timesheets.

  • jira_worklog_add — время в формате Jira (1h 30m) или секундами; started в ISO 8601, без пояса берётся пояс машины, по умолчанию — сейчас. Повторный вызов спишет время ещё раз.
  • jira_worklogs — по умолчанию только свои ворклоги, с фильтром по дням date_from/date_to.
  • jira_worklog_update, jira_worklog_delete — только свои ворклоги; чужие отвергаются до записи.

План недели

План — список строк {key, time_spent, started, comment}. started — день ГГГГ-ММ-ДД или время в ISO 8601; строки одного дня без времени встают подряд с 09:00 в порядке плана. Время в пакете уходит в Jira секундами, 1d = 8 часов.

jira_worklog_check(entries)   только чтение: раскладка по дням + уже списанное, проблемы
        │ план поправлен, ok = true
jira_worklog_batch(entries)   одно подтверждение на весь план → запись строка за строкой
  • Проверка ищет всё сразу: формат, задача существует и не переехала, повтор строки, 21 день вперёд, 12 часов в день вместе с уже списанным на любые задачи.
  • jira_worklog_batch повторяет проверку перед записью и при любой проблеме не пишет ничего.
  • Jira не умеет транзакций: если строка упала (например, неделя утверждена), созданные до неё остаются, остальные не пишутся. В ответе — created, failed_index, error, not_attempted; лишнее удаляет jira_worklog_delete. Повтор вызова спишет время второй раз.

Ограничения Tempo из глобальной настройки: не больше 12 часов в день, не дальше 21 дня в будущее, утверждение по неделям — в утверждённую неделю Jira не пустит.

Пакет записей

jira_batch — разнотипные записи по одной или многим задачам с одним подтверждением человека: пять закрытий, десять сборок и три комментария — это один вопрос, а не восемнадцать. Операция — объект с полем op (comment, update, build_fields, transition) и полями соответствующего одиночного инструмента:

[
  {"op": "comment", "key": "8352", "body": "Проверено на dev-03"},
  {"op": "build_fields", "key": "8352", "backend": "zpas-core:1.4.0-rc.2"},
  {"op": "transition", "key": "8352", "transition_id": "31"}
]
  • Весь пакет проверяется до вопроса: ключи, формат сборок, доступность перехода по jira_transitions, повторы, не больше 50 записей. Проблемы собираются списком по всем строкам; при любой из них не пишется ничего.
  • Записи идут по порядку. Jira не умеет транзакций: при сбое созданное остаётся, в ответе done, failed_index, error, not_attempted.
  • Списание времени сюда не входит — оно отдельно, через jira_worklog_batch.

Предохранители

Каждый изменяющий инструмент Jira (jira_comment_add, jira_update, jira_build_fields, jira_transition, jira_batch, jira_worklog_add/batch/update/delete) пишет только после «да» человека. Кто спрашивает, зависит от клиента; auth_status показывает это в поле confirm.

Клиент Кто спрашивает
Claude Code (claude-code): терминал, вкладка Code десктопа, SDK сам клиент окном разрешения: у инструментов _meta["anthropic/requiresUserInteraction"], окно появляется на каждом вызове даже в auto и bypassPermissions, в dontAsk вызов отклоняется
другой клиент с elicitation (form) сервер: вопрос, что именно запишется и куда
клиент без elicitation никто: запись отклоняется, обхода через конфигурацию нет

Claude Code сервер через elicitation не спрашивает: вкладка Code десктопа запускает его в print mode, и он отменяет вопрос сам, не показывая диалог (в логе MCP — Elicitation request received in print mode).

Вход проверяется до вопроса и в одиночных инструментах: jira_transition отвергает id, которого нет в jira_transitions; jira_worklog_delete/update — чужой или несуществующий ворклог; jira_update — пустые поля; jira_build_fields — неверный формат. Человека не спрашивают о записи, которая заведомо не пройдёт.

Отказ или отмена возвращают ошибку «Человек не подтвердил запись». На ревизии 2026-07-28 вопрос уходит в InputRequiredResult, клиент отвечает повторным вызовом. requestState между раундами запечатан SDK (AEAD, привязан к аргументам вызова, TTL 10 мин): сервер между раундами ничего не хранит. Поэтому текст вопроса не меняется между раундами: без started в нём «сейчас», а не текущее время.

Ключ config.toml / переменная По умолчанию Что делает
socks / ZYFRA_JIRA_SOCKS не задан хост:порт SOCKS5-прокси до Jira, например VPN-VM 192.168.250.2:1080; имя Jira разрешает прокси. Без него — напрямую
— / ZYFRA_JIRA_CONFIG %LOCALAPPDATA%\zyfra-jira\config.toml другой путь к файлу настроек

jira.zyfra.com видна только через корпоративный VPN. Если связи нет, инструмент отвечает «Нет связи с jira.zyfra.com (путь): причина» и просит проверить VPN, а не перевыпускать токены. auth_status в этом случае показывает offline и путь запросов в поле route.

Что ещё проверяется

  • Запросы с Bearer уходят только на хост jira.zyfra.com: URL вложения из ответа API проверяется, редирект на чужой хост запрещён.
  • 401 — токен не принят. 403 — AuthError только при заголовке X-Seraph-LoginReason, иначе это «нет прав» и обычная ошибка: перевыпуск токена тут не поможет.
  • HTML вместо JSON (страница логина или SSO) — тоже AuthError.
  • Имена вложений: зарезервированные имена Windows (CON, NUL, …) получают префикс _, коллизии — суффикс (2). Одно битое вложение не рвёт скачивание остальных: оно уходит в skipped.
  • Битый config.toml даёт понятную ошибку, а не падение инструмента.

Разработка

uv run pytest

Тесты сети не трогают. tests/test_stdio.py поднимает сервер отдельным процессом и проверяет согласование ревизии, состав инструментов и работу предохранителей.

Модули src/zyfra_jira_mcp/:

server.py       сборка: порядок импорта = порядок tools/list
app.py          экземпляр MCPServer, аннотации, общие типы
confirm.py      согласие человека (режимы host / elicitation / none)
ops.py          записи как подготовленные операции: проверить → описать → выполнить;
                общие для одиночных инструментов и jira_batch
tools_jira.py   инструменты Jira
tools_conf.py   инструменты Confluence
tools_auth.py   auth_status, auth_reload
worklog.py      ворклоги и план недели
batch.py        jira_batch
cli.py          команда zyfra-jira
serve.py        Streamable HTTP
http.py, jira.py, confluence.py, export.py, markdown.py,
creds.py, config.py, util.py, models.py   как были

В stdout не должно попадать ничего, кроме JSON-RPC: одна строка print рвёт сессию целиком. Диагностика — через stderr или логирование SDK.

Что осталось в навыках

Сервер — только точка доступа к Jira и Confluence: чтение, поиск, скачивание, выгрузка в файл и запись с согласия человека. Про Obsidian и формат заметок он не знает ничего.

Что Где теперь
теги, вид заметки, перспектива по префиксу, рендер заметки задачи zpas-task-note/scripts/task_note.py (agent-skills-work)
шаблон заметки задачи System/Templates/jira/task-note.j2 в Obsidian-RDS
чистка экспорта постановки, ссылки на вложения, ширина картинок zpas-spec-import/scripts/spec_clean.py (agent-skills-work)

Скрипты читают файлы jira_export и conf_export, поэтому большой JSON не проходит через контекст модели. Сервер оставляет у себя только общий перевод в Markdown — wiki Jira и view Confluence — он нужен и jira_issue, и conf_page.

Requirements

Requires Python: >=3.14
Details
PyPI
2026-10-27 12:26:43 +03:00
2
Shahovalov MIkhail
114 KiB
Assets (2)
Versions (2) View all
0.3.2 2026-10-30
0.3.1 2026-10-27