Skip to main content

Jira MCP Server

A comprehensive MCP (Model Context Protocol) server for Jira Cloud integration, built with FastMCP v3. Provides 8 focused tools for complete Jira issue and project management, with automatic ADF formatting, multi-tenant HTTP support, and interactive Elicitation for guided issue creation.

Version 3.1.0 — Single-server FastMCP v3 rewrite with multi-tenant support, Elicitation, resource templates, and 118 tests. Published on PyPI.

Features

  • 8 Focused Tools for complete Jira issue and project management
  • Interactive Elicitationissue_create and issue_delete interactively request missing or confirmatory input
  • Streamable-HTTP + Stdio Transport — run as an HTTP server or stdio for Claude Desktop
  • Multi-Tenant HTTP — different clients can connect with different Jira credentials per request
  • Smart Field Normalization — case-insensitive types/priorities, display-name assignee resolution, component name-to-ID lookup
  • Automatic ADF Formatting — plain text and Markdown auto-converted to Atlassian Document Format
  • Transition Resolution — use transition names ("In Progress") instead of numeric IDs
  • JQL Search with verbosity control (ids / summary / full)
  • Jira Cloud API v3 compatibility
  • Docker Support with hot-reload development mode
  • 118 Tests — unit tests and FastMCP Client integration tests

Installation

Prerequisites

Install from PyPI (Recommended)

The package is published to PyPI. Choose any Python package manager:

uvx (Run Without Installing)

The fastest way to try it — runs in an ephemeral environment with no permanent install:

uvx atlassian-jira-mcp-server

uv tool (Persistent Install)

Installs the CLI commands (jira-mcp-server, jira-mcp-http) in an isolated environment:

uv tool install atlassian-jira-mcp-server

pipx (Isolated Install)

Same concept as uv tool but using pipx:

pipx install atlassian-jira-mcp-server

pip (Virtual Environment)

pip install atlassian-jira-mcp-server

After any of the above, two CLI commands are available:

Command Transport Use Case
jira-mcp-server stdio Claude Desktop, Claude Code (stdio), any MCP client
jira-mcp-http streamable-http Multi-tenant HTTP deployment, remote access

MCP Client Configuration

Claude Desktop (stdio via PyPI)

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "jira": {
      "command": "jira-mcp-server",
      "env": {
        "JIRA_BASE_URL": "https://your-domain.atlassian.net",
        "JIRA_USERNAME": "your-email@domain.com",
        "JIRA_API_TOKEN": "your-api-token",
        "JIRA_DEFAULT_PROJECT": "PROJ"
      }
    }
  }
}

Claude Desktop (stdio via uvx — no install needed)

{
  "mcpServers": {
    "jira": {
      "command": "uvx",
      "args": ["atlassian-jira-mcp-server"],
      "env": {
        "JIRA_BASE_URL": "https://your-domain.atlassian.net",
        "JIRA_USERNAME": "your-email@domain.com",
        "JIRA_API_TOKEN": "your-api-token",
        "JIRA_DEFAULT_PROJECT": "PROJ"
      }
    }
  }
}

Claude Code (stdio)

claude mcp add jira -- jira-mcp-server

Or with uvx (no install):

claude mcp add jira -- uvx atlassian-jira-mcp-server

Then set environment variables in your shell or .env file.

Claude Code / Any HTTP Client

Start the HTTP server first:

jira-mcp-http
# or: uvx --from atlassian-jira-mcp-server jira-mcp-http

Then configure the client:

{
  "mcpServers": {
    "jira": {
      "url": "http://localhost:8000/mcp",
      "headers": {
        "X-Jira-Base-Url": "https://your-domain.atlassian.net",
        "X-Jira-Username": "your-email@domain.com",
        "X-Jira-Api-Token": "your-api-token"
      }
    }
  }
}

Install from Source (Development)

git clone https://github.com/your-username/atlassian-jira-mcp-server
cd atlassian-jira-mcp-server

python3 -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install with dev dependencies
pip install -e ".[dev]"

Or install directly as a uv tool from source:

git clone https://github.com/your-username/atlassian-jira-mcp-server
cd atlassian-jira-mcp-server
uv tool install . --force --reinstall

Docker (HTTP Transport)

git clone https://github.com/your-username/atlassian-jira-mcp-server
cd atlassian-jira-mcp-server

# Create a .env file with your credentials (see Configuration section)
cp .env.example .env  # edit with your values

# Build and start the server
docker-compose up -d

The server will be available at http://localhost:8000/mcp.

For multi-tenant mode, leave Jira credentials out of .env and provide them via headers or query params on each request instead.

Configuration

Environment Variables

Create a .env file in the project root:

# Jira credentials (required for single-tenant / env-based config)
JIRA_BASE_URL=https://your-domain.atlassian.net
JIRA_USERNAME=your-email@domain.com
JIRA_API_TOKEN=your-api-token
JIRA_DEFAULT_PROJECT=PROJ          # Optional default project key

# HTTP server settings (HTTP transport only)
FASTMCP_HOST=0.0.0.0               # Default: 0.0.0.0
FASTMCP_PORT=8000                  # Default: 8000

# Optional bearer-token authentication for the MCP endpoint
MCP_AUTH_TOKEN=your-secure-bearer-token

Multi-Source Credential Resolution (HTTP Transport)

For HTTP deployments, credentials are resolved per request using the following priority (highest to lowest):

  1. Query parameters?jira_base_url=...&jira_username=...&jira_api_token=...
  2. Authorization: Basic header — standard HTTP Basic Auth (username:api_token base64-encoded)
  3. X-Jira-* headersX-Jira-Base-Url, X-Jira-Username, X-Jira-Api-Token, X-Jira-Default-Project
  4. Environment variables — fallback for single-tenant / Docker deployments

This allows a single HTTP server instance to serve multiple Jira tenants simultaneously.

Example: Query Parameter Auth

curl "http://localhost:8000/mcp?jira_base_url=https://your-domain.atlassian.net&jira_username=user@example.com&jira_api_token=your-token"

Example: Basic Auth Header

curl -H "Authorization: Basic $(echo -n 'user@example.com:api-token' | base64)" \
     http://localhost:8000/mcp

Example: X-Jira-* Headers

curl -H "X-Jira-Base-Url: https://your-domain.atlassian.net" \
     -H "X-Jira-Username: your-email@domain.com" \
     -H "X-Jira-Api-Token: your-api-token" \
     http://localhost:8000/mcp

Usage

Running the Server

HTTP Transport

# With Docker (recommended)
docker-compose up -d

# Manually via Python entry point
python -c "from server import run_http; run_http()"

# Or via the installed CLI command
jira-mcp-http

Stdio Transport (Claude Desktop)

# Via installed CLI command
jira-mcp-server

# Or directly
python server.py

Connecting with MCP Clients

See the MCP Client Configuration section under Installation above for Claude Desktop, Claude Code, and HTTP client setup.

For multi-tenant HTTP deployments, configure clients to include credentials per request via headers or query params. The server resolves credentials fresh per request so different clients can target different Jira instances.

Available Tools

Issue Operations

issue_get — Get a single issue

issue_get(
    issue_key="PROJ-123",
    verbosity="full",          # "ids" | "summary" | "full" (default: "full")
    include_transitions=True,
    include_comments=True,
)

Verbosity levels:

  • ids — issue key only (minimal tokens)
  • summary — key fields: summary, status, assignee, priority, dates
  • full — complete details including description, comments, transitions

issue_search — Search with JQL

issue_search(
    jql="project = PROJ AND status = 'In Progress'",
    verbosity="summary",       # default: "summary"
    max_results=50,
)

Common JQL patterns:

# My open issues
issue_search(jql="assignee = currentUser() AND statusCategory != Done")

# High priority bugs
issue_search(jql="priority = High AND issuetype = Bug")

# Sprint backlog
issue_search(jql="sprint in openSprints() AND project = PROJ")

issue_create — Create a new issue

Supports interactive Elicitation for missing required fields (project_key, issue_type, summary).

issue_create(
    project_key="PROJ",
    issue_type="Story",        # case-insensitive; "story", "bug", "task" all work
    summary="Implement login feature",
    description="## Overview\nDetailed description with **markdown** support",
    priority="high",           # aliases: "high"→"High", "critical"→"Highest"
    assignee="John Smith",     # display name, email, or account ID
    components=["frontend"],   # component names auto-resolved to IDs
    labels=["backend", "auth"],
    auto_normalize=True,       # default; normalizes all field values
)

issue_update — Update fields, transition status, and add comments (unified)

# Update fields
issue_update(issue_key="PROJ-123", priority="High", labels=["backend"])

# Transition status (by name or ID)
issue_update(issue_key="PROJ-123", transition="In Progress")

# Add a comment
issue_update(issue_key="PROJ-123", comment="Review complete")

# Combined: transition + assign + comment in one call
issue_update(
    issue_key="PROJ-123",
    transition="In Progress",
    assignee="dev@example.com",
    comment="Starting work on this",
)

issue_delete — Delete an issue

Uses Elicitation to confirm before deleting.

issue_delete(issue_key="PROJ-123", delete_subtasks=False)

issue_comment — Add a comment

issue_comment(
    issue_key="PROJ-123",
    body="Looks good to me — merging.",
    mentions=["Jason Schulz"],  # display name, email, or account ID
)

You can also write the mention directly into the body, where you want it to appear:

issue_comment(
    issue_key="PROJ-123",
    body="cc @[Jason Schulz] — the fix is on `main`.",
)

Mentions are verified against Jira before the comment is posted. An unknown or ambiguous mention fails the call — nothing is posted — and the error names the failed query plus the candidate users. On success the response carries mentions_resolved (query, account_id, display_name) so callers confirm who was tagged without re-reading the comment.

This matters because Jira does not reject a bad mention: a comment carrying a fabricated account ID posts successfully and renders as inert text that notifies nobody. Refusing up front is the only way to prevent that.

Body references are handled by intent:

Written in the body Behavior
@[Full Name] Explicit — must resolve, or the comment is refused
@Name, @Name Surname, @user@host.com Converted to a real mention when it matches exactly one user
Anything that doesn't resolve Left as the literal text you wrote, and returned in mentions_unmatched
Code spans, code blocks, link text Never touched

The same verification applies to a pre-rendered ADF body containing hand-built mention nodes, and to issue_update(comment=..., mentions=[...]) — where resolution happens before any write, so a bad mention means no transition, no field update and no comment rather than a half-applied change.

Project Operations

project_get — Get project details

project_get(
    project_key="PROJ",
    verbosity="summary",
    include_components=True,
    include_versions=True,
)

Validation

issue_validate_fields — Validate and normalize fields before creation

Use this to preview normalization and catch errors before calling issue_create:

result = issue_validate_fields(
    project_key="PROJ",
    issue_type="bug",
    priority="high",
    assignee="John",
    components=["frontend"],
)

# result["valid"] == True/False
# result["normalized"] == normalized field values
# result["errors"] == list of field errors with suggestions

Resource Endpoints

Resource Templates (Parameterized)

Read any Jira entity by URI — MCP clients discover these via list_resource_templates():

URI Template Description
issue://{issue_key} Get any issue by key (e.g., issue://PROJ-123)
project://{project_key} Get any project by key (e.g., project://PROJ)

Static Resources

Access workspace data through fixed MCP resource URIs:

URI Description
current-user://info Current authenticated Jira user
current-user://issues Issues assigned to the current user
workspace://users Active users in the workspace
workspace://projects Accessible Jira projects
workspace://recent-issues Issues updated in the last 7 days
workspace://verbose-metadata Combined metadata: priorities and statuses
workspace://issue-creation-metadata Issue types per project + priorities + assignees
adf-examples:// ADF formatting examples and reference

ADF (Atlassian Document Format) Support

The server automatically converts plain text and Markdown to ADF format (via marklassian) — no manual formatting required. Headings, bold/italic/strikethrough, nested lists, code blocks, blockquotes, links, tables, and GFM task lists are all supported. Content that is already ADF passes through untouched — whether given as an ADF object or as a serialized ADF JSON string.

# Plain text
description = "Simple text description"

# Markdown (headings, bold, lists, code blocks, links all supported)
description = """
## Overview
This issue implements **important** features.

### Acceptance Criteria
- First criterion
- Second criterion

```python
def example():
    return "code block"

"""


## Architecture

Version 3.0.0 uses a single FastMCP instance with direct tool/resource/prompt registration:

atlassian-jira-mcp-server/ ├── server.py # Single FastMCP server entry point ├── src/ │ ├── client/ │ │ ├── jira.py # JiraClient — per-request HTTP client │ │ ├── config.py # TenantConfig — multi-source credential resolution │ │ └── errors.py # Typed error classes (PermanentError, etc.) │ ├── tools/ │ │ ├── issue.py # issue_get, issue_search, issue_create, issue_update, │ │ │ # issue_delete, issue_comment │ │ ├── project.py # project_get │ │ ├── validation.py # issue_validate_fields │ │ └── helpers.py # Shared utilities (get_jira_client, elicit_or_error) │ ├── resources/ │ │ ├── static.py # ADF examples resource │ │ └── workspace.py # Dynamic workspace/user/project resources │ ├── prompts/ │ │ └── workflows.py # Workflow guidance prompts │ ├── middleware/ │ │ ├── auth.py # BearerAuthMiddleware — MCP endpoint token validation │ │ └── tenant.py # TenantMiddleware — per-request credential resolution │ └── utilities/ │ ├── adf.py # Markdown→ADF (marklassian) + ADF builders/rendering │ ├── resolvers.py # Component/transition name→ID resolution with caching │ └── formatting.py # Issue/project response formatting + verbosity ├── tests/ # 118 tests (unit + FastMCP Client integration) ├── Dockerfile ├── docker-compose.yml ├── pyproject.toml └── requirements.txt


### Key Design Patterns

1. **Single-Server Architecture** — no mounted sub-servers; tools, resources, and prompts register directly on one `FastMCP` instance
2. **Middleware Stack** — `BearerAuthMiddleware` (endpoint auth) → `TenantMiddleware` (per-request credential injection)
3. **Per-Request JiraClient** — no global HTTP client; each tool call gets a fresh `JiraClient` scoped to the request's `TenantConfig`
4. **Multi-Tenant by Default** — HTTP mode supports any number of Jira tenants with zero configuration changes
5. **Elicitation** — `issue_create` and `issue_delete` use FastMCP v3's `ctx.elicit()` for interactive guidance

## Development

### Running Tests

```bash
# Run full test suite
pytest tests/ -v

# Run a specific test file
pytest tests/test_integration.py -v

# With venv
.venv/bin/pytest tests/ -v

The test suite includes:

  • Unit tests for individual functions (test_client.py, test_config.py, test_errors.py, test_tools_*.py, etc.)
  • FastMCP Client integration tests (test_integration.py) — exercise the full MCP stack in-memory using FastMCP's Client without a running server
  • Middleware tests (test_middleware.py) — validate auth and tenant credential resolution

After Code Changes

# Reinstall uv tool
uv tool uninstall atlassian-jira-mcp-server && uv tool install . --reinstall

# Rebuild Docker image
docker-compose down && docker-compose build && docker-compose up -d

Verifying Imports

python -c "from src.tools import register_tools; print('OK')"
python -c "from server import create_server; print('OK')"

Adding New Tools

  1. Implement in src/tools/ (e.g., src/tools/issue.py):
@mcp.tool
async def issue_new_operation(ctx: Context, issue_key: str) -> dict:
    """Tool description shown to the LLM."""
    async with await get_jira_client(ctx) as client:
        result = await client.request("GET", f"issue/{issue_key}/subtasks")
        return result
  1. Register in src/tools/__init__.py:
from src.tools import new_module
new_module.register(mcp)

Error Handling

The server distinguishes temporary from permanent errors:

Error Type Retry? Example
Connection error Yes "Connection Error: Unable to connect..."
Timeout Yes "Timeout Error: Request timed out..."
Session error Yes "No valid session ID provided"
Permission error (403) No "Permission Error (HTTP 403): ..."
Validation error (400) No "Jira API Error: field 'x' is required"
Not found (404) No Project or issue does not exist

Troubleshooting

"externally-managed-environment" Error (pip)

Use uv tool install (Method 2) or a virtual environment (Method 3) instead of a system pip install.

Python Not Found with pyenv

# Set a global Python version
pyenv global 3.12.11

# Or use python3 explicitly
python3 -m venv .venv

Authentication Failures

  • Verify JIRA_BASE_URL includes https:// and no trailing slash
  • Confirm the API token belongs to the same email as JIRA_USERNAME
  • Check that the token has not expired in Atlassian account settings

Permission Errors (HTTP 403)

  • Confirm the Jira user has Browse Project and the relevant Create/Edit/Delete permissions
  • Check that the issue type is available in the target project

Contributing

Contributions are welcome. Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality (pytest tests/ -v must pass)
  4. Submit a pull request

License

MIT License — see LICENSE file for details.

Support

Download files

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

Source Distribution

atlassian_jira_mcp_server-3.3.0.tar.gz (74.2 kB view details)

Uploaded Source

Built Distribution

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

atlassian_jira_mcp_server-3.3.0-py3-none-any.whl (55.9 kB view details)

Uploaded Python 3

File details

Details for the file atlassian_jira_mcp_server-3.3.0.tar.gz.

File metadata

File hashes

Hashes for atlassian_jira_mcp_server-3.3.0.tar.gz
Algorithm Hash digest
SHA256 d27a192254e3b9380842b1d83606a06d9f8486f68caa3ebaa65470d2b0d24747
MD5 fc72c5c6a421a89cb63d781e34ab1734
BLAKE2b-256 79e2e2c8af43b535c4557a221ba125ca124dd5f87979926b3d2c65b62da0db96

See more details on using hashes here.

Provenance

The following attestation bundles were made for atlassian_jira_mcp_server-3.3.0.tar.gz:

Publisher: publish.yml on jasonpaulso/jira-openapi-generated

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file atlassian_jira_mcp_server-3.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for atlassian_jira_mcp_server-3.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 31bf9294ca37254dd65061497aefb722c5334be27dd24551e4977806e2acf88f
MD5 119383f8ca6c0ba041a40d8a3bc762dd
BLAKE2b-256 381ce484aad7a72806b786fb35308ddfa81c6776d3c73010e0d6cb2df91f09c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for atlassian_jira_mcp_server-3.3.0-py3-none-any.whl:

Publisher: publish.yml on jasonpaulso/jira-openapi-generated

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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