git-proxy-mcp
Your Git credentials stay on your machine. Your repo lives in the AI's workspace.
A secure MCP server that lets cloud-based AI assistants (Claude.ai, ChatGPT, Gemini, etc.) work with private Git repositories using your existing Git credentials — without those credentials ever leaving your machine.
The Problem
Cloud-based AI coding assistants face a fundamental dilemma:
The result: AI assistants that can write code but can't actually work on your projects like a real developer would.
The Solution
git-proxy-mcp acts as an authenticated streaming proxy between Git providers and AI workspaces:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Git Providers │ │ YOUR PC │ │ AI's VM │
│ │ │ │ │ │
│ GitHub │◄────►│ git-proxy-mcp │◄────►│ Claude.ai │
│ GitLab │ │ │ │ │
│ Bitbucket │ │ (credentials │ │ /home/claude/ │
│ Azure DevOps │ │ stay here) │ │ repo/ │
│ Self-hosted │ │ │ │ (files live │
│ │ │ │ │ here) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Key insight: The AI has its own VM with full Linux capabilities. It just can't authenticate to your private repos. We solve only that problem.
How It Works
- Clone: AI requests a repo → MCP server authenticates → streams files directly to AI's VM
- Work: AI has a complete local git repo. Branch, edit, test, commit — all native.
- Push: AI sends commits → MCP server authenticates → pushes to remote
Credentials never leave your machine. Files never touch your machine.
Who Is This For?
Comparison: GitHub MCP vs git-proxy-mcp
Architecture
Security Model
┌─────────────────────────────────────────────────────────────────┐
│ YOUR PC (credentials stay here, files don't) │
│ │
│ ┌──────────────────┐ ┌─────────────────────────────────┐ │
│ │ git-proxy-mcp │ │ Your Git Configuration │ │
│ │ │◄────►│ │ │
│ │ • Auth callbacks │ │ • ~/.gitconfig │
│ │ • Object stream │ │ • SSH keys (ssh-agent) │ │
│ │ • No file storage│ │ • Credential helpers │ │
│ └────────┬─────────┘ └─────────────────────────────────┘ │
│ │ │
└───────────┼─────────────────────────────────────────────────────┘
│
│ Streaming: files/patches (NOT credentials)
▼
┌─────────────────────────────────────────────────────────────────┐
│ AI's VM (files live here, credentials don't) │
│ │
│ ┌──────────────────┐ │
│ │ /home/claude/ │ AI workflow (all local, no network): │
│ │ repo/ │ • git checkout -b feature │
│ │ .git/ │ • vim src/main.rs │
│ │ src/ │ • cargo test │
│ │ Cargo.toml │ • git commit -m "fix bug" │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
What Flows Where
MCP Tools
Tier 1: Single-Response Tools
repo_clone
Stream a repository to the AI's workspace (small-to-medium repos).
{
"name": "repo_clone",
"arguments": {
"url": "https://github.com/user/private-repo",
"branch": "main",
"depth": 1,
"sparse": ["src/", "Cargo.toml"]
}
}
Optional arguments not shown above:
exclude_binary(bool) — skip binary filesmax_file_size(number, bytes) — skip files exceeding the size limitresolve_lfs(bool) — fetch and substitute LFS pointer files with their actual contentinclude_submodules(bool) — recursively fetch submodulessubmodule_depth(number) — submodule recursion depth. Omit for unlimited (mirroringgit clone --recurse-submodules).1= top-level submodules only;0= skip submodules entirely (overridinginclude_submodules: true).submodule_include(array of glob patterns) — only fetch submodules matching at least one patternsubmodule_exclude(array of glob patterns) — skip submodules matching any pattern (takes precedence over include)
Response: Base64-encoded tar.gz archive, commit SHA, branch, file_count, archive_size
(bytes, before base64), and a hint string pointing at helper_script for extraction.
Optional counters appear only when non-zero: skipped_by_filter, skipped_binary,
skipped_too_large, skipped_path_too_long, lfs_resolved, lfs_failed,
submodules_included, submodules_failed.
repo_push
Push a git bundle from AI's workspace to remote.
{
"name": "repo_push",
"arguments": {
"url": "https://github.com/user/private-repo",
"branch": "feature/fix-bug",
"bundle": "<base64-encoded git bundle>",
"force": false
}
}
Response: branch, pushed commit SHA, force flag (echoed back), sanitised remote_url,
and a hint string explaining how to create bundles for follow-up pushes.
Tier 2: Chunked Streaming Tools (Large Repos)
For repositories too large to transfer in a single response.
repo_clone_start
Start a chunked clone session.
{
"name": "repo_clone_start",
"arguments": {
"url": "https://gitlab.com/org/large-repo",
"branch": "main",
"depth": 1,
"chunk_size": 1048576
}
}
Optional arguments not shown above — same semantics as the corresponding repo_clone arguments documented above:
sparse(array of paths/globs)exclude_binary(bool)max_file_size(number, bytes)resolve_lfs(bool)include_submodules(bool)submodule_depth(number)submodule_include(array of glob patterns)submodule_exclude(array of glob patterns)
Response: session_id, total_chunks, total_size (bytes, total archive size before base64),
chunk_size (bytes, the negotiated per-chunk size after clamping), commit, branch, file_count,
and a hint string. The same optional skipped/LFS/submodule counters as repo_clone appear when non-zero.
repo_clone_chunk
Get a chunk from a streaming session.
{
"name": "repo_clone_chunk",
"arguments": {
"session_id": "stream_abc123",
"chunk_index": 0
}
}
Response: Base64-encoded data, the chunk_index (echoed back), chunk_size (this chunk's
size in bytes before base64), is_last flag, and next_missing_chunk (omitted when no chunks
remain — used to resume after an interrupted transfer).
repo_clone_status
Check progress and resume state of a chunked clone session.
{
"name": "repo_clone_status",
"arguments": {
"session_id": "stream_abc123"
}
}
Response: session_id (echoed back), total_chunks, delivered_chunks, next_missing_chunk
(null when all chunks have been retrieved), progress_percent (0.0–100.0), and is_complete flag.
repo_clone_cancel
Cancel a streaming session (optional, auto-expires after the configured timeout).
{
"name": "repo_clone_cancel",
"arguments": {
"session_id": "stream_abc123"
}
}
Response: { "cancelled": <bool> } — true if a session was found and removed, false if no such session existed (not an error).
Other Tools
repo_pull
Sync new changes from remote to AI's workspace.
{
"name": "repo_pull",
"arguments": {
"url": "https://github.com/user/private-repo",
"branch": "main",
"since_commit": "abc123"
}
}
Response: Unified diff, base64 tar.gz of changed/added files (files_archive), changed_files list with per-file change types,
deleted_files list, base_commit and new_commit SHAs, change stats, up_to_date flag, and a
hint string pointing at helper_script for extracting files_archive.
repo_diff
Get diff between two commits.
{
"name": "repo_diff",
"arguments": {
"url": "https://github.com/user/private-repo",
"base_commit": "abc123",
"head_commit": "def456"
}
}
Response: Unified diff text, stats (additions/deletions/files-changed counts), and the
fully-resolved base_commit and head_commit SHAs (so the AI can cache the comparison without
re-resolving the original refs).
repo_refs
List remote branches and tags.
{
"name": "repo_refs",
"arguments": {
"url": "https://github.com/user/private-repo"
}
}
Response: branches and tags lists (each entry has the ref name and commit SHA),
default_branch (e.g. main or master, taken from the remote HEAD), and total_refs count.
Utilities
helper_script
Get a Python helper script for processing results (decoding base64, extracting tar.gz).
{
"name": "helper_script",
"arguments": {}
}
Response: Python script source code, plus suggested filename, brief usage instructions, and the script version.
Installation
Prerequisites
Git CLI
Most of the server's Git operations (clone, push, pull, diff, refs) authenticate in-process
via libgit2's credential callbacks and never shell out to git (see src/git2_ops/auth.rs).
The git CLI is required nonetheless, because the server invokes it for two narrower tasks:
git credential fill (to read your stored credentials via the OS credential helper, used for
Git LFS object downloads) and git fetch from a bundle file (to apply a repo_push payload
before the authenticated push — see src/git2_ops/push.rs). Any reasonably modern git (2.x)
on PATH works; bundles produced by git ≥ 2.53 (with the # v3 git bundle header) are also
accepted.
Git authentication
Configure Git to authenticate without prompting:
# macOS
git config --global credential.helper osxkeychain
# Windows
git config --global credential.helper manager
# Linux
git config --global credential.helper libsecret
For SSH, ensure your key is in ssh-agent:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# macOS: also cache the key's passphrase in the Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
Usage with Claude Desktop
Add to your Claude Desktop MCP configuration:
{
"mcpServers": {
"git-proxy": {
"command": "git-proxy-mcp",
"args": []
}
}
}
Configuration
Configuration file location:
- Linux/macOS:
~/.git-proxy-mcp/config.json - Windows:
%USERPROFILE%\.git-proxy-mcp\config.json
{
"git_identity": {
"name": "Claude AI",
"email": "[email protected]"
},
"security": {
"allow_force_push": false,
"protected_branches": ["main", "master"]
},
"logging": {
"level": "warn",
"audit_log_path": "~/.git-proxy-mcp/audit.log"
},
"timeouts": {
"request_timeout_secs": 300
},
"limits": {
"max_output_bytes": 10485760
},
"rate_limits": {
"max_burst": 20,
"refill_rate_per_sec": 5.0
},
"proxy": {
"url": "http://proxy.example.com:8080",
"no_proxy": "*.internal.com,localhost"
},
"sessions": {
"timeout_secs": 3600,
"max_streaming_sessions": 10,
"max_repo_sessions": 100
},
"lfs": {
"retry_max_attempts": 3,
"max_object_size": 104857600
},
"submodules": {
"exclude_patterns": ["vendor/*"]
}
}
For a fully-populated example showing every section and option, see config/example-config.json.
Configuration Options
Validation
The configuration is validated when it loads; an invalid value aborts startup
with a configuration validation failed: … message naming the offending field.
The checks reject only values that would render a subsystem unusable: a zero
timeouts.request_timeout_secs or any of the lfs.*_timeout_secs (every request
would time out immediately), a zero limits.max_output_bytes (every command's
combined stdout+stderr would be truncated to nothing), rate_limits.max_burst
of 0 (every operation blocked forever), a non-finite or negative
rate_limits.refill_rate_per_sec
(0.0 is allowed — it means "burst once, never refill"), a zero
sessions.timeout_secs / sessions.max_streaming_sessions /
sessions.max_repo_sessions, and a logging.level outside
trace/debug/info/warn/error.
Contributing
Contributions welcome! See CONTRIBUTING.md for guidelines.
- Follow the style guide in STYLE.md
- Security issues: see SECURITY.md
Licence
Copyright (C) 2026 Matej Gomboc https://github.com/MatejGomboc/git-proxy-mcp.
GNU General Public License v3.0 or later — see LICENCE.