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. This is a spec-first repository: the
complete contract — all 27 tools, the write gate, the error catalog, the
corner cases — is written down in docs/ and is normative. The
core HTTP layer and the first typed API wrappers are implemented and covered
by hermetic tests behind a network fence; the MCP layer and the tool surface
are being built against that spec now. There is no tagged release and no npm
package yet.
Three steps, once it ships
No developer app, no OAuth consent screen, no App Review — a Jira Cloud site and an API token are the whole setup. Node 22 or newer.
Not on npm yet. jira-mcp-ai is pre-1.0 and unpublished. The registration below is the target contract the docs already guarantee — it will resolve as-is once the first release is tagged. Until then, watch the repository.
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.1.0"],
"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.1.0"],
"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.
{
"servers": {
"jira": {
"type": "stdio",
"command": "npx",
"args": ["-y", "jira-mcp-ai@0.1.0"],
"env": {
"JIRA_SITE": "mycompany",
"JIRA_EMAIL": "me@example.com",
"JIRA_API_TOKEN": "<api-token>"
}
}
}
}
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 two 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
Seven packages, 27 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 |
2 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.
|
issues |
5 read | — | One issue with ADF flattened to plain text (raw on request), comments, transitions, changelog, worklogs. |
issues-write |
7 write | plan |
Create, update, transition, comment, assign, log work, link — every one behind the plan → apply gate. |
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 1 write | plan |
Boards, sprints and sprint issues; moving issues into a sprint is the one write. |
What it deliberately does not do
No deletes of any kind, no bulk operations, no attachment upload or download — 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 27-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
19 toolsNever mutates anything. Safe to expose first, and to leave on.
standard
8 tools
Every write in v1. 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
0 toolsEmpty on purpose. Deletes, bulk edits and attachments are excluded from v1 by decision — the tier exists so anything added later inherits rules that are already written down, instead of negotiating them in a pull request.
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. -
The HTTP transport fails closed. It binds loopback
only, refuses to start without
JIRA_HTTP_TOKEN, and checksOriginagainst DNS rebinding. stdio stays the default.
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. No browser OAuth dance, 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.
| 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_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_TRANSPORT |
stdio |
stdio or http. |
JIRA_HTTP_PORT |
3334 |
TCP port for the http transport; the bind host is
fixed to loopback.
|
JIRA_HTTP_TOKEN |
— |
Secret. Bearer token required by the
http transport; it fails closed without it.
|
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. Two are done, two are moving, four are queued.
| 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 | in progress |
First runnable artifact: wire-tier HTTP policy tests green;
doctor exits 0 against a real site.
|
| 2a · MCP layer + search | in progress | First usable build: registered in Claude Code, a real JQL search end-to-end. |
| 2b · ADF + issue read | planned | Issue reads with ADF flattening proven against recorded fixtures. |
| 3 · Full read surface | planned | Every read tool green against a live scratch site; coverage thresholds enforced from here on. |
| 4 · Writes | planned | The plan → apply gate proven end-to-end in both modes; write journal; the full 27-tool surface locked. |
| 5 · Hardening & release | planned | npm publish via OIDC trusted publishing with provenance; corner cases traceable to tests. |
The full plan, with per-phase exit gates and open decisions, is docs/IMPLEMENTATION-PLAN.md; the longer horizon 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 — Basic auth with your own API token — 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 OAuth and a hosted service fit 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.
v1 has no delete, bulk or attachment tools at all, and 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?
This is a spec-first repository. The complete contract — all 27 tools,
the write gate, the error catalog, the corner cases — is written down in
docs/
and is normative. The core HTTP layer and the first typed API wrappers
are implemented and covered by hermetic tests behind a network fence;
the MCP layer and the tool surface are being built against that spec
now.
There is no tagged release and no npm package yet.
When will it be published to npm?
In the release phase, after the full read surface and the write gate have been proven against a real Jira site. Publication is planned through npm trusted publishing (OIDC) with provenance attestation, so the artifact on npm is verifiably built from this repository. Until then there is nothing to install — watch the repository instead.
Which MCP clients will it work with?
Any client that speaks the Model Context Protocol over stdio — Claude
Code, Claude Desktop, VS Code and Cursor among them. An optional
loopback-only Streamable HTTP transport is specified for advanced
setups; it binds 127.0.0.1 only and refuses to start
without a bearer token.
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.