jira-mcp-ai
Give your AI assistant a real seat in Jira Cloud — JQL search, issue reads, transitions, comments, worklogs, boards and sprints. It runs on your machine, with your API token, fenced to a single host, and every write is disarmed until you say otherwise.
Status, honestly. The surface is complete and green
offline: all 58 tools, the write gate, the error catalog and the corner
cases are implemented against the spec in docs/, which stays
normative — 1,700+ hermetic tests behind a network fence, with layering,
coverage floors and docs consistency enforced in CI. The package is on npm
as jira-mcp-ai, and two runs against a real Atlassian tenant
have proven 40 of the 58 tools live. The 18 still unproven — 17 writes and
the bulk queue-status read — are why the version stays below 1.0.0.
Three steps
No developer app, no consent screen, no App Review — a Jira Cloud site and an API token are the whole setup; OAuth 2.0 is an opt-in, never a requirement. Node 22 or newer.
On npm as jira-mcp-ai@0.9.4, and pre-1.0 on
purpose. The surface is complete, the offline gate is green, and the
build has now been run against a real Atlassian tenant — 40 of the 58 tools
have been proven live, and the three bugs that run found are fixed in this
version. Of the 18 still unproven, most need a permission nobody has pointed
at it yet — project administrator for the older ones, the site-wide Make
bulk changes for the three bulk tools that landed after that run — so the
number stays below 1.0.0 until they are covered too. The
registration below resolves as written; it installs the published package,
provenance and all.
Mint an API token
In your
Atlassian profile
under Security → API tokens. The token inherits exactly your
permissions — nothing more. Cloud tokens expire (a year at most), so
note the date: set JIRA_TOKEN_EXPIRES and the server warns
you 30 days ahead instead of failing cold one morning.
Register the server
{
"mcpServers": {
"jira": {
"command": "npx",
"args": ["-y", "jira-mcp-ai@0.9.4"],
"env": {
"JIRA_SITE": "mycompany",
"JIRA_EMAIL": "me@example.com",
"JIRA_API_TOKEN": "<api-token>",
"JIRA_WRITE_MODE": "plan"
}
}
}
}
{
"mcpServers": {
"jira": {
"command": "npx",
"args": ["-y", "jira-mcp-ai@0.9.4"],
"env": {
"JIRA_SITE": "mycompany",
"JIRA_EMAIL": "me@example.com",
"JIRA_API_TOKEN": "<api-token>",
"JIRA_WRITE_MODE": "plan"
}
}
}
}
On Windows the file lives at
%APPDATA%\Claude\claude_desktop_config.json.
If the server does not appear, it is almost always PATH.
Claude Desktop launches MCP servers from a minimal environment that
does not include your shell's PATH, so a node/npx
installed by nvm, Homebrew or fnm is invisible to it and the launch
fails inside the client, before this server runs — you get the
client's generic "server failed" message and nothing on this server's
stderr, because there was no process. Fix it by giving an absolute
path: "command": "/usr/local/bin/npx" (which npx
prints yours). Claude Code, run from a terminal, inherits your PATH
and is not affected.
Everything this server writes goes to stderr, never
stdout — stdout is the MCP protocol. Claude Desktop keeps it in
~/Library/Logs/Claude/mcp*.log on macOS and
%APPDATA%\Claude\logs\ on Windows; Claude Code in
~/.claude/logs/. The startup report and any
JIRA_* configuration error will be there.
{
"servers": {
"jira": {
"type": "stdio",
"command": "npx",
"args": ["-y", "jira-mcp-ai@0.9.4"],
"env": {
"JIRA_SITE": "mycompany",
"JIRA_EMAIL": "me@example.com",
"JIRA_API_TOKEN": "<api-token>",
"JIRA_WRITE_MODE": "plan"
}
}
}
}
The version is pinned deliberately: an unpinned
npx -y jira-mcp-ai re-resolves to whatever is newest at
spawn time, which lets a published package start running new code
inside an agent session with no review step. Bump the pin when you have
read the changelog. Prefer keeping the token out of project files —
the server also loads ~/.config/jira-mcp-ai/.env via
Node's own process.loadEnvFile(); there is no dotenv in
the chain, because dotenv ≥ 17 prints a banner to stdout, and stdout
belongs to the MCP protocol.
Stay read-only until you mean it
Out of the box every write describes instead of executing. Narrow the surface further, or open the gate, with three variables:
JIRA_WRITE_MODE=plan # default — writes return a plan, nothing executes JIRA_TOOL_PACKAGES=reader # reads only — write tools are never registered JIRA_WRITE_MODE=apply # writes execute, when a call also passes apply:true JIRA_ALLOW_IRREVERSIBLE=true # and only this adds the deletes and bulk writes; apply alone never does
Ten packages, 58 tools
Packages are the unit of exposure: JIRA_TOOL_PACKAGES picks a
profile (core, reader, all — the
default) or an explicit list, JIRA_PACKAGES_DENY removes one
regardless, and JIRA_PACKAGES_READONLY keeps a package's reads
while dropping its writes. core is always on.
| Package | Tools | Write mode | What it covers |
|---|---|---|---|
core |
2 read | — |
jira_capabilities self-describes the running surface;
jira_get_myself verifies auth. Always registered — even
a deny list cannot remove it.
|
search |
4 read | — |
JQL search and approximate count over the current
/rest/api/3/search/jql endpoint — the legacy search API
Atlassian removed in 2025 is never called — plus the saved-filter
reads that turn a filter into the JQL behind it.
|
issues |
6 read | — | One issue with ADF flattened to plain text (raw on request), comments, transitions, changelog, worklogs, and the status of a queued bulk operation. |
issues-write |
8 write | plan |
Create, update, transition, comment, edit a comment, assign, log work, link — every one behind the plan → apply gate. |
issues-delete |
8 irreversible | plan + opt-in |
Delete an issue, a comment, a worklog, a component, a version or a
sprint — or bulk-edit and bulk-delete up to 1,000 issues at once.
The one package that is not covered by
JIRA_WRITE_MODE=apply alone — it also needs
JIRA_ALLOW_IRREVERSIBLE=true, set on purpose.
|
attachments |
2 read 1 write | plan |
List, download and upload attachments. Bytes only ever cross the
boundary through JIRA_MEDIA_DIR; with no directory set
the binary tools refuse, and file contents never enter a tool result.
|
collab |
4 read 8 write | plan |
Watchers, votes, components, versions and project roles — the project-shaping surface around an issue rather than the issue itself. |
meta |
6 read | — | Projects, fields, create-meta, statuses and link types — the discovery calls that make writes land on the first try. |
users |
1 read | — |
User search: the only path from a human name to the
accountId every other tool requires. Emails stay hidden
unless you opt in.
|
agile |
3 read 5 write | plan |
Boards, sprints and sprint issues; the writes move issues into a sprint or the backlog and create, start and close a sprint. The one package that speaks the Agile API rather than the platform one. |
What it deliberately does not do
No deleting a project, no admin surface, no free-form bulk — the bulk delete and bulk edit are two fixed tools inside the irreversible tier — excluded by decision, not by omission. No Confluence, no Jira Service Management, no Data Center. A tool that does not exist cannot be talked into running.
Built for 2026 Jira
v3 REST only: ADF documents in, readable plain text out. Users are
addressed exclusively by accountId — the GDPR-era contract.
Search paginates by nextPageToken, and
jira_count returns Jira's approximate count and labels it as
such instead of pretending it is exact.
The full 55-tool catalog — input schemas, hints and the error contract per tool — lives in docs/TOOLS.md.
A write model you can reason about
Every tool declares a tier. The tier — not the tool, and not the model's confidence — decides what has to happen before your Jira site is touched.
read
28 toolsNever mutates anything. Safe to expose first, and to leave on.
standard
22 tools
Every reversible write. Governed by JIRA_WRITE_MODE: under
plan — the default — the tool returns exactly what it would
have sent and performs zero network mutations; under
apply it executes, and only when the call itself also opts
in.
irreversible
8 tools
Deleting an issue, a comment, a worklog, a component, a version or a
sprint, and bulk-editing or bulk-deleting up to 1,000 issues at once.
These need a second, separate switch —
JIRA_ALLOW_IRREVERSIBLE=true — on top of everything the
standard tier already requires, because an operator who turned writes on
to move a ticket did not thereby agree to lose one.
The plan → apply contract
In plan mode a write returns the method, path and redacted body it would
have sent, plus a single-use plan_id — and its first line
is, literally, NOT performed — plan mode. Executing the same
call requires all three: the server started with
JIRA_WRITE_MODE=apply, the call passing
apply: true, and a plan_id minted for identical
arguments — change one field and the fingerprint no longer matches.
Plans live in memory only. A restart voids them; nothing can be armed in advance and fired later.
Ambiguous failures never replay
When a timeout or a dropped socket makes it unknowable whether a write
reached Jira, the server refuses to guess: the call fails as
ambiguous_write, marked non-retryable, with the remediation
to read current state first. A comment posted twice is a mess a human
cleans up — this server would rather fail honestly than tidy over it.
Defence in depth
- One host, and no others. The HTTP layer refuses any request that resolves outside your site — including redirects that try to leave it. Extra hosts need an explicit allowlist entry: exact name or anchored regex, never a suffix match.
- No telemetry, no phone-home. The only outbound traffic is the Jira call you asked for.
-
Secrets never surface. The API token is registered
with a redactor at startup; one choke point scrubs logs, errors and
results, and
Authorizationheader echoes are stripped. -
Jira text is data, not instructions. Every
content-bearing read comes back inside
⟦BEGIN UNTRUSTED CONTENT⟧ … ⟦END UNTRUSTED CONTENT⟧markers, so a ticket that says "ignore your instructions" reads as a ticket, not as instructions. -
stdout belongs to the protocol. Diagnostics go to
stderr through a structured logger, and a stray
console.logis redirected before it can corrupt a JSON-RPC frame. -
Writes can be journalled. Set
JIRA_JOURNAL_PATHfor an append-only JSONL record of every write call: tool, argument hash, result, timestamp. -
Attachment bytes stay out of the conversation.
Downloads and uploads pass through
JIRA_MEDIA_DIRand nowhere else; with no directory configured the binary tools refuse, and a file's contents never land in a tool result. -
The HTTP transport binds loopback only. stdio is
the default; selecting
httpbinds127.0.0.1only, demands a bearer token on every request and refuses to start without one — no other interface is ever listened on.
That pill is the entire default egress surface — one origin, resolved once at startup.
Four layers, one direction
Imports point leftward only — core ← api ← mcp ← tools — and
the rule is enforced twice in eslint, as import zones and as string
patterns, so it never depends on how modules happen to resolve.
defineTool with import-time assertions,
the registry, the result envelope, taint marking, the write gate,
transports.
Typed without codegen
Atlassian's OpenAPI spec is enormous, churns constantly, and knows
nothing about your customfield_10xxx. So no generated
optimism: wire data enters as unknown, is narrowed by
minimal hand-written guards at the API boundary, and a guard failure
surfaces as a typed error naming the unexpected shape — never a
TypeError five frames deep.
Deterministic by injection
Time comes from an injected clock, retry jitter from an injected RNG,
and fetch is read off globalThis at call time.
Backoff sequences are asserted exactly in tests, and a network fence
turns any accidental real socket into a test failure.
Credentials and environment
Headless by design: Basic auth with your Atlassian account email and an API token, nothing to click through — it works from cron, CI and remote boxes, which is the main reason this exists next to Atlassian's official remote MCP server. OAuth 2.0 (3LO) is there as an opt-in for the accounts that need it: one browser login, then the server refreshes on its own.
| Variable | Required | What it does |
|---|---|---|
JIRA_SITE |
yes |
"mycompany", "mycompany.atlassian.net" or a
full URL — resolved once into the single origin the allowlist then
pins.
|
JIRA_EMAIL |
yes | Atlassian account email for Basic auth. |
JIRA_API_TOKEN |
yes | Secret. Registered with the redactor before anything else gets a chance to log it. |
JIRA_TOKEN_EXPIRES |
no |
ISO date of the token's expiry — Cloud tokens live a year at most.
When set, startup and doctor warn 30 days out.
|
JIRA_ALLOWED_HOSTS |
no | Extra allowed hosts for Server/DC or vanity domains: exact host or anchored regex. Suffix matching is banned. |
The full environment surface
| Variable | Default | Description |
|---|---|---|
JIRA_ENV_FILE |
— |
Explicit env-file path; wins over
~/.config/jira-mcp-ai/.env and the project-local
.env.
|
JIRA_SITE |
— | Site name, host or URL. Required. |
JIRA_EMAIL |
— | Account email for Basic auth. Required. |
JIRA_API_TOKEN |
— | Secret. API token. Required. |
JIRA_TOKEN_EXPIRES |
— | ISO expiry date; warning 30 days out when set. |
JIRA_ALLOWED_HOSTS |
— | Comma list of extra allowed hosts — exact or anchored regex. |
JIRA_PROFILE_<NAME>_SITE / _EMAIL / _API_TOKEN |
— | Named profile credentials for multi-site setups. |
JIRA_ACTIVE_PROFILE |
— | Profile used when a call does not name one. |
JIRA_LOCK_PROFILE |
true |
Per-call profile switching is rejected by default — a model that can pick the tenant per call can leak issue text across tenants, so unlocking is a deliberate act. |
JIRA_TOOL_PACKAGES |
all |
Profile (core, reader,
all) or explicit comma list of packages.
|
JIRA_PACKAGES_DENY |
— | Deny list; wins over selection; core is force-re-added. |
JIRA_PACKAGES_READONLY |
— | Packages whose write-tier tools are dropped; reads stay. |
JIRA_WRITE_MODE |
plan |
plan = writes describe instead of execute;
apply = writes execute when the call passes
apply: true.
|
JIRA_ALLOW_IRREVERSIBLE |
false |
Opt-in for the irreversible tier. Without it the deletes and
the bulk writes refuse even under apply — blanket
write mode never covers them.
|
JIRA_REQUEST_TIMEOUT_MS |
30000 |
Per-request timeout. |
JIRA_CALL_BUDGET_MS |
120000 |
Wall-clock budget for one tool call's total HTTP activity — retry waits and queueing count against it. |
JIRA_HOST_CONCURRENCY |
4 |
Per-host semaphore slots. |
JIRA_RETRY_ATTEMPTS |
3 |
Max retry attempts — for requests that are safe to retry. Ambiguous write outcomes are never replayed. |
JIRA_MAX_RESULT_CHARS |
25000 |
Truncation budget for tool results; whole items are dropped so output stays valid JSON. |
JIRA_MAX_PAGES |
20 |
Loop guard for paginated fetches. |
JIRA_MEDIA_DIR |
— | Directory attachment downloads land in and uploads are read from. Unset, the binary attachment tools refuse; listing metadata needs no directory. |
JIRA_TRANSPORT |
stdio |
stdio (default) or http — the
loopback Streamable HTTP transport.
|
JIRA_HTTP_PORT |
3334 |
Loopback port the http transport binds —
127.0.0.1 only, never another interface.
|
JIRA_HTTP_TOKEN |
— |
Secret. Bearer token required on every
http request; without it the server refuses to
start.
|
JIRA_LOG_LEVEL |
info |
Stderr log verbosity: debug, info,
warn, error.
|
JIRA_JOURNAL_PATH |
— | Optional write journal — JSONL of every write tool call: tool, argument hash, result, timestamp. |
The defaults are chosen so that an environment holding nothing beyond the three credentials is already a sensible posture: every package registered, every write disarmed, exactly one host reachable. The normative table, with validation ranges and edge cases, is docs/CONFIGURATION.md.
Where it stands
The spec was written first, reviewed, and revised before serious code — so the phases below are gates with exit criteria, not vibes. All eight are closed, and the package is on npm. What is left before 1.0.0 is mostly not code either: one more gate run for the sprint tools, and a Jira site where the test account may administer a project for the rest.
| Phase | Status | Exit gate |
|---|---|---|
| 0 · Scaffold | done | Repo furniture, the full docs corpus and the initial core/api skeleton committed. |
| 0.5 · Spec revision | done | External review-panel findings folded back into the spec; both exit gates met on the spec side. |
| 1 · Core | done | Config, host pinning, errors, redaction, logging and the one HTTP module; wire-tier policy tests green. |
| 2a · MCP layer + search | done |
defineTool, the registry, the result envelope, taint
marking — and JQL search over the current endpoint.
|
| 2b · ADF + issue read | done | Issue reads with ADF flattening proven against recorded fixtures. |
| 3 · Full read surface | done | Every read tool implemented; coverage floors enforced by the gate from here on. |
| 4 · Writes | done | The plan → apply gate proven in both modes, plan fingerprinting, the optional write journal. |
| 5 · Hardening & release prep | done | Every corner case traceable to a named test; the publish pipeline built and left inert by construction. |
| 6 · v1.5 pulled forward | done | Markdown ↔ ADF, the agile write surface, saved filters, comment editing. |
| 7 · v2 subset pulled forward | done | Attachments, the collaboration surface, and the irreversible tier with its own opt-in. |
| Hardening waves 8–13 | done | Adversarial passes over the shipped tarball rather than the source, release mechanics, and a live-verification driver rehearsed offline. |
| Live verification · read half | done | First contact with a real Atlassian tenant, 2026-08-17. Every one of the 27 read tools proven against the live API, and three bugs found that no fixture could have produced — all three fixed in 0.9.4. |
| Live verification · write half | partial | Run 2026-08-18 against a sandbox project: 13 write tools applied successfully, taking the live-proven surface to 40 — of today's 55 tools. Seven of the writes still unproven wait on permissions the test account does not hold — both watcher writes, both version writes, both component writes and issue delete, each answering with a correctly shaped refusal rather than a result. Five are the sprint writes, which the run never reached: the gate asked Jira for a sprint name past a length cap Atlassian does not document — that was the harness, not the server, and it is fixed here. The last three, the component, version and sprint deletes, landed after this run and have never been sent at all. |
| 0.9.4 on npm | done | Published from a tag with provenance. Still on the one-run bootstrap token, because npm cannot register a trusted publisher for a package that does not exist yet and the switch has not been made. |
| 1.0.0 on npm | planned | The 15 remaining tools proven — the sprint five on any site with a Scrum board, the seven permission-gated writes and the component and version deletes on a site where the account may administer a project, the sprint delete where it may manage sprints — and the semver promise made: from then on OIDC trusted publishing, no token in the repository. |
The full plan, with per-phase exit gates and open decisions, is docs/IMPLEMENTATION-PLAN.md; the longer horizon — a Data Center adapter, parked until a real host exists — is docs/ROADMAP.md.
Questions worth asking first
Is jira-mcp-ai affiliated with Atlassian?
No. This is an independent, community-built project and is not affiliated with, endorsed by, or sponsored by Atlassian Pty Ltd. Jira, Atlassian and related marks are trademarks of Atlassian Pty Ltd, used here only nominatively to indicate compatibility.
Why not Atlassian's official remote MCP server?
The official server is remote and OAuth-based: fine for interactive desktop use, unusable anywhere nobody can click through a browser consent screen. jira-mcp-ai is headless by default — Basic auth with your own API token, with OAuth 2.0 (3LO) as an opt-in for accounts that need it — so it runs from cron, CI and remote sessions, keeps all traffic between your machine and your Jira site, and adds a plan-and-apply write gate the official server does not have.
If a hosted service fits your setup, use the official one; this exists for everyone else.
Which credentials does it need?
Three environment variables: JIRA_SITE,
JIRA_EMAIL and JIRA_API_TOKEN — the token is
minted in your Atlassian profile under Security → API tokens and
inherits exactly your permissions, nothing more. Jira Cloud tokens
expire (a year at most); set JIRA_TOKEN_EXPIRES and the
server warns you 30 days ahead instead of failing cold.
Is it safe to let an AI assistant write to my Jira site?
Writes ship disarmed. In the default plan mode a write tool returns the
exact method, path and redacted body it would have sent, plus a
single-use plan_id — its first line is, literally,
NOT performed — plan mode. Executing requires all three: the
server started with JIRA_WRITE_MODE=apply, the call passing
apply: true, and a plan_id minted for identical
arguments.
The irreversible tier — the six deletes (issue, comment, worklog,
component, version, sprint) and the two bulk writes (delete, edit) —
needs a fourth thing, JIRA_ALLOW_IRREVERSIBLE=true, because
blanket write mode must never cover it. A write whose outcome is
unknowable — a timeout mid-flight — is never retried: the server asks
you to check state instead of guessing.
What actually works today?
The whole surface, offline. All 58 tools across 10 packages are
implemented, along with the plan-and-apply write gate, the error catalog
and the corner cases — the spec in
docs/
stays normative and the code is checked against it by 1,700+ hermetic
tests behind a network fence, with layering, coverage floors and docs
consistency all enforced in CI.
And now online too, in part. Two runs against a real Atlassian tenant have proven 40 of the 58 tools against the live API — 27 of the 28 reads, and 13 of the 30 writes: issue create and update, comments and comment edit, worklogs, transitions, links, assignment, votes, attachment upload and the comment and worklog deletes. Of the 18 still unproven, seven need a project administrator — the watcher pair, the version pair, the component pair and issue delete — five are the sprint writes, which that run never reached because the gate itself asked Jira for an over-long sprint name, three are the component, version and sprint deletes, which landed after the last live run, and three are the bulk operations — bulk edit, bulk delete and the queue-status read — which landed after it too and additionally need the site-wide Make bulk changes permission. All 18 are implemented and tested offline; the sprint five need only another run, and the rest need a site where the account holds the permission.
Is it on npm?
Yes —
jira-mcp-ai, currently 0.9.4. It is published from a git tag by GitHub Actions with
provenance attestation, so the artifact on npm is verifiably built from
this repository; npm audit signatures will say so.
The version is deliberately below 1.0.0 and will stay there until the live verification above is complete. Pin the exact version in your client configuration — the changelog is written for someone deciding whether to move that pin.
Which MCP clients does it work with?
Any client that speaks the Model Context Protocol — Claude Code,
Claude Desktop, VS Code and Cursor among them. stdio is
the default transport; a loopback-only Streamable HTTP transport can
be selected instead, binding 127.0.0.1 only and requiring
a bearer token on every request — without the token the server refuses
to start.
How can I support the project?
jira-mcp-ai is MIT-licensed and free, with no paid tier. It is maintained by one person in their own time, so donations are welcome — see Support below. Donating buys no priority support and no SLA; starring the repository or filing a good bug report helps just as much.
Free, MIT, and built in spare time
There is no paid tier and no hosted version. If this saves you an afternoon, a donation keeps the coffee flowing — but a star, a bug report or a fix helps just as much.
Found a bug?
Open an issue with what you asked the assistant to do, the tool call it
made, and the ok: false envelope it returned — the error's
kind and the hints answer most of the first round of
questions.
Found a security problem?
Please report it privately through GitHub's private vulnerability
reporting rather than in a public issue — for a security problem,
opening the issue is the disclosure. If that button is not
there, email ivanbbaev@gmail.com with
jira-mcp-ai security in the subject; both routes reach the
same person.