Jirum
README.md
# Jirum
An MCP server that lets Claude (Desktop or Code) create, search and update Jira Cloud work items **as you**, using Atlassian OAuth 2.0 (3LO). Works with any Atlassian tenant; each user authorizes with their own Atlassian account and tokens stay on their machine.
```
Claude Desktop / Claude Code
│ MCP (stdio)
▼
Jirum ──── OAuth 2.0 access token ────▶ api.atlassian.com/ex/jira/{cloudId}
│
└── ~/.config/jirum/tokens.json (0600, rotating refresh token)
```
## What Claude can do
| Area | Tools |
| --- | --- |
| Auth | `jira_connect`, `jira_auth_status`, `jira_list_sites`, `jira_select_site`, `jira_disconnect` |
| Discovery | `jira_myself`, `jira_list_projects`, `jira_get_project`, `jira_get_issue_types`, `jira_get_create_fields`, `jira_get_fields`, `jira_get_link_types`, `jira_get_priorities`, `jira_search_users` |
| Issues | `jira_search_issues` (JQL), `jira_count_issues`, `jira_get_issue`, `jira_create_issue`, `jira_update_issue`, `jira_assign_issue`, `jira_get_transitions`, `jira_transition_issue`, `jira_add_comment`, `jira_get_comments`, `jira_link_issues`, `jira_delete_issue` |
| Hierarchy | `jira_create_epic`, `jira_create_child_issue` (Feature/Story/Task/Bug under an Epic), `jira_create_subtask`, `jira_create_epic_with_children`, `jira_get_children` |
| Sprints | `jira_get_boards`, `jira_get_sprints`, `jira_get_sprint_issues`, `jira_add_to_sprint` |
| Opinionated | `jira_search_my_tickets`, `jira_find_duplicate_ticket`, `jira_create_ticket_from_context` |
Descriptions and comments are written in **Markdown** and converted to Atlassian Document Format (headings, lists, code blocks, tables, links, bold/italic/strike, inline code). Issue descriptions and comments are returned as Markdown.
Your Jira permissions still apply: OAuth scopes never grant more than your Jira user can do.
## 1. Create the Atlassian OAuth app (once per organisation)
Step-by-step with screenshots-level detail and troubleshooting: see [GUIDE.md](GUIDE.md). Short version:
Atlassian's guidance is to ship **one** OAuth app per integration rather than asking every user to create their own. Whoever owns the integration does this once and shares the client ID/secret with users through your usual secrets channel.
1. Open the [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/) and choose **Create → OAuth 2.0 integration**. Name it (e.g. `Jirum`).
2. **Permissions → Jira API → Add → Configure**, then add these scopes:
- Classic tab: `read:jira-user`, `read:jira-work`, `write:jira-work`
- Granular tab: `read:project:jira`
3. Still on the **Granular** tab of Jira API (there is no separate Jira Software card), search `jira-software` and add:
- `read:board-scope:jira-software`, `read:sprint:jira-software`, `write:sprint:jira-software`, `read:issue:jira-software`
4. **Authorization → OAuth 2.0 (3LO) → Configure** and set the callback URL to exactly:
```
http://localhost:8787/oauth/callback
```
5. **Settings** → copy the **Client ID** and **Secret**.
6. Optional but recommended: under **Distribution**, set the app to *Sharing* so users outside your Atlassian org can authorize it. Fill the required details (privacy policy URL etc.).
Skip step 3 and remove the Jira Software scopes from `JIRA_SCOPES` if you do not want sprint/board tools.
## 2. Install
```bash
git clone https://github.com/RakshitRabugotra/jirum.git
cd jirum
pnpm install
pnpm build
```
Provide the credentials either as environment variables (set them in the Claude config below) or in `~/.config/jirum/config.json`:
```json
{ "clientId": "…", "clientSecret": "…" }
```
See `.env.example` for all options (`JIRA_OAUTH_REDIRECT_URI`, `JIRA_SCOPES`, `JIRA_DEFAULT_SITE`, `JIRUM_CONFIG_DIR`).
## 3. Authorize
Either from the terminal:
```bash
ATLASSIAN_CLIENT_ID=… ATLASSIAN_CLIENT_SECRET=… node dist/index.js login
```
or later from Claude by asking it to *"connect Jira"* (it calls `jira_connect`, which opens the browser and returns the authorization URL). Approve the consent screen; the browser tab says "Jira is connected". If you take longer than the tool's wait, the login keeps waiting in the background for up to 5 minutes and Claude can confirm with `jira_auth_status`.
If your account can access several Jira sites, pick one:
```bash
node dist/index.js sites
node dist/index.js select acme
```
Other CLI commands: `status`, `logout`, `config`.
## 4. Connect to Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) and add the server (see `claude_desktop_config.example.json`):
```json
{
"mcpServers": {
"jirum": {
"command": "node",
"args": ["/absolute/path/to/jirum/dist/index.js"],
"env": {
"ATLASSIAN_CLIENT_ID": "…",
"ATLASSIAN_CLIENT_SECRET": "…"
}
}
}
}
```
Restart Claude Desktop. Use the full path to `node` (`which node`) if Claude cannot find it, since GUI apps do not load your shell profile.
## 5. Connect to Claude Code
```bash
claude mcp add jirum -s user -e ATLASSIAN_CLIENT_ID=… -e ATLASSIAN_CLIENT_SECRET=… -- node /absolute/path/to/jirum/dist/index.js
```
Both clients share the same token file, so you only authorize once per machine.
## Example prompts
- "Create a Jira ticket in THEOS for the Vite 9 migration, assign it to me, add the acceptance criteria we discussed."
- "Is there already a ticket about PostgreSQL schema permissions in THEOS?"
- "Create an epic 'Billing v2' with features 'Invoices' and 'Refunds', each with sub-tasks for API and UI."
- "Move THEOS-42 to In Progress and comment that the PR is up."
- "What's in the active sprint for THEOS that's assigned to me?"
- "Link THEOS-51 as blocked by THEOS-49."
## How it works
- **OAuth**: authorization-code flow with `offline_access`. A throwaway HTTP server listens on the callback port only while a login is in progress. Atlassian rotates refresh tokens; the new one is persisted after every refresh. Concurrent tool calls share a single refresh.
- **Multi-site**: after login the server stores every accessible site from `accessible-resources`. Every tool accepts an optional `site` argument; otherwise the selected site (or `JIRA_DEFAULT_SITE`) is used.
- **Name resolution**: issue types, users (name/email/"me"), priorities, link-type phrases ("is blocked by"), custom fields (by name) and sprints ("active", "next", a name) are resolved to ids server-side so Claude does not have to.
- **Errors** come back as readable tool errors (`NOT_CONNECTED: …`, `Jira 403 … missing a scope`) so Claude can recover, for example by calling `jira_connect`.
- Nothing is written to stdout except MCP protocol messages; logs go to stderr.
## Development
```bash
pnpm dev # run from source via tsx
pnpm typecheck
pnpm test:adf # markdown -> ADF conversion checks
```
## Security notes
- Tokens are stored in `~/.config/jirum/tokens.json` with mode 0600. Delete it (or run `jirum logout`) to disconnect this machine; revoke the app at https://id.atlassian.com/manage-profile/apps to revoke entirely.
- Atlassian 3LO does not support PKCE, so the client secret is required for token exchange. Treat it like any other shared app secret.
- `jira_delete_issue` is the only destructive tool; it is annotated as such so clients can require confirmation.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues