push_files
Push multiple files to a GitHub repository in one commit, specifying owner, repo, branch, files, and commit message using the PyGithub MCP Server.
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
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Implementation Reference
- MCP tool handler for 'push_files'. Validates input with PushFilesParams schema and delegates to operations layer.@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 input schema for push_files tool, extending RepositoryRef with branch, files list, and message, including validators.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
- src/pygithub_mcp_server/tools/repositories/__init__.py:16-39 (registration)Registration of push_files tool (and other repository tools) with the MCP server instance.from .tools import ( get_repository, create_repository, fork_repository, search_repositories, get_file_contents, create_or_update_file, push_files, create_branch, list_commits ) # Register all repository tools register_tools(mcp, [ get_repository, create_repository, fork_repository, search_repositories, get_file_contents, create_or_update_file, push_files, create_branch, list_commits ])
- Core business logic for push_files: fetches existing file SHAs, creates/updates each file via GitHub API, returns 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")