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")
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but offers minimal behavioral context. It mentions that parameters are optional and returns 'Updated issue details from GitHub API,' but lacks details on permissions needed, error handling, rate limits, or what happens when optional fields are omitted. For a mutation tool with zero annotation coverage, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections for 'Args' and 'Returns,' and each sentence adds value. It could be slightly more concise by integrating the parameter list more tightly, but overall it's efficient and front-loaded with the core purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (mutation with multiple parameters), lack of annotations, and no output schema, the description is moderately complete. It covers parameters well but misses behavioral aspects like authentication needs or error cases. It's adequate for basic use but has gaps for robust agent operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds significant value beyond the input schema, which has 0% schema description coverage. It lists all parameters with brief explanations (e.g., 'New title (optional)'), clarifying optionality and basic semantics that the schema lacks. This fully compensates for the schema's deficiency.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Update an existing issue.' It specifies the verb ('update') and resource ('issue'), but doesn't differentiate from sibling tools like 'update_issue_comment' or 'add_issue_labels' beyond the basic resource type.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives like 'add_issue_labels' or 'remove_issue_label' for partial updates, or 'create_issue' for new issues. The description only states what it does, not when it's appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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