Commit c2d3838c authored by MD. SHAHIDUL ISLAM's avatar MD. SHAHIDUL ISLAM Committed by Md. Shahjahan

Dev shahidul

parent 69f3e258
File added
venv venv
__pycache__ __pycache__
*.egg-info/
dist/
build/
*.whl
*.tar.gz
.eggs/
*.egg
.pytest_cache/
.coverage
htmlcov/
jira_mcp.egg-info
dist
...@@ -2,6 +2,36 @@ ...@@ -2,6 +2,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [1.0.1] - 2026-03-11
### IMPROVEMENTS
**Simplified Installation** - Convert project to installable Python package for convenient pip-based installation and deployment.
**Enhanced Configuration** - Rebranding MCP server name to `dohatec-jira` and CLI command to `dohatec-jira-mcp` for consistency across platforms.
**Documentation Improvements** - Simplified README with clearer setup instructions, moved development-specific content to dedicated DEVELOPMENT.md file, and improved gitignore handling.
**Configuration Management** - Switched to JSON-only configuration approach for improved cross-platform compatibility and reduced environment variable complexity.
**Project Structure** - Reorganized installation and setup documentation for better clarity and easier onboarding.
### COMMITS
- 5adc016 - feat: Convert project to installable Python package
- ec09953 - docs: Add comprehensive installation guide
- 3c28d32 - feat: Separate development setup into dedicated file
- d9d4ffd - docs: Keep README simple and easy - move manual paths to DEVELOPMENT.md
- 278e405 - feat: Remove redundant environment variable documentation
- 81cd738 - feat: Remove environment variable configuration - JSON only approach
- 8e67bb6 - feat: Remove 'Running the Server' section - not needed
- 190e118 - feat: Update MCP server name to dohatec-jira in config examples
- 7c096e4 - feat: Rename CLI command from jira-mcp to dohatec-jira-mcp
- 1b514a6 - docs: Remove INSTALLATION.md and update .gitignore to include README.md
- c288d57 - fix: Update .gitignore to include additional directories and files
---
## [1.0.0] - 2026-03-08 ## [1.0.0] - 2026-03-08
### OVERVIEW ### OVERVIEW
......
# Development Setup Guide
This guide is for developers who want to contribute to the Jira MCP Server project or run it locally during development.
## Manual Installation
If you prefer to clone the repository and set up a development environment instead of using `pip install git+...`:
### Mac / Linux
1. Clone the repository:
```bash
git clone https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
cd mcp-server-jira
```
2. Create a virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate
```
3. Install in editable mode (with all dependencies):
```bash
pip install -e .
```
4. Verify installation:
```bash
dohatec-jira-mcp --help
# or
python -m jira_mcp
```
### Windows
1. Clone the repository:
```cmd
git clone https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
cd mcp-server-jira
```
2. Create a virtual environment:
```cmd
python -m venv venv
venv\Scripts\activate
```
3. Install in editable mode:
```cmd
pip install -e .
```
4. Verify installation:
```cmd
dohatec-jira-mcp --help
```
## Running Tests
The repository includes test files for development purposes:
```bash
# Test Jira connection
python test_jira.py
# Test MCP API
python test_mcp.py
# Test issue creation
python test_mcp_create.py
```
Before running tests, ensure environment variables are set:
```bash
export JIRA_SERVER="http://103.41.111.60:8085"
export JIRA_USERNAME="your-username"
export JIRA_PASSWORD="your-password"
```
## Building Distributions
To build wheel and source distributions:
```bash
# Install build tools
pip install build
# Build distributions
python -m build
# View built packages
ls -lh dist/
```
Output will be in the `dist/` directory:
- `jira_mcp-1.0.0-py3-none-any.whl` — Wheel package
- `jira_mcp-1.0.0.tar.gz` — Source distribution
## Project Structure
```
jira_mcp/
├── __init__.py # Package initialization & version
├── server.py # Main MCP server implementation
└── __main__.py # Module runner entry point
jira_mcp_server.py # Legacy file (now moved to jira_mcp/server.py)
pyproject.toml # Modern Python package configuration
MANIFEST.in # Distribution file includes
README.md # Package documentation
DEVELOPMENT.md # This file
INSTALLATION.md # Detailed installation guide
test_*.py # Test files for development
```
## Making Changes
1. **Edit code** in `jira_mcp/server.py` (or other files)
2. **No reinstall needed** — changes auto-apply in editable mode
3. **Run tests** to verify changes work
4. **Commit and push** when ready
Example:
```bash
# Make changes to jira_mcp/server.py
# Changes automatically available
python -m jira_mcp # Run with your changes
```
## Updating Dependencies
If you need to add new dependencies:
1. Add to `dependencies` list in `pyproject.toml`
2. Reinstall in editable mode:
```bash
pip install -e .
```
3. Commit `pyproject.toml` changes
## Creating a New Release
When ready to release a new version:
1. Update version in `pyproject.toml` (and `jira_mcp/__init__.py`)
2. Update CHANGELOG.md
3. Commit changes:
```bash
git commit -m "Release v1.1.0"
git tag v1.1.0
```
4. Build distributions:
```bash
python -m build
```
5. Optional: Publish to PyPI
```bash
pip install twine
twine upload dist/*
```
## Troubleshooting
### Changes not reflecting?
```bash
# Reinstall in editable mode
pip install -e .
```
### Tests fail with import errors?
```bash
# Ensure venv is activated
source venv/bin/activate # Mac/Linux
venv\Scripts\activate # Windows
# Reinstall dependencies
pip install -e .
```
### Jira connection errors in tests?
```bash
# Check environment variables
printenv | grep JIRA_
# Ensure Jira server is accessible
curl http://103.41.111.60:8085
```
## Advanced Configuration
If you've done manual installation with a custom virtual environment path, you may need to specify the full Python path in Claude Desktop or Cursor configurations.
### Claude Desktop with Custom Python Path
**Mac Example:**
```json
{
"mcpServers": {
"jira-mcp-dev": {
"command": "/Users/your-username/Documents/resource/jiramcp/venv/bin/python3",
"args": ["-m", "jira_mcp"],
"env": {
"JIRA_SERVER": "http://103.41.111.60:8085",
"JIRA_USERNAME": "your-username",
"JIRA_PASSWORD": "your-password",
"JIRA_DEFAULT_PROJECT": "PCM",
"JIRA_QA_TESTER": "Samia Islam"
}
}
}
}
```
**Windows Example:**
```json
{
"mcpServers": {
"jira-mcp-dev": {
"command": "C:\\Users\\your-username\\Documents\\resource\\jiramcp\\venv\\Scripts\\python.exe",
"args": ["-m", "jira_mcp"],
"env": {
"JIRA_SERVER": "http://103.41.111.60:8085",
"JIRA_USERNAME": "your-username",
"JIRA_PASSWORD": "your-password",
"JIRA_DEFAULT_PROJECT": "PCM",
"JIRA_QA_TESTER": "Samia Islam"
}
}
}
}
```
### Cursor with Custom Python Path
1. Set environment variables in your shell before launching Cursor:
```bash
export JIRA_MSERVER="/path/to/venv/bin/python3"
export JIRA_SERVER="http://103.41.111.60:8085"
export JIRA_USERNAME="your-username"
export JIRA_PASSWORD="your-password"
```
2. In Cursor Settings → MCP Servers, use:
```bash
/path/to/venv/bin/python3 -m jira_mcp
```
Replace `/path/to/venv` with your actual virtual environment path.
## For More Information
- See [README.md](README.md) for general usage
- See [INSTALLATION.md](INSTALLATION.md) for end-user installation
- Review `pyproject.toml` for package configuration details
include README.md
include CHANGELOG.md
include LICENSE
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude * .DS_Store
exclude test_*.py
...@@ -28,107 +28,66 @@ This repository contains a Model Context Protocol (MCP) server that connects you ...@@ -28,107 +28,66 @@ This repository contains a Model Context Protocol (MCP) server that connects you
## [CONFIG] How to Setup ## [CONFIG] How to Setup
### Prerequisites ### Prerequisites
Make sure you have Python installed on your system. Make sure you have Python 3.9+ installed on your system.
### Installation ### Installation (Recommended)
#### Mac / Linux Install the Jira MCP server directly from the Git repository as a Python package:
1. Clone this repository or download the source code:
```bash ```bash
git clone <REPOSITORY_URL> pip install git+https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
cd <REPOSITORY_DIRECTORY> ```
```
2. Set up a virtual environment and install dependencies: This will:
```bash - Install the `jira-mcp` package with all dependencies
python3 -m venv venv - Create a `dohatec-jira-mcp` command-line entry point
source venv/bin/activate - Enable `python -m jira_mcp` execution
pip install mcp jira
``` ---
#### Windows ## Configuration
1. Clone this repository or download the source code:
```cmd ### Using with Claude Desktop
git clone <REPOSITORY_URL>
cd <REPOSITORY_DIRECTORY> Edit your `claude_desktop_config.json`:
```
2. Set up a virtual environment and install dependencies:
```cmd
python -m venv venv
venv\Scripts\activate
pip install mcp jira
```
### 1. Claude Desktop Configuration
Edit your `claude_desktop_config.json` file to include the server. Replace the placeholder paths with your actual absolute paths, and fill in your Jira credentials.
* **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json` * **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
Add this configuration with your Jira credentials:
```json ```json
{ {
"mcpServers": { "mcpServers": {
"jira-mcp": {
"command": "<ABSOLUTE_PATH_TO_PYTHON_EXECUTABLE_IN_VENV>",
"args": ["<ABSOLUTE_PATH_TO_YOUR_REPOSITORY>/jira_mcp_server.py"],
"env": {
"JIRA_SERVER": "https://your-jira-domain.com",
"JIRA_USERNAME": "your_email_or_username",
"JIRA_PASSWORD": "your_api_token_or_password",
"JIRA_DEFAULT_PROJECT": "PROJ",
"JIRA_QA_TESTER": "tester_username"
}
}
}
}
```
Example for mac:
```json
"dohatec-jira": { "dohatec-jira": {
"command": "/Users/mdshahidulislam/Documents/resource/jiramcp/venv/bin/python3", "command": "dohatec-jira-mcp",
"args": [
"/Users/mdshahidulislam/Documents/resource/jiramcp/jira_mcp_server.py"
],
"env": { "env": {
"JIRA_SERVER": "http://103.41.111.60:8085", "JIRA_SERVER": "http://103.41.111.60:8085",
"JIRA_USERNAME": "shahidul", "JIRA_USERNAME": "shahidul",
"JIRA_PASSWORD": "[PASSWORD]", "JIRA_PASSWORD": "your-password",
"JIRA_DEFAULT_PROJECT": "PCM", "JIRA_DEFAULT_PROJECT": "PCM",
"JIRA_QA_TESTER": "Samia Islam" "JIRA_QA_TESTER": "Samia Islam"
} }
} }
```
Example for windows:
```json
"dohatec-jira": {
"command": "C:\\Users\\mdshahidulislam\\Documents\\resource\\jiramcp\\venv\\Scripts\\python.exe",
"args": [
"C:\\Users\\mdshahidulislam\\Documents\\resource\\jiramcp\\jira_mcp_server.py"
],
"env": {
"JIRA_SERVER": "http://103.41.111.60:8085",
"JIRA_USERNAME": "shahidul",
"JIRA_PASSWORD": "[PASSWORD]",
"JIRA_DEFAULT_PROJECT": "PCM",
"JIRA_QA_TESTER": "Samia Islam"
}
} }
}
``` ```
That's it! The credentials are configured in the JSON file.
*(Note: You can remove `JIRA_DEFAULT_PROJECT` or `JIRA_QA_TESTER` if you don't need them).* ### Using with Cursor
### 2. Cursor Configuration 1. Open Cursor Settings → Features → **MCP Servers**
1. Open Cursor Settings -> Features -> **MCP Servers**
2. Click **+ Add New MCP Server** 2. Click **+ Add New MCP Server**
3. **Name:** `Jira Server` 3. **Name:** `dohatec-jira`
4. **Type:** `command` 4. **Type:** `command`
5. **Command:** `<ABSOLUTE_PATH_TO_PYTHON_EXECUTABLE_IN_VENV> <ABSOLUTE_PATH_TO_YOUR_REPOSITORY>/jira_mcp_server.py` 5. **Command:** `dohatec-jira-mcp`
6. Click Save and make sure it has the green status circle indicating it's connected. 6. **Environment Variables:** Set in the UI:
- `JIRA_SERVER`: `http://103.41.111.60:8085`
*(Note: In Cursor, environment variables are typically inherited from your shell. If you find Cursor isn't passing environment variables correctly, you can also inject them by prefixing the command with `env JIRA_SERVER=... JIRA_USERNAME=... JIRA_PASSWORD=... <command>` on Mac/Linux, or fallback to setting them safely if you know how).* - `JIRA_USERNAME`: `your-username`
- `JIRA_PASSWORD`: `your-password`
- `JIRA_DEFAULT_PROJECT`: `PCM` (optional)
- `JIRA_QA_TESTER`: `Samia Islam` (optional)
--- ---
...@@ -313,3 +272,17 @@ We welcome contributions from everyone! Whether you're fixing bugs, adding new f ...@@ -313,3 +272,17 @@ We welcome contributions from everyone! Whether you're fixing bugs, adding new f
Please ensure your code follows Python best practices and is well-documented. Include comments for complex logic and write clear commit messages. Please ensure your code follows Python best practices and is well-documented. Include comments for complex logic and write clear commit messages.
Thank you for helping us build and improve this project! Thank you for helping us build and improve this project!
<<<<<<< HEAD
---
## Manual Installation (For Development)
For developers who want to contribute or run the project locally, see [DEVELOPMENT.md](DEVELOPMENT.md) for:
- Step-by-step local setup instructions (Mac/Linux/Windows)
- Virtual environment configuration
- Running tests
- Building distributions
- Advanced configuration with custom Python paths
=======
>>>>>>> c288d570f06841645e0d2bce66b1afa9445e8f36
image.png

819 KB

"""
Dohatec Jira MCP Server
A Model Context Protocol (MCP) server that connects AI assistants to a
self-hosted Dohatec Jira instance.
"""
__version__ = "1.0.0"
__author__ = "Md Shahidul Islam"
__description__ = "Jira MCP Server for Dohatec"
__all__ = ["__version__", "__author__", "__description__"]
"""
Entry point for running the Jira MCP server as a module.
Enables execution with: python -m jira_mcp
"""
from jira_mcp.server import main
if __name__ == "__main__":
main()
# ==============================================================================
# _____ _ _
# | __ \ | | | |
# | | | | ___ | |__ __ _| |_ ___ ___
# | | | |/ _ \| '_ \ / _` | __/ _ \/ __|
# | |__| | (_) | | | | (_| | || __/ (__
# |_____/ \___/|_| |_|\__,_|\__\___|\___|
# ^
# /
#
# Dohatec Jira MCP Server
#
# Description:
# A Model Context Protocol (MCP) server that connects AI assistants to a
# self-hosted Dohatec Jira instance. It provides seamless integration to
# fetch assigned open tasks, retrieve issue details, create new issues,
# assign tasks to the current user, and transition issue statuses directly
# from the AI interface.
#
# Author: Md Shahidul Islam
# ==============================================================================
import os
from mcp.server.fastmcp import FastMCP
from jira import JIRA
from typing import Optional, List
# Initialize FastMCP Server
mcp = FastMCP("Dohatec Jira Mcp")
# Initialize Jira Client
JIRA_SERVER = os.environ.get("JIRA_SERVER")
JIRA_USERNAME = os.environ.get("JIRA_USERNAME")
JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD")
ACTIVE_PROJECT = os.environ.get("JIRA_DEFAULT_PROJECT")
JIRA_QA_TESTER = os.environ.get("JIRA_QA_TESTER")
BASE_URL = JIRA_SERVER.rstrip("/") if JIRA_SERVER else ""
# We use Basic Auth explicitly for this local instance.
try:
jira_client = JIRA(
server=JIRA_SERVER,
basic_auth=(JIRA_USERNAME, JIRA_PASSWORD)
)
except Exception as e:
# If initialization fails, FastMCP will still start but tools might fail.
# We log initialization error to stdout (which stderr usually prints to in MCP).
import sys
print(f"Failed to connect to Jira at {JIRA_SERVER}: {e}", file=sys.stderr)
jira_client = None
def _get_jira():
if not jira_client:
raise RuntimeError("Jira client failed to initialize.")
return jira_client
@mcp.tool()
def set_active_project(project_key: str) -> str:
"""Sets the active Jira project for the current session."""
global ACTIVE_PROJECT
ACTIVE_PROJECT = project_key
return f"Active project successfully set to '{ACTIVE_PROJECT}'."
@mcp.tool()
def get_active_project() -> str:
"""Gets the currently active Jira project. Call this to check context if user doesn't specify a project."""
if ACTIVE_PROJECT:
return f"The current active project is '{ACTIVE_PROJECT}'."
return "No active project is currently set. Please specify a project key or use set_active_project."
@mcp.tool()
def get_my_assigned_issues() -> str:
"""Fetches all open Jira issues assigned to the current user.
IMPORTANT: When responding to the user, you MUST format issue keys as clickable markdown links using the URLs provided in this tool's output.
"""
jira = _get_jira()
# JQL to find user's unresolved issues
jql = 'assignee = currentUser() AND resolution = Unresolved ORDER BY priority DESC, created DESC'
try:
issues = jira.search_issues(jql, maxResults=50)
if not issues:
return "No open issues assigned to you."
result = ["Your Open Assigned Issues:"]
for issue in issues:
result.append(f"- [[{issue.key}]]({BASE_URL}/browse/{issue.key}) {issue.fields.summary} (Status: {issue.fields.status.name}, Priority: {issue.fields.priority.name if issue.fields.priority else 'None'})")
return "\n".join(result)
except Exception as e:
return f"Error fetching issues: {str(e)}"
@mcp.tool()
def get_issue_details(issue_key: str) -> str:
"""Retrieves full details for a specific issue ID (e.g. PCM-131).
IMPORTANT: When responding to the user, you MUST format the issue key as a clickable markdown link using the URL provided in this tool's output.
"""
jira = _get_jira()
try:
issue = jira.issue(issue_key)
assignee = issue.fields.assignee.displayName if issue.fields.assignee else "Unassigned"
description = issue.fields.description or "No description provided."
details = [
f"Issue: [{issue.key}]({BASE_URL}/browse/{issue.key})",
f"Summary: {issue.fields.summary}",
f"Type: {issue.fields.issuetype.name}",
f"Status: {issue.fields.status.name}",
f"Priority: {issue.fields.priority.name if issue.fields.priority else 'None'}",
f"Assignee: {assignee}",
f"Reporter: {issue.fields.reporter.displayName if issue.fields.reporter else 'Unknown'}",
f"Created: {issue.fields.created}",
f"Updated: {issue.fields.updated}",
f"\nDescription:\n{description}"
]
return "\n".join(details)
except Exception as e:
return f"Error fetching issue {issue_key}: {str(e)}"
@mcp.tool()
def get_multiple_issues_details(issue_keys: List[str]) -> str:
"""Retrieves details for multiple specific issue IDs (e.g. ['PCM-131', 'PCM-132']) at once.
IMPORTANT: When responding to the user, you MUST format issue keys as clickable markdown links using the URLs provided in this tool's output.
"""
jira = _get_jira()
try:
if not issue_keys:
return "No issue keys provided."
keys_str = ", ".join([f'"{k}"' for k in issue_keys])
jql = f"issuekey in ({keys_str})"
issues = jira.search_issues(jql, maxResults=len(issue_keys))
if not issues:
return "No issues found matching the provided keys."
result = []
for issue in issues:
assignee = issue.fields.assignee.displayName if issue.fields.assignee else "Unassigned"
description = issue.fields.description or "No description provided."
# Truncate description if it's too long
if len(description) > 200:
description = description[:197] + "..."
priority = issue.fields.priority.name if getattr(issue.fields, 'priority', None) else 'None'
details = [
f"Issue: [{issue.key}]({BASE_URL}/browse/{issue.key})",
f"Summary: {issue.fields.summary}",
f"Type: {issue.fields.issuetype.name} | Status: {issue.fields.status.name} | Priority: {priority}",
f"Assignee: {assignee}",
f"Description snippet: {description}",
"---"
]
result.append("\n".join(details))
return "\n".join(result)
except Exception as e:
return f"Error fetching multiple issues details: {str(e)}"
@mcp.tool()
def create_issue(summary: str, description: str, project_key: str = None, issue_type: str = "Task") -> str:
"""Creates a new Jira issue. If project_key is not provided, uses the active project.
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
global ACTIVE_PROJECT
jira = _get_jira()
target_project = project_key or ACTIVE_PROJECT
if not target_project:
return "Error: No project_key provided and no active project is set."
try:
issue_dict = {
'project': {'key': target_project},
'summary': summary,
'description': description,
'issuetype': {'name': issue_type},
}
new_issue = jira.create_issue(fields=issue_dict)
return f"Successfully created issue [{new_issue.key}]({BASE_URL}/browse/{new_issue.key})."
except Exception as e:
return f"Error creating issue: {str(e)}"
@mcp.tool()
def create_issue_advanced(
summary: str,
description: str,
issue_type: str = "Bug",
project_key: str = None,
priority: str = None,
assignee: str = None,
components: List[str] = None,
labels: List[str] = None,
environment: str = None,
due_date: str = None,
original_estimate: str = None
) -> str:
"""Creates a Jira issue with advanced fields matched to the web interface.
Use this when you need to specify assignee, labels, components, or estimations.
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
global ACTIVE_PROJECT
jira = _get_jira()
target_project = project_key or ACTIVE_PROJECT
if not target_project:
return "Error: No project_key provided and no active project is set."
try:
issue_dict = {
'project': {'key': target_project},
'summary': summary,
'description': description,
'issuetype': {'name': issue_type},
}
if priority:
issue_dict['priority'] = {'name': priority}
if assignee and assignee.lower() != 'automatic':
issue_dict['assignee'] = {'name': assignee}
if components:
issue_dict['components'] = [{'name': c} for c in components]
if labels:
issue_dict['labels'] = labels
if environment:
issue_dict['environment'] = environment
if due_date: # Format: YYYY-MM-DD
issue_dict['duedate'] = due_date
if original_estimate: # Format: e.g. '3w 4d 12h'
issue_dict['timetracking'] = {'originalEstimate': original_estimate}
new_issue = jira.create_issue(fields=issue_dict)
return f"Successfully created {issue_type} [{new_issue.key}]({BASE_URL}/browse/{new_issue.key})."
except Exception as e:
return f"Error creating advanced issue: {str(e)}"
@mcp.tool()
def create_issue_with_subtasks(summary: str, description: str, subtasks: List[str], project_key: str = None, parent_issue_type: str = "Story") -> str:
"""Creates a new Jira Issue (Story or Task) along with a list of Sub-tasks. If project_key is not provided, uses the active project.
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
global ACTIVE_PROJECT
jira = _get_jira()
target_project = project_key or ACTIVE_PROJECT
if not target_project:
return "Error: No project_key provided and no active project is set."
try:
# 1. Create the parent issue (Story or Task)
parent_dict = {
'project': {'key': target_project},
'summary': summary,
'description': description,
'issuetype': {'name': parent_issue_type},
}
parent_issue = jira.create_issue(fields=parent_dict)
# 2. Create the subtasks linked to the parent issue
created_subtasks = []
for st_summary in subtasks:
subtask_dict = {
'project': {'key': target_project},
'summary': st_summary,
'issuetype': {'name': 'Sub-task'},
'parent': {'id': parent_issue.key}
}
st_issue = jira.create_issue(fields=subtask_dict)
created_subtasks.append(st_issue.key)
subtasks_str = ", ".join(created_subtasks)
return f"Successfully created {parent_issue_type} [{parent_issue.key}]({BASE_URL}/browse/{parent_issue.key}) with subtasks: {subtasks_str}."
except Exception as e:
return f"Error creating {parent_issue_type} with subtasks: {str(e)}"
@mcp.tool()
def create_bulk_issues(issues_data: List[dict], project_key: str = None) -> str:
"""Creates multiple Jira issues in one operation.
issues_data must be a list of dictionaries with keys: 'summary', 'description', and optional 'issue_type' (defaults to 'Task').
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
global ACTIVE_PROJECT
jira = _get_jira()
target_project = project_key or ACTIVE_PROJECT
if not target_project:
return "Error: No project_key provided and no active project is set."
try:
issue_list = []
for issue_params in issues_data:
summary = issue_params.get('summary')
if not summary:
continue
description = issue_params.get('description', '')
issue_type = issue_params.get('issue_type', 'Task')
issue_list.append({
'project': {'key': target_project},
'summary': summary,
'description': description,
'issuetype': {'name': issue_type},
})
if not issue_list:
return "No valid issues provided in the request."
# Bulk create execution
created_issues = jira.create_issues(field_list=issue_list)
result = [f"Successfully created {len(created_issues)} issues:"]
for issue_result in created_issues:
if issue_result.get('issue'):
iss = issue_result['issue']
result.append(f"- [[{iss.key}]]({BASE_URL}/browse/{iss.key})")
else:
result.append(f"- Failed to create issue: {issue_result.get('error')}")
return "\n".join(result)
except Exception as e:
return f"Error creating bulk issues: {str(e)}"
@mcp.tool()
def assign_issue_to_me(issue_key: str) -> str:
"""Assigns the specified issue to the authenticated user.
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
jira = _get_jira()
try:
# Provide the username we authenticated with
jira.assign_issue(issue_key, JIRA_USERNAME)
return f"Successfully assigned issue [{issue_key}]({BASE_URL}/browse/{issue_key}) to {JIRA_USERNAME}."
except Exception as e:
return f"Error assigning issue {issue_key}: {str(e)}"
@mcp.tool()
def assign_issue(issue_key: str, assignee_username: str) -> str:
"""Assigns the specified issue to the given username (e.g. 'samia').
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
jira = _get_jira()
try:
jira.assign_issue(issue_key, assignee_username)
return f"Successfully assigned issue [{issue_key}]({BASE_URL}/browse/{issue_key}) to {assignee_username}."
except Exception as e:
return f"Error assigning issue {issue_key}: {str(e)}"
@mcp.tool()
def transition_issue(issue_key: str, transition_name: str) -> str:
"""Transitions an issue to a new status by name (e.g., 'Ready for QA').
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
jira = _get_jira()
try:
# First, find available transitions
transitions = jira.transitions(issue_key)
transition_id = None
available_names = []
for t in transitions:
available_names.append(t['name'])
# Case-insensitive match
if t['name'].lower() == transition_name.lower():
transition_id = t['id']
break
if not transition_id:
available_str = ", ".join(f"'{name}'" for name in available_names)
return f"Transition '{transition_name}' not found for issue {issue_key}. Available transitions are: {available_str}"
# Execute the transition
jira.transition_issue(issue_key, transition_id)
return f"Successfully transitioned issue [{issue_key}]({BASE_URL}/browse/{issue_key}) to '{transition_name}'."
except Exception as e:
return f"Error transitioning issue {issue_key}: {str(e)}"
@mcp.tool()
def search_issues(jql_query: str) -> str:
"""Searches Jira for issues using a JQL (Jira Query Language) string.
Example: 'project = PCM AND text ~ "Dashboard" ORDER BY created DESC'
IMPORTANT: When responding to the user, you MUST format issue keys as clickable markdown links using the URLs provided in this tool's output.
"""
jira = _get_jira()
try:
issues = jira.search_issues(jql_query, maxResults=50)
if not issues:
return "No issues found matching the query."
result = [f"Found {len(issues)} issues matching your query:"]
for issue in issues:
assignee = issue.fields.assignee.displayName if issue.fields.assignee else "Unassigned"
priority = issue.fields.priority.name if getattr(issue.fields, 'priority', None) else 'None'
result.append(f"- [[{issue.key}]]({BASE_URL}/browse/{issue.key}) {issue.fields.summary} (Status: {issue.fields.status.name}, Assignee: {assignee}, Priority: {priority})")
return "\n".join(result)
except Exception as e:
return f"Error searching issues: {str(e)}"
@mcp.tool()
def get_all_projects() -> str:
"""Fetches a list of all Jira projects available to the authenticated user."""
jira = _get_jira()
try:
projects = jira.projects()
if not projects:
return "No projects found."
result = [f"Found {len(projects)} projects:"]
for project in projects:
result.append(f"- {project.name} (Key: {project.key})")
return "\n".join(result)
except Exception as e:
return f"Error fetching projects: {str(e)}"
@mcp.tool()
def add_comment_to_issue(issue_key: str, comment: str) -> str:
"""Adds a new comment to a Jira issue.
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
jira = _get_jira()
try:
jira.add_comment(issue_key, comment)
return f"Successfully added comment to [{issue_key}]({BASE_URL}/browse/{issue_key})."
except Exception as e:
return f"Error adding comment to {issue_key}: {str(e)}"
@mcp.tool()
def get_issue_comments(issue_key: str) -> str:
"""Retrieves all comments for a specific Jira issue."""
jira = _get_jira()
try:
issue = jira.issue(issue_key)
comments = issue.fields.comment.comments
if not comments:
return f"No comments found for {issue_key}."
result = [f"Comments for {issue_key}:"]
for c in comments:
author = c.author.displayName if c.author else "Unknown"
result.append(f"--- [{c.created}] {author} ---\n{c.body}\n")
return "\n".join(result)
except Exception as e:
return f"Error fetching comments for {issue_key}: {str(e)}"
@mcp.tool()
def log_work_on_issue(issue_key: str, time_spent: str) -> str:
"""Logs work (time spent) on a Jira issue. Format Example for time_spent: '2h 30m' or '1d'
IMPORTANT: You must explicitly ask the user for confirmation before executing this tool.
"""
jira = _get_jira()
try:
jira.add_worklog(issue_key, timeSpent=time_spent)
return f"Successfully logged {time_spent} of work on [{issue_key}]({BASE_URL}/browse/{issue_key})."
except Exception as e:
return f"Error logging work on {issue_key}: {str(e)}"
@mcp.tool()
def get_jira_context() -> str:
"""Returns a hardcoded list of known Project Names -> Project Keys, and known User Names.
Use this to look up a project key if the user only provides the project name, or to see the correct spellings of active users.
"""
context_str = """
Known Projects:
- e-GP Bhutan Phase III (Key: EGPBIII)
- eGPBhutan-RnD (Key: RND)
- Model Analyzer (Key: MA)
- Order Management System (Key: OMS)
- PQCAL INT (Key: PQCI)
- PQCAL Mobile (Key: PCM)
- Project Management Information System (Key: PMIS)
Active Users:
- Shahidul Islam
- Samia Islam
- Md. Sazzadul Islam
- Ruhit Arman
- Sudipta Anupan Datta
- Mehreen Rashid
- Asif Anam
- Ayasha Hossain Jui
"""
if JIRA_QA_TESTER:
context_str += f"\nConfigured QA Tester for this environment: {JIRA_QA_TESTER}\n"
return context_str
def main():
"""Entry point for the Jira MCP server."""
mcp.run()
if __name__ == "__main__":
main()
[build-system]
requires = ["setuptools>=65.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "jira-mcp"
version = "1.0.0"
description = "A Model Context Protocol (MCP) server that connects AI assistants to a self-hosted Dohatec Jira instance"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Md Shahidul Islam", email = "shahidul@dohatec.com.bd"}
]
keywords = ["jira", "mcp", "model-context-protocol", "ai", "dohatec"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Internet",
]
requires-python = ">=3.9"
dependencies = [
"mcp>=0.1.0",
"jira>=3.0.0",
]
[project.urls]
Repository = "https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git"
Documentation = "https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira"
Issues = "https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira/-/issues"
[project.scripts]
dohatec-jira-mcp = "jira_mcp.server:main"
[tool.setuptools]
packages = ["jira_mcp"]
[tool.setuptools.package-data]
jira_mcp = []
...@@ -3,7 +3,7 @@ from jira import JIRA ...@@ -3,7 +3,7 @@ from jira import JIRA
JIRA_SERVER = os.environ.get("JIRA_SERVER", "http://103.41.111.60:8085") JIRA_SERVER = os.environ.get("JIRA_SERVER", "http://103.41.111.60:8085")
JIRA_USERNAME = os.environ.get("JIRA_USERNAME", "shahidul") JIRA_USERNAME = os.environ.get("JIRA_USERNAME", "shahidul")
JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD", "Shahidul@Jira#2024") JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD", "[]")
jira = JIRA(server=JIRA_SERVER, basic_auth=(JIRA_USERNAME, JIRA_PASSWORD)) jira = JIRA(server=JIRA_SERVER, basic_auth=(JIRA_USERNAME, JIRA_PASSWORD))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment