Metadata-Version: 2.4
Name: atlassian-jira-mcp-server
Version: 3.0.0
Summary: Jira MCP (Model Context Protocol) server built with FastMCP for Jira Cloud API v3
Author-email: Jason Schulz <jason@schulz.studio>
License: MIT
Project-URL: Homepage, https://github.com/your-username/atlassian-jira-mcp-server
Project-URL: Repository, https://github.com/your-username/atlassian-jira-mcp-server
Project-URL: Issues, https://github.com/your-username/atlassian-jira-mcp-server/issues
Keywords: mcp,jira,api,fastmcp,model-context-protocol,atlassian
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastmcp<4,>=3.0.0
Requires-Dist: httpx>=0.25.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: adf-lib>=0.2.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: typing-extensions>=4.7.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
Dynamic: license-file

# 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.0.0** — Single-server FastMCP v3 rewrite with multi-tenant support, Elicitation, and 104 tests.

## Features

- **8 Focused Tools** for complete Jira issue and project management
- **Interactive Elicitation** — `issue_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
- **104 Tests** — unit tests and FastMCP Client integration tests

## Installation

### Prerequisites

- Python 3.10+
- Jira Cloud account with an API token ([create one here](https://id.atlassian.com/manage-profile/security/api-tokens))
- [uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended) or pip
- Docker (optional, for containerized HTTP deployment)

### Method 1: Docker (Recommended for HTTP Transport)

```bash
# Clone repository
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`.

To pass Jira credentials at runtime (multi-tenant mode), leave them out of `.env` and provide them via headers or query params on each request instead.

### Method 2: UV Tool (Recommended for Stdio / Claude Desktop)

```bash
# Install directly from source
git clone https://github.com/your-username/atlassian-jira-mcp-server
cd atlassian-jira-mcp-server
uv tool install . --force --reinstall
```

Test the installation:

```bash
# Starts stdio mode (will block waiting for MCP client)
jira-mcp-server
```

### Method 3: Virtual Environment (Development)

```bash
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]"
```

## Configuration

### Environment Variables

Create a `.env` file in the project root:

```env
# 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-*` headers** — `X-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

```bash
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

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

#### Example: X-Jira-* Headers

```bash
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

```bash
# 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)

```bash
# Via installed CLI command
jira-mcp-server

# Or directly
python server.py
```

### Connecting with MCP Clients

#### Claude Desktop (Stdio)

Add to `claude_desktop_config.json`:

```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 Code / HTTP Client (Single-Tenant)

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

#### Multi-Tenant HTTP (per-request credentials)

Configure the MCP client to include credentials in each request URL or headers. The server resolves credentials fresh per request so different clients can target different Jira instances.

```json
{
  "mcpServers": {
    "jira": {
      "url": "http://localhost:8000/mcp?jira_base_url=https://your-domain.atlassian.net",
      "headers": {
        "Authorization": "Basic base64(user@example.com:api-token)"
      }
    }
  }
}
```

## Available Tools

### Issue Operations

#### `issue_get` — Get a single issue

```python
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

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

Common JQL patterns:

```python
# 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`).

```python
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)

```python
# 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.

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

#### `issue_comment` — Add a comment

```python
issue_comment(
    issue_key="PROJ-123",
    body="Looks good to me — merging.",
    mentions=["user@example.com"],  # optional @mentions
)
```

### Project Operations

#### `project_get` — Get project details

```python
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`:

```python
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

Access workspace data through 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 — no manual formatting required.

```python
# 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 conversion
│       ├── resolvers.py         # Component/transition name→ID resolution with caching
│       └── formatting.py        # Issue/project response formatting + verbosity
├── tests/                       # 104 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

```bash
# 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

```bash
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`):

```python
@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
```

2. Register in `src/tools/__init__.py`:

```python
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

```bash
# 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

- [Report issues](https://github.com/your-username/atlassian-jira-mcp-server/issues)
- [Jira Cloud API Documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/)
- [MCP Documentation](https://modelcontextprotocol.io/)
- [FastMCP Documentation](https://gofastmcp.com)
