Skip to main content

Jira MCP Server

Production-ready Jira MCP server exposing Jira projects, issues, sprints, users, attachments, and worklogs as Model Context Protocol tools, resources, and prompts — to any MCP client (Claude Desktop, Claude Code, Cursor, VS Code) over stdio (local) or HTTP/SSE / Streamable HTTP (remote or team-shared).

┌──────────────────────────────────────────────────────────────────┐
│                         MCP Client                                │
│   Claude Desktop / Claude Code / Cursor / VS Code / custom        │
└───────────────┬───────────────────────────────────┬──────────────┘
                │ stdio (JSON-RPC over stdin/stdout) │   HTTP (SSE/Streamable)
                ▼                                    ▼
        ┌──────────────────┐              ┌──────────────────────┐
        │   stdio transport │              │   HTTP transport     │
        │   (default)       │              │   /sse /mcp /health  │
        └───────┬──────────┘              └──────────┬───────────┘
                │       same registered server        │
                ▼                                     ▼
        ┌──────────────────────────────────────────────────────────┐
        │               jira_mcp_server.server.create_server()      │
        │   27 tools · 4 resources · 4 prompts (transport-agnostic) │
        └───────────────────────────────┬──────────────────────────┘
                                        ▼
                       ┌────────────────────────────────┐
                       │  JiraClient (httpx async)       │
                       │  auth · rate-limit · retry      │
                       └────────────────┬───────────────┘
                                        ▼
                          Jira REST API / Agile API (HTTPS)

Features

  • Dual transports — stdio for local integration; HTTP with SSE and Streamable HTTP for remote/team deployments, switchable via CLI flag or MCP_TRANSPORT.
  • Dual Jira auth — Jira Cloud (basic: email + API token) and Jira Data Center / Server (Bearer PAT).
  • Security first — HTTPS-only (JIRA_BASE_URL refuses plaintext), no credentials in logs (token masking), client-side token-bucket rate limiting, project whitelist enforcement, tool-level CRUD permission control (JIRA_TOOLS), confirm guard on deletion, attachment size limits, optional static Bearer auth for the HTTP transport, CORS control.
  • Automatic retry + pagination — 429/5xx retry with exponential backoff; list endpoints paginate automatically.
  • ADF support — plain-text → Atlassian Document Format conversion for descriptions/comments; ADF → text rendering for reads.
  • Type-safe — Pydantic-validated tool schemas and config.
  • Docker readyDockerfile + docker-compose.yml.

Requirements

  • Python 3.11+ (3.12 recommended).
  • A Jira instance:
    • Jira Cloud — an API token (email + token for Basic auth), and the user should have at least: Browse projects, Create issues, Edit issues, Manage attachments, Manage worklogs, and (for board/sprint tools) access to the relevant boards.
    • Jira Data Center / Server — a Personal Access Token, and a TLS-terminated HTTPS endpoint (the server refuses plaintext).

Token management: create tokens per environment, scope them to the least privilege you need, and rotate them regularly. Never commit .env. If a token must be granted broad Jira permissions, pair it with JIRA_TOOLS (see Tool permissions) to restrict which of those permissions MCP clients can actually drive.

Installation

1. pip

pip install jira-mcp-server-hopcos

2. uv

uv pip install jira-mcp-server-hopcos

3. From source

git clone https://github.com/your-org/jira-mcp-server.git
cd jira-mcp-server
python -m venv .venv
source .venv/bin/activate      # Linux / macOS
pip install -e ".[dev]"

On Windows, use PowerShell instead:

git clone https://github.com/your-org/jira-mcp-server.git
cd jira-mcp-server
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"

or (CMD):

git clone https://github.com/your-org/jira-mcp-server.git
cd jira-mcp-server
python -m venv .venv
.\.venv\Scripts\activate.bat
python -m pip install -e ".[dev]"

A successful editable install automatically writes the jira-mcp-server console entry point — and on Windows the jira-mcp-server.exe launcher — into the active environment's Scripts directory. See Building the jira-mcp-server.exe console script.

4. Docker (HTTP mode)

The Jira credentials are set on the server container (server-side), not on any MCP client:

docker run -p 8080:8080 \
  -e JIRA_BASE_URL=https://your-domain.atlassian.net \
  -e JIRA_API_TOKEN=your-token \
  -e JIRA_USER_EMAIL=your@email.com \
  -e JIRA_TOOLS=read,create,update \
  jira-mcp-server:latest

See Docker deployment for full details.

Quick start (3 minutes)

stdio mode — Claude Desktop

Configure the server once, server-side, so the token never appears in the client config (and is never exposed to the model). The server loads its configuration from a fixed-name config file that sits in the same directory as the server — so the MCP command stays a bare jira-mcp-server with no path arguments (nothing to expose to the model) and file values never need to enter the environment.

Option A — a jira_server.toml (or jira_server.json) next to the server (the recommended, most discreet choice — secrets never enter the environment):

# jira_server.toml – server-side only, never commit, permissions 0600
base_url = "https://your-domain.atlassian.net"
auth_method = "basic"
user_email = "your-email@example.com"
api_token = "your-api-token"        # never in env, never in client config
tools = "read,create,update"

The server auto-detects this file in its working directory on startup.

Option B — a .env KEY=VALUE file next to the server installation:

# .env – server-side configuration (never commit this file)
JIRA_BASE_URL=https://your-domain.atlassian.net
JIRA_AUTH_METHOD=basic
JIRA_USER_EMAIL=your-email@example.com
JIRA_API_TOKEN=your-api-token
JIRA_TOOLS=read,create,update

Then register the server in Claude Desktop's claude_desktop_config.json with a bare command — no credentials, no env block:

{
  "mcpServers": {
    "jira": {
      "command": "jira-mcp-server",
      "args": ["--transport", "stdio"]
    }
  }
}

Restart Claude Desktop, open a conversation, and try:

"List my Jira projects." → the server calls jira_list_projects. "Create a Bug in PROJ titled 'Login fails on Safari'."jira_create_issue.

stdio mode — Cursor / VS Code

The same setup applies — the Jira connection lives in a server-side file next to the server (jira_server.toml / jira_server.json / .env), and the editor's MCP configuration references the bare command. The matching mcpServers block for Cursor (~/.cursor/mcp.json) or the VS Code MCP panel is identical to the Claude Desktop block above — the server auto-discovers its own config file, never the editor config.

Why a config file in the server directory? A token set in the MCP client's env block (or in shell environment that the client shows) is visible to the AI model running inside the client. A config file next to the server is found by the server automatically, so the launch command has zero arguments and the secret stays out of both the environment and the client config. Because discovery uses a fixed filename, no path needs to be passed to the server either.

HTTP/SSE mode

Start the service on port 8080 (the Jira connection comes from the server-side .env, exactly as in stdio mode):

jira-mcp-server --transport http --port 8080
# or using env vars
export MCP_TRANSPORT=http
export MCP_HOST=127.0.0.1
export MCP_PORT=8080
export JIRA_TOOLS=read,create,update
jira-mcp-server

Connect from any SSE-capable MCP client:

{
  "mcpServers": {
    "jira": {
      "url": "http://127.0.0.1:8080/sse",
      "headers": { "Authorization": "Bearer your-secret-token" }
    }
  }
}

Check it is up:

curl http://127.0.0.1:8080/health
# {"status":"healthy","server":"jira-mcp-server","version":"0.2.0","transport":"http","jira_configured":true}

To require a client token (recommended for anything beyond localhost), start with:

jira-mcp-server --transport http --port 8080 --auth-mode token --server-token your-secret-token

The token protects who can connect to the MCP server; the Jira credentials (JIRA_API_TOKEN) still govern what the server can do in Jira.

Client token (HTTP auth)

In HTTP/SSE mode you can require clients to authenticate with a static Client token before the MCP session is allowed. Think of it as the server's "front door" password: it decides who may connect, whereas JIRA_API_TOKEN decides what Jira operations the server may perform.

Create a client token

Any reasonably long random string works (the server compares the presented token by exact string equality). Generate one with secrets, so it cannot be guessed:

# Python is cross-platform and already available (the project requires it).
python -c "import secrets; print(secrets.token_urlsafe(32))"
# e.g. 9xKp2vY6QwE3rT8zU1iA0sD5fG7hJ4kN6lM9oP2qR5uW8

Configure the server side

Enable token auth by supplying the same client token through one of:

# Option A — CLI flag (highest precedence).
jira-mcp-server --transport http --port 8080 \
  --auth-mode token --server-token 9xKp2vY6QwE3rT8zU1iA0sD5fG7hJ4kN6lM9oP2qR5uW8
# Option B — environment variables (equivalent; avoids the token in shell history).
export MCP_AUTH_MODE=token
export MCP_SERVER_TOKEN=9xKp2vY6QwE3rT8zU1iA0sD5fG7hJ4kN6lM9oP2qR5uW8
jira-mcp-server --transport http --port 8080
# Option C — docker-compose (environment passthrough, already wired).
environment:
  JIRA_TOOLS: ${JIRA_TOOLS:-}
  MCP_TRANSPORT: http
  MCP_AUTH_MODE: token
  MCP_SERVER_TOKEN: ${MCP_SERVER_TOKEN:-}

Set both MCP_AUTH_MODE=token and MCP_SERVER_TOKEN together. Note the exact behavior when one is missing:

  • MCP_AUTH_MODE=token and no MCP_SERVER_TOKEN → the server logs a warning at startup and serves unauthenticated (no auth middleware is attached). This is a footgun: if you intend to require a client token, set the token too.
  • MCP_AUTH_MODE=none (or unset) → no client auth at all, regardless of MCP_SERVER_TOKEN.

Configure the client side

The client sends the same token in an Authorization: Bearer … header:

{
  "mcpServers": {
    "jira": {
      "url": "http://127.0.0.1:8080/sse",
      "headers": { "Authorization": "Bearer 9xKp2vY6QwE3rT8zU1iA0sD5fG7hJ4kN6lM9oP2qR5uW8" }
    }
  }
}

Claude Code uses --header; Claude Desktop and Cursor/VS Code use the headers map above:

claude mcp add jira --scope project \
  --url http://127.0.0.1:8080/sse \
  --header "Authorization: Bearer 9xKp2vY6QwE3rT8zU1iA0sD5fG7hJ4kN6lM9oP2qR5uW8"

Sanity-check both sides with curl:

curl -i http://127.0.0.1:8080/health | head -1        # 401 without a token
curl -i -H "Authorization: Bearer 9xKp2vY6QwE3rT8zU1iA0sD5fG7hJ4kN6lM9oP2qR5uW8" \
  http://127.0.0.1:8080/health | head -1              # 200 with the token

Rotate the token

Generate a new token (see above), restart the server with it, and update every client config. The two-layer layout (server process holds the Jira credentials, clients only hold the client token) means rotating the client token does not require reissuing Jira tokens.

Connecting to Claude Code

Claude Code registers local MCP servers as stdio commands or remote HTTP URLs. With the package installed (so jira-mcp-server is on PATH — on Windows that means D:\develop\Python312\Scripts\jira-mcp-server.exe exists). If the executable is not on your machine yet, generate it from source first (see Building the jira-mcp-server.exe console script), then return here. From the project root:

stdio (recommended for a single user)

Configure the Jira connection on the server process, never in the client: the stdio command that Claude Code stores and runs must not contain JIRA_API_TOKEN, the base URL, or even a config-file path — because Claude Code shows the whole command to the AI.

Step 1 — put a config file next to the server (in the interpreter's Scripts directory where jira-mcp-server.exe lives, OR where the MCP client launches the server — both are auto-detected). The server auto-detects a fixed-named file, so you never pass its location:

# jira_server.toml — server-side only, never commit, permissions 0600.
# Put this in the same Scripts directory as jira-mcp-server.exe (or the server's
# working directory). The server finds it automatically on startup; the MCP
# command stays a bare `jira-mcp-server`.
base_url = "https://your-domain.atlassian.net"
auth_method = "basic"
user_email = "you@email.com"
api_token = "ATATT3YOUR_REAL_TOKEN"
tools = "read,create,update"

(jira_server.json works too, or a KEY=VALUE .env file in the same directory — see Configuration.)

Step 2 — register the server with a bare command in Claude Code:

# No --config, no --token-file, no -e/--env: the server reads jira_server.toml
# from its own directory automatically. Nothing sensitive (or even a path to
# something sensitive) is stored in .mcp.json.
claude mcp add jira --scope project \
  -- jira-mcp-server --transport stdio

The server discovers jira_server.toml (or jira_server.json / .env) in two places: the working directory it is launched from, and the interpreter's Scripts directory where jira-mcp-server.exe lives. That is why "next to the server" works: no path is ever passed, so nothing to change per client, and you can drop the file next to the exe even if your MCP client starts the server from a different cwd. --token-file is a manual fallback that merges KEY=VALUE entries into the process environment.

Notes:

  • None of the strategy above puts a secret or a path on the command line. The MCP client stores only jira-mcp-server --transport stdio; the token and its location are never shown to the model.
  • Config file values take precedence over environment variables for the fields the file sets; the environment fills any field the file leaves out.
  • --token-file / env vars still work if you prefer them (see Configuration), but the file-next-to-server setup is the least exposed.
  • Keep credentials off the claude mcp add … command line entirely. Do NOT pass -e/--env JIRA_API_TOKEN=…. If you must use environment variables, export them in the shell that launches claude (not into the MCP config) so the child process inherits them:
    export JIRA_BASE_URL=https://your-domain.atlassian.net
    export JIRA_USER_EMAIL=you@email.com
    export JIRA_API_TOKEN=ATATT3YOUR_REAL_TOKEN
    export JIRA_AUTH_METHOD=basic
    export JIRA_TOOLS=read,create,update
    claude mcp add jira --scope project \
      -e JIRA_BASE_URL="$JIRA_BASE_URL" \
      -e JIRA_USER_EMAIL="$JIRA_USER_EMAIL" \
      -e JIRA_AUTH_METHOD=basic \
      -e JIRA_TOOLS="$JIRA_TOOLS" \
      -- jira-mcp-server
    
    Exporting keeps the values out of the persisted client config, but they still cross the process boundary into the stdio child. jira_server.toml next to the server avoids even that.

Verify:

claude mcp list            # jira: Command - ✔ Connected
claude mcp get jira        # shows the resolved command; credentials NOT listed

Restart Claude Code (or run /mcp to check connection status) and start a new session — the jira_* tools will be available.

If the server fails to start with Jira API Token is required when JIRA_AUTH_METHOD=basic, the most likely cause is that JIRA_API_TOKEN was not visible to the stdio child process. Fix it by pointing --token-file at a readable file (or exporting the variables before launching claude) — never by adding the token to the client config.

HTTP/SSE (team-shared / multi-client)

# terminal 1: start the service (it holds the Jira credentials)
export JIRA_BASE_URL=https://your-domain.atlassian.net
export JIRA_USER_EMAIL=you@email.com
export JIRA_API_TOKEN=<token>
export JIRA_TOOLS=read,create,update
jira-mcp-server --transport http --host 127.0.0.1 --port 8080 \
  --auth-mode token --server-token <client-token>

# terminal 2: register the URL in Claude Code
claude mcp add jira --scope project \
  --url http://127.0.0.1:8080/sse \
  --header "Authorization: Bearer <client-token>"

This gives two-layer auth: <client-token> guards who may connect to the MCP server, JIRA_API_TOKEN governs what it can do in Jira. The <client-token> placeholder is the shared secret you generate yourself (see Client token (HTTP auth)) — it is not the Jira API token.

Because the Jira credentials live in the server process (terminal 1), they never appear in Claude Code's mcp.json/.mcp.json — an advantage of the HTTP layout when you want to keep JIRA_API_TOKEN out of the client config.

Environment variables

All JIRA_* variables are configured on the MCP server process — in a jira_server.toml/jira_server.json next to the server (preferred), its environment, its .env file, or a --token-file file — never on the MCP client. In stdio mode the command the client stores and runs must not carry these values, or the AI would see the token.

Precedence (highest first): auto-discovered config file next to the server (jira_server.toml / jira_server.json in the working directory or in the interpreter's Scripts dir) → env vars / .env--token-file. For each option, only the fields it actually sets are applied, so the config file can hold just the secrets while env supplies the rest.

Variable Type Required Default Mode Description Example
JIRA_BASE_URL str yes –(unset) all Jira instance URL (server-side); must be https:// https://acme.atlassian.net
JIRA_AUTH_METHOD enum yes basic all basic (Cloud) or bearer (Data Center) basic
JIRA_USER_EMAIL str if basic all Account email for Cloud Basic auth dev@acme.com
JIRA_API_TOKEN str yes all Jira API token (Cloud) or PAT (Server) ATATT3xxxx…
JIRA_PROJECT_KEYS str no all Comma-separated allowlist; reads scoped & writes blocked outside it ENG,SALES
JIRA_TOOLS str no all Tool permission allowlist (read/create/update/delete/write or exact tool names); empty ⇒ all read,create,update
JIRA_SEARCH_ENGINE enum no jql all Issue search API: jql (default) → enhanced POST /rest/api/3/search/jql; getGET /rest/api/3/search/jql; auto → alias for jql. The old /rest/api/3/search was removed by Jira Cloud (410) get
JIRA_RATE_LIMIT int no 100 all Client-side requests/minute (token bucket) 200
JIRA_REQUEST_TIMEOUT float no 30 all Per-request timeout (s) 30
JIRA_CONNECT_TIMEOUT float no 10 all Connection-establishment timeout (s) 10
JIRA_TOKEN_FILE str no both Path to a server-side KEY=VALUE credentials file merged into the process env /etc/jira-mcp/secrets.env
HTTPS_PROXY str no all Outbound proxy (honored by httpx trust_env) https://proxy:8080
MCP_TRANSPORT enum no stdio both stdio, http (SSE), or http-streamable http
MCP_HOST str no 127.0.0.1 http Bind address (use 127.0.0.1 + reverse proxy in prod) 0.0.0.0
MCP_PORT int no 8080 http TCP port 8080
MCP_AUTH_MODE enum no none http none or token token
MCP_SERVER_TOKEN str if token http Bearer token clients must send change-me
MCP_CORS_ORIGINS str no * http Comma-separated allowed origins https://app.example.com
MCP_LOG_LEVEL enum no INFO all DEBUG/INFO/WARNING/ERROR DEBUG
MCP_LOG_FILE str no all Log file; empty ⇒ stderr (never stdout) /var/log/jira-mcp.log

Server-side config file (TOML or JSON)

The recommended way to configure the Jira connection: put a fixed-name config file next to the serverjira_server.toml (or jira_server.json) in the interpreter's Scripts directory (the folder where jira-mcp-server.exe lives) or in the directory the MCP client launches the server from. The server auto-discovers it in either place, so the MCP launch command stays a bare jira-mcp-server with no --config, no path, and no environment variables — nothing sensitive (or even a path to something sensitive) is ever stored in or shown to the MCP client.

The file mirrors the JIRA_* variables without the prefix and takes precedence over them. Only the fields present are applied, so you can put just the secrets (e.g. api_token) in the file and leave the rest to the environment:

# jira_server.toml — server-side only, never commit, permissions 0600.
# Put it in the same directory as the server; the server auto-discovers it and
# the MCP command stays a bare `jira-mcp-server`.
base_url = "https://acme.atlassian.net"
auth_method = "basic"
user_email = "dev@acme.com"
api_token = "ATATT3YourSecretToken0001"
project_keys = "ENG,SALES"
tools = "read,create,update"
search_engine = "get"        # optional; default "jql" → enhanced POST /rest/api/3/search/jql
rate_limit = 200

JSON works too (flat, or nested under a "jira" key), and tools in the file is honored exactly like JIRA_TOOLS (it restricts which MCP tools are exposed). search_engine mirrors JIRA_SEARCH_ENGINE. Values never enter the environment, so they are invisible to the MCP client and its AI; the file should be chmod 0600 and never committed.

Copy .env.example.env (next to the server installation) for local dev. CLI flags take precedence over environment variables.

CLI reference

jira-mcp-server [OPTIONS]

Options:
  --transport [stdio|http|http-streamable]  Transport mode (default: stdio; env MCP_TRANSPORT)
  --token-file TEXT        Server-side credentials file (KEY=VALUE); JIRA_* entries are
                           merged into the process env, never into the client command (env JIRA_TOKEN_FILE)
  --search-engine [jql|get|auto]  Issue search API (default: jql → enhanced POST
                           /rest/api/3/search/jql; get → GET /rest/api/3/search/jql;
                           auto → alias for jql; the old /rest/api/3/search was
                           removed by Jira Cloud, returning 410) (env JIRA_SEARCH_ENGINE)
  --host TEXT                HTTP bind host (default: 127.0.0.1; env MCP_HOST)
  --port INTEGER             HTTP bind port (default: 8080; env MCP_PORT)
  --auth-mode [none|token]   HTTP client auth (default: none; env MCP_AUTH_MODE)
  --server-token TEXT        Bearer token for client connections (env MCP_SERVER_TOKEN)
  --cors-origins TEXT        CORS origins, comma-separated (default: *)
  --log-level [DEBUG|INFO|WARNING|ERROR]  Logging level (env MCP_LOG_LEVEL)
  --log-file TEXT            Optional log file (env MCP_LOG_FILE)
  --version                  Print version and exit
  --help                     Show help

Transports

Characteristic stdio HTTP/SSE
Best for single local user remote / team-shared / CI
Deployment spawned by the client standalone service
Clients Claude Desktop, Cursor, VS Code any MCP SSE/Streamable client
Concurrency one client many clients
Jira credentials server-side env / .env / --token-file server-side env / .env
Client auth none (credentials not offered to client) Bearer token (MCP_SERVER_TOKEN)
Network local only network reachable
Endpoint stdin/stdout /sse + /messages/ (SSE) or /mcp (streamable); /health, /

Tools

All tools return JSON text. On failure they return {"isError": true, "content": [{"type": "text", "text": "Jira API Error [403]: …"}]}.

Issues

Tool Description Key params
jira_create_issue Create an issue (auto-converts description to ADF) project_key, summary, issue_type; optional description, priority, assignee_account_id, labels, components, custom_fields, parent_key
jira_update_issue Update fields issue_key, fields
jira_get_issue Get full issue issue_key, fields*, expand*
jira_delete_issue Delete (guarded) issue_key, confirm (must be true), delete_subtasks*
jira_transition_issue Transition by id or status name issue_key, transition_id*/target_status*, comment*, fields*
jira_get_transitions List available transitions issue_key
jira_add_comment Add a comment issue_key, body, visibility*, visibility_value*
jira_get_comments List comments issue_key, max_results*, start_at*
jira_link_issues Create a link between issues inward_issue_key, outward_issue_key, link_type, comment*
jira_get_issue_links List links issue_key
jira_search_issues JQL search (whitelist-scoped) via the enhanced POST /rest/api/3/search/jql (or GET variant when JIRA_SEARCH_ENGINE=get); includes readable fields by default and annotates the effective scope jql, max_results*, start_at*, fields*, expand*, fields_by_keys*
jira_search_issues_jql_only Compact JQL search (key/summary/status/assignee) on the enhanced search API jql, max_results* (default 20)

Sprints / Boards

Tool Description Key params
jira_list_boards List boards project_key*, board_type*
jira_list_sprints List sprints board_id, state*, max_results*
jira_get_sprint_issues Issues in a sprint sprint_id
jira_move_issues_to_sprint Move issues to a sprint sprint_id, issue_keys

Projects

Tool Description Key params
jira_list_projects List accessible projects max_results*
jira_get_project Get project detail project_key
jira_get_project_versions List project versions project_key

Users

Tool Description Key params
jira_search_users Search users (privacy-filtered) query, max_results*
jira_get_myself Authenticated user info / debug auth

Attachments

Tool Description Key params
jira_add_attachment Upload a file (≤ 10 MiB) issue_key, file_path
jira_list_attachments List attachments issue_key

Worklogs

Tool Description Key params
jira_add_worklog Log time issue_key, time_spent (e.g. 2h 30m), comment*, started*
jira_get_worklogs List worklogs issue_key

Example return value

jira_create_issue(project_key="PROJ", summary="Login fails", issue_type="Bug")

{"issue":{"id":"10004","key":"PROJ-9","self":"https://acme.atlassian.net/rest/api/3/issue/10004"}}

Resources

MCP resources expose read-only context the model can reference:

URI (template) Content
jira://projects Static listing of accessible projects (key – name).
jira://project/{project_key}/meta Create-issue metadata: issue types + editable fields.
jira://issue/{issue_key} Live snapshot of a single issue.
jira://issue/{issue_key}/transitions Available workflow transitions.

Reading jira://project/PROJ/meta, for example, returns JSON such as:

{"projects": [{"key": "PROJ", "issuetypes": [{"name": "Bug", "fields": {"summary": {"required": true}}}]}]}

Prompts

Prompts guide the model through structured workflows:

Prompt Description Variables
create_bug_report Draft a structured Bug + create it project_key, summary, steps_to_reproduce, expected_behavior, actual_behavior, severity
sprint_review_summary Summarize a sprint for review sprint_id
triage_issue Recommend priority/component/assignee issue_key
daily_standup_report Group last-24h issue changes by people project_key, sprint_id (optional)

Example — after invoking create_bug_report the model will assemble the details and call jira_create_issue automatically.

Tool permissions (CRUD control)

A Jira API token is often granted broad rights (e.g. create, edit, delete), but a given deployment may only need a subset of them. Rather than maintaining a separate token per workflow, the server can restrict which tools it exposes via JIRA_TOOLS. Disabled tools are never registered, so clients cannot see them in tools/list — an over-permissioned token can still only drive the tools you opt in to.

JIRA_TOOLS is a comma-separated allowlist of category keywords and/or exact tool names. Empty or unset keeps today's behavior (all tools enabled).

Keyword Effect
read All read-only tools: jira_get_*, jira_list_*, jira_search_*, jira_get_*_meta. No mutating operations.
create Create issues, add comments / attachments / worklogs, link issues, move issues to sprints.
update Update issue fields and transition issues.
delete jira_delete_issue.
write Shorthand for create,update,delete.

Examples:

# Read-only deployment (the Jira token may be full-admin; clients can only read).
JIRA_TOOLS=read

# Everything except deletion.
JIRA_TOOLS=read,create,update

# Read + exactly one extra tool.
JIRA_TOOLS=read,jira_add_comment

Notes:

  • Values are case-insensitive; entries must be a known keyword or a real tool name. A typo (e.g. JIRA_TOOLS=rede) fails startup instead of silently dropping tools.

  • The current tool-category mapping is:

    Category Tools
    read jira_get_issue, jira_get_transitions, jira_get_comments, jira_get_issue_links, jira_get_issue_meta, jira_get_project_meta, jira_search_issues, jira_search_issues_jql_only, jira_list_projects, jira_get_project, jira_get_project_versions, jira_list_boards, jira_list_sprints, jira_get_sprint_issues, jira_list_attachments, jira_get_worklogs, jira_search_users, jira_get_myself
    create jira_create_issue, jira_add_comment, jira_add_attachment, jira_add_worklog, jira_link_issues, jira_move_issues_to_sprint
    update jira_update_issue, jira_transition_issue
    delete jira_delete_issue

This layer protects the tool surface, not Jira itself. Authorization also follows JIRA_PROJECT_KEYS: combine both to scope by operation and by project (e.g. JIRA_TOOLS=read JIRA_PROJECT_KEYS=ENG = read-only on one project).

Security & best practices

  • HTTPS onlyJIRA_BASE_URL must start with https://; plaintext URLs are refused at startup.
  • No hardcoded credentials — everything comes from the server environment or server-side files.
  • Server-side credentials onlyJIRA_API_TOKEN (and the base URL / user email) are configured on the MCP server process, never in the MCP client config or on the stdio command. In stdio mode an AI model runs inside the client; a token in the client config would be visible to it. Keep the token in a server-side jira_server.toml/jira_server.json (preferred), .env, or --token-file next to the installation.
  • Config file keeps secrets out of the environment — for maximum protection, put api_token in the auto-discovered jira_server.toml/jira_server.json (read by the server only), so the value never appears in any environment variable that a client or process dump could expose, and the MCP launch command needs no path to it.
  • Token masking — tokens are logged as ATATT****, never in full.
  • Never send MCP clients the Jira credentials — the server exposes JQL as MCP tools (so the model never needs to "find the stored credentials" to query Jira); the credentials live only in the server-side file/environment.
  • Current search API — issue search uses the enhanced POST /rest/api/3/search/jql by default (the only search endpoint Jira Cloud still serves; the old /rest/api/3/search returns 410), falling back to its GET variant with JIRA_SEARCH_ENGINE=get. Search requests include a readable default field set so results always carry key, summary, status, and assignee.
  • Project whitelist — set JIRA_PROJECT_KEYS=ENG,SALES to scope all JQL searches and block writes to other projects.
  • Tool whitelistJIRA_TOOLS restricts which tools are exposed (see Tool permissions); use it to keep an over-permissioned token from driving destructive tools.
  • Deletion guardjira_delete_issue requires confirm=true.
  • Attachment guard — uploads over 10 MiB are rejected.
  • Rate limiting — client-side token bucket (default 100 req/min).
  • HTTP transport auth — enable --auth-mode token and a strong MCP_SERVER_TOKEN for anything networked.
  • Production HTTP — bind 127.0.0.1 and put the server behind a reverse proxy (nginx/Caddy) that terminates TLS; restrict the port to the proxy and approved clients; enable request/audit logging.
  • Rotate tokens — use distinct tokens per environment, rotate quarterly (or on any suspected leak), and remove users from Jira when they leave.
  • Least privilege — give the token user only the Jira permissions the workflows need (browse + create/edit specific projects; not Global Admin).

Docker deployment

Dockerfile (included)

FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml README.md ./
COPY src/ src/
RUN pip install --no-cache-dir .
ENV MCP_TRANSPORT=http MCP_HOST=0.0.0.0 MCP_PORT=8080 JIRA_AUTH_METHOD=basic JIRA_TOOLS=read,create,update
EXPOSE 8080
CMD ["jira-mcp-server", "--transport", "http", "--host", "0.0.0.0", "--port", "8080"]

docker-compose.yml (included)

services:
  jira-mcp:
    build: .
    ports: ["8080:8080"]
    environment:
      JIRA_BASE_URL: ${JIRA_BASE_URL}
      JIRA_API_TOKEN: ${JIRA_API_TOKEN}
      JIRA_USER_EMAIL: ${JIRA_USER_EMAIL}
      JIRA_PROJECT_KEYS: ${JIRA_PROJECT_KEYS:-}
      JIRA_TOOLS: ${JIRA_TOOLS:-}
      MCP_TRANSPORT: http
      MCP_HOST: 0.0.0.0
      MCP_PORT: 8080
      MCP_AUTH_MODE: ${MCP_AUTH_MODE:-none}
      MCP_SERVER_TOKEN: ${MCP_SERVER_TOKEN:-}
    restart: unless-stopped
docker compose up -d                        # reads .env for JIRA_* / MCP_SERVER_TOKEN
curl http://127.0.0.1:8080/health

Kubernetes (example)

apiVersion: apps/v1
kind: Deployment
metadata: { name: jira-mcp }
spec:
  replicas: 2
  template:
    metadata: { labels: { app: jira-mcp } }
    spec:
      containers:
        - name: jira-mcp
          image: your-registry/jira-mcp-server:latest
          ports: [{ containerPort: 8080 }]
          envFrom: [{ secretRef: { name: jira-mcp-secrets } }]
          readinessProbe:
            httpGet: { path: /health, port: 8080 }
---
apiVersion: v1
kind: Service
metadata: { name: jira-mcp }
spec:
  selector: { app: jira-mcp }
  ports: [{ port: 8080, targetPort: 8080 }]

Troubleshooting

Symptom Likely cause Fix
Jira API Error [401] Jira token missing/wrong/expired Regenerate at the API-token page; re-run
Jira API Error [403] Token lacks permission for the operation Grant the user the Jira permission or widen API token scope
Jira API Error [404] Wrong issue/project key Verify the key; check the project exists and is accessible
Jira API Error [400] Invalid field/value for the transition Read the message; adjust fields
Jira API Error [429] Jira's own rate limit Slow down; honor retry-after; raise JIRA_RATE_LIMIT
Connection timed out Wrong URL / blocked egress / proxy Check JIRA_BASE_URL, outbound network, HTTPS_PROXY
Request validation failed on /sse DNS-rebinding protection from the SDK Use a real Host header; the SDK allows 127.0.0.1/localhost as default. For proxies, pass --host 0.0.0.0 or a configured host
SSE drops then reconnects Idle/long-lived connection timeout on the network Reconnect is automatic in MCP clients; check proxies for < SSE > 60s timeouts
401 on the HTTP endpoint --auth-mode token wrong/absent Authorization Send Authorization: Bearer <MCP_SERVER_TOKEN>
Connection closed / -32000 in Claude Code stdio The server crashed at startup (often JIRA_USER_EMAIL required …): the config file wasn't found, so JIRA_BASE_URL/token weren't set Drop jira_server.toml next to jira-mcp-server.exe (in the Python Scripts dir) and restart; run jira-mcp-server --transport stdio manually to see the error

Diagnosing Jira auth quickly: run

jira-mcp-server --transport stdio
# and in a client, call jira_get_myself — it returns the Jira user the token resolves to.

Rate limiting: the client limiter blocks the calling task instead of erroring, so batch prompts just run a little slower. Jira's own limits are upstream of the token; space out CI loops and use max_results liberally.

Development

Add a new tool

  1. Open src/jira_mcp_server/tools/issues.py (or the matching module).
  2. Inside the existing register(registry) function add:
@registry.tool(name="my_new_tool", title="...", description="...")
async def my_new_tool(
    ctx: Context,
    param1: Annotated[str, Field(description="...")],
    optional: Annotated[int | None, Field(default=None, description="...")] = None,
) -> Any:
    """Docstring used as the default description."""
    client = get_client()
    try:
        data = await client.some_endpoint(param1)
        return dict_result(data, label="result")
    except Exception as exc:
        return error_result(exc)
  1. Add the matching client method in src/jira_mcp_server/client.py.
  2. Run the tests.

Add a new resource

In src/jira_mcp_server/tools/resources.py, inside register():

@registry.resource("jira://issue/{issue_key}/comments")
def comments_for_issue(issue_key: str) -> str:
    return json_dumps(get_client().get_comments_blocking_for_resource(issue_key))

Tests

pytest                                # full suite
pytest -m "not network"               # offline unit + integration tests
pytest tests/test_transport_http.py   # HTTP/SSE app tests

The suite uses pytest + pytest-asyncio; HTTP tests run against an in-process Starlette app via httpx.ASGITransport (no live network).

Style

ruff (lint + format) and mypy are configured in pyproject.toml:

ruff check src tests
ruff format --check src tests
mypy src

Commit messages: conventional-commit style (e.g. feat: add jira_export_issues). Changes land via PR; each PR must pass lint, type checks, and the test suite.

Building wheel / sdist

Distribution artifacts (wheel for pip install, sdist for source) are built with build — a build-time tool, not a runtime dependency, so it is installed into the virtual environment on demand but never added to pyproject.toml.

Windows — scripts\build.bat

scripts\build.bat

If .venv does not exist it is created; then the script installs build into it, clears stale build\ / dist\ / *.egg-info, and runs python -m build. Artifacts land in dist\:

File Purpose
jira_mcp_server-<version>-py3-none-any.whl Binary distribution; use directly with pip install
jira_mcp_server-<version>.tar.gz Source distribution (sdist); rebuild the wheel from source

Windows note: do not delete .venv from inside the script — a live Python holds locks on native .pyd/.dll files. Recreate it from a shell that is not using it: rmdir /s /q .venv && python -m venv .venv.

macOS / Linux (steps this script automates)

python -m venv .venv
source .venv/bin/activate
pip install build            # build-time tool only; not a project dependency
python -m build              # builds wheel + sdist into dist/

What goes into each artifact

  • Wheel — the installed package: all src/jira_mcp_server/** modules, the py.typed type marker, LICENSE, entry point, and METADATA/RECORD. Tests and .env.example are not installed (they are dev-only).
  • sdist — self-contained source: src/, tests/, pyproject.toml, MANIFEST.in, LICENSE, README.md, py.typed, and .env.example. python -m build verifies the sdist by rebuilding the wheel from it, so a broken manifest fails the build.

Release checklist (before building)

  1. Bump version in pyproject.toml and src/jira_mcp_server/__init__.py (they must match — the CLI reads the latter, packaging the former).
  2. Update README.md ## Changelog; verify jira-mcp-server --version.
  3. Run python -m pytest (all green), ruff check src tests, mypy src.
  4. scripts\build.bat (or the macOS/Linux commands above).
  5. Inspect dist\ — exactly one .whl and one .tar.gz for the new version.

Publishing to PyPI

The artifacts in dist\ are ready to upload once they pass the checklist above. Tags/releases are outside this repo's automation; push a Git tag (v<version>) and upload:

pip install twine              # upload tool; build/publish-time only
twine check dist\*.whl dist\*.tar.gz     # verify metadata + long description
twine upload dist\jira_mcp_server-<version>-py3-none-any.whl \
              dist\jira_mcp_server-<version>.tar.gz

twine upload prompts for the PyPI API token (or use TWINE_USERNAME=__token__ TWINE_PASSWORD=<token> for CI). The project declares its metadata (name, version, description, license, classifiers, [project.urls]) in pyproject.toml, so no setup.py is needed.

Version discipline: never upload as 0.2.0 twice — PyPI rejects an existing version. Bump pyproject.toml + src/jira_mcp_server/__init__.py in lockstep and rebuild before every release.

Verify the published package

Test the exact artifact before announcing a release:

python -m venv /tmp/verify && /tmp/verify/bin/python -m pip install \
  dist\jira_mcp_server-<version>-py3-none-any.whl
/tmp/verify/bin/jira-mcp-server --version

Building the jira-mcp-server.exe console script

There is no separate "build" step for the executable — on Windows it is created automatically when you install the project, from the [project.scripts] entry point in pyproject.toml:

[project.scripts]
jira-mcp-server = "jira_mcp_server.cli:app"

What the entry point produces

pip (via the setuptools backend) generates a small launcher:

  • On Windows: jira-mcp-server.exe inside the Python environment's Scripts folder (e.g. D:\develop\Python312\Scripts\jira-mcp-server.exe, or .venv\Scripts\jira-mcp-server.exe when using a virtual env).
  • On macOS/Linux: a jira-mcp-server shell script on PATH.

The launcher is a thin shim that imports jira_mcp_server.cli and calls app (a Typer command). All real logic lives in src/; the .exe is just a starter.

Build it from source (Windows)

# 1. Create and activate a virtual environment.
python -m venv .venv
.\.venv\Scripts\Activate.ps1

# 2. Install the project (editable keeps src/ live; plain install copies it).
#    Either is fine. "-e" means you can edit src/ and restart the MCP client
#    without reinstalling.
python -m pip install -e ".[dev]"      # editable, recommended for development
# python -m pip install .              # regular install, recommended for a fixed version

# 3. The .exe is now on PATH inside the venv (or the base Scripts dir).
jira-mcp-server --version              # -> jira-mcp-server 0.2.0

Verify

python -m jira_mcp_server --version   # same behavior as the exe
jira-mcp-server --version             # the generated launcher/exe

If jira-mcp-server is not found, ensure the environment's Scripts directory is on PATH (Python's installer usually adds it). To reinstall after deleting the launcher, re-run pip install -e ..

Editable vs. regular install

  • pip install -e . ("editable") registers the launcher but points it back at your src/ tree. Edit source, restart the MCP client, and the change is picked up without reinstalling — the normal choice while developing.
  • pip install . ("regular") copies the package into the environment's site-packages; the launcher runs that frozen copy. Use it for a fixed version you do not expect to edit.

Both commands produce the same jira-mcp-server.exe; only where the code lives differs.

Build without a virtual environment

If you installed Python globally and cannot or do not want a venv, run pip install -e . directly. The launcher then lands in the base interpreter's Scripts folder (e.g. D:\develop\Python312\Scripts\jira-mcp-server.exe), which must be on PATH for MCP clients to find the jira-mcp-server command.

Architecture / how to extend

src/jira_mcp_server/
├── cli.py            # typer CLI → transport selection
├── server.py         # create_server(): MCPServer + registration
├── config.py         # pydantic-settings (JIRA_* env), validated, lazy
├── safety.py         # credential masking / normalization
├── permissions.py    # JIRA_TOOLS allowlist: CRUD categories → tool sets
├── client.py         # JiraClient: httpx async, retry, pagination, scope
├── auth.py           # Basic / Bearer header construction
├── errors.py         # JiraError hierarchy + status mapping
├── formatters.py     # ADF ⇄ plain text
├── validators.py     # safe JQL building
├── rate_limiter.py   # token bucket
├── middleware.py     # HTTP auth / logging / CORS middleware + /health
├── transport/
│   ├── stdio.py      # stdio runner
│   ├── http.py       # SSE + Streamable HTTP apps (extends SDK app)
│   └── logging.py    # stderr/file logging, sensitive filter
└── tools/
    ├── core.py       # ToolRegistry, register_server, shared client
    ├── serde.py      # CallToolResult helpers
    ├── issues.py     # 14 issue tools
    ├── projects.py   # 3 project tools
    ├── sprints.py    # 4 board/sprint tools
    ├── users.py      # 2 user tools
    ├── attachments.py# 2 attachment tools
    ├── worklog.py    # 2 worklog tools
    ├── resources.py  # 4 MCP resources
    └── prompts.py    # 4 MCP prompts

Planned / extension points

  • Webhook events (the transport layer is decoupled so an HTTP Webhook route can be added without touching the tools).
  • Multi-Jira-instance support (the config layer is a single Settings object; a future MCP_INSTANCES could create one per instance).
  • Per-tool granular Prompts / completion metadata.

Changelog

v0.3.0 (2026-08-07)

  • Search results now always carry issue keys — the enhanced /rest/api/3/search/jql endpoint returns bare {"id": ...} records unless a fields array is requested, so jira_search_issues without an explicit fields used to surface empty keys/summaries (models interpreted this as "no results"). Searches now request a readable default field set (summary, status, assignee) so every result includes key and the core fields.
  • Search results are annotated with the effective project scope — when a JIRA_PROJECT_KEYS allowlist is configured, search payloads now include the allowed projects (and a note when a query matches nothing), so a model understands when the allowlist — not a bad query — is why a search returns no rows for a project outside the whitelist.
  • Config file discovery now also checks the interpreter's Scripts directoryjira_server.toml/jira_server.json placed next to jira-mcp-server.exe (in <python>/Scripts or <venv>/Scripts) is found even when the MCP client launches the server from a different working directory. This fixes the Windows failure where Claude Code reported "Connection closed / -32000" because the server crashed at startup with no config (the toml was next to the exe, but only the cwd was searched).
  • Issue search moved off the deprecated endpointjira_search_issues / jira_search_issues_jql_only now use the current Jira search API: GET /rest/api/3/search by default (documented by Atlassian as the primary search endpoint), and POST /rest/api/3/search/jql (enhanced search, with fields_by_keys support) when JIRA_SEARCH_ENGINE=jql. The deprecated POST /rest/api/3/search is never called, so tools no longer look "legacy" and models have no reason to try to reach past the MCP tools for Jira data.
  • CLI/config: added --search-engine (env JIRA_SEARCH_ENGINE; also settable as search_engine in jira_server.toml/jira_server.json), validated at startup. Startup logs now state which search API is enabled.
  • Credential guard on search results — issue-search tool output is sanitized server-side: any key named like a credential (token, password, credentials, authorization, access_token, …) is stripped, and token-like strings inside nested values are masked. Even if Jira (or a proxy) ever returned such a field, the model would never see it — which also means the model has no need to seek out the server's stored credentials itself.
  • Docs: README tool table, env-var table, CLI reference, config-file section, and security checklist updated for the search engine and the "use the MCP tools, never reach for stored credentials" note.

v0.2.0 (2026-08-06)

  • Credentials are server-side only (JIRA_API_TOKEN out of the MCP client) — the Jira base URL, user email, and API token are no longer accepted as MCP client parameters in stdio mode: passing them through the client config would expose the token to the AI running in the client. The server reads them from its own environment (a .env file next to the installation) or from a separate --token-file/JIRA_TOKEN_FILE KEY=VALUE file. All stdio examples in this document now register a credential-free command. JIRA_BASE_URL is resolved server-side only (defaults to unset at construction; the validation still refuses plaintext).
  • CLI: added jira-mcp-server --token-file <path> (env JIRA_TOKEN_FILE) which merges JIRA_* entries into the process environment before settings are built; secrets never appear on the client command line.
  • Config file next to the server (auto-discovered) — the server now reads a fixed-name jira_server.toml / jira_server.json in the directory it is launched from, so the whole Jira connection (e.g. api_token, base_url, auth_method) lives in one server-side file with no --config/path on the MCP launch command and without entering any environment variable (visible to MCP clients/AI). tools there restricts the MCP tool surface exactly like JIRA_TOOLS. Existing JIRA_* env / .env / --token-file paths keep working alongside.
  • Tests: test environment is now isolated from real host .env and JIRA_*/MCP_* variables (added config-file load tests and env isolation in tests/conftest.py).
  • Docs: all documentation is now English-only (previously contained Chinese sections and changelog entries).
  • Packaging: added scripts\build.bat and MANIFEST.in, added the missing py.typed PEP 561 marker, and documented wheel/sdist building and PyPI publishing. python -m build now produces a complete jira_mcp_server-<version>-py3-none-any.whl and .tar.gz (previously the sdist was missing pyproject.toml and the wheel lacked py.typed).

v0.1.1 (2026-08-06)

  • Tool permission control (JIRA_TOOLS) — restrict which MCP tools are exposed via comma-separated CRUD keywords (read/create/update/ delete/write) or exact tool names. Disabled tools are not registered, so clients never see them; typos fail startup. See Tool permissions.
  • Docs: added the full from-source install guide (Windows PowerShell/CMD) and how to generate jira-mcp-server.exe locally; added the Client token (HTTP auth) section covering client-token generation, server-side and client-side configuration, and rotation.

v0.1.0 (2026-08-06)

  • Initial release.

Jira MCP Server is not affiliated with, endorsed by, or sponsored by Atlassian. "Jira" is a trademark of Atlassian Pty Ltd.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jira_mcp_server_hopcos-0.2.0.tar.gz (116.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jira_mcp_server_hopcos-0.2.0-py3-none-any.whl (76.4 kB view details)

Uploaded Python 3

File details

Details for the file jira_mcp_server_hopcos-0.2.0.tar.gz.

File metadata

  • Download URL: jira_mcp_server_hopcos-0.2.0.tar.gz
  • Upload date:
  • Size: 116.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.4

File hashes

Hashes for jira_mcp_server_hopcos-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7e0b080ef2b8e5848c225bfbb00bbe7fc9993e9687468a853722aa04fd38187a
MD5 9b6c81fb676c1fc19aa6ff8dd1ceb6d9
BLAKE2b-256 707d7d6e89f5abf8c08850b7e2ea3920141c1fd17e120280621f74199ad5a299

See more details on using hashes here.

File details

Details for the file jira_mcp_server_hopcos-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for jira_mcp_server_hopcos-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6fde58bc92fbadab3455adb3c6fd60723ebf951c3ba353b3d96230d3728dfd8b
MD5 3bb1e8ba3a111fb9a0df17439ae16fee
BLAKE2b-256 746d4e965af97fdcaf47ee5f270a7886d11f0cf8c9e6f79e446c21b4aae56642

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page