diff --git a/src/git/README.md b/src/git/README.md index 3a46b01c6f..66f6afd751 100644 --- a/src/git/README.md +++ b/src/git/README.md @@ -94,6 +94,16 @@ Please note that mcp-server-git is currently in early development. The functiona - `not_contains` (string, optional): The commit sha that branch should NOT contain. Do not pass anything to this param if no commit sha is specified - Returns: List of branches +13. `git_push` + - Pushes changes to a remote repository + - Inputs: + - `repo_path` (string): Path to the Git repository + - `remote` (string, optional): The remote repository to push to (default: 'origin') + - `branch` (string, optional): The branch to push. If not specified, pushes the current branch + - `force` (boolean, optional): Force push the branch, overwriting remote history if necessary (default: false) + - `set_upstream` (boolean, optional): Set the upstream branch when pushing (git push -u) (default: false) + - Returns: Confirmation of push operation with output details + ## Installation ### Using uv (recommended) diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index 9950da66ea..cab2c35469 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -90,6 +90,28 @@ class GitBranch(BaseModel): description="The commit sha that branch should NOT contain. Do not pass anything to this param if no commit sha is specified", ) +class GitPush(BaseModel): + repo_path: str = Field( + ..., + description="The path to the Git repository.", + ) + remote: str = Field( + default="origin", + description="The remote repository to push to (default: 'origin').", + ) + branch: Optional[str] = Field( + None, + description="The branch to push. If not specified, pushes the current branch.", + ) + force: bool = Field( + default=False, + description="Force push the branch, overwriting remote history if necessary.", + ) + set_upstream: bool = Field( + default=False, + description="Set the upstream branch when pushing (git push -u).", + ) + class GitTools(str, Enum): STATUS = "git_status" @@ -103,6 +125,7 @@ class GitTools(str, Enum): CREATE_BRANCH = "git_create_branch" CHECKOUT = "git_checkout" SHOW = "git_show" + PUSH = "git_push" BRANCH = "git_branch" @@ -230,6 +253,24 @@ def git_branch(repo: git.Repo, branch_type: str, contains: str | None = None, no return branch_info +def git_push(repo: git.Repo, remote: str = "origin", branch: str | None = None, force: bool = False, set_upstream: bool = False) -> str: + args = [] + if force: + args.append("--force") + if set_upstream: + args.append("-u") + + if branch: + args.extend([remote, branch]) + else: + args.append(remote) + + try: + result = repo.git.push(*args) + return f"Pushed successfully to {remote}: {result}" + except git.GitCommandError as e: + return f"Error pushing to {remote}: {e}" + async def serve(repository: Path | None) -> None: logger = logging.getLogger(__name__) @@ -302,7 +343,11 @@ async def list_tools() -> list[Tool]: description="Shows the contents of a commit", inputSchema=GitShow.model_json_schema(), ), - + Tool( + name=GitTools.PUSH, + description="Pushes changes to a remote repository", + inputSchema=GitPush.model_json_schema(), + ), Tool( name=GitTools.BRANCH, description="List Git branches", @@ -447,6 +492,19 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]: text=result )] + case GitTools.PUSH: + result = git_push( + repo, + arguments.get("remote", "origin"), + arguments.get("branch", None), + arguments.get("force", False), + arguments.get("set_upstream", False), + ) + return [TextContent( + type="text", + text=result + )] + case _: raise ValueError(f"Unknown tool: {name}") diff --git a/src/git/tests/test_server.py b/src/git/tests/test_server.py index c26a385b72..f3069af4d3 100644 --- a/src/git/tests/test_server.py +++ b/src/git/tests/test_server.py @@ -1,7 +1,7 @@ import pytest from pathlib import Path import git -from mcp_server_git.server import git_checkout, git_branch, git_add +from mcp_server_git.server import git_checkout, git_branch, git_add, git_push import shutil @pytest.fixture @@ -91,3 +91,7 @@ def test_git_add_specific_files(test_repository): assert "file1.txt" in staged_files assert "file2.txt" not in staged_files assert result == "Files staged successfully" + +def test_git_push_no_remote(test_repository): + result = git_push(test_repository) + assert "Error pushing to origin" in result