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
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
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)
- src/pygithub_mcp_server/tools/issues/tools.py:439-463 (registration)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")