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

Dev shahidul

parent 3507a61d
venv
__pycache__
\ No newline at end of file
# Changelog
All notable changes to this project will be documented in this file.
## [1.0.0] - 2026-03-08
### OVERVIEW
Initial release of Jira MCP Server - a comprehensive Model Context Protocol server for seamless Jira integration. Provides complete issue management, project administration, and workflow automation capabilities with user confirmation workflows for safety-critical operations.
### HIGHLIGHTS
**Complete Issue Management** - Fetch, create, view, assign, and transition Jira issues with full support for stories, tasks, and subtasks.
**Advanced Issue Creation** - Create issues with subtasks in a single operation, supporting both Story and Task parent types with intelligent structuring.
**Bulk Operations** - Create multiple issues simultaneously with comprehensive support for complex issue hierarchies.
**Project & User Management** - List all Jira projects, manage active project context, and retrieve comprehensive user information for better collaboration.
**Issue Search & Filtering** - Powerful JQL-based search to query and filter issues across projects with advanced criteria.
**Work Tracking** - Log work, add comments to issues, and manage issue transitions with comprehensive workflow support.
**Context-Aware Tools** - Get active Jira context including known projects and users to enhance command accuracy and relevance.
**Safety-First Design** - Explicit user confirmation warnings on all state-modifying operations to prevent accidental changes.
**Markdown Link Support** - Automatically format Jira issue links as clickable Markdown for better integration with documentation.
**Multi-Platform Configuration** - Ready-to-use setup guides for Mac and Windows environments with clear configuration examples.
### KEY FEATURES
- **Issue Tools**: `create_issue`, `create_issue_advanced`, `create_issue_with_subtasks`, `get_issue_details`, `get_multiple_issues_details`, `assign_issue`, `transition_issue`, `add_issue_comment`, `log_work`
- **Project Tools**: `get_all_projects`, `set_active_project`
- **User Tools**: `get_jira_context`
- **Search Tools**: `search_jira_issues`
- **Confirmation Workflows**: Safety warnings on all creation, assignment, and transition operations
- **Environment Support**: Mac and Windows with detailed configuration examples
### COMMITS
- 6b61c80 - docs: Update README section headers for improved clarity and organization
- 08b7d74 - docs: Update README to reflect project name change and enhance example queries
- c5513d2 - docs: Add Mac and Windows configuration examples and new example queries
- 36b31d3 - feat: Add `assign_issue` tool and include `JIRA_QA_TESTER` in the context
- 65093f9 - feat: add `get_jira_context` tool to provide known Jira projects and active users
- 3c8b8df - feat: Add QA tester tracking and enhanced issue assignment documentation
- 760d118 - docs: Update tool docstrings with markdown link formatting instructions
- 9cbb732 - feat: Add explicit user confirmation warning to Jira tool docstrings
- 6e57989 - feat: Add `get_multiple_issues_details` tool for batch issue retrieval
- b69cd95 - feat: format Jira issue links in output messages as Markdown clickable links
- 29a85ba - feat: Include issue URLs in various command outputs and success messages
- 8415dbb - feat: Add bulk issue creation functionality and comprehensive README
- 37d0890 - feat: Add `create_issue_advanced` MCP tool and initial README documentation
- 0f46289 - feat: Add comprehensive README documentation and enhanced `create_issue_with_subtasks`
- a66fca1 - feat: Add `create_story_with_subtasks` tool and comprehensive README documentation
- f566467 - feat: add active project management, issue commenting, work logging
- 7cdf77a - feat: Add `get_all_projects` tool to list Jira projects
- fd65f80 - feat: Add JQL issue search functionality and document server setup
- 32dbf8a - feat: introduce Jira MCP server with core issue management and setup guide
### RELEASE ARTIFACTS
- Jira MCP Server (`jira_mcp_server.py`)
- Comprehensive test suite (`test_jira.py`, `test_mcp.py`, `test_mcp_create.py`)
- Complete documentation (README with setup guides and example prompts)
---
## Versioning
This project follows [Semantic Versioning](https://semver.org/). Release format: `MAJOR.MINOR.PATCH`
## Getting Started
See [README.md](README.md) for setup instructions and usage examples.
# mcp-server-jira # Dohatech Jira MCP Server
Connect with JIRA and use its datasource as a knowledge base and respond to queries regarding JIRA. Require Authorization to perform. This repository contains a Model Context Protocol (MCP) server that connects your AI assistants (like Cursor, Gemini, Claude Desktop) directly to any self-hosted Jira Server or Jira Cloud instance. It enables your AI to seamlessly manage tasks, create issues, and query your project management workflow without leaving the chat interface.
\ No newline at end of file
## Author
**Md Shahidul Islam** - Project Creator & Developer
---
## Features Supported
- **Fetch Assigned Tasks:** Get all open tasks currently assigned to you.
- **View Issue Details:** Retrieve full information about a specific issue (description, status, priority, reporter).
- **List Projects:** View exactly what projects exist and their keys.
- **Active Project Context:** Set an active project for your session so you don't have to keep repeating the project key when creating issues.
- **Create Issues:** Generate new Jira tasks directly from the chat interface.
- **Create Bulk Issues:** Effortlessly create dozen of issues, bugs, and tasks simultaneously in a single command.
- **Create Advanced Issues:** Specify labels, components, assignee, due date, original estimates, priority, and environment when creating issues, just like the web interface.
- **Create Issue with Subtasks:** Automatically generate a parent Story or Task and link multiple subtasks to it in a single command.
- **Assign Issues:** Automatically assign existing or newly created issues to yourself, or to others.
- **QA Tester Tracking:** Know who the global QA tester is and seamlessly assign issues to them based on configuration.
- **Update Status:** Transition issues through your Jira workflow (e.g., to "Ready for QA").
- **Search Jira (JQL):** Run advanced queries against you Jira using standard JQL syntax.
- **Issue Comments:** Read the conversation history on a ticket or post a new comment.
- **Log Work:** Record time spent on a task directly from your AI interface.
---
## [CONFIG] How to Setup
### Prerequisites
Make sure you have Python installed on your system.
### Installation
#### Mac / Linux
1. Clone this repository or download the source code:
```bash
git clone <REPOSITORY_URL>
cd <REPOSITORY_DIRECTORY>
```
2. Set up a virtual environment and install dependencies:
```bash
python3 -m venv venv
source venv/bin/activate
pip install mcp jira
```
#### Windows
1. Clone this repository or download the source code:
```cmd
git clone <REPOSITORY_URL>
cd <REPOSITORY_DIRECTORY>
```
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`
* **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"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": {
"command": "/Users/mdshahidulislam/Documents/resource/jiramcp/venv/bin/python3",
"args": [
"/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"
}
}
```
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"
}
}
```
*(Note: You can remove `JIRA_DEFAULT_PROJECT` or `JIRA_QA_TESTER` if you don't need them).*
### 2. Cursor Configuration
1. Open Cursor Settings -> Features -> **MCP Servers**
2. Click **+ Add New MCP Server**
3. **Name:** `Jira Server`
4. **Type:** `command`
5. **Command:** `<ABSOLUTE_PATH_TO_PYTHON_EXECUTABLE_IN_VENV> <ABSOLUTE_PATH_TO_YOUR_REPOSITORY>/jira_mcp_server.py`
6. Click Save and make sure it has the green status circle indicating it's connected.
*(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).*
---
## [EXAMPLES] Example Prompts
Once the MCP server is running, the AI has direct access to your Jira. You don't need to specify the URL or API details anymore. Just talk to the AI naturally!
Here are examples of what you can ask:
### [TASKS] Checking Your Work
```
What are my open assigned tasks in Jira?
```
```
Can you show me the details for issue PQCI-875?
```
```
Summarize the description of PQCI-877 for me.
```
```
What is my active project?
```
```
Set my active project to PQCI.
```
```
Show me the details for issues PCM-131, PCM-132, and PCM-133.
```
```
List only those issues which status is qa assign today
```
```
List all issues in PCM that were assigned to QA status yesterday.
```
```
List all issues in PCM that are currently 'In Progress' and assigned to me.
```
### [+] Creating Tasks
You can specify absolutely any Issue Type that is valid on your Jira server when creating an issue. E.g.:
`Bug`, `New Feature`, `Task`, `Improvement`, `Epic`, `Story`, `UAT-Feedback`, `Enhancement`, `Test Result`, `Use Case`, `After UAT - Feedback`.
#### Basic Task Creation
```
Create a new task in the PQCAL Mobile project (PCM) to 'Fix the broken login button on iOS'. The description should say that it crashes on the staging environment.
```
```
I just realized we need a new button on the dashboard. Please create a Jira ticket for this. (Requires an active project to be set!)
```
#### Creating Issues with Subtasks
```
Create a story titled 'Allow Admin to Change Trader of an Investor' with the following subtasks:
- Front-end: Design
- Back-end: Develop
```
```
Create a Task titled 'Migrate database' with subtasks:
- Backup
- Run migration
- Verify data
```
#### Advanced Task Creation
```
Create a major priority bug for the 'login' component, assigned to 'shahidul', with label 'frontend', environment 'iOS', due '2026-10-31', and an original estimate of '3d 4h'. Support this with description X.
```
#### Bulk Issue Creation
```
I have a list of 5 tasks I need created at once in PQCI:
- Update logo
- Fix header bug (issue_type=Bug)
- Write documentation
- Audit dependencies
- Update readme
```
### [USER] Modifying Assignments
```
Assign the ticket PCM-228 to me.
```
```
Assign PCM-155 to the QA tester.
```
```
Who is the designated tester?
```
```
Create a new task for 'Update API Documentation' in PCM, and assign it to me right away.
```
### [=>] Updating Statuses
```
I just finished working on PCM-131. Please transition it to 'Ready for QA'.
```
```
Move PQCI-873 to 'Start Progress'.
```
```
What transitions are currently available for my task PCM-228?
```
### [COMM] Communication & Time Logging
```
Can you fetch the comments on issue PCM-123?
```
```
Add a comment to PQCI-875 saying: 'I've addressed the feedback in my latest PR.'
```
```
Log 2h 30m of work on PCM-229.
```
### [SEARCH] Advanced Search (JQL)
```
Find all issues in the PCM project containing the word 'login' that are unassigned.
```
```
List all high-priority bugs reported this week.
```
```
What issues are currently in 'Ready for QA' status for PQCI?
```
---
## [CONTRIB] Contributing
We welcome contributions from everyone! Whether you're fixing bugs, adding new features, improving documentation, or suggesting enhancements, your help is greatly appreciated.
### How to Contribute
1. **Fork the repository** - Click the fork button on GitHub
2. **Create a new branch** - `git checkout -b feature/your-feature-name` or `git checkout -b bugfix/your-bug-fix`
3. **Make your changes** - Implement your feature or bug fix
4. **Test your changes** - Ensure everything works as expected
5. **Commit your changes** - `git commit -m "Clear description of your changes"`
6. **Push to your branch** - `git push origin feature/your-feature-name`
7. **Create a Pull Request** - Submit a PR with a clear description of your changes
### Areas for Enhancement
- Additional Jira operations and features
- Better error handling and logging
- Support for more MCP clients
- Improved documentation and examples
- Performance optimizations
- Unit tests and test coverage
- Bug fixes and reliability improvements
### Code Style
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!
# ==============================================================================
# _____ _ _
# | __ \ | | | |
# | | | | ___ | |__ __ _| |_ ___ ___
# | | | |/ _ \| '_ \ / _` | __/ _ \/ __|
# | |__| | (_) | | | | (_| | || __/ (__
# |_____/ \___/|_| |_|\__,_|\__\___|\___|
# ^
# /
#
# 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
if __name__ == "__main__":
# Start the FastMCP server
mcp.run()
import os
from jira import JIRA
JIRA_SERVER = os.environ.get("JIRA_SERVER", "http://103.41.111.60:8085")
JIRA_USERNAME = os.environ.get("JIRA_USERNAME", "shahidul")
JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD", "Shahidul@Jira#2024")
jira = JIRA(server=JIRA_SERVER, basic_auth=(JIRA_USERNAME, JIRA_PASSWORD))
try:
# Find samia's username by searching a ticket we know she is assigned to (e.g. PCM-216 or PCM-180)
issue = jira.issue("PCM-180")
samia_username = issue.fields.assignee.name
print("Samia Username:", samia_username)
# Assign PCM-155
print("Assigning PCM-155 to", samia_username)
jira.assign_issue("PCM-155", samia_username)
# Find transition ID for "QA Assigned" or "Ready for QA"
issue155 = jira.issue("PCM-155")
transitions = jira.transitions(issue155)
qa_transition_id = None
for t in transitions:
if "qa" in t['name'].lower():
qa_transition_id = t['id']
print("Found transition:", t['name'], "ID:", t['id'])
break
if qa_transition_id:
jira.transition_issue("PCM-155", qa_transition_id)
print("Transitioned successfully.")
else:
print("Could not find QA transition in:", [t['name'] for t in transitions])
print("Success!")
except Exception as e:
print(f"Error: {e}")
import sys
from jira_mcp_server import (
mcp,
jira_client,
get_my_assigned_issues,
get_issue_details,
create_issue,
assign_issue_to_me,
transition_issue
)
print("=== Starting Jira MCP Server API Tests ===\n")
if not jira_client:
print("Failed: Jira client is not initialized.")
sys.exit(1)
# Test 1: Fetch My Issues
print("\n--- Test 1: Get My Assigned Issues ---")
my_issues_result = get_my_assigned_issues()
print(my_issues_result)
# Try fetching details of the first issue if any exist
lines = my_issues_result.split('\n')
test_issue_key = None
if len(lines) > 1 and lines[1].startswith('- ['):
# Extract the issue key like "PCM-131" from "- [PCM-131] Summary..."
test_issue_key = lines[1].split(']')[0].split('[')[1]
if test_issue_key:
# Test 2: Get Issue Details
print(f"\n--- Test 2: Get Issue Details for {test_issue_key} ---")
details_result = get_issue_details(test_issue_key)
print(details_result)
else:
print("\n--- Skipping Test 2: No open assigned issues found to detail. ---")
# Let's verify we can find the available transitions for the test issue
if test_issue_key:
print(f"\n--- Test 3: Checking Transitions for {test_issue_key} ---")
try:
transitions = jira_client.transitions(test_issue_key)
available = [t['name'] for t in transitions]
print(f"Available transitions: {', '.join(available)}")
except Exception as e:
print(f"Failed to fetch transitions: {e}")
print("\n=== Tests Complete ===")
import sys
import time
from jira_mcp_server import (
mcp,
jira_client,
get_my_assigned_issues,
get_issue_details,
create_issue,
assign_issue_to_me,
transition_issue
)
print("=== Starting Jira MCP Server API Tests ===\n")
if not jira_client:
print("Failed: Jira client is not initialized.")
sys.exit(1)
# Test 4: Create a new Issue
print("\n--- Test 4: Create a new Issue ---")
try:
# We use "PCM" project as seen from the user's issues list (e.g. PCM-228)
create_result = create_issue("PCM", "MCP Automated Test Issue", "This issue was created by the FastMCP testing script.")
print(create_result)
# Extract the new issue key from the result string. e.g "Successfully created issue PCM-229. URL:..."
new_test_key = create_result.split(' ')[3].replace('.', '')
# Test 5: Assign Issue to me
if new_test_key.startswith('PCM'):
print(f"\n--- Test 5: Assign Issue {new_test_key} to me ---")
assign_result = assign_issue_to_me(new_test_key)
print(assign_result)
# Give Jira a second to index the transition
time.sleep(2)
print(f"\n--- Test 6: Verify {new_test_key} Details ---")
details = get_issue_details(new_test_key)
print(details)
except Exception as e:
print(f"Tests failed: {e}")
print("\n=== Tests Complete ===")
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