Skip to main content
Glama
AstroMined

PyGithub MCP Server

by AstroMined

list_issues

Retrieve GitHub repository issues with filtering options for state, labels, sorting, and pagination to manage and analyze project tasks.

Instructions

List issues from a GitHub repository.

Args:
    params: Parameters for listing issues including:
        - owner: Repository owner (user or organization)
        - repo: Repository name
        - state: Issue state (open, closed, all)
        - labels: Filter by labels
        - sort: Sort field (created, updated, comments)
        - direction: Sort direction (asc, desc)
        - since: Filter by date
        - page: Page number for pagination
        - per_page: Number of results per page (max 100)

Returns:
    List of issues from GitHub API

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Implementation Reference

  • MCP tool handler for 'list_issues'. Converts input params to ListIssuesParams, calls operations.issues.list_issues(), formats JSON response or error.
    def list_issues(params: ListIssuesParams) -> dict:
        """List issues from a GitHub repository.
        
        Args:
            params: Parameters for listing issues including:
                - owner: Repository owner (user or organization)
                - repo: Repository name
                - state: Issue state (open, closed, all)
                - labels: Filter by labels
                - sort: Sort field (created, updated, comments)
                - direction: Sort direction (asc, desc)
                - since: Filter by date
                - page: Page number for pagination
                - per_page: Number of results per page (max 100)
        
        Returns:
            List of issues from GitHub API
        """
        try:
            logger.debug(f"list_issues called with params: {params}")
            # Pass the Pydantic model directly to the operation
            result = issues.list_issues(params)
            logger.debug(f"Got result: {result}")
            response = {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
            logger.debug(f"Returning response: {response}")
            return response
        except GitHubError as e:
            logger.error(f"GitHub error: {e}")
            return {
                "content": [{"type": "error", "text": format_github_error(e)}],
                "is_error": True
            }
        except Exception as e:
            logger.error(f"Unexpected error: {e}")
            logger.error(traceback.format_exc())
            error_msg = str(e) if str(e) else "An unexpected error occurred"
            return {
                "content": [{"type": "error", "text": f"Internal server error: {error_msg}"}],
                "is_error": True
            }
  • Pydantic schema ListIssuesParams defining input parameters and validation for the list_issues tool.
    class ListIssuesParams(RepositoryRef):
        """Parameters for listing issues."""
    
        model_config = ConfigDict(strict=True)
        
        state: Optional[str] = Field(
            None, 
            description=f"Issue state: {', '.join(VALID_ISSUE_STATES)}"
        )
        labels: Optional[List[str]] = Field(
            None, 
            description="Filter by labels (list of label names)"
        )
        sort: Optional[str] = Field(
            None, 
            description=f"Sort by: {', '.join(VALID_SORT_VALUES)}"
        )
        direction: Optional[str] = Field(
            None, 
            description=f"Sort direction: {', '.join(VALID_DIRECTION_VALUES)}"
        )
        since: Optional[datetime] = Field(
            None, 
            description="Filter by date (ISO 8601 format with timezone: YYYY-MM-DDThh:mm:ssZ)"
        )
        page: Optional[int] = Field(
            None, 
            description="Page number for pagination (1-based)"
        )
        per_page: Optional[int] = Field(
            None, 
            description="Results per page (max 100)"
        )
        
        @field_validator('state')
        @classmethod
        def validate_state(cls, v):
            """Validate that state is one of the allowed values."""
            if v is not None and v not in VALID_ISSUE_STATES:
                raise ValueError(f"Invalid state: {v}. Must be one of: {', '.join(VALID_ISSUE_STATES)}")
            return v
        
        @field_validator('sort')
        @classmethod
        def validate_sort(cls, v):
            """Validate that sort is one of the allowed values."""
            if v is not None and v not in VALID_SORT_VALUES:
                raise ValueError(f"Invalid sort value: {v}. Must be one of: {', '.join(VALID_SORT_VALUES)}")
            return v
        
        @field_validator('direction')
        @classmethod
        def validate_direction(cls, v):
            """Validate that direction is one of the allowed values."""
            if v is not None and v not in VALID_DIRECTION_VALUES:
                raise ValueError(f"Invalid direction: {v}. Must be one of: {', '.join(VALID_DIRECTION_VALUES)}")
            return v
        
        @field_validator('page')
        @classmethod
        def validate_page(cls, v):
            """Validate that page is a positive integer."""
            if v is not None and v < 1:
                raise ValueError("Page number must be a positive integer")
            return v
        
        @field_validator('per_page')
        @classmethod
        def validate_per_page(cls, v):
            """Validate that per_page is a positive integer <= 100."""
            if v is not None:
                if v < 1:
                    raise ValueError("Results per page must be a positive integer")
                if v > 100:
                    raise ValueError("Results per page cannot exceed 100")
            return v
        
        @field_validator('since', mode='before')
        @classmethod
        def validate_since(cls, v):
            """Convert string dates to datetime objects.
            
            Accepts:
            - ISO 8601 format strings with timezone (e.g., "2020-01-01T00:00:00Z")
            - ISO 8601 format strings with timezone without colon (e.g., "2020-01-01T12:30:45-0500")
            - ISO 8601 format strings with short timezone (e.g., "2020-01-01T12:30:45+05")
            - ISO 8601 format strings with single digit timezone (e.g., "2020-01-01T12:30:45-5")
            - datetime objects
            
            Returns:
            - datetime object
            
            Raises:
            - ValueError: If the string cannot be converted to a valid datetime object
            """
            if isinstance(v, str):
                # Basic validation - must have 'T' and some form of timezone indicator
                if not ('T' in v and ('+' in v or 'Z' in v or '-' in v.split('T')[1])):
                    raise ValueError(
                        f"Invalid ISO format datetime: {v}. "
                        f"Must include date, time with 'T' separator, and timezone."
                    )
                
                try:
                    # Try to convert using our flexible converter
                    return convert_iso_string_to_datetime(v)
                except ValueError as e:
                    # Only raise if conversion actually fails
                    raise ValueError(f"Invalid ISO format datetime: {v}. {str(e)}")
            return v
  • Registers the list_issues tool (among others) with the MCP server using register_tools.
    def register(mcp: FastMCP) -> None:
        """Register all issue tools with the MCP server.
        
        Args:
            mcp: The MCP server instance
        """
        from pygithub_mcp_server.tools import register_tools
        
        # List of all issue tools to register
        issue_tools = [
            create_issue,
            list_issues,
            get_issue,
            update_issue,
            add_issue_comment,
            list_issue_comments,
            update_issue_comment,
            delete_issue_comment,
            add_issue_labels,
            remove_issue_label,
        ]
        
        register_tools(mcp, issue_tools)
        logger.debug(f"Registered {len(issue_tools)} issue tools")
  • Core implementation that fetches issues using PyGithub repository.get_issues(), handles pagination and conversion. Called by the MCP tool handler.
    def list_issues(params: ListIssuesParams) -> List[Dict[str, Any]]:
        """List issues in a repository.
    
        Args:
            params: Validated parameters for listing issues
    
        Returns:
            List of issues from GitHub API
    
        Raises:
            GitHubError: If the API request fails
        """
        try:
            # No need for parameter validation as Pydantic already validated the input
            client = GitHubClient.get_instance()
            repository = client.get_repo(f"{params.owner}/{params.repo}")
    
            # Default to 'open' if state is None
            state = params.state or 'open'
    
            # Build kwargs for get_issues using fields from the Pydantic model
            kwargs = {"state": state}
            
            # Add optional parameters only if provided
            if params.sort:
                kwargs["sort"] = params.sort
            if params.direction:
                kwargs["direction"] = params.direction
            if params.since:
                kwargs["since"] = params.since
                logger.debug(f"Using UTC since parameter: {params.since.isoformat()}")
            if params.labels is not None:
                # Convert to PyGithub-compatible format
                from ..converters.parameters import convert_labels_parameter
                kwargs["labels"] = convert_labels_parameter(params.labels)
                logger.debug(f"Using labels filter: {kwargs['labels']}")
                
            # Get paginated issues
            logger.debug(f"Getting issues for {params.owner}/{params.repo} with kwargs: {kwargs}")
            try:
                paginated_issues = repository.get_issues(**kwargs)
                logger.debug(f"Got PaginatedList of issues: {paginated_issues}")
            except AssertionError as e:
                logger.error(f"PyGithub assertion error: {e}")
                logger.error(f"Error type: {type(e)}")
                logger.error(f"Error args: {e.args}")
                raise GitHubError("Invalid parameter values for get_issues")
            except GithubException as e:
                # Let the GitHub client handle the exception properly
                raise GitHubClient.get_instance()._handle_github_exception(e)
            except Exception as e:
                logger.error(f"Error getting issues: {e}")
                logger.error(f"Error type: {type(e)}")
                logger.error(f"Error args: {e.args}")
                raise GitHubError(f"Failed to get issues: {str(e)}")
    
            try:
                # Use our pagination utility to safely handle paginated lists
                issues = get_paginated_items(paginated_issues, params.page, params.per_page)
                
                logger.debug(f"Retrieved {len(issues)} issues")
    
                # Convert each issue to our schema
                converted_issues = [convert_issue(issue) for issue in issues]
                logger.debug(f"Converted {len(converted_issues)} issues to schema")
                return converted_issues
    
            except Exception as e:
                logger.error(f"Error handling pagination: {str(e)}")
                raise GitHubError(f"Error retrieving issues: {str(e)}")
    
        except GithubException as e:
            # Convert PyGithub exception to our error type
            error = GitHubClient.get_instance()._handle_github_exception(e)
            raise error

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/AstroMined/pygithub-mcp-server'

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