Metadata-Version: 2.4
Name: jira-aie-mcp
Version: 0.4.0
Summary: MCP server for the AI Engineering Jira project — rich ADF issue creation and working-day gantt scheduling.
Author: Sindre Tellevik
License-Expression: MIT
License-File: LICENSE
Keywords: adf,fastmcp,gantt,jira,mcp,scheduler
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Requires-Python: >=3.10
Requires-Dist: fastmcp>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# jira-aie-mcp

An MCP server for the AIE team's Jira instance. Exposes the two things
`mcp-atlassian` can't do natively:

1. **Rich Atlassian Document Format (ADF) issue creation/update** — info /
   warning / success / error / note **panels**, inline **status lozenges**,
   **date chips**, **expand/collapse** blocks, and **task lists**. Accepts
   a Markdown string, a compact block-list DSL, or a pre-built ADF doc.
2. **Working-day gantt scheduler** — reads Blocks dependencies and
   Original Estimates from a JQL-scoped set of issues, computes start/end
   dates on the Norwegian public-holiday calendar, and optionally writes
   the end dates back to each issue's `duedate` field.

Plus a **semantic link shortcut** (`jira_link_issues`) that picks the
right inward/outward direction for Blocks, Relates, Duplicates, Clones,
and Causes without forcing the caller to memorise Jira's link model.

`mcp-atlassian` still handles everything else — search, transitions,
comments, watchers, worklogs, etc. This server is a **gap-filler**, not
a replacement.

## Install

Run it on demand with `uvx` — no install step needed; `uvx` fetches the
package from PyPI into an ephemeral environment on first use and caches
it for subsequent runs:

```bash
uvx jira-aie-mcp
```

Or install it as a persistent tool:

```bash
uv tool install jira-aie-mcp
jira-aie-mcp
```

Either form starts the server on stdio, which is what MCP clients expect.

## Configure (MCP client)

All configuration lives in the MCP client's server definition — no
separate config file. Minimum required env vars:

| Variable | Required | Default |
| --- | --- | --- |
| `JIRA_BASE_URL` | yes | — |
| `JIRA_EMAIL` | yes | — |
| `JIRA_API_TOKEN` | yes | — |
| `JIRA_DEFAULT_PROJECT_KEY` | no | — (fallback for `jira_create_issue` when the tool call omits `project_key`) |
| `JIRA_WORKSTREAM_FIELD_ID` | no | — (tenant-specific custom field ID, e.g. `customfield_10742`, that `schedule_compute` groups by in the Mermaid gantt) |

Generate an API token at
<https://id.atlassian.com/manage-profile/security/api-tokens>.

### Cowork / Claude Desktop config

```json
{
  "mcpServers": {
    "jira-aie": {
      "command": "uvx",
      "args": ["jira-aie-mcp"],
      "env": {
        "JIRA_BASE_URL": "https://apply-team.atlassian.net",
        "JIRA_EMAIL": "you@apply.no",
        "JIRA_API_TOKEN": "atlassian-api-token-here"
      }
    }
  }
}
```

To pin a specific version (recommended, so upstream releases don't
silently change behaviour):

```json
"args": ["jira-aie-mcp==0.1.2"]
```

Reload the client after editing the config.

> ⚠️ **If you ran 0.1.0 against a real Jira instance with `write_duedate=True`,
> the persisted duedates are inverted** — upgrade to ≥0.1.1 and re-run
> `schedule_compute(..., write_duedate=True)` once to overwrite them. See
> `CHANGELOG.md` for the fix details.

## Tools

| Tool | What it does |
| --- | --- |
| `jira_create_issue` | Create one issue. `description` accepts Markdown, block-DSL, or raw ADF. |
| `jira_update_issue` | Update summary/labels/fields/description on an existing issue. |
| `jira_get_issue_raw` | Fetch the raw REST payload (including ADF description). |
| `jira_link_issues` | Create an issue link. Semantic shortcuts for Blocks/Relates/Duplicates/Clones/Causes. |
| `jira_batch_ops` | Mixed array of `{op: "create"|"update"|"link", ...}`. Errors on one entry don't halt the batch. |
| `schedule_compute` | Forward-pass gantt from a JQL scope. Format: `json` / `markdown` / `mermaid`. Optional `write_duedate`. |
| `schedule_holidays` | Print the Norwegian red-day calendar for a given year. |

## Block DSL (descriptions)

```jsonc
[
  {"h": 2, "text": "What broke"},
  {"p": "Reranker started returning 500s on ~30% of requests at 14:20 UTC."},
  {"panel": "warning", "content": [
    {"p": "Customer-facing; search relevance degraded but not down."}
  ]},
  {"h": 2, "text": "Timeline"},
  {"ul": [
    "🔴 14:20 UTC — first 500s observed",
    "🟡 14:26 UTC — paged on-call",
    "🟢 14:41 UTC — rolled back cross-encoder to v1"
  ]},
  {"h": 2, "text": "Follow-ups"},
  {"tasks": [
    {"text": "Postmortem", "done": false},
    {"text": "Add 5xx alarm to SLO dashboard", "done": false}
  ]}
]
```

All block keys (full list):

```
h      — heading (1-6)                   p      — paragraph
ul     — bullet list                     ol     — numbered list
code   — code block (+ `lang`)           quote  — blockquote
rule   — horizontal rule                 panel  — info|note|success|warning|error
status — inline status lozenge           date   — inline date chip
expand — collapsible block               tasks  — task list with done flags
table  — {headers, rows}                 raw    — pass-through ADF node
```

Inline specs (inside `p` / `h` text or a list item):

```jsonc
{"text": "bold", "marks": ["strong"]}
{"text": "link", "link": "https://..."}
{"mention": "account-id", "text": "@Alice"}
{"emoji": ":rocket:", "text": "🚀"}
{"status": "In progress", "color": "yellow"}
{"date": "2026-04-20"}
{"hard_break": true}
```

## Scheduler

The forward pass follows Blocks dependencies, uses Jira's
`timetracking.originalEstimate` (`1w 2d 3h 30m` syntax) for durations, and
skips weekends and 12 Norwegian public holidays per year (Easter-derived).

```
schedule_compute(
  jql="project = AIE AND \"Epic Link\" = AIE-5 AND statusCategory != Done",
  start="2026-05-04",
  format="markdown",
  write_duedate=False
)
```

Returns a JSON plan (`project_start`, `project_end`, `critical_path`,
`issues[]`, `warnings[]`) and, for `markdown`/`mermaid` formats, a
`rendered` string.

Rescheduling is just re-running with overrides:

```
pins   = {"AIE-15": "2026-06-01"}      # force start date
delays = {"AIE-7": "3d"}               # shift start N working days later
```

## Module layout

```
src/jira_aie_mcp/
  __main__.py     # entry point — starts FastMCP over stdio
  server.py       # MCP tool registrations
  config.py       # env-var loading → Config dataclass
  client.py       # httpx-based Jira REST v3 client + link shortcuts
  adf.py          # block DSL + Markdown → ADF
  holidays.py     # Easter + Norwegian red days + duration parser
  scheduler.py    # graph build + forward pass + critical path
  formats.py      # JSON / Markdown / Mermaid plan output
tests/
  test_scheduler.py   # Blocks-direction contract + diamond end-to-end
```

## Development

Install with the `dev` extras and run the test suite:

```bash
uv pip install -e ".[dev]"
pytest
```

The test suite is intentionally small and focused on the pieces most
likely to regress (the Blocks-direction contract, the working-day forward
pass, and the critical path). The REST client and the FastMCP wiring are
exercised via the live smoke tests in the team's Jira — if you want those
to be unit-level too, mock `httpx.Client.post` / `.put`.

## License

MIT.
