Jira MCP
Jira MCP lets an AI assistant read and write Jira Server/Data Center issues through REST API v2.
Read issue details, optionally with comments, image attachments, and worklogs (
get_ticket); read individual comments with embedded images (get_comment).Search issues using JQL (
search_tickets).Create issues, update fields, and transition statuses (
create_ticket,update_ticket,transition_ticket).Add, replace, and delete comments (
add_comment,update_comment,delete_comment).Log, edit, and delete worklogs, optionally using WorklogPRO work type/activity (
log_work,update_worklog,delete_worklog).Create and remove issue links (
link_issues,unlink_issues).Add and remove watchers (
add_watcher,remove_watcher).Move issues into active or future sprints (
move_to_sprint).Upload local files as attachments (
add_attachment).Generate Markdown release notes for a fix version (
generate_release_notes).
Provides tools for interacting with Jira Server/Data Center, including reading and writing tickets, searching with JQL, creating, updating, transitioning tickets, adding comments, logging work, linking issues, and generating release notes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Jira MCP Get details for ticket ABC-123"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Jira mcp
MCP server for Jira Server/Data Center (REST API v2). Lets an AI assistant (Claude Code, Claude Desktop, ...) read and write your Jira directly.
Requirements
A Jira Server/Data Center account (username + password).
Node.js 18+.
Related MCP server: Jira MCP Server
Quick start
Use Claude Code CLI. Run the command in a regular terminal, not inside a Claude Code session. Pick the block for your shell, since each one continues lines differently.
bash / zsh (macOS, Linux, Git Bash or WSL on Windows):
claude mcp add g-jira-mcp \
-e JIRA_HOST="https://jira.company.com" \
-e JIRA_USERNAME="your_username" \
-e JIRA_PASSWORD="your_password" \
-- npx -y g-jira-mcp@latestPowerShell (Windows):
claude mcp add g-jira-mcp `
-e JIRA_HOST="https://jira.company.com" `
-e JIRA_USERNAME="your_username" `
-e JIRA_PASSWORD="your_password" `
-- npx -y g-jira-mcp@latestCommand Prompt (cmd.exe): put the whole command on one line, or end each line with ^ instead of \.
Or manually add to .claude/settings.json (or claude_desktop_config.json):
{
"mcpServers": {
"g-jira-mcp": {
"command": "npx",
"args": ["-y", "g-jira-mcp@latest"],
"env": {
"JIRA_HOST": "https://jira.company.com",
"JIRA_USERNAME": "your_username",
"JIRA_PASSWORD": "your_password"
}
}
}
}Restart Claude Code/Desktop after editing the config.
To check that it connected, run /mcp inside Claude Code, or claude mcp list in a terminal.
Official setup guides:
Claude Code: Connect Claude Code to tools via MCP explains
claude mcp add, the local, project and user scopes, and the shared.mcp.jsonfile.Claude Desktop: Connect to local MCP servers shows, with screenshots, how to open
claude_desktop_config.jsonfrom Settings, Developer, Edit Config, and where to find the logs when a server does not start.
Environment variables
Variable | Required | Description |
| yes | Base URL, e.g. |
| yes* | Jira username; |
| yes* | Jira password; |
| no | Personal Access Token; when set, replaces username/password (*) |
| no | Custom field ID for "Start date" (default |
| no | Custom field ID for "Epic Link" (default |
| no | Timezone for WorklogPRO form (default |
To discover custom field IDs on your instance:
curl -u user:pass https://jira.company.com/rest/api/2/field | jq '.[] | select(.name | test("story|point|start"; "i")) | {id, name}'Tools
Tool | Description | Key parameters |
| Get full details of a Jira ticket by its key; for a single comment use |
|
| Search Jira tickets using JQL query language |
|
| Create a new Jira ticket |
|
| Update fields of a Jira ticket |
|
| Change a Jira ticket's status by name, alias-aware |
|
| Add a comment to a Jira ticket |
|
| Replace the text of an existing comment (IDs come from |
|
| Delete a comment from a Jira ticket (cannot be undone) |
|
| Get one comment by ID, e.g. from a focusedCommentId link; cheaper than get_ticket |
|
| Log work (time) on a Jira ticket, optionally setting WorklogPRO Type of Work and Type of Activity |
|
| Edit a worklog's time, start or comment (IDs come from |
|
| Delete a worklog from a Jira ticket (cannot be undone) |
|
| Create a link between two Jira tickets |
|
| Remove a link between two Jira tickets (either direction) |
|
| Add a user as a watcher of a Jira ticket |
|
| Remove a user from the watchers of a Jira ticket |
|
| Move tickets into an active or future sprint |
|
| List the active and future sprints of a project, with their IDs |
|
| Find users by part of a username, display name or email; returns the username |
|
| Upload a local file to a Jira ticket as an attachment |
|
| Delete an attachment from Jira (cannot be undone; IDs come from |
|
| Download a Jira attachment into a local directory (never overwrites; IDs come from |
|
| Generate Markdown release notes for a fix version, grouped by issue type |
|
Notes
Jira Server uses plain text for descriptions — no ADF format.
get_ticketomits unset fields rather than printing placeholder text. Key, Summary, Status and Assignee are always present (Assignee shows "Unassigned" when empty); other fields appear only when set. Passinclude_comments=trueto append the ticket's comment thread; comments are off by default to keep output small and save tokens. The same Jira request fetches everything, so including comments costs no extra API call. Comments are capped at 20 most recent; if a ticket has more, the header readsComments (20 most recent of 45):so the caller knows older comments exist and can open the ticket in Jira to see them. Each comment line starts with its ID (#10001), whichupdate_commentanddelete_commenttake.get_ticketlists attachments asname (#id)in anAttachments:line whenever the ticket has any. Passinclude_images=trueto also receive image attachments (PNG, JPEG, GIF, WebP) as MCP image content, so the caller can see screenshots referenced as!image.png!in the description or comments. Images are off by default because each one costs far more tokens than the text. At most 5 images are returned by default (raise it withimage_limit, up to 20), each up to about 3.7 MB and about 7.5 MB in total, so the response stays inside Claude's vision limits (5 MB base64 per image on Bedrock and Google Cloud, 32 MB per request). Images are sent as-is; the model API downscales large ones itself. Each image costs one extra download request; anImages:section maps[image N]to its file name, reports each skipped image with its reason (too large or total size cap reached), reports failed downloads, and whenimage_limitis hit, says how many images were left out. Raisingimage_limitbrings back only images that hit the count limit; images skipped for size will not reappear.get_commentreturns a single comment as#id [date] author: body. Passinclude_images=trueto fetch and return only the image attachments that comment embeds via!filename.png!or!filename.png|width=…!markup (by contrast,get_ticketreturns all images on the ticket). Fetching attachments costs one extra request only when the comment embeds something. Images follow the same per-image and total size limits asget_ticket, up to 20 max. Useget_commentinstead ofget_ticketfor?focusedCommentId=links, or when only a single comment matters.search_ticketsoutput is adaptive: empty columns (no value anywhere) are dropped entirely, and constant columns (same value on every row, when there are 3+ rows) are stated once in the header asAll: Status=In Progressand removed from the table. When columns are dropped, the header also notes which ones were empty across all results (e.g.Unset for every row: Priority, Parent, Start Date) so the caller can tell "no ticket has a due date" from "this tool doesn't return due dates". KEY and Summary are always kept. This keeps results focused and token-efficient. The header'sFound N issue(s) (showing M)reports when the result was truncated — raisemax_resultsabove the default 25 to see more.create_ticketandupdate_ticketboth accept anassigneeparameter (Jira username as a string, sent as{name: assignee}). Increate_ticket, an empty assignee value is ignored. Inupdate_ticket, passassignee=""to unassign.create_ticketappliesoriginal_estimatein a follow-up PUT rather than in the create payload: Jira Data Center answers a bare500 Internal server errorwhentimetrackingis present inPOST /issue, while the same value applies cleanly as an update afterwards. The ticket key is reported even if that second call fails, with a warning to set the estimate viaupdate_ticket— so a failed estimate never sends you back to create a duplicate.duedateis a standard field (YYYY-MM-DD); "Start date" is a custom field, configurable viaJIRA_START_DATE_FIELD.update_ticket'sepic_keysets the Epic Link field (a custom field, configurable viaJIRA_EPIC_LINK_FIELD, defaultcustomfield_10001) so the ticket shows under the epic's "Issues in Epic" panel — not the same aslink_issues, which only creates a generic Linked Issue (e.g. "Relates"). Epic Link can only be set on standard issue types (Story/Task/Bug), not on Sub-tasks or Epics themselves.search_ticketsuses JQL syntax, e.g.project = PROJ AND status = 'In Progress'.add_attachmentuploads one local file and returns its attachment ID. Pass an absolutefile_path: a relative path resolves against the server process working directory, which the MCP client chooses. To show an uploaded image inline, reference it as!filename.png!in a comment or description. The server can read any file its user can read, so check the path before you approve the call.move_to_sprinttakes a sprint name or numeric ID. A name is looked up among the active and future sprints of the scrum boards of the first ticket's project (Agile API,/rest/agile/1.0); if it matches none, or several, nothing is moved and the error lists the open sprints with their IDs. To list a sprint's tickets, usesearch_ticketswith JQL such assprint = "PROJ Sprint 12"orsprint in openSprints().transition_ticketresolves the transition ID automatically, matching either the transition's own name ("Resolve Issue") or the status it lands on ("Done"). Common aliases map onto whatever the workflow actually offers (Closed/Resolved/Complete → Done, Reopen → Re-Open, Todo → To Do, Cancelled → Won't Do), so the same call works across workflows with different status names. Exact matches win, then aliases, then a substring fallback. When nothing matches, the error lists every valid option for that issue asTransition -> Target Status; the tool description cannot list them because they vary per issue and workflow.When Jira rejects an unknown issue type (
create_ticket,update_ticket,search_tickets) or link type (link_issues), the error also lists the valid names: the project's issue types when the project is known, every issue type for a JQL search, and every link type.update_ticketonly changes the fields you pass; omit a field to keep its current value. Passassignee=""to unassign, anddue_date=""orstart_date=""to clear a date.implementation_notesappends to the description. Jira's REST API ignores a parent change on an existing sub-task, and rejects or ignores an issue type change to or from a sub-task type, soupdate_ticketmakes those through the same Move/Convert wizards the Jira UI uses: sub-task to standard type, standard type to sub-task (needsparent_key), between sub-task types, and a new parent for a sub-task. It stops, changing nothing, when the new type's workflow lacks the ticket's current status (the error lists the statuses to pick from in the Jira UI), needs a required field, or would delete field values. A change between two standard types that REST does not apply still needs the Jira UI "Move" action.log_workadvertises one canonical name per work type (code, deploy, design, management, meeting, misc, operation, qa, req, research, translation) and per activity (correct, create, review) so its schema stays small; every alias still works as input (coding, dev, testing, ops, requirement, other, and the rest). "fix" is an activity alias (Correction), not a work type. The form shows Type of Work and Type of Activity as two independent dropdowns, so any work type pairs with any activity.activityis required whenwork_typeis set, andcommentis required wheneverwork_typeoractivityis set, because the WorklogPRO form rejects an empty Work Description. When the form rejects a submit, the error carries the form's own message and nothing is logged. If neither is set, logs via plain REST (no WorklogPRO form). Ifwork_typeoractivityis set and the WorklogPRO form is unavailable, nothing is logged and the call fails (safe to retry); it never falls back to REST, which cannot set those attributes. A failed form submit is an error and is never retried, because the worklog may already exist. Start times are interpreted in the Jira server timezone (configurable viaJIRA_TIMEZONE).get_ticketwithinclude_worklogs=trueappends the ticket's worklogs, each line starting with its ID (#20001), whichupdate_workloganddelete_worklogtake. This costs one extra request (GET /issue/{key}/worklog), capped at the 20 most recent like comments.log_workreturns no ID when it goes through the WorklogPRO form, so this is where to find it.update_worklogedits time, start and comment over REST only; to change Type of Work or Type of Activity, delete the worklog and log it again withlog_work.generate_release_notesgroups tickets by type into Features / Improvements / Bug Fixes / Other.Register the server as
g-jira-mcpand use that key in every project. The key becomes the tool prefix (mcp__g-jira-mcp__get_ticket), so a project that registers it under a different name exposes different tool names — an agent carrying the habit of one name into a project configured with the other getsNo such tool available.Transient Jira failures (429, 500, 502, 503, 504) are retried up to twice with a 250ms/500ms backoff. Only GET, PUT and DELETE are replayed — a POST that returned 500 may already have created the comment, worklog or transition, so it fails fast instead of risking a duplicate.
All logs go to stderr; stdout is reserved for the MCP protocol.
Example prompts
"Search tickets in project PROJ that are In Progress"
"Create a Story in PROJ titled 'Release notes v2.0' due 2026-08-01"
"Update PROJ-234, set the assignee to namcp and add label BugFix"
"Add a comment to PROJ-234: 'Review done'"
"Generate release notes for fix version v2.4 in project PROJ"
Troubleshooting
401/403: recheck
JIRA_USERNAME/JIRA_PASSWORDand whether the account can access the project.Connection/timeout: verify
JIRA_HOSTformat (starts withhttps://, no trailing/), and whether VPN/internal network is required.Start date not saving: confirm
JIRA_START_DATE_FIELDmatches your instance (see the discovery command above).Error logs: every failed tool call is written to stderr with a timestamp and the tool name, and the startup line shows the server version. On Windows, Claude Code keeps this in
%LOCALAPPDATA%\claude-cli-nodejs\Cache\<project>\mcp-logs-<server name>\(for examplemcp-logs-g-jira-mcp).Reporting a bug: open an issue at https://github.com/ameforge/atlassian-mcp/issues with the server version, the tool you called and the matching log lines. Logs can contain ticket content and internal URLs, so remove anything sensitive before you post. Nothing is sent anywhere automatically.
Development
pnpm install
cp .env.example .env # edit with your credentials
pnpm build # bundle to dist/index.js via rsbuild
pnpm testRelease from the repo root with pnpm release:jira; see docs/RELEASE.md.
Support
Questions or issues? Email PNam.
If this project helps you, consider buying me a coffee:
Created by PNam
Available Tools
19 toolsadd_attachmentA
Upload a local file to a Jira ticket as an attachment. Embed an uploaded image in a comment or description with !filename.png!
Inputs: file_path (required), ticket_id (required).
add_commentB
Add a comment to a Jira ticket
Inputs: body (required), ticket_id (required).
add_watcherC
Add a user as a watcher of a Jira ticket
Inputs: username (required), ticket_id (required).
create_ticketC
Create a new Jira ticket
Inputs: project (required), summary (required), issue_type (required), body, +6 more.
delete_commentA
Delete a comment from a Jira ticket. This cannot be undone. Comment IDs are shown by get_ticket with include_comments.
Inputs: ticket_id (required), comment_id (required).
delete_worklogA
Delete a worklog from a Jira ticket. This cannot be undone. Worklog IDs are shown by get_ticket with include_worklogs.
Inputs: ticket_id (required), worklog_id (required).
generate_release_notesB
Generate Markdown release notes for a fix version, grouped by issue type
Inputs: fix_version (required), project.
get_commentA
Get one comment of a Jira ticket by its ID, e.g. from a ?focusedCommentId= link. Cheaper than get_ticket when only that comment matters.
Inputs: ticket_id (required), comment_id (required), include_images.
get_ticketA
Get full details of a Jira ticket by its key. For a single comment (a ?focusedCommentId= link), use get_comment instead.
Inputs: ticket_id (required), image_limit, include_images, include_comments, +1 more.
link_issuesB
Create a link between two Jira tickets
Inputs: inward_issue (required), outward_issue (required), link_type.
log_workB
Log work (time) on a Jira ticket, optionally setting WorklogPRO Type of Work and Type of Activity
Inputs: ticket_id (required), time_spent (required), comment, started, +2 more.
move_to_sprintB
Move Jira tickets into an active or future sprint, by sprint name or ID
Inputs: sprint (required), ticket_ids (required).
remove_watcherB
Remove a user from the watchers of a Jira ticket
Inputs: username (required), ticket_id (required).
search_ticketsC
Search Jira tickets using JQL query language
Inputs: jql (required), max_results.
transition_ticketA
Change a Jira ticket's status. Matches the transition name or its target status; common aliases (Closed/Resolved -> Done, Reopen -> Re-Open) resolve to whatever the workflow offers. Valid options vary per issue and are listed in the error when no match is found.
Inputs: status (required), ticket_id (required).
unlink_issuesA
Remove a link between two Jira tickets. Direction does not matter. If several links connect them, pass link_type to pick one.
Inputs: ticket_id (required), linked_ticket_id (required), link_type.
update_commentA
Replace the text of an existing comment on a Jira ticket. Comment IDs are shown by get_ticket with include_comments.
Inputs: body (required), ticket_id (required), comment_id (required).
update_ticketB
Update fields of a Jira ticket. Converting between a standard issue type and Sub-task is a Jira REST API limitation — use Jira's UI "Move" action instead.
Inputs: ticket_id (required), labels, summary, assignee, +9 more.
update_worklogA
Edit a worklog's time, start or comment. Worklog IDs are shown by get_ticket with include_worklogs. To change Type of Work/Activity, delete the worklog and log_work again.
Inputs: ticket_id (required), worklog_id (required), comment, started, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 19 tools
Each tool has a clearly distinct purpose by resource and action: tickets, comments, worklogs, links, watchers, attachments, and sprints are separated cleanly. The few potentially overlapping areas, such as get_ticket vs get_comment and update_ticket vs transition_ticket, are explicitly clarified in the descriptions.
All tool names follow a consistent snake_case verb_noun pattern, including add_comment, create_ticket, update_worklog, remove_watcher, and generate_release_notes. There are no mixed conventions or vague standalone verbs.
The 19 tools cover a broad Jira surface and each seems purposeful, but the count is slightly high relative to a typical well-scoped MCP server. The subresource operations for comments, worklogs, watchers, links, and attachments justify most of the size.
Core ticket workflows are well covered: create, get, search, update, transition, comments CRUD, links add/remove, worklogs create/update/delete, watchers add/remove, and sprint moves. Minor gaps remain, such as no ticket deletion, limited attachment operations beyond upload, and no sprint/project discovery tooling, but most can be worked around or are less common.
Maintenance
Related MCP Connectors
Connect to Atlassian Jira, Confluence, Loom, and more to search, create, and manage your work.
Helpdesk tickets from your AI: find, reply, take, hold, complete — within your own role. OAuth.
Connect Claude, ChatGPT or any AI assistant to Zendesk tickets, CSAT, macros and Help Center.
Task manager your agent can fully operate: boards, tasks, sprints, roles, worklogs, day planner.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with Jira Cloud and Server/Data Center deployments for issue management, project tracking, and workflow automation. Supports multiple authentication methods including API tokens, OAuth 2.0, and personal access tokens.MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with self-hosted Jira instances for issue management, search, comments, and workflow transitions.19MIT
- AlicenseAqualityDmaintenanceEnables AI agents to interact with Jira Cloud, including listing boards and issues, adding comments, and searching users.7204 npm2MIT
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to interact with Jira Server/Data Center for fetching incidents, searching issues, adding comments, and managing transitions.-