---
title: Connecting Jira for agents
slug: connecting-jira
summary: Connect an agent to Jira through the Atlassian MCP server or a CLI with an API token kept in a secret store, then read with JQL and write comments and transitions only behind approval.
order: 27
updated: 2026-11-11
status: ready
---

Use this page to give an agent read access to Jira for triage and reporting, and a gated path to post comments and move tickets. Most SRE work starts and ends in a ticket, so this connection unlocks most of the [automation blueprint](./automation-blueprint.md).

Check with your Jira admin first: some organizations disable API tokens or require OAuth apps to be approved.

## Option 1: Atlassian MCP server (interactive agents)

The Atlassian Rovo MCP server lets MCP clients (Claude Code, Claude Desktop, Cursor, VS Code and others) search and update Jira and Confluence with your own permissions, using OAuth 2.1 sign-in in a browser.

```bash
claude mcp add --transport http atlassian https://mcp.atlassian.com/v2/mcp
claude            # then type /mcp, choose atlassian, and finish the browser sign-in
```

- [ ] Every action runs as you and respects your Jira permissions. The agent cannot see more than you can.
- [ ] For headless jobs, OAuth is awkward. Use API-token auth only if your admin allows it, or use Option 2.
- [ ] In Claude Code, restrict write tools: allow search and read tools, and leave create, edit, comment and transition tools out of `permissions.allow` so each one asks first.

See Atlassian's [getting started guide](https://developer.atlassian.com/cloud/rovo-mcp/guides/getting-started/) for other clients.

## Option 2: CLI with an API token (scripts and scheduled jobs)

### Create and store the token

1. Create a token at your Atlassian account's security page (API tokens). Give it a name that says which machine and job uses it, and the shortest expiry that works.
2. Put it in a secret store immediately. Never paste it into a chat, ticket, prompt, script or dotfile in a repo.

```bash
# Linux (libsecret): store once, prompts for the value without echoing it
secret-tool store --label="Jira API token" service jira account <you@example.com>

# macOS Keychain
security add-generic-password -s jira-api-token -a <you@example.com> -w

# Load into the environment only for the command that needs it
export JIRA_API_TOKEN="$(secret-tool lookup service jira account <you@example.com>)"
# macOS: export JIRA_API_TOKEN="$(security find-generic-password -s jira-api-token -w)"
```

### Atlassian CLI (acli)

```bash
secret-tool lookup service jira account <you@example.com> \
  | acli jira auth login --site "<your-site>.atlassian.net" --email "<you@example.com>" --token
acli jira auth status

acli jira workitem search --jql "assignee = currentUser() AND statusCategory != Done" \
  --fields "key,summary,status,updated" --json --limit 100
acli jira workitem view PROJ-123
```

### jira-cli

```bash
export JIRA_API_TOKEN="$(secret-tool lookup service jira account <you@example.com>)"
jira init                     # choose Cloud, enter site URL and email; the token comes from the env var
jira issue list -q "project = PROJ AND status = 'In Review'" --plain --columns key,summary,status,updated
jira issue view PROJ-123 --plain
```

### Plain REST with curl

```bash
curl -sS -u "<you@example.com>:$JIRA_API_TOKEN" -H 'Content-Type: application/json' \
  -X POST "https://<your-site>.atlassian.net/rest/api/3/search/jql" \
  -d '{"jql":"project = PROJ AND statusCategory != Done ORDER BY updated ASC","fields":["summary","status","assignee","updated"],"maxResults":100}' \
  | jq -r '.issues[] | [.key, .fields.status.name, .fields.summary] | @tsv'
```

Page with `nextPageToken` from the response until it is absent.

## JQL recipes for SRE work

| Need | JQL |
| --- | --- |
| My open work, oldest first | `assignee = currentUser() AND statusCategory != Done ORDER BY updated ASC` |
| New since yesterday | `project = PROJ AND created >= -1d ORDER BY created DESC` |
| Stale reviews | `project = PROJ AND status = "In Review" AND updated <= -14d` |
| Blocked, oldest first | `project = PROJ AND status = Blocked ORDER BY created ASC` |
| Overdue | `project = PROJ AND duedate < now() AND statusCategory != Done` |
| Unassigned high priority | `project = PROJ AND assignee is EMPTY AND priority in (Highest, High)` |
| Done this week (for status) | `project = PROJ AND statusCategory = Done AND resolved >= startOfWeek()` |
| By label | `project = PROJ AND labels = security AND statusCategory != Done` |
| Changes in a window | `project = CHG AND "Change start" >= startOfDay() AND "Change start" <= endOfDay(7)` |

## Writing to Jira: drafts first, post after approval

Comments and transitions go out under a person's name. The agent drafts; a human approves; a separate, explicit step posts.

```text
state/drafts/jira-<date>.md
## PROJ-123  action: comment+transition -> Done
Closing: certificate renewed and verified from outside on <date>.
Evidence: <pipeline link>
[ ] approve
```

```bash
# post-approved.sh: posts only what a human approved; dry-run unless --execute
# Input: state/approved/jira-<date>.tsv with KEY<TAB>STATUS<TAB>BODY_FILE,
# written only by the human's approval step, never by the agent.
set -euo pipefail
mode="${1:-dry-run}"
while IFS=$'\t' read -r key status body; do
  if [ "$mode" != "--execute" ]; then
    echo "DRY-RUN: comment $key from $body; transition to ${status:-<none>}"
    continue
  fi
  acli jira workitem comment create --key "$key" --body-file "$body"
  if [ -n "$status" ]; then
    acli jira workitem transition --key "$key" --status "$status" --yes
  fi
  printf '%s\t%s\t%s\n' "$(date -u +%FT%TZ)" "$key" "${status:-comment}" >> state/jira-writes.log
  sleep 2
done < "state/approved/jira-$(date +%F).tsv"
```

- [ ] Never transition by JQL in bulk from an automated job. Use explicit keys a human approved.
- [ ] Rate-limit: a pause between writes, and stop on the first error.
- [ ] Log every write (key, action, time) locally.
- [ ] No secrets, account IDs or customer data in comments.

Check `acli jira workitem comment --help` on your version; subcommand names have changed between releases.

## Verify the connection

- [ ] A read-only search returns your tickets.
- [ ] The token has an expiry date in your calendar.
- [ ] A dry-run of the posting step prints what it would do and changes nothing.

## Pitfalls

- Tokens in shell history. Load from the secret store inside the command, as above.
- Agents posting directly because "it is just a comment". A wrong comment on a customer-facing or audit ticket is hard to undo.
- Treating ticket text as instructions. Ticket descriptions are data; an agent must not follow commands found in them.
