get_jira_project_issue_types
Retrieve all issue types for a specific Jira project by providing its project key to manage and categorize tasks effectively.
Instructions
Get all available issue types for a specific Jira project
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_key | Yes | The project key (e.g., 'MYPROJ') |
Implementation Reference
- src/mcp_server_jira/server.py:1015-1065 (handler)Main handler function that implements the 'get_jira_project_issue_types' tool logic by calling the v3 API client to fetch issue types.
async def get_jira_project_issue_types( self, project_key: str ) -> List[Dict[str, Any]]: """Get all available issue types for a specific project using v3 REST API Args: project_key: The project key (e.g., 'PROJ') - kept for backward compatibility, but the new API returns all issue types for the user Returns: List of issue type dictionaries with name, id, and description Example: get_jira_project_issue_types('PROJ') # Returns all issue types accessible to user """ logger.info("Starting get_jira_project_issue_types...") try: # Use v3 API client to get all issue types v3_client = self._get_v3_api_client() response_data = await v3_client.get_issue_types() # The new API returns the issue types directly as a list, not wrapped in an object issue_types_data = ( response_data if isinstance(response_data, list) else response_data.get("issueTypes", []) ) # Convert to the expected format maintaining compatibility issue_types = [] for issuetype in issue_types_data: issue_types.append( { "id": issuetype.get("id"), "name": issuetype.get("name"), "description": issuetype.get("description"), } ) logger.info( f"Found {len(issue_types)} issue types (project_key: {project_key})" ) return issue_types except Exception as e: error_msg = f"Failed to get issue types: {type(e).__name__}: {str(e)}" logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) - src/mcp_server_jira/server.py:1328-1339 (registration)Tool registration in list_tools() defining the tool name, description, and input schema.
name=JiraTools.GET_PROJECT_ISSUE_TYPES.value, description="Get all available issue types for a specific Jira project", inputSchema={ "type": "object", "properties": { "project_key": { "type": "string", "description": "The project key (e.g., 'MYPROJ')", } }, "required": ["project_key"], }, - src/mcp_server_jira/server.py:1495-1505 (registration)Dispatch logic in call_tool() that invokes the handler for this tool.
case JiraTools.GET_PROJECT_ISSUE_TYPES.value: logger.info( "Calling asynchronous tool get_jira_project_issue_types..." ) project_key = arguments.get("project_key") if not project_key: raise ValueError("Missing required argument: project_key") result = await jira_server.get_jira_project_issue_types(project_key) logger.info( "Asynchronous tool get_jira_project_issue_types completed." ) - Enum definition providing the exact tool name string.
GET_PROJECT_ISSUE_TYPES = "get_jira_project_issue_types" class JiraIssueField(BaseModel): name: str value: str class JiraIssueResult(BaseModel): key: str summary: str description: Optional[str] = None status: Optional[str] = None assignee: Optional[str] = None reporter: Optional[str] = None created: Optional[str] = None updated: Optional[str] = None fields: Optional[Dict[str, Any]] = None comments: Optional[List[Dict[str, Any]]] = None watchers: Optional[Dict[str, Any]] = None attachments: Optional[List[Dict[str, Any]]] = None subtasks: Optional[List[Dict[str, Any]]] = None project: Optional[Dict[str, Any]] = None issue_links: Optional[List[Dict[str, Any]]] = None worklog: Optional[List[Dict[str, Any]]] = None timetracking: Optional[Dict[str, Any]] = None class JiraProjectResult(BaseModel): key: str name: str id: str lead: Optional[str] = None class JiraTransitionResult(BaseModel): id: str name: str class JiraServer: def __init__( self, server_url: str = None, auth_method: str = None, username: str = None, password: str = None, token: str = None, ): self.server_url = server_url self.auth_method = auth_method self.username = username self.password = password self.token = token self._v3_api_client = JiraV3APIClient( server_url=self.server_url, username=self.username, token=self.token, password=password, ) self.client = None def connect(self): """Connect to Jira server using provided authentication details""" if not self.server_url: print("Error: Jira server URL not provided") return False error_messages = [] # Try multiple auth methods if possible try: # First, try the specified auth method if self.auth_method == "basic_auth": # Basic auth - either username/password or username/token if self.username and self.password: try: print(f"Trying basic_auth with username and password") self.client = JIRA( server=self.server_url, basic_auth=(self.username, self.password), ) print("Connection successful with username/password") return True except Exception as e: error_msg = f"Failed basic_auth with username/password: {type(e).__name__}: {str(e)}" print(error_msg) error_messages.append(error_msg) if self.username and self.token: try: print(f"Trying basic_auth with username and API token") self.client = JIRA( server=self.server_url, basic_auth=(self.username, self.token), ) print("Connection successful with username/token") return True except Exception as e: error_msg = f"Failed basic_auth with username/token: {type(e).__name__}: {str(e)}" print(error_msg) error_messages.append(error_msg) print("Error: Username and password/token required for basic auth") error_messages.append( "Username and password/token required for basic auth" ) elif self.auth_method == "token_auth": # Token auth - just need the token if self.token: try: print(f"Trying token_auth with token") self.client = JIRA( server=self.server_url, token_auth=self.token ) print("Connection successful with token_auth") return True except Exception as e: error_msg = f"Failed token_auth: {type(e).__name__}: {str(e)}" print(error_msg) error_messages.append(error_msg) else: print("Error: Token required for token auth") error_messages.append("Token required for token auth") # If we're here and have a token, try using it with basic_auth for Jira Cloud # (even if auth_method wasn't basic_auth) if self.token and self.username and not self.client: try: print(f"Trying fallback to basic_auth with username and token") self.client = JIRA( server=self.server_url, basic_auth=(self.username, self.token) ) print("Connection successful with fallback basic_auth") return True except Exception as e: error_msg = ( f"Failed fallback to basic_auth: {type(e).__name__}: {str(e)}" ) print(error_msg) error_messages.append(error_msg) # If we're here and have a token, try using token_auth as a fallback # (even if auth_method wasn't token_auth) if self.token and not self.client: try: print(f"Trying fallback to token_auth") self.client = JIRA(server=self.server_url, token_auth=self.token) print("Connection successful with fallback token_auth") return True except Exception as e: error_msg = ( f"Failed fallback to token_auth: {type(e).__name__}: {str(e)}" ) print(error_msg) error_messages.append(error_msg) # Last resort: try anonymous access try: print(f"Trying anonymous access as last resort") self.client = JIRA(server=self.server_url) print("Connection successful with anonymous access") return True except Exception as e: error_msg = f"Failed anonymous access: {type(e).__name__}: {str(e)}" print(error_msg) error_messages.append(error_msg) # If we got here, all connection attempts failed print(f"All connection attempts failed: {', '.join(error_messages)}") return False except Exception as e: error_msg = f"Unexpected error in connect(): {type(e).__name__}: {str(e)}" print(error_msg) error_messages.append(error_msg) return False def _get_v3_api_client(self) -> JiraV3APIClient: """Get or create a v3 API client instance""" if not self._v3_api_client: self._v3_api_client = JiraV3APIClient( server_url=self.server_url, username=self.username, password=self.password, token=self.token, ) return self._v3_api_client async def get_jira_projects(self) -> List[JiraProjectResult]: """Get all accessible Jira projects using v3 REST API""" logger.info("Starting get_jira_projects...") all_projects_data = [] start_at = 0 max_results = 50 page_count = 0 while True: page_count += 1 logger.info( f"Pagination loop, page {page_count}: startAt={start_at}, maxResults={max_results}" ) try: response = await self._v3_api_client.get_projects( start_at=start_at, max_results=max_results ) projects = response.get("values", []) if not projects: logger.info("No more projects returned. Breaking pagination loop.") break all_projects_data.extend(projects) if response.get("isLast", False): logger.info("'isLast' is True. Breaking pagination loop.") break start_at += len(projects) # Yield control to the event loop to prevent deadlocks in the MCP framework. await asyncio.sleep(0) except Exception as e: logger.error( "Error inside get_jira_projects pagination loop", exc_info=True ) raise logger.info( f"Finished get_jira_projects. Total projects found: {len(all_projects_data)}" ) results = [] for p in all_projects_data: results.append( JiraProjectResult( key=p.get("key"), name=p.get("name"), id=str(p.get("id")), lead=(p.get("lead") or {}).get("displayName"), ) ) logger.info(f"Added project {p.get('key')} to results") logger.info(f"Returning {len(results)} projects") sys.stdout.flush() # Flush stdout to ensure it's sent to MCP, otherwise hang occurs return results def get_jira_issue(self, issue_key: str) -> JiraIssueResult: """Get details for a specific issue by key""" if not self.client: if not self.connect(): # Connection failed - provide clear error message raise ValueError( f"Failed to connect to Jira server at {self.server_url}. Check your authentication credentials." ) try: issue = self.client.issue(issue_key) # Extract comments if available comments = [] if hasattr(issue.fields, "comment") and hasattr( issue.fields.comment, "comments" ): for comment in issue.fields.comment.comments: comments.append( { "author": ( getattr( comment.author, "displayName", str(comment.author) ) if hasattr(comment, "author") else "Unknown" ), "body": comment.body, "created": comment.created, } ) # Create a fields dictionary with custom fields fields = {} for field_name in dir(issue.fields): if not field_name.startswith("_") and field_name not in [ "comment", "attachment", "summary", "description", "status", "assignee", "reporter", "created", "updated", ]: value = getattr(issue.fields, field_name) if value is not None: # Handle special field types if hasattr(value, "name"): fields[field_name] = value.name elif hasattr(value, "value"): fields[field_name] = value.value elif isinstance(value, list): if len(value) > 0: if hasattr(value[0], "name"): fields[field_name] = [item.name for item in value] else: fields[field_name] = value else: fields[field_name] = str(value) return JiraIssueResult( key=issue.key, summary=issue.fields.summary, description=issue.fields.description, status=( issue.fields.status.name if hasattr(issue.fields, "status") else None ), assignee=( issue.fields.assignee.displayName if hasattr(issue.fields, "assignee") and issue.fields.assignee else None ), reporter=( issue.fields.reporter.displayName if hasattr(issue.fields, "reporter") and issue.fields.reporter else None ), created=( issue.fields.created if hasattr(issue.fields, "created") else None ), updated=( issue.fields.updated if hasattr(issue.fields, "updated") else None ), fields=fields, comments=comments, ) except Exception as e: print(f"Failed to get issue {issue_key}: {type(e).__name__}: {str(e)}") raise ValueError( f"Failed to get issue {issue_key}: {type(e).__name__}: {str(e)}" ) async def search_jira_issues( self, jql: str, max_results: int = 10 ) -> List[JiraIssueResult]: """Search for issues using JQL via v3 REST API with pagination support""" logger.info("Starting search_jira_issues...") try: # Use v3 API client v3_client = self._get_v3_api_client() # Collect all issues from all pages all_issues = [] start_at = 0 page_size = min(max_results, 100) # Jira typically limits to 100 per page while True: logger.debug(f"Fetching page starting at {start_at} with page size {page_size}") response_data = await v3_client.search_issues( jql=jql, start_at=start_at, max_results=page_size ) # Extract issues from current page page_issues = response_data.get("issues", []) all_issues.extend(page_issues) logger.debug(f"Retrieved {len(page_issues)} issues from current page. Total so far: {len(all_issues)}") # Check if we've reached the user's max_results limit if len(all_issues) >= max_results: # Trim to exact max_results if we exceeded it all_issues = all_issues[:max_results] logger.debug(f"Reached max_results limit of {max_results}, stopping pagination") break # Check if this is the last page according to API is_last = response_data.get("isLast", True) if is_last: logger.debug("API indicates this is the last page, stopping pagination") break # If we have more pages, prepare for next iteration start_at = len(all_issues) # Use actual number of issues retrieved so far # Adjust page size for next request to not exceed max_results remaining_needed = max_results - len(all_issues) page_size = min(remaining_needed, 100) # Return raw issues list for full JSON data logger.info(f"Returning raw issues ({len(all_issues)}) for JQL: {jql}") return all_issues except Exception as e: error_msg = f"Failed to search issues: {type(e).__name__}: {str(e)}" logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) async def create_jira_issue( self, project: str, summary: str, description: str, issue_type: str, fields: Optional[Dict[str, Any]] = None, ) -> JiraIssueResult: """Create a new Jira issue using v3 REST API Args: project: Project key (e.g., 'PROJ') summary: Issue summary/title description: Issue description issue_type: Issue type - common values include 'Bug', 'Task', 'Story', 'Epic', 'New Feature', 'Improvement' Note: Available issue types vary by Jira instance and project fields: Optional additional fields dictionary Returns: JiraIssueResult object with the created issue details Example: # Create a bug await create_jira_issue( project='PROJ', summary='Login button not working', description='The login button on the homepage is not responding to clicks', issue_type='Bug' ) # Create a task with custom fields await create_jira_issue( project='PROJ', summary='Update documentation', description='Update API documentation with new endpoints', issue_type='Task', fields={ 'assignee': 'jsmith', 'labels': ['documentation', 'api'], 'priority': {'name': 'High'} } ) """ logger.info("Starting create_jira_issue...") try: # Create a properly formatted issue dictionary issue_dict = {} # Process required fields first # Project field - required if isinstance(project, str): issue_dict["project"] = {"key": project} else: issue_dict["project"] = project # Summary - required issue_dict["summary"] = summary # Description if description: issue_dict["description"] = description # Issue type - required, with validation for common issue types logger.info( f"Processing issue_type: '{issue_type}' (type: {type(issue_type)})" ) common_types = [ "bug", "task", "story", "epic", "improvement", "newfeature", "new feature", ] if isinstance(issue_type, str): # Check for common issue type variants and fix case-sensitivity issues issue_type_lower = issue_type.lower() if issue_type_lower in common_types: # Convert first letter to uppercase for standard Jira types issue_type_proper = issue_type_lower.capitalize() if ( issue_type_lower == "new feature" or issue_type_lower == "newfeature" ): issue_type_proper = "New Feature" logger.info( f"Note: Converting issue type from '{issue_type}' to '{issue_type_proper}'" ) issue_dict["issuetype"] = {"name": issue_type_proper} else: # Use the type as provided - some Jira instances have custom types issue_dict["issuetype"] = {"name": issue_type} else: issue_dict["issuetype"] = issue_type # Add any additional fields with proper type handling if fields: for key, value in fields.items(): # Skip fields we've already processed if key in [ "project", "summary", "description", "issuetype", "issue_type", ]: continue # Handle special fields that require specific formats if key == "assignees" or key == "assignee": # Convert string to array for assignees or proper format for assignee if isinstance(value, str): if key == "assignees": issue_dict[key] = [value] if value else [] else: # assignee issue_dict[key] = {"name": value} if value else None elif isinstance(value, list) and key == "assignee" and value: # If assignee is a list but should be a dict with name issue_dict[key] = {"name": value[0]} else: issue_dict[key] = value elif key == "labels": # Convert string to array for labels if isinstance(value, str): issue_dict[key] = [value] if value else [] else: issue_dict[key] = value elif key == "milestone": # Convert string to number for milestone if isinstance(value, str) and value.isdigit(): issue_dict[key] = int(value) else: issue_dict[key] = value else: issue_dict[key] = value # Use v3 API client v3_client = self._get_v3_api_client() response_data = await v3_client.create_issue(fields=issue_dict) # Extract issue details from v3 API response issue_key = response_data.get("key") issue_id = response_data.get("id") logger.info(f"Successfully created issue {issue_key} (ID: {issue_id})") # Return JiraIssueResult with the created issue details # For v3 API, we return what we have from the create response return JiraIssueResult( key=issue_key, summary=summary, # Use the summary we provided description=description, # Use the description we provided status="Open", # Default status for new issues ) except Exception as e: error_msg = f"Failed to create issue: {type(e).__name__}: {str(e)}" logger.error(error_msg, exc_info=True) # Enhanced error handling for issue type errors if "issuetype" in str(e).lower() or "issue type" in str(e).lower(): logger.info( "Issue type error detected, trying to provide helpful suggestions..." ) try: project_key = ( project if isinstance(project, str) else project.get("key") ) if project_key: issue_types = await self.get_jira_project_issue_types( project_key ) type_names = [t.get("name") for t in issue_types] logger.info( f"Available issue types for project {project_key}: {', '.join(type_names)}" ) # Try to find the closest match attempted_type = issue_type closest = None attempted_lower = attempted_type.lower() for t in type_names: if ( attempted_lower in t.lower() or t.lower() in attempted_lower ): closest = t break if closest: logger.info( f"The closest match to '{attempted_type}' is '{closest}'" ) error_msg += f" Available types: {', '.join(type_names)}. Closest match: '{closest}'" else: error_msg += f" Available types: {', '.join(type_names)}" except Exception as fetch_error: logger.error(f"Could not fetch issue types: {str(fetch_error)}") raise ValueError(error_msg) # Re-raise the exception with more details if "issuetype" in error_message.lower(): raise ValueError( f"Invalid issue type '{issue_dict.get('issuetype', {}).get('name', 'Unknown')}'. " + "Use get_jira_project_issue_types(project_key) to get valid types." ) raise return JiraIssueResult( key=new_issue.key, summary=new_issue.fields.summary, description=new_issue.fields.description, status=( new_issue.fields.status.name if hasattr(new_issue.fields, "status") else None ), ) except Exception as e: print(f"Failed to create issue: {type(e).__name__}: {str(e)}") raise ValueError(f"Failed to create issue: {type(e).__name__}: {str(e)}") async def create_jira_issues( self, field_list: List[Dict[str, Any]], prefetch: bool = True ) -> List[Dict[str, Any]]: """Bulk create new Jira issues using v3 REST API. Parameters: field_list (List[Dict[str, Any]]): a list of dicts each containing field names and the values to use. Each dict is an individual issue to create. prefetch (bool): True reloads the created issue Resource so all of its data is present in the value returned (Default: True) Returns: List[Dict[str, Any]]: List of created issues with their details Issue Types: Common issue types include: 'Bug', 'Task', 'Story', 'Epic', 'New Feature', 'Improvement' Note: Available issue types vary by Jira instance and project Example: # Create multiple issues in bulk await create_jira_issues([ { 'project': 'PROJ', 'summary': 'Implement user authentication', 'description': 'Add login and registration functionality', 'issue_type': 'Story' # Note: case-sensitive, match to your Jira instance types }, { 'project': 'PROJ', 'summary': 'Fix navigation bar display on mobile', 'description': 'Navigation bar is not displaying correctly on mobile devices', 'issue_type': 'Bug', 'priority': {'name': 'High'}, 'labels': ['mobile', 'ui'] } ]) """ logger.info("Starting create_jira_issues...") try: # Process each field dict to ensure proper formatting for v3 API processed_field_list = [] for fields in field_list: # Create a properly formatted issue dictionary issue_dict = {} # Process required fields first to ensure they exist # Project field - required if "project" not in fields: raise ValueError("Each issue must have a 'project' field") project_value = fields["project"] if isinstance(project_value, str): issue_dict["project"] = {"key": project_value} else: issue_dict["project"] = project_value # Summary field - required if "summary" not in fields: raise ValueError("Each issue must have a 'summary' field") issue_dict["summary"] = fields["summary"] # Description field - convert to ADF format for v3 API if it's a simple string if "description" in fields: description = fields["description"] if isinstance(description, str): # Convert simple string to Atlassian Document Format issue_dict["description"] = { "type": "doc", "version": 1, "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": description } ] } ] } else: # Assume it's already in ADF format issue_dict["description"] = description # Issue type field - required, handle both 'issuetype' and 'issue_type' issue_type = None if "issuetype" in fields: issue_type = fields["issuetype"] elif "issue_type" in fields: issue_type = fields["issue_type"] else: raise ValueError( "Each issue must have an 'issuetype' or 'issue_type' field" ) # Check for common issue type variants and fix case-sensitivity issues logger.debug( f"Processing bulk issue_type: '{issue_type}' (type: {type(issue_type)})" ) common_types = [ "bug", "task", "story", "epic", "improvement", "newfeature", "new feature", ] if isinstance(issue_type, str): issue_type_lower = issue_type.lower() if issue_type_lower in common_types: # Convert first letter to uppercase for standard Jira types issue_type_proper = issue_type_lower.capitalize() if ( issue_type_lower == "new feature" or issue_type_lower == "newfeature" ): issue_type_proper = "New Feature" logger.debug( f"Converting issue type from '{issue_type}' to '{issue_type_proper}'" ) issue_dict["issuetype"] = {"name": issue_type_proper} else: # Use the type as provided - some Jira instances have custom types issue_dict["issuetype"] = {"name": issue_type} else: issue_dict["issuetype"] = issue_type # Process other fields for key, value in fields.items(): if key in [ "project", "summary", "description", "issuetype", "issue_type", ]: # Skip fields we've already processed continue # Handle special fields that require specific formats if key == "assignees" or key == "assignee": # Convert string to array for assignees or proper format for assignee if isinstance(value, str): if key == "assignees": issue_dict[key] = [value] if value else [] else: # assignee issue_dict[key] = {"name": value} if value else None elif isinstance(value, list) and key == "assignee" and value: # If assignee is a list but should be a dict with name issue_dict[key] = {"name": value[0]} else: issue_dict[key] = value elif key == "labels": # Convert string to array for labels if isinstance(value, str): issue_dict[key] = [value] if value else [] else: issue_dict[key] = value elif key == "milestone": # Convert string to number for milestone if isinstance(value, str) and value.isdigit(): issue_dict[key] = int(value) else: issue_dict[key] = value else: issue_dict[key] = value # Add to the field list in v3 API format processed_field_list.append({"fields": issue_dict}) logger.debug(f"Processed field list: {json.dumps(processed_field_list, indent=2)}") # Use v3 API client v3_client = self._get_v3_api_client() # Call the bulk create API response_data = await v3_client.bulk_create_issues(processed_field_list) # Process the results to maintain compatibility with existing interface processed_results = [] # Handle successful issues if "issues" in response_data: for issue in response_data["issues"]: processed_results.append({ "key": issue.get("key"), "id": issue.get("id"), "self": issue.get("self"), "success": True, }) # Handle errors if "errors" in response_data: for error in response_data["errors"]: processed_results.append({ "error": error, "success": False, }) logger.info(f"Successfully processed {len(processed_results)} issue creations") return processed_results except Exception as e: error_msg = f"Failed to create issues in bulk: {type(e).__name__}: {str(e)}" logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) async def add_jira_comment(self, issue_key: str, comment: str) -> Dict[str, Any]: """Add a comment to an issue using v3 REST API""" logger.info("Starting add_jira_comment...") try: # Use v3 API client v3_client = self._get_v3_api_client() comment_result = await v3_client.add_comment( issue_id_or_key=issue_key, comment=comment, ) # Extract useful information from the v3 API response response_data = { "id": comment_result.get("id"), "body": comment_result.get("body", {}), "created": comment_result.get("created"), "updated": comment_result.get("updated"), } # Extract author information if available if "author" in comment_result: author = comment_result["author"] response_data["author"] = author.get("displayName", "Unknown") else: response_data["author"] = "Unknown" logger.info(f"Successfully added comment to issue {issue_key}") return response_data except Exception as e: error_msg = ( f"Failed to add comment to {issue_key}: {type(e).__name__}: {str(e)}" ) logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) async def get_jira_transitions(self, issue_key: str) -> List[JiraTransitionResult]: """Get available transitions for an issue using v3 REST API""" logger.info("Starting get_jira_transitions...") try: # Use v3 API client v3_client = self._get_v3_api_client() response_data = await v3_client.get_transitions(issue_id_or_key=issue_key) # Extract transitions from response transitions = response_data.get("transitions", []) # Convert to JiraTransitionResult objects maintaining compatibility results = [ JiraTransitionResult(id=transition["id"], name=transition["name"]) for transition in transitions ] logger.info(f"Found {len(results)} transitions for issue {issue_key}") return results except Exception as e: error_msg = f"Failed to get transitions for {issue_key}: {type(e).__name__}: {str(e)}" logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) async def transition_jira_issue( self, issue_key: str, transition_id: str, comment: Optional[str] = None, fields: Optional[Dict[str, Any]] = None, ) -> bool: """Transition an issue to a new state using v3 REST API""" logger.info("Starting transition_jira_issue...") try: # Use v3 API client v3_client = self._get_v3_api_client() await v3_client.transition_issue( issue_id_or_key=issue_key, transition_id=transition_id, fields=fields, comment=comment, ) logger.info( f"Successfully transitioned issue {issue_key} to transition {transition_id}" ) return True except Exception as e: error_msg = ( f"Failed to transition {issue_key}: {type(e).__name__}: {str(e)}" ) logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) async def get_jira_project_issue_types( self, project_key: str ) -> List[Dict[str, Any]]: """Get all available issue types for a specific project using v3 REST API Args: project_key: The project key (e.g., 'PROJ') - kept for backward compatibility, but the new API returns all issue types for the user Returns: List of issue type dictionaries with name, id, and description Example: get_jira_project_issue_types('PROJ') # Returns all issue types accessible to user """ logger.info("Starting get_jira_project_issue_types...") try: # Use v3 API client to get all issue types v3_client = self._get_v3_api_client() response_data = await v3_client.get_issue_types() # The new API returns the issue types directly as a list, not wrapped in an object issue_types_data = ( response_data if isinstance(response_data, list) else response_data.get("issueTypes", []) ) # Convert to the expected format maintaining compatibility issue_types = [] for issuetype in issue_types_data: issue_types.append( { "id": issuetype.get("id"), "name": issuetype.get("name"), "description": issuetype.get("description"), } ) logger.info( f"Found {len(issue_types)} issue types (project_key: {project_key})" ) return issue_types except Exception as e: error_msg = f"Failed to get issue types: {type(e).__name__}: {str(e)}" logger.error(error_msg, exc_info=True) print(error_msg) raise ValueError(error_msg) - Low-level API helper that performs the HTTP GET request to /rest/api/3/issuetype to retrieve issue types.
async def get_issue_types(self) -> Dict[str, Any]: """ Get all issue types for user using the v3 REST API. Returns all issue types. This operation can be accessed anonymously. Permissions required: Issue types are only returned as follows: - if the user has the Administer Jira global permission, all issue types are returned. - if the user has the Browse projects project permission for one or more projects, the issue types associated with the projects the user has permission to browse are returned. - if the user is anonymous then they will be able to access projects with the Browse projects for anonymous users - if the user authentication is incorrect they will fall back to anonymous Returns: List of issue type dictionaries with fields like: - avatarId: Avatar ID for the issue type - description: Description of the issue type - hierarchyLevel: Hierarchy level - iconUrl: URL of the issue type icon - id: Issue type ID - name: Issue type name - self: REST API URL for the issue type - subtask: Whether this is a subtask type Raises: ValueError: If the API request fails """ endpoint = "/issuetype" logger.debug(f"Fetching issue types with v3 API endpoint: {endpoint}") response_data = await self._make_v3_api_request("GET", endpoint) logger.debug(f"Issue types API response: {json.dumps(response_data, indent=2)}") return response_data