jira-mcp-server
A production-ready MCP server enabling AI assistants to interact with Jira projects, issues, sprints, users, attachments, and worklogs via natural language, with support for both local stdio and remote HTTP transports, dual authentication methods, and enterprise-grade security controls.
README
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_URLrefuses plaintext), no credentials in logs (token masking), client-side token-bucket rate limiting, project whitelist enforcement, tool-level CRUD permission control (JIRA_TOOLS),confirmguard 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 ready —
Dockerfile+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 withJIRA_TOOLS(see Tool permissions) to restrict which of those permissions MCP clients can actually drive.
Installation
1. pip
pip install jira-mcp-server
2. uv
uv pip install jira-mcp-server
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)
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
Add this to Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"jira": {
"command": "jira-mcp-server",
"args": ["--transport", "stdio"],
"env": {
"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"
}
}
}
}
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 mcpServers block goes into the MCP configuration of your editor
(~/.cursor/mcp.json for Cursor, or the VS Code MCP panel). The
command stays jira-mcp-server with the same env.
HTTP/SSE mode
Start the service on port 8080:
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.1.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 认证)
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=tokenand noMCP_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 ofMCP_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)
claude mcp add jira --scope project \
--env JIRA_BASE_URL=https://your-domain.atlassian.net \
--env JIRA_USER_EMAIL=you@email.com \
--env JIRA_AUTH_METHOD=basic \
--env JIRA_API_TOKEN=ATATT3YOUR_REAL_TOKEN \
--env JIRA_TOOLS=read,create,update \
jira-mcp-server
JIRA_API_TOKENis required: the server refuses to start withJIRA_AUTH_METHOD=basicunless an API token is present. Include it in the command (or see the safer alternatives below).- Omitting
--scope projectwrites to the global config~/.claude/mcp.json(all projects);--scope projectwrites a project-level.mcp.jsonso credentials are not shared across projects. - You can also pass
--scope localto store the entry in~/.claude.jsonfor the current user only (3.x). - Where does the token come from? The server reads credentials from the
environment that the stdio child process inherits when Claude Code starts
it. Two ways to supply it:
--env JIRA_API_TOKEN=...in the command above (easiest; token is stored in plain text in.mcp.json/mcp.json).- Keep it out of the config by exporting it in the shell that launches
claude(then omit the--env JIRA_API_TOKENline). The child process inherits the exported variable: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 \ --env JIRA_BASE_URL="$JIRA_BASE_URL" \ --env JIRA_USER_EMAIL="$JIRA_USER_EMAIL" \ --env JIRA_AUTH_METHOD=basic \ --env JIRA_TOOLS="$JIRA_TOOLS" \ jira-mcp-server - A project-root
.envfile is not relied upon: the server tries to load it lazily, but only when a code layout allows it, and that is unreliable across installs. Use options 1 or 2.
Verify:
claude mcp list # jira: Command - ✔ Connected
claude mcp get jira # shows the resolved command + env
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 thatJIRA_API_TOKENwas not visible to the stdio child process. Either add it with--env JIRA_API_TOKEN=...or export it before launchingclaude.
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 认证)) — 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
| Variable | Type | Required | Default | Mode | Description | Example |
|---|---|---|---|---|---|---|
JIRA_BASE_URL |
str | yes | – | all | Jira instance URL; 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_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 |
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 |
Copy .env.example → .env 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)
--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 |
| Client auth | per-process env | 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) | jql, max_results*, start_at*, fields*, expand* |
jira_search_issues_jql_only |
Compact JQL search (key/summary/status/assignee) | 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 readjira_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_myselfcreatejira_create_issue,jira_add_comment,jira_add_attachment,jira_add_worklog,jira_link_issues,jira_move_issues_to_sprintupdatejira_update_issue,jira_transition_issuedeletejira_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 only —
JIRA_BASE_URLmust start withhttps://; plaintext URLs are refused at startup. - No hardcoded credentials — everything comes from the environment.
- Token masking — tokens are logged as
ATATT****, never in full. - Project whitelist — set
JIRA_PROJECT_KEYS=ENG,SALESto scope all JQL searches and block writes to other projects. - Tool whitelist —
JIRA_TOOLSrestricts which tools are exposed (see Tool permissions); use it to keep an over-permissioned token from driving destructive tools. - Deletion guard —
jira_delete_issuerequiresconfirm=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 tokenand a strongMCP_SERVER_TOKENfor anything networked. - Production HTTP — bind
127.0.0.1and 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> |
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
- Open
src/jira_mcp_server/tools/issues.py(or the matching module). - 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)
- Add the matching client method in
src/jira_mcp_server/client.py. - 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 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.exeinside the Python environment'sScriptsfolder (e.g.D:\develop\Python312\Scripts\jira-mcp-server.exe, or.venv\Scripts\jira-mcp-server.exewhen using a virtual env). - On macOS/Linux: a
jira-mcp-servershell script onPATH.
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.1.1
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 yoursrc/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'ssite-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
Settingsobject; a futureMCP_INSTANCEScould create one per instance). - Per-tool granular Prompts / completion metadata.
Changelog
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: 补充 基于源码安装(含 Windows PowerShell/CMD 命令)与本地生成
jira-mcp-server.exe的完整说明;新增 Client token (HTTP 认证) 章节,说明 client-token 的生成、服务端与 客户端两侧的配置方式及旋转方法。
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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。