Skip to main content
Glama
issues.py9.35 kB
"""Issue operations for JIRA.""" import logging from typing import Any, Dict, List, Optional from jira import JIRA, Issue as JiraIssue from jira.exceptions import JIRAError from mcp_jira.client import JiraClient logger = logging.getLogger(__name__) class IssueOperations: """Handles JIRA issue operations.""" def __init__(self, client: JiraClient): """Initialize issue operations. Args: client: JiraClient instance """ self.client = client def create_issue( self, project: str, summary: str, issue_type: str, description: Optional[str] = None, priority: Optional[str] = None, assignee: Optional[str] = None, labels: Optional[List[str]] = None, components: Optional[List[str]] = None, custom_fields: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Dict[str, Any]: """Create a new JIRA issue. Args: project: Project key summary: Issue summary issue_type: Issue type name description: Issue description (optional) priority: Priority name (optional) assignee: Assignee account ID or username (optional) labels: List of labels (optional) components: List of component names (optional) custom_fields: Dictionary of custom field names/IDs to values (optional) **kwargs: Additional fields Returns: Dictionary with created issue information Raises: JIRAError: If issue creation fails """ try: jira = self.client.jira # Build fields dictionary fields: Dict[str, Any] = { "project": {"key": project}, "summary": summary, "issuetype": {"name": issue_type}, } if description: fields["description"] = description if priority: fields["priority"] = {"name": priority} if assignee: # Try account ID first, fallback to name if assignee.startswith("account"): fields["assignee"] = {"accountId": assignee} else: fields["assignee"] = {"name": assignee} if labels: fields["labels"] = labels if components: fields["components"] = [{"name": comp} for comp in components] # Add custom fields if custom_fields: prepared_custom = self.client.custom_fields.prepare_custom_fields(custom_fields) fields.update(prepared_custom) # Add any additional fields fields.update(kwargs) logger.info(f"Creating issue in project {project}") issue = jira.create_issue(fields=fields) return { "key": issue.key, "id": issue.id, "self": issue.self, "url": f"{self.client.config.jira_url}/browse/{issue.key}", } except JIRAError as e: logger.error(f"Failed to create issue: {e}") raise def get_issue( self, issue_key: str, fields: Optional[List[str]] = None, expand: Optional[str] = None, ) -> Dict[str, Any]: """Get issue details. Args: issue_key: Issue key (e.g., 'PROJ-123') fields: List of fields to retrieve (optional, defaults to all) expand: Comma-separated list of entities to expand (optional) Returns: Dictionary with issue details including custom fields Raises: JIRAError: If issue not found or retrieval fails """ try: jira = self.client.jira logger.info(f"Retrieving issue {issue_key}") issue = jira.issue(issue_key, fields=fields, expand=expand) return self._format_issue(issue) except JIRAError as e: logger.error(f"Failed to get issue {issue_key}: {e}") raise def update_issue( self, issue_key: str, fields: Optional[Dict[str, Any]] = None, custom_fields: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Dict[str, Any]: """Update an existing issue. Args: issue_key: Issue key fields: Dictionary of standard fields to update custom_fields: Dictionary of custom fields to update **kwargs: Additional fields to update Returns: Dictionary with update result Raises: JIRAError: If update fails """ try: jira = self.client.jira update_fields = fields or {} # Prepare custom fields if custom_fields: prepared_custom = self.client.custom_fields.prepare_custom_fields(custom_fields) update_fields.update(prepared_custom) # Add additional fields update_fields.update(kwargs) logger.info(f"Updating issue {issue_key}") issue = jira.issue(issue_key) issue.update(fields=update_fields) return { "key": issue_key, "updated": True, "message": f"Successfully updated issue {issue_key}", } except JIRAError as e: logger.error(f"Failed to update issue {issue_key}: {e}") raise def delete_issue(self, issue_key: str) -> Dict[str, Any]: """Delete an issue. Args: issue_key: Issue key Returns: Dictionary with deletion result Raises: JIRAError: If deletion fails """ try: jira = self.client.jira logger.info(f"Deleting issue {issue_key}") issue = jira.issue(issue_key) issue.delete() return { "key": issue_key, "deleted": True, "message": f"Successfully deleted issue {issue_key}", } except JIRAError as e: logger.error(f"Failed to delete issue {issue_key}: {e}") raise def assign_issue( self, issue_key: str, assignee: Optional[str] = None ) -> Dict[str, Any]: """Assign issue to a user. Args: issue_key: Issue key assignee: Account ID, username, or None to unassign Returns: Dictionary with assignment result Raises: JIRAError: If assignment fails """ try: jira = self.client.jira logger.info(f"Assigning issue {issue_key} to {assignee or 'unassigned'}") jira.assign_issue(issue_key, assignee) return { "key": issue_key, "assigned": True, "assignee": assignee, "message": f"Successfully assigned issue {issue_key}", } except JIRAError as e: logger.error(f"Failed to assign issue {issue_key}: {e}") raise def add_watcher(self, issue_key: str, watcher: str) -> Dict[str, Any]: """Add a watcher to an issue. Args: issue_key: Issue key watcher: Account ID or username Returns: Dictionary with result Raises: JIRAError: If operation fails """ try: jira = self.client.jira logger.info(f"Adding watcher {watcher} to issue {issue_key}") jira.add_watcher(issue_key, watcher) return { "key": issue_key, "watcher_added": True, "watcher": watcher, } except JIRAError as e: logger.error(f"Failed to add watcher to issue {issue_key}: {e}") raise def _format_issue(self, issue: JiraIssue) -> Dict[str, Any]: """Format JIRA issue object to dictionary. Args: issue: JIRA issue object Returns: Formatted dictionary """ fields = issue.raw.get("fields", {}) # Extract custom fields custom = self.client.custom_fields.extract_custom_fields(fields) return { "key": issue.key, "id": issue.id, "self": issue.self, "url": f"{self.client.config.jira_url}/browse/{issue.key}", "summary": fields.get("summary"), "description": fields.get("description"), "status": fields.get("status", {}).get("name"), "priority": fields.get("priority", {}).get("name") if fields.get("priority") else None, "assignee": fields.get("assignee", {}).get("displayName") if fields.get("assignee") else None, "reporter": fields.get("reporter", {}).get("displayName") if fields.get("reporter") else None, "created": fields.get("created"), "updated": fields.get("updated"), "labels": fields.get("labels", []), "components": [c.get("name") for c in fields.get("components", [])], "custom_fields": custom, "raw_fields": fields, }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tarangbhavsar/mcp-jira-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server