jira-mcp
# jira-mcp
MCP-сервер для доступа к **Jira Server / Data Center** (и, опционально, к
**Confluence Server / DC**) через
[`atlassian-python-api`](https://github.com/atlassian-api/atlassian-python-api).
Аутентификация — Personal Access Token (Bearer). Транспорт — stdio.
## Требования
- Python ≥ 3.11 (в разработке используется 3.13)
- [`uv`](https://docs.astral.sh/uv/)
## Установка
Сервер ставится и запускается прямо из репозитория через `uvx` — клонировать
исходники и указывать путь до них не нужно. `uvx` скачает пакет, соберёт его в
изолированном окружении и запустит точку входа:
```bash
uvx --from git+https://github.com/nsleader/jira-mcp.git jira-mcp
```
Чтобы зафиксировать версию, можно указать тег или коммит:
```bash
uvx --from git+https://github.com/nsleader/jira-mcp.git@v0.1.0 jira-mcp
```
## Конфигурация
Креды передаются как **переменные окружения в конфигурации подключения MCP-сервера**
(блок `env`), а не через `.env` файл.
| Переменная | Обязательна | По умолчанию | Описание |
|-----------------------|-------------|--------------|-------------------------------------------|
| `JIRA_URL` | да | — | Базовый URL Jira, напр. `https://jira.example.com` |
| `JIRA_PERSONAL_TOKEN` | да | — | Personal Access Token (Bearer) |
| `JIRA_SSL_VERIFY` | нет | `true` | Проверка TLS-сертификата |
| `JIRA_TIMEOUT` | нет | `75` | Таймаут запроса, сек |
| `CONFLUENCE_URL` | для Confluence | — | Базовый URL Confluence, напр. `https://wiki.example.com` |
| `CONFLUENCE_PERSONAL_TOKEN` | нет | `JIRA_PERSONAL_TOKEN` | Отдельный PAT Confluence |
| `CONFLUENCE_SSL_VERIFY` | нет | как у Jira | Проверка TLS-сертификата |
| `CONFLUENCE_TIMEOUT` | нет | как у Jira | Таймаут запроса, сек |
PAT создаётся в Jira: **Profile → Personal Access Tokens**; в Confluence — там же
в своём профиле. Confluence — отдельное приложение со своим адресом и, как
правило, своим токеном, поэтому `CONFLUENCE_URL` из `JIRA_URL` не выводится.
Без него сервер работает как обычно, а confluence-инструменты возвращают
понятную ошибку о ненастроенном подключении.
## Запуск
Сервер общается по stdio и запускается MCP-клиентом. Для локальной проверки можно
задать переменные вручную:
```bash
JIRA_URL=https://jira.example.com JIRA_PERSONAL_TOKEN=xxxx \
uvx --from git+https://github.com/nsleader/jira-mcp.git jira-mcp
```
## Подключение к Claude Code
```bash
claude mcp add jira \
-e JIRA_URL=https://jira.example.com \
-e JIRA_PERSONAL_TOKEN=your-personal-access-token \
-- uvx --from git+https://github.com/nsleader/jira-mcp.git jira-mcp
```
Чтобы включить инструменты Confluence, добавьте туда же
`-e CONFLUENCE_URL=https://wiki.example.com` и (если токен отдельный)
`-e CONFLUENCE_PERSONAL_TOKEN=your-confluence-token`.
## Подключение к Claude Desktop
Пошаговая инструкция для чистого Mac и пользователя без опыта разработки —
[docs/install-macos-claude-desktop.md](docs/install-macos-claude-desktop.md).
Короткий вариант — в `claude_desktop_config.json`:
```json
{
"mcpServers": {
"jira": {
"command": "uvx",
"args": ["--refresh", "--from", "git+https://github.com/nsleader/jira-mcp.git", "jira-mcp"],
"env": {
"JIRA_URL": "https://jira.example.com",
"JIRA_PERSONAL_TOKEN": "your-personal-access-token"
}
}
}
}
```
Claude Desktop запускает MCP-серверы с урезанным `PATH`, поэтому `"command": "uvx"`
у многих не находится (`spawn uvx ENOENT`). В этом случае укажите абсолютный путь,
например `/Users/<пользователь>/.local/bin/uvx` или `/opt/homebrew/bin/uvx`.
`--refresh` заставляет `uvx` перечитать HEAD репозитория на каждом старте: иначе
сборка берётся из кэша и установка остаётся на том коммите, на котором была
поставлена. Стоит это меньше секунды, но требует доступа к GitHub при запуске.
## Доступные инструменты
| Инструмент | Описание |
|------------------------|------------------------------------------------------------------|
| `get_issue` | Получить задачу по ключу (`PROJ-123`) |
| `search_issues` | Поиск задач по JQL |
| `add_comment` | Добавить комментарий к задаче |
| `get_issue_types` | Типы задач инстанса или конкретного проекта (что можно передать в `create_issue`) |
| `create_issue` | Создать эпик / задачу / подзадачу — **вызывается строго по явной просьбе пользователя** |
| `get_transitions` | Куда задачу можно перевести из текущего статуса (и какие поля обязательны) |
| `transition_issue` | Сменить статус задачи — **вызывается строго по явной просьбе пользователя** |
| `add_worklog` | Залогировать время на задачу — **вызывается строго по явной просьбе пользователя** |
| `get_all_projects` | Список всех проектов, видимых пользователю |
| `get_project` | Один проект по ключу или id |
| `get_project_roles` | Роли проекта и их состав (пользователи и группы) — где искать реального РП |
| `search_users` | Поиск пользователей по логину, имени или email (подстрока) |
| `get_all_users` | Все пользователи Jira — объединение групп, дающих доступ к Jira |
| `find_groups` | Найти группы Jira по названию → узнать нужную для `get_all_users` |
| `get_user_worklog` | Часы одного пользователя за период (Tempo Timesheets) |
| `get_users_worklog` | Часы по списку пользователей за период, с разбивкой по каждому |
| `get_team_worklog` | Часы всех участников команды Tempo (`teamId`) за период — отчёт «Logged Time» |
| `get_all_teams` | Список всех команд Tempo (id, название, лид) |
| `find_team` | Найти команды Tempo по названию (подстрока) → получить `teamId` |
| `get_team_members` | Все участники команды Tempo (ключ, имя, роль, период членства) |
| `get_user_plan` | Планы Tempo Planner: на какие проекты и на сколько часов в день запланирован пользователь |
| `get_users_plan` | Планы Tempo Planner по списку пользователей за один запрос, с разбивкой по каждому |
| `search_confluence` | Поиск страниц Confluence: текст, пространство, метка (CQL под капотом) |
| `get_confluence_page` | Прочитать страницу по id / ссылке / `space` + `title` — тело в Markdown |
| `get_confluence_page_children` | Дочерние страницы — обход дерева пространства вниз |
| `get_confluence_spaces` | Список пространств Confluence (ключи для поиска) |
## Структура
```
src/jira_mcp/
├── config.py # настройки из окружения (pydantic-settings)
├── client.py # фабрики Jira- и Confluence-клиентов (кеш)
├── identity.py # логин ↔ ключ пользователя: чем ловится «0 часов»
├── server.py # FastMCP + main()
└── tools/
├── issues.py # инструменты по задачам + create_issue, transition_issue
├── projects.py # проекты и роли проекта (get_project_roles)
├── worklogs.py # отчёты по времени (Tempo Timesheets) + add_worklog
├── tempo.py # Tempo Planner: планирование (get_user_plan)
└── confluence.py # поиск и чтение статей Confluence
```
## Чтение задач
`get_issue` и `search_issues` отдают ответ Jira как есть, с одной правкой:
`customfield_*` получают человеческие имена, а пустые выбрасываются. Jira
возвращает каждое кастомное поле, заведённое на инстансе, — на одной задаче это
под сотню записей `customfield_11704: null`, среди которых пара заполненных
ничем не отличается от остального.
Читающему модель это ломает: она видит `description: null` и отвечает, что
описания нет, — а текст лежит в «Описании пресейла». Поэтому
```
"Описание пресейла (customfield_12001)": "{*}Контекст сделки{*}: ..."
```
Id остаётся в ключе: писать в поля всё равно приходится по id — `extra_fields`
у `create_issue`, экраны переходов у `transition_issue`. Каталог полей читается
раз на сессию, тем же запросом, которым ищутся agile-поля.
## Создание задач
`create_issue` создаёт issue через `rest/api/2/issue` от имени владельца токена.
Иерархия Jira Server/DC не лежит в обычных полях, поэтому сервер сам разбирается
с agile-полями Greenhopper (их id различаются от инстанса к инстансу и находятся
по типу схемы в `rest/api/2/field`):
- `issue_type="Epic"` → в обязательное поле **Epic Name** пишется `epic_name`,
а если он не задан — `summary`
- обычный тип + `parent="PROJ-1"` → ключ эпика пишется в **Epic Link**
- подзадача (`subtask: true`) + `parent="PROJ-2"` → поле `parent`; без `parent`
вызов отклоняется до похода в Jira
Тип задачи резолвится по имени (регистронезависимо) или id заранее: если такого
типа нет, ошибка перечисляет доступные, а не возвращает голый HTTP 400. Имена
типов на инстансах переименовывают и локализуют — при сомнениях сначала
`get_issue_types(project_key)`.
Остальные поля — `description`, `assignee` (логин Server/DC), `priority`,
`labels`, `components`, `due_date`; всё, что не покрыто сигнатурой (story points,
обязательные кастомные поля), передаётся через `extra_fields`, например
`{"customfield_10004": 3}`.
Как и `add_worklog`, инструмент пишущий и в описании помечен как вызываемый
**строго по просьбе пользователя**: удалить созданную задачу сервер не умеет.
## Смена статуса
Статус в Jira Server/DC нельзя присвоить напрямую — он результат перехода
workflow, а набор переходов зависит от текущего статуса, схемы проекта и прав
владельца токена. Поэтому `transition_issue` сначала читает
`rest/api/2/issue/{key}/transitions`, а потом постит найденный переход.
- `target` — имя целевого статуса (`Ready For Test`), имя перехода
(`Start Progress`) или числовой id перехода; сравнение регистронезависимое,
приоритет у целевого статуса (кнопка и её результат в workflow нередко
называются по-разному)
- если такого перехода нет, ошибка перечисляет доступные из текущего статуса —
вместо голого HTTP 400
- задача уже в нужном статусе → `changed: false`, без запроса на запись
- `comment` уходит в `update` (добавление комментария в рамках перехода),
`resolution` — в поля закрывающего перехода, всё остальное для экранов
перехода передаётся через `fields` по id поля (см. `required_fields` в
`get_transitions`)
Инструмент пишущий и помечен как вызываемый **строго по просьбе пользователя**:
переход виден всей команде (доски, уведомления, автоматизации), а обратного
перехода в workflow может не быть.
## Логирование времени
`add_worklog` пишет worklog через core-API Jira
(`rest/api/2/issue/{key}/worklog`) от имени владельца токена; Tempo на Server/DC
читает те же worklog'и, поэтому списанное время видно в отчётах выше.
- `time_spent` — синтаксис Jira: `2h`, `90m`, `2h 30m`, `1d 4h`
- `started` — `YYYY-MM-DD` (привязывается к 09:00 локального времени, чтобы
worklog не уехал на предыдущий день в восточных таймзонах), `YYYY-MM-DDTHH:MM`
или метка времени со смещением; по умолчанию — сейчас
В описании инструмента явно указано, что он вызывается **строго по просьбе
пользователя**: это единственный инструмент, пишущий время, и отменить запись
из сервера нельзя.
## Логин и ключ пользователя
У человека в Jira два имени: **логин** (`acutina`, меняется при смене фамилии)
и **ключ** (`JIRAUSER18613`, не меняется никогда). До Jira 6.0 ключ совпадал с
логином, поэтому у старых сотрудников разницы не видно — у всех, кого завели
позже, она есть.
Две части Tempo расходятся в том, какое из имён им нужно:
| API | Фильтр | Принимает |
|---|---|---|
| Tempo Timesheets (часы) | `worker` | **ключ** |
| Tempo Planner (планы, календарь) | `assigneeKeys`, `user` | **логин** |
Ни одна из них не ругается на чужое имя: фильтр просто ни с кем не совпадает, и
ответ приходит пустой. Отчёт от этого не ломается — он врёт: «Кутина за неделю
залогировала 0 часов» выглядит как факт.
Поэтому инструменты принимают **любое** из двух имён и приводят его к нужному
сами (`identity.py`, кеш на время жизни процесса, поиск через `rest/api/2/user`).
`get_user_worklog` возвращает разрешённый `worker_key`, `get_user_plan` —
`login`, а `by_user` в групповых отчётах ключуется ключом и несёт `display` с
человеческим именем. Имя человека («Кутина») именем пользователя не является —
его сначала ищут через `search_users`.
## Tempo Planner
`get_user_plan` читает раздел планирования через `rest/tempo-planning/1/allocation`
и рабочий календарь пользователя (`rest/tempo-core/1/user/schedule`), поэтому
разбивка по дням не учитывает выходные и праздники — так же, как сам Tempo.
На Server/DC это плагин Jira: работает тот же `JIRA_URL` и PAT, отдельный
Tempo-токен не нужен.
## Confluence
Четыре инструмента только на чтение: найти статью и прочитать её.
**Поиск.** `search_confluence` собирает CQL из обычных аргументов —
`query` (полнотекстовый `text ~`), `space`, `label`, `content_type` — и по
умолчанию сортирует по релевантности; `newest_first=true` переключает на
«что менялось недавно». Для запросов, которые сигнатурой не выразить
(`creator = jsmith AND lastmodified > -7d`), есть параметр `cql` — он идёт на
сервер как есть. Маркеры подсветки совпадений (`@@@hl@@@`), которые Server
подмешивает в заголовок и excerpt, вычищаются. В ответе `total` — общее число
совпадений, а не размер выданной страницы результатов.
**Чтение.** `get_confluence_page` принимает id, любую ссылку на страницу
(`?pageId=123`, `/pages/123/Title`, `/display/SPACE/Title`) либо пару
`space` + `title`. Тело берётся из отрендеренного `body.view` (макросы уже
раскрыты) и конвертируется в Markdown: заголовки, списки, таблицы и блоки кода
сохраняются, разметка макросов — нет; относительные ссылки достраиваются до
абсолютных. Другие варианты — `format="text"`, `"html"` и `"storage"`
(исходный XHTML, нужен только чтобы где-то отредактировать страницу).
Длинные статьи режутся по `max_chars` (по умолчанию 20000), чтобы не забивать
контекст: в ответе есть `content_chars`, `truncated` и `next_offset` —
следующий кусок читается тем же инструментом с `offset`.
Рядом с текстом возвращается контекст статьи: пространство, версия, кто и когда
менял, метки и хлебные крошки `ancestors`. Вниз по дереву —
`get_confluence_page_children`; ключи пространств — `get_confluence_spaces`
(персональные `~user` скрыты, если не попросить `include_personal`).
## Разработка
```bash
uv run ruff check
uv run pytest
```
TDQS
Scored across 19 tools
Each tool targets a distinct resource and action. For example, get_project vs get_all_projects, search_users vs get_all_users, and the three worklog tools (get_user_worklog, get_users_worklog, get_team_worklog) have clear scopes (single user, group, team) with no overlap.
Tool names follow a consistent verb_noun pattern: get_* for retrieval, search_* for search, add_* for creation, create_issue, and find_* for lookup. No mixed conventions or vague verbs.
19 tools is well-scoped for a Jira/Tempo integration covering projects, issues, users, teams, worklogs, and plans. Each tool serves a clear purpose without being too few or too many.
The tool set covers core workflows: project/issue retrieval, user/team discovery, worklog and plan queries, and limited write operations (add_worklog, create_issue, add_comment). Missing update/delete operations for issues, but the primary focus on reporting is well-supported.