Skip to main content
Glama

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
NameRequiredDescriptionDefault
project_keyYesThe project key (e.g., 'MYPROJ')

Implementation Reference

  • 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)
  • 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"],
    },
  • 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

Tool Definition Quality

Score is being calculated. Check back soon.

Install Server

Other Tools

Related Tools

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/InfinitIQ-Tech/mcp-jira'

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