list_issues
Retrieve and filter GitHub repository issues based on criteria like state, labels, sorting, and date, using intelligent parameter handling and pagination support.
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
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Input Schema (JSON Schema)
{
"$defs": {
"ListIssuesParams": {
"description": "Parameters for listing issues.",
"properties": {
"direction": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Sort direction: asc, desc",
"title": "Direction"
},
"labels": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Filter by labels (list of label names)",
"title": "Labels"
},
"owner": {
"description": "Repository owner (username or organization)",
"title": "Owner",
"type": "string"
},
"page": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Page number for pagination (1-based)",
"title": "Page"
},
"per_page": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Results per page (max 100)",
"title": "Per Page"
},
"repo": {
"description": "Repository name",
"title": "Repo",
"type": "string"
},
"since": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Filter by date (ISO 8601 format with timezone: YYYY-MM-DDThh:mm:ssZ)",
"title": "Since"
},
"sort": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Sort by: created, updated, comments",
"title": "Sort"
},
"state": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Issue state: open, closed, all",
"title": "State"
}
},
"required": [
"owner",
"repo"
],
"title": "ListIssuesParams",
"type": "object"
}
},
"properties": {
"params": {
"$ref": "#/$defs/ListIssuesParams"
}
},
"required": [
"params"
],
"title": "list_issuesArguments",
"type": "object"
}
Implementation Reference
- MCP tool handler for list_issues. Validates input with ListIssuesParams, calls operations.issues.list_issues, formats response as JSON text 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 for ListIssuesParams, including input validation for parameters like state, labels, sort, direction, since, page, per_page.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
- src/pygithub_mcp_server/tools/issues/tools.py:439-463 (registration)Registers the list_issues tool (and other issue tools) with the MCP server instance 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 performs the actual GitHub API call to list issues using PyGithub repository.get_issues, handles pagination and conversion.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