Commit 5adc0162 authored by MD. SHAHIDUL ISLAM's avatar MD. SHAHIDUL ISLAM

Convert project to installable Python package

- Create jira_mcp package directory with server.py, __init__.py, and __main__.py
- Add modern pyproject.toml with pip install from git support
- Add entry points: 'jira-mcp' CLI command and 'python -m jira_mcp' module runner
- Create MANIFEST.in for distribution includes
- Update .gitignore for build artifacts (dist, build, *.egg-info)
- Update README with new installation instructions and simplified configs
- Package now installable via: pip install git+https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
parent 7f887542
venv venv
__pycache__ __pycache__
*.egg-info/
dist/
build/
*.whl
*.tar.gz
.eggs/
*.egg
.pytest_cache/
.coverage
htmlcov/
\ No newline at end of file
include README.md
include CHANGELOG.md
include LICENSE
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude * .DS_Store
exclude test_*.py
...@@ -33,67 +33,110 @@ This repository contains a Model Context Protocol (MCP) server that connects you ...@@ -33,67 +33,110 @@ 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)
Install the Jira MCP server directly from the Git repository as a Python package:
```bash
pip install git+https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
```
This will:
- Install the `jira-mcp` package with all dependencies
- Create a `jira-mcp` command-line entry point
- Enable `python -m jira_mcp` execution
### Manual Installation (Alternative)
If you prefer to clone and run locally:
#### Mac / Linux #### Mac / Linux
1. Clone this repository or download the source code: 1. Clone this repository:
```bash ```bash
git clone <REPOSITORY_URL> git clone https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
cd <REPOSITORY_DIRECTORY> cd mcp-server-jira
``` ```
2. Set up a virtual environment and install dependencies: 2. Set up a virtual environment and install:
```bash ```bash
python3 -m venv venv python3 -m venv venv
source venv/bin/activate source venv/bin/activate
pip install mcp jira pip install -e .
``` ```
#### Windows #### Windows
1. Clone this repository or download the source code: 1. Clone this repository:
```cmd ```cmd
git clone <REPOSITORY_URL> git clone https://repo.dohatec.com.bd/ai-solutions/mcp-server-jira.git
cd <REPOSITORY_DIRECTORY> cd mcp-server-jira
``` ```
2. Set up a virtual environment and install dependencies: 2. Set up a virtual environment and install:
```cmd ```cmd
python -m venv venv python -m venv venv
venv\Scripts\activate venv\Scripts\activate
pip install mcp jira pip install -e .
``` ```
### 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.
## Configuration
### Environment Variables
Before running the MCP server, set these environment variables:
```bash
export JIRA_SERVER="https://your-jira-domain.com"
export JIRA_USERNAME="your_email_or_username"
export JIRA_PASSWORD="your_api_token_or_password"
export JIRA_DEFAULT_PROJECT="PROJ" # Optional
export JIRA_QA_TESTER="tester_username" # Optional
```
Example for Dohatec Jira:
```bash
export JIRA_SERVER="http://103.41.111.60:8085"
export JIRA_USERNAME="shahidul"
export JIRA_PASSWORD="your-password-or-token"
export JIRA_DEFAULT_PROJECT="PCM"
export JIRA_QA_TESTER="Samia Islam"
```
### Using with Claude Desktop
Edit your `claude_desktop_config.json`:
* **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`
**After installing via pip**, use this simple configuration:
```json ```json
{ {
"mcpServers": { "mcpServers": {
"jira-mcp": { "jira-mcp": {
"command": "<ABSOLUTE_PATH_TO_PYTHON_EXECUTABLE_IN_VENV>", "command": "jira-mcp",
"args": ["<ABSOLUTE_PATH_TO_YOUR_REPOSITORY>/jira_mcp_server.py"],
"env": { "env": {
"JIRA_SERVER": "https://your-jira-domain.com", "JIRA_SERVER": "http://103.41.111.60:8085",
"JIRA_USERNAME": "your_email_or_username", "JIRA_USERNAME": "shahidul",
"JIRA_PASSWORD": "your_api_token_or_password", "JIRA_PASSWORD": "your-password",
"JIRA_DEFAULT_PROJECT": "PROJ", "JIRA_DEFAULT_PROJECT": "PCM",
"JIRA_QA_TESTER": "tester_username" "JIRA_QA_TESTER": "Samia Islam"
} }
} }
} }
} }
``` ```
Example for mac:
Alternatively, if you need Python path or used manual installation:
**Mac Example:**
```json ```json
{
"mcpServers": {
"dohatec-jira": { "dohatec-jira": {
"command": "/Users/mdshahidulislam/Documents/resource/jiramcp/venv/bin/python3", "command": "/Users/mdshahidulislam/Documents/resource/jiramcp/venv/bin/python3",
"args": [ "args": ["-m", "jira_mcp"],
"/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",
...@@ -102,16 +145,17 @@ Example for mac: ...@@ -102,16 +145,17 @@ Example for mac:
"JIRA_QA_TESTER": "Samia Islam" "JIRA_QA_TESTER": "Samia Islam"
} }
} }
}
}
``` ```
Example for windows: **Windows Example:**
```json ```json
{
"mcpServers": {
"dohatec-jira": { "dohatec-jira": {
"command": "C:\\Users\\mdshahidulislam\\Documents\\resource\\jiramcp\\venv\\Scripts\\python.exe", "command": "C:\\Users\\mdshahidulislam\\Documents\\resource\\jiramcp\\venv\\Scripts\\python.exe",
"args": [ "args": ["-m", "jira_mcp"],
"C:\\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",
...@@ -120,20 +164,37 @@ Example for windows: ...@@ -120,20 +164,37 @@ Example for windows:
"JIRA_QA_TESTER": "Samia Islam" "JIRA_QA_TESTER": "Samia Islam"
} }
} }
}
}
``` ```
### Using with Cursor
*(Note: You can remove `JIRA_DEFAULT_PROJECT` or `JIRA_QA_TESTER` if you don't need them).* 1. Open Cursor Settings → Features → **MCP Servers**
### 2. Cursor Configuration
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:** `Jira Server`
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:** `jira-mcp` (if installed via pip) or `python -m jira_mcp` (if using manual installation)
6. Click Save and make sure it has the green status circle indicating it's connected. 6. Set environment variables in your shell before launching Cursor:
```bash
export JIRA_SERVER="http://103.41.111.60:8085"
export JIRA_USERNAME="your-username"
export JIRA_PASSWORD="your-password"
```
---
*(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).* ## Running the Server
### Using the Command Entry Point
```bash
jira-mcp
```
### Using Python Module Runner
```bash
python -m jira_mcp
```
--- ---
......
"""
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]
jira-mcp = "jira_mcp.server:main"
[tool.setuptools]
packages = ["jira_mcp"]
[tool.setuptools.package-data]
jira_mcp = []
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