Skip to main content
Glama
AstroMined

PyGithub MCP Server

by AstroMined

update_issue

Modify GitHub issues by updating titles, descriptions, states, labels, assignees, or milestones to track and manage project tasks.

Instructions

Update an existing issue.

Args:
    params: Parameters for updating an issue including:
        - owner: Repository owner (user or organization)
        - repo: Repository name
        - issue_number: Issue number to update
        - title: New title (optional)
        - body: New description (optional)
        - state: New state (optional)
        - labels: New labels (optional)
        - assignees: New assignees (optional)
        - milestone: New milestone number (optional)

Returns:
    Updated issue details from GitHub API

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Implementation Reference

  • MCP tool handler for 'update_issue'. Validates input parameters using UpdateIssueParams, delegates to operations.issues.update_issue, handles errors, and returns MCP-formatted response (JSON content or error).
    @tool()
    def update_issue(params: UpdateIssueParams) -> dict:
        """Update an existing issue.
        
        Args:
            params: Parameters for updating an issue including:
                - owner: Repository owner (user or organization)
                - repo: Repository name
                - issue_number: Issue number to update
                - title: New title (optional)
                - body: New description (optional)
                - state: New state (optional)
                - labels: New labels (optional)
                - assignees: New assignees (optional)
                - milestone: New milestone number (optional)
        
        Returns:
            Updated issue details from GitHub API
        """
        try:
            logger.debug(f"update_issue called with params: {params}")
            # Pass the Pydantic model directly to the operation
            result = issues.update_issue(params)
            logger.debug(f"Got result: {result}")
            return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
        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 (UpdateIssueParams) defining and validating input parameters for the update_issue tool, including repository reference, issue number, and optional update fields like title, body, state, labels, assignees, milestone.
    class UpdateIssueParams(RepositoryRef):
        """Parameters for updating an issue."""
    
        model_config = ConfigDict(strict=True)
        
        issue_number: int = Field(..., description="Issue number to update")
        title: Optional[str] = Field(None, description="New title")
        body: Optional[str] = Field(None, description="New description")
        state: Optional[str] = Field(
            None, 
            description="New state (open or closed)"
        )
        labels: Optional[List[str]] = Field(
            None, 
            description="New labels (list of label names)"
        )
        assignees: Optional[List[str]] = Field(
            None, 
            description="New assignees (list of usernames)"
        )
        milestone: Optional[int] = Field(
            None, 
            description="New milestone number (or None to clear)"
        )
        
        @field_validator('state')
        @classmethod
        def validate_state(cls, v):
            """Validate that state is one of the allowed values."""
            # For update, only open and closed are valid (not 'all')
            valid_states = ["open", "closed"]
            if v is not None and v not in valid_states:
                raise ValueError(f"Invalid state: {v}. Must be one of: {', '.join(valid_states)}")
            return v
        
        @field_validator('title')
        @classmethod
        def validate_title(cls, v):
            """Validate that title is not empty if provided."""
            if v is not None and not v.strip():
                raise ValueError("title cannot be empty")
            return v
  • Core implementation of issue update logic using PyGithub: retrieves repository and issue, builds update kwargs from params, calls issue.edit(), fetches updated issue, applies custom conversion, and returns formatted result.
    def update_issue(params: UpdateIssueParams) -> Dict[str, Any]:
        """Update an existing issue.
    
        Args:
            params: Validated parameters for updating an issue
    
        Returns:
            Updated issue details from GitHub API
    
        Raises:
            GitHubError: If the API request fails
        """
        try:
            logger.debug(f"update_issue called with params: {params}")
            client = GitHubClient.get_instance()
            repository = client.get_repo(f"{params.owner}/{params.repo}")
            issue = repository.get_issue(params.issue_number)
            logger.debug(f"Got issue with title: {issue.title}")
    
            # Build kwargs with only provided values
            kwargs = {}
            
            if params.title is not None:
                kwargs["title"] = params.title
                logger.debug(f"Adding title={params.title} to kwargs")
            if params.body is not None:
                kwargs["body"] = params.body
            if params.state is not None:
                kwargs["state"] = params.state
            if params.labels is not None:
                kwargs["labels"] = params.labels
            if params.assignees is not None:
                kwargs["assignees"] = params.assignees
            if params.milestone is not None:
                try:
                    kwargs["milestone"] = repository.get_milestone(params.milestone)
                except Exception as e:
                    logger.error(f"Failed to get milestone {params.milestone}: {e}")
                    raise GitHubError(f"Invalid milestone number: {params.milestone}")
    
            logger.debug(f"kwargs for edit: {kwargs}")
    
            # If no changes provided, return current issue state
            if not kwargs:
                return convert_issue(issue)
    
            # Update issue using PyGithub with only provided values
            # PyGithub's edit() method returns None, not the updated issue
            issue.edit(**kwargs)
            
            # Get fresh issue data to ensure we have the latest state
            updated_issue = repository.get_issue(params.issue_number)
            logger.debug(f"After edit, updated_issue.title: {updated_issue.title}")
    
            # Create a custom converter for this specific case to handle empty strings properly
            def custom_convert_issue(issue):
                result = convert_issue(issue)
                # Ensure empty strings remain empty strings and don't become None
                if params.body == "":
                    result["body"] = ""
                return result
            
            # Return the updated issue with special handling for empty strings
            result = custom_convert_issue(updated_issue)
            logger.debug(f"Converted result: {result}")
            return result
    
        except GithubException as e:
            raise GitHubClient.get_instance()._handle_github_exception(e)
  • Registration function that collects all issue-related tool functions (including update_issue) and registers them 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")

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