Skip to main content
Glama
AstroMined

PyGithub MCP Server

by AstroMined

push_files

Push multiple files to a GitHub repository in a single commit, enabling batch file management through the PyGithub MCP Server for efficient repository updates.

Instructions

Push multiple files to a GitHub repository in a single commit.

Args:
    params: Dictionary with file parameters
        - owner: Repository owner (username or organization)
        - repo: Repository name
        - branch: Branch to push to
        - files: List of files to push, each with path and content
        - message: Commit message

Returns:
    MCP response with file push result

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Implementation Reference

  • MCP tool handler for 'push_files', decorated with @tool(). Validates input with PushFilesParams, delegates to repositories.push_files, and formats response for MCP.
    @tool()
    def push_files(params: Dict) -> Dict:
        """Push multiple files to a GitHub repository in a single commit.
    
        Args:
            params: Dictionary with file parameters
                - owner: Repository owner (username or organization)
                - repo: Repository name
                - branch: Branch to push to
                - files: List of files to push, each with path and content
                - message: Commit message
    
        Returns:
            MCP response with file push result
        """
        try:
            logger.debug(f"push_files called with params: {params}")
            # Convert dict to Pydantic model
            push_params = PushFilesParams(**params)
            
            # Call operation
            result = repositories.push_files(push_params)
            
            logger.debug(f"Pushed {len(push_params.files)} files")
            return {
                "content": [{"type": "text", "text": json.dumps(result, indent=2)}]
            }
        except ValidationError as e:
            logger.error(f"Validation error: {e}")
            return {
                "content": [{"type": "error", "text": f"Validation error: {str(e)}"}],
                "is_error": True
            }
        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 PushFilesParams defining input parameters (owner/repo from RepositoryRef, branch, files list, message) with field validators for non-empty values.
    class PushFilesParams(RepositoryRef):
        """Parameters for pushing multiple files in a single commit."""
    
        model_config = ConfigDict(strict=True)
        
        branch: str = Field(..., description="Branch to push to")
        files: List[FileContent] = Field(..., description="Files to push")
        message: str = Field(..., description="Commit message")
    
        @field_validator('branch')
        @classmethod
        def validate_branch(cls, v):
            """Validate that branch is not empty."""
            if not v.strip():
                raise ValueError("branch cannot be empty")
            return v
    
        @field_validator('files')
        @classmethod
        def validate_files(cls, v):
            """Validate that files list is not empty."""
            if not v:
                raise ValueError("files list cannot be empty")
            return v
    
        @field_validator('message')
        @classmethod
        def validate_message(cls, v):
            """Validate that message is not empty."""
            if not v.strip():
                raise ValueError("message cannot be empty")
            return v
  • Core business logic for pushing multiple files: fetches existing SHAs, creates/updates each file via GitHub API's create_file (with SHA for updates), collects results.
    def push_files(params: PushFilesParams) -> Dict[str, Any]:
        """Push multiple files to a repository in a single commit.
        
        This is a convenience wrapper around multiple create_file operations.
        Note: This does not support directories or binary files yet.
    
        Args:
            params: Parameters for pushing multiple files
    
        Returns:
            Result including commit info
    
        Raises:
            GitHubError: If file push fails
        """
        logger.debug(f"Pushing {len(params.files)} files to {params.owner}/{params.repo}")
        
        # Validate file content first
        for file_content in params.files:
            if not file_content.content:
                raise GitHubError("File content cannot be empty")
        
        try:
            client = GitHubClient.get_instance()
            repository = client.get_repo(f"{params.owner}/{params.repo}")
            
            # Get current file SHAs if they exist
            file_shas = {}
            for file_content in params.files:
                try:
                    existing_file = repository.get_contents(
                        path=file_content.path, 
                        ref=params.branch
                    )
                    if not isinstance(existing_file, list):
                        file_shas[file_content.path] = existing_file.sha
                except GithubException:
                    # File doesn't exist yet, no SHA needed
                    pass
            
            # Create a commit for each file
            results = []
            for file_content in params.files:
                kwargs = {
                    "path": file_content.path,
                    "message": params.message,
                    "content": file_content.content,
                    "branch": params.branch
                }
                
                # Add SHA if updating an existing file
                if file_content.path in file_shas:
                    kwargs["sha"] = file_shas[file_content.path]
                
                result = repository.create_file(**kwargs)
                
                # Extract content SHA safely
                content_sha = None
                try:
                    if hasattr(result["content"], "sha"):
                        content_sha = result["content"].sha
                    elif isinstance(result["content"], dict) and "sha" in result["content"]:
                        content_sha = result["content"]["sha"]
                except (AttributeError, KeyError, TypeError) as e:
                    logger.warning(f"Error extracting content SHA for {file_content.path}: {e}")
                    # If we can't get the SHA, we'll proceed without it
                
                results.append({
                    "path": file_content.path,
                    "sha": content_sha
                })
            
            logger.debug(f"Files pushed successfully to {params.owner}/{params.repo}")
            return {
                "message": params.message,
                "branch": params.branch,
                "files": results
            }
        except GithubException as e:
            logger.error(f"GitHub exception when pushing files: {str(e)}")
            raise client._handle_github_exception(e, resource_hint="content_file")

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