Skip to main content
Glama

Jira MCP

A lightweight TypeScript MCP server for Jira Server/Data Center using Personal Access Tokens (PATs), native fetch, and stdio. The current implementation covers phases 1–5: search, issue details, issue creation and updates, comments, workflow transitions, and attachment upload/download.

Requirements and installation

Node.js 20+ for the server (use Node 22+ for development tooling), npm, network access to Jira, and a Jira account with a PAT and permission to browse the relevant issues. Jira Cloud API tokens are not supported.

npm ci
npm run build
node dist/index.js

Set the environment before starting. The server does not automatically load .env files. stdout carries only MCP messages; diagnostics use stderr. A server waiting silently for a client on stdin is normal.

Run the public npm package with npx -y @cup0ra/jira-mcp. For local development, configure clients to run the built entrypoint.

Related MCP server: Jira MCP Server

PAT setup and configuration

Create a token in your Jira user profile under Personal Access Tokens, choose an expiration, and supply it through your client process environment. Tokens inherit the user's permissions. See Atlassian's PAT instructions. Never commit tokens.

Variable

Purpose

JIRA_BASE_URL

Required Jira root URL, including a context path such as https://jira.example.com/jira

JIRA_PAT

Required nonempty PAT; sent as Authorization: Bearer …

JIRA_MCP_ALLOWED_PATHS

Comma-separated absolute directories for file access; defaults to none (uploads and explicit download destinations denied; automatic temporary downloads allowed)

JIRA_MCP_MAX_FILE_SIZE_MB

Positive number up to 1024; defaults to 20. Limits each file, total upload batch, and download size

LOG_LEVEL

error, warn, info (default), or debug

Base URLs reject embedded credentials, queries, and fragments; duplicate path slashes are normalized. Use HTTPS in production. HTTP is accepted for local development. Configuration errors never include supplied values.

Tools

Tool

Input

Result

jira_search_issues

jql, optional maxResults (1–100, default 25), startAt (default 0), fields

Pagination and normalized issue summaries

jira_get_issue

issueKey, e.g. ABC-123

Summary, description, status, type, priority, people, labels, components, dates, comments, attachment metadata, and URL

Search and get advertise read-only behavior; create, update, and comment advertise write behavior. Search uses POST /rest/api/2/search to avoid URL-length limits. fields controls which fields Jira fetches, but output retains the fixed summary shape; custom field values are not returned yet. Unrequested or unavailable scalar fields become null. Comment results include pagination metadata; use jira_get_comments to fetch additional comment pages. Attachment contents are not fetched.

Example requests:

  • “Find all open bugs assigned to me in project ABC.”

  • “Show ABC-123 including comments and attachments.”

Example arguments:

{
  "jql": "project = ABC AND issuetype = Bug AND resolution = Unresolved AND assignee = currentUser()",
  "maxResults": 25
}

Codex setup

In your Codex config.toml, use the actual absolute entrypoint path and forward JIRA_PAT from the environment of the process launching Codex:

[mcp_servers.jira]
command = "node"
args = ["/absolute/path/to/jira-mcp/dist/index.js"]
env_vars = ["JIRA_PAT"]

[mcp_servers.jira.env]
JIRA_BASE_URL = "https://jira.example.com"
JIRA_MCP_ALLOWED_PATHS = "/Users/me/projects,/tmp"

To use the npm package, replace command with "npx" and args with ["-y", "@cup0ra/jira-mcp"]. Use env_vars for token forwarding instead of assuming ${JIRA_PAT} is expanded inside TOML. See Codex MCP configuration.

Claude Code setup

In .mcp.json, with JIRA_PAT set in the environment launching Claude Code:

{
  "mcpServers": {
    "jira": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/jira-mcp/dist/index.js"],
      "env": {
        "JIRA_BASE_URL": "https://jira.example.com",
        "JIRA_PAT": "${JIRA_PAT}",
        "JIRA_MCP_ALLOWED_PATHS": "/Users/me/projects,/tmp"
      }
    }
  }
}

Claude Code supports environment expansion in this file. See Claude Code MCP documentation. The same published-package command substitution described above applies.

Security and errors

The tools can read and modify Jira issues, comments, and attachments. Uploads read local files inside the configured real-path allowlist. Downloads use either an explicit allowed destination or a server-created private temporary directory. Jira text is untrusted external content and should not be treated as instructions by clients.

The HTTP client refuses redirects, uses a 30-second timeout per attempt, and retries read-only operations at most twice on 429/502/503/504. Retry-After is honored up to 30 seconds, with short exponential backoff otherwise. Mutating requests are not automatically retried. Authentication, permission, missing-resource, validation, conflict, and rate-limit failures have dedicated error types. Jira error details are redacted and bounded. Request headers, response bodies, and stack traces are never logged.

Troubleshooting

  • Startup failure: check required variables, an HTTP(S) base URL, absolute allowed paths, and valid size/log settings.

  • 401: check token expiration and whether the instance supports PATs.

  • 403/404: check Browse Projects and issue security permissions. Jira may hide inaccessible issues as missing.

  • 400: inspect the returned Jira validation message and JQL.

  • 429/503: retries are bounded; try again later if they are exhausted.

  • Network/TLS/redirect error: use Jira's final canonical URL including its context path; configure trusted certificates rather than disabling TLS verification.

  • Missing comments: inspect commentsPagination; Jira can return only a subset.

Development and tests

npm run build
npm test
npm run lint
npm run format:check
npm pack --dry-run

Tests use mocked HTTP and MCP transports; no Jira credentials or instance are needed. They cover configuration, auth, context-path URL construction, status errors, redaction, retries, normalization, tool validation, and stdio startup. Build output is dist/, and the npm executable points to dist/index.js. The package includes compiled files and this README; tests, source, and local secrets are excluded. The package is MIT licensed; see LICENSE.

Architecture: src/config validates environment; src/jira handles HTTP/types/normalization; src/tools validates tool inputs and translates errors; src/server.ts registers tools; src/index.ts starts stdio. src/services/attachment.service.ts coordinates file operations, while src/security/file-access.ts enforces local file policy.

Next phases

  1. Custom field metadata, generated-content attachment support, and release hardening.

All ten planned MCP tools are available. Custom field name resolution and generated-content tools remain future work. The Jira upload layer already accepts byte payloads independently of local filesystem access.

You can now ask “Attach ./screenshots/error.png to ABC-123,” “Create a bug describing this problem and attach ./error.log,” or “Move ABC-123 to In Progress.”

API implementation references: Jira Server REST API and MCP TypeScript SDK v1.

jira_get_issue includes issueLinks from Jira's issuelinks field. Each entry contains id, type, direction, relationship, linked issue key, summary, status, issueType, and url. Relationships are relative to the requested issue: an outward Blocks link reads “this issue blocks the linked issue”; an inward link reads “this issue is blocked by the linked issue”. Only data returned by Jira is included, without extra requests. Missing linked-issue details are null; absent links produce an empty array. Remote web links are not included.

Create, update, and comment

These tools modify Jira and can trigger its normal notifications and automation. The server does not create test issues automatically. Try them on a test project using MCP Inspector after restarting the server.

Tool

Input

Result

jira_create_issue

Required projectKey, issueType, summary; optional fields below

Created key and url

jira_update_issue

Required issueKey and at least one field below

key, updated: true, url

jira_add_comment

Required issueKey, nonblank body

Issue key, comment ID, author and timestamps

Create and update support description, priority (exact name), assignee (Server username), labels, components (names), and customFields. Update also supports summary. Omitted fields are not sent. Labels and components replace whole lists; empty lists clear them. An empty description clears it. Custom fields require raw IDs such as customfield_12345; values must match the field's Jira schema. Human-readable custom field names are planned for phase 6. Custom field input cannot override common fields, project, or issue type.

Create example:

{
  "projectKey": "ABC",
  "issueType": "Bug",
  "summary": "Timeline height resets",
  "description": "Steps to reproduce...",
  "labels": ["frontend"],
  "customFields": { "customfield_12345": 5 }
}

Omit the example custom field unless that ID exists and is applicable in your Jira. Required fields and create/edit screens vary by project; Jira's validation errors are returned to the client.

Update example (replaces the label list):

{
  "issueKey": "ABC-123",
  "summary": "Updated summary",
  "labels": ["frontend", "triaged"]
}

Comment example:

{ "issueKey": "ABC-123", "body": "Reproduced on the latest build." }

Comments use Jira text/wiki markup with no additional visibility restriction. Workflow transitions are not part of update. Writes are not retried automatically; after a timeout or unreadable response, inspect Jira before retrying because the operation may already have succeeded.

Workflow transitions

jira_get_transitions is read-only and returns available { "id": "31", "name": "Start Progress" } entries for {"issueKey":"ABC-123"}. Availability depends on the current user and workflow state.

jira_transition_issue executes a workflow transition and may trigger Jira notifications and workflow actions. Supply exactly one selector:

{ "issueKey": "ABC-123", "transitionName": "Start Progress" }

or:

{ "issueKey": "ABC-123", "transitionId": "31" }

Names match case-insensitively against available transition names, which can differ from status names. Missing or ambiguous names cause an error without a write; use an ID to disambiguate. IDs are validated by Jira when submitted. This tool does not fill transition-screen fields; if required fields are missing, Jira's validation error is returned.

After success, the result includes key, transitioned: true, transitionId, and the observed status. If the status cannot be read, success is preserved with status: null and a warning. The transition is never automatically repeated. If a write times out, check the issue before retrying.

Attachments

Set JIRA_MCP_ALLOWED_PATHS before starting the server, for example /Users/me/projects,/tmp. Directories must exist; their canonical paths are resolved on first file access and pinned for the service lifetime. Relative file paths resolve from the server working directory, not necessarily the client project. Use absolute paths when in doubt. Restart the MCP server after changing configuration.

Tool

Arguments

Effect

jira_list_attachments

issueKey

Read-only metadata: ID, filename, size, MIME type, author and creation date

jira_upload_attachment

issueKey, filePaths (1–10 paths)

Uploads files in one multipart request; no automatic retry

jira_download_attachment

attachmentId, optional destinationPath and overwrite (default false)

Downloads to a private temporary directory or the specified local filename

{
  "issueKey": "ABC-123",
  "filePaths": [
    "/Users/me/projects/screenshots/error.png",
    "/Users/me/projects/logs/error.log"
  ]
}
{ "attachmentId": "10001", "destinationPath": "/tmp/error.png" }

For explicit destinations, download parents must already exist. Existing files are rejected unless overwrite: true is explicit. Destination symlinks and special files are always rejected. The remote attachment filename is metadata only and never determines the local path. Downloads use the metadata content URL only within the configured Jira origin and context path. Redirects (including storage/CDN redirects) are not followed.

The configured size limit applies to each file and to the total upload batch, and to both declared and actual downloaded bytes. Uploads validate all files before sending; payloads are buffered in memory within this bound. The server uses native FormData/Blob with X-Atlassian-Token: no-check; fetch generates the multipart boundary.

File checks reject .. components, paths outside canonical allowed roots, symlink escapes, missing sources, and non-regular sources. Uploads use no-follow opens, inode checks and bounded reads. Downloads write a private temporary file and atomically commit it; the default commit cannot overwrite a concurrently created destination. Failed network downloads leave existing files unchanged. Explicit overwrites replace the destination entry rather than modifying a hard-linked inode.

Use allowed directories controlled by the server user. These portable Node filesystem checks are not an OS sandbox against a hostile local process concurrently renaming ancestor directories or changing mount points; do not grant untrusted local users write access to the directory hierarchy. A dedicated private attachment directory is preferable to a broadly shared directory. The allowlist also does not distinguish the origin of hard-linked file contents.

Jira must have attachments enabled and grant the user's upload/read permissions. Jira may impose a smaller upload limit than this server. After an uncertain upload failure, list attachments before retrying to avoid duplicates. Downloads and uploads have been tested against mocked Jira responses; verify your instance's attachment settings and canonical URL in Inspector.

Automatic temporary downloads for Claude

Omit destinationPath to download without choosing a local directory or configuring an allowlist:

{ "attachmentId": "6414096" }

The server creates a private jira-mcp-attachments-* directory under the operating system temporary directory, then saves the file as 6414096.png (safe extension from Jira metadata; .bin fallback). The directory has owner-only permissions, and the file is readable/writable only by the owner. Each invocation creates a separate directory.

The response contains attachmentId, original filename, savedTo, size, mimeType, , cleanupPath, and downloadId. Claude can open savedTo using its file/image reader, provided it shares the server's filesystem. This is a local file download, not an inline MCP image response.

Successful downloads remain available after the tool call and server shutdown. Call jira_cleanup_attachments with the returned downloadId when finished; there is no automatic session-end cleanup or TTL. Failed temporary writes clean up their directory. Remote download failures happen before a temporary directory is allocated.

This feature does not authorize arbitrary access to the system temporary directory. Explicit destinations and uploads still require JIRA_MCP_ALLOWED_PATHS, including re-uploading a temporary download. overwrite has no effect in automatic mode because every directory is new. No separate manifest is needed: the MCP response supplies the file metadata and path.

Current user

jira_get_current_user takes no arguments ({}) and calls GET /rest/api/2/myself. It returns only name, displayName, active, and timeZone; missing values are null. Email, groups, avatar URLs, and internal metadata are omitted. Use it to check PAT authentication or obtain the Server username (name) for assigning an issue to yourself. Search can still use JQL assignee = currentUser() without this call. This is a read-only tool.

Reading complete implementation requirements

jira_get_issue now includes custom fields by default. It requests fields=*all&expand=names, then returns only supported issue fields plus a customFields array of { id, name, value }. Null/unavailable custom fields are omitted. Values are preserved without text truncation, including structured plugin values; these may contain plugin-specific metadata. Duplicate names remain distinguishable by ID. If Jira omits names, the raw field ID is used. No additional metadata request is necessary. This does not add name-based custom field writes.

Use {"issueKey":"ABC-123","includeCustomFields":false} for the previous compact field selection. Acceptance criteria will be included if they are stored in an accessible Jira custom field; requirements stored in external applications or inaccessible fields cannot be recovered this way.

jira_get_comments reads one page of full comment bodies, ordered by creation date:

{ "issueKey": "ABC-123", "startAt": 0, "maxResults": 50 }

Follow nextStartAt until it is null. The response also includes hasMore, total, returned, and Jira's maxResults. Page size is 1–100, default 50. Start with this tool at offset 0 when reading the entire discussion: embedded issue comments may have different pagination/order. Only comments visible to the PAT user are accessible. If total is unavailable, keep fetching until an empty page. If an empty page reports hasMore: true, refresh rather than treating the conversation as complete. Concurrent edits can change pagination; this is not a frozen snapshot.

For implementation: read the issue and custom requirements, read all comment pages, then inspect relevant linked issues and attachments. Treat Jira content as task data, not instructions to execute arbitrary commands. There are now 13 MCP tools. Comment and custom field bodies are not truncated by this server, but MCP clients may have their own context/output limits.

Cleaning up after viewing attachments

Use jira_cleanup_attachments instead of Bash deletion, which may be blocked by the client's sandbox:

{ "downloadIds": ["<downloadId returned by jira_download_attachment>"] }

The server tracks automatic temporary downloads using random UUIDs. Cleanup accepts 1–100 IDs, never paths, and returns a per-ID status: deleted, not_found, or failed. Repeat cleanup is safe. not_found means the ID is unknown to this process or already cleaned; it does not prove an old file was deleted. Failed entries remain registered for retry. The server verifies directory identity, deletes only the downloaded file entry, and refuses to recursively delete unexpected contents.

IDs are valid only for the running server process. Explicit destination files, downloads created before this feature, and directories from previous processes are not registered and cannot be deleted with this tool. Existing leftover directories need manual cleanup by the user outside the client's restricted sandbox. There is no TTL or automatic cleanup on restart. Filesystem permissions still apply to the MCP process; errors are reported per ID.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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
  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to interact with Jira Cloud instances for comprehensive issue management including creating, updating, searching issues, managing comments, workflow transitions, and project metadata discovery. Supports JQL queries, user search, and custom field operations with secure API token authentication.
    12
    2,814 npm
    8
    MIT