server.py•7.49 kB
#!/usr/bin/env python3
"""
Git Helper MCP Server
Provides git repository information and statistics tools for Claude
"""
import subprocess
import json
from typing import Any
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.server.stdio
def run_git_command(args: list[str], cwd: str = ".") -> dict[str, Any]:
"""Run a git command and return the result"""
try:
result = subprocess.run(
["git"] + args,
cwd=cwd,
capture_output=True,
text=True,
check=True
)
return {"success": True, "output": result.stdout.strip(), "error": None}
except subprocess.CalledProcessError as e:
return {"success": False, "output": None, "error": e.stderr.strip()}
except Exception as e:
return {"success": False, "output": None, "error": str(e)}
# Create the MCP server
app = Server("git-helper")
@app.list_tools()
async def list_tools() -> list[Tool]:
"""List available git tools"""
return [
Tool(
name="get_status",
description="Get the current git repository status (staged, unstaged, untracked files)",
inputSchema={
"type": "object",
"properties": {
"repo_path": {
"type": "string",
"description": "Path to git repository (defaults to current directory)",
"default": "."
}
}
}
),
Tool(
name="list_branches",
description="List all branches in the repository with current branch highlighted",
inputSchema={
"type": "object",
"properties": {
"repo_path": {
"type": "string",
"description": "Path to git repository",
"default": "."
}
}
}
),
Tool(
name="get_commit_history",
description="Get recent commit history with messages and authors",
inputSchema={
"type": "object",
"properties": {
"repo_path": {
"type": "string",
"description": "Path to git repository",
"default": "."
},
"limit": {
"type": "integer",
"description": "Number of commits to show",
"default": 10
}
},
"required": []
}
),
Tool(
name="get_branch_info",
description="Get detailed information about a branch (current or specified)",
inputSchema={
"type": "object",
"properties": {
"repo_path": {
"type": "string",
"description": "Path to git repository",
"default": "."
},
"branch": {
"type": "string",
"description": "Branch name (defaults to current branch)"
}
}
}
),
Tool(
name="get_diff",
description="Get git diff (changes in working directory or staged changes)",
inputSchema={
"type": "object",
"properties": {
"repo_path": {
"type": "string",
"description": "Path to git repository",
"default": "."
},
"staged": {
"type": "boolean",
"description": "Show staged changes instead of unstaged",
"default": False
}
}
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: Any) -> list[TextContent]:
"""Handle tool calls"""
repo_path = arguments.get("repo_path", ".")
if name == "get_status":
result = run_git_command(["status", "--short"], repo_path)
if result["success"]:
output = result["output"] if result["output"] else "Working directory clean"
return [TextContent(type="text", text=f"Git Status:\n{output}")]
else:
return [TextContent(type="text", text=f"Error: {result['error']}")]
elif name == "list_branches":
result = run_git_command(["branch", "-a"], repo_path)
if result["success"]:
return [TextContent(type="text", text=f"Branches:\n{result['output']}")]
else:
return [TextContent(type="text", text=f"Error: {result['error']}")]
elif name == "get_commit_history":
limit = arguments.get("limit", 10)
result = run_git_command(
["log", f"-{limit}", "--pretty=format:%h - %s (%an, %ar)"],
repo_path
)
if result["success"]:
return [TextContent(type="text", text=f"Recent Commits:\n{result['output']}")]
else:
return [TextContent(type="text", text=f"Error: {result['error']}")]
elif name == "get_branch_info":
branch = arguments.get("branch")
if branch:
# Get info about specific branch
result = run_git_command(["log", branch, "-1", "--pretty=format:%h - %s (%an, %ar)"], repo_path)
else:
# Get current branch name and info
branch_result = run_git_command(["branch", "--show-current"], repo_path)
if not branch_result["success"]:
return [TextContent(type="text", text=f"Error: {branch_result['error']}")]
current_branch = branch_result["output"]
result = run_git_command(["log", "-1", "--pretty=format:%h - %s (%an, %ar)"], repo_path)
if result["success"]:
return [TextContent(type="text", text=f"Current Branch: {current_branch}\nLast Commit: {result['output']}")]
else:
return [TextContent(type="text", text=f"Error: {result['error']}")]
if result["success"]:
return [TextContent(type="text", text=f"Branch Info:\n{result['output']}")]
else:
return [TextContent(type="text", text=f"Error: {result['error']}")]
elif name == "get_diff":
staged = arguments.get("staged", False)
args = ["diff", "--cached"] if staged else ["diff"]
result = run_git_command(args, repo_path)
if result["success"]:
output = result["output"] if result["output"] else "No changes"
diff_type = "Staged" if staged else "Unstaged"
return [TextContent(type="text", text=f"{diff_type} Changes:\n{output}")]
else:
return [TextContent(type="text", text=f"Error: {result['error']}")]
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
"""Run the MCP server"""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
import asyncio
asyncio.run(main())