GitMCP

lets AI assistants explore GitHub repositories, source code, issues, pull requests and commits through 14 validated, read-only tools, plus 4 opt-in write tools that are off by default.

Documentation

GitMCP

A GitHub server for AI assistants: lets Claude and other MCP clients read repositories, issues, pull requests and commits through validated tools, with writes disabled unless you opt in.

Release Python Tests

  • 14 read-only tools plus 4 opt-in write tools, each with a strict Pydantic input schema
  • Transient failures (429, 5xx, timeouts) are retried within a hard budget; auth, permission and 404 errors never are
  • Safe by default: writes off, token never logged or returned, response sizes capped, no arbitrary URL/shell access
  • Works with Claude Code and Claude Desktop over stdio; public repos need no token

Honest expectations: stdio only, so each user runs their own local copy. Code search and private repositories need a GitHub token. File reads are UTF-8 text only.

What I built

GitMCP is a Python MCP server that adapts the GitHub REST API into 18 tools an AI assistant can discover and call. The GitHub logic (github/*.py) is independent of MCP, so it is tested without a running server; a thin server.py registers the tools and turns every failure into a short, safe, actionable message. It was built against the current official MCP Python SDK (2.x) and verified with a real MCP stdio client.

Results at a glance

Measured on a Windows 11 laptop, Python 3.14, anonymous (no token) against the live GitHub API.

MetricResult
Automated tests112 passing, 1 skipped (live integration test, needs a token), ~13 s
Lint / typesruff clean, mypy --strict clean (18 source files)
Cold start: spawn process, MCP handshake, list 14 tools1.3 s
github_get_repository latency0.6 s first call (TLS setup), ~0.02 s median on reused connection
Code size~2,100 lines in src/, ~1,400 lines of tests

Latency is dominated by GitHub and the network, not by the server. Design trade-offs are in DECISIONS.md.

Architecture

flowchart LR
    Client["MCP client<br/>(Claude Code / Desktop)"] -- "stdio JSON-RPC" --> Server["server.py<br/>tool registration + error guard"]
    Server --> Repos["github/repositories.py"]
    Server --> Issues["github/issues.py"]
    Server --> PRs["github/pull_requests.py"]
    Server --> Commits["github/commits.py"]
    Repos & Issues & PRs & Commits --> GH["github/client.py<br/>auth, errors, size limit"]
    GH --> Retry["retry.py<br/>backoff + jitter + Retry-After"]
    GH -- HTTPS --> API[("GitHub REST API")]
    Server -. validates with .-> Schemas["schemas/<br/>Pydantic in/out models"]
    Config["config.py<br/>env settings"] --> Server
ModuleResponsibility
server.pyCreates the MCPServer, registers tools, converts errors to safe tool errors, closes the HTTP client on shutdown
github/*.pyBusiness logic. No MCP imports, so it is testable without a server
github/client.pyAuthenticated httpx client, HTTP status → typed error mapping, response-size cap
retry.pyRetry policy (429 / 5xx / network), Retry-After and reset-header handling
schemas/Strict Pydantic input models (extra="forbid") and output models
errors.pyTyped exceptions with safe messages and hints
logging_utils.pystderr logging with secret redaction

Why it is built this way: see DECISIONS.md.

Quick start

Requires Python 3.11+. Repository: https://github.com/wablevaibhav/gitmcp

python -m pip install git+https://github.com/wablevaibhav/gitmcp
python -m gitmcp          # starts the server (waits silently for an MCP client)

Then connect it to your AI client: see Claude Code or Claude Desktop. You do not need to clone the repository to use GitMCP.

Latest release: v0.1.0. To install exactly that version:

python -m pip install git+https://github.com/wablevaibhav/gitmcp@v0.1.0

To upgrade later:

python -m pip install --upgrade --force-reinstall git+https://github.com/wablevaibhav/gitmcp

Alternatives (not required): pipx install git+https://github.com/wablevaibhav/gitmcp, or with uv: uvx --from git+https://github.com/wablevaibhav/gitmcp gitmcp.

Development install (contributors)

git clone https://github.com/wablevaibhav/gitmcp.git
cd gitmcp
python -m venv .venv
# Windows (PowerShell):  .venv\Scripts\Activate.ps1
# macOS / Linux:         source .venv/bin/activate
pip install -e ".[dev]"

requirements-lock.txt pins the exact versions the project was tested with (pip install -r requirements-lock.txt, then pip install -e . --no-deps).

GitHub token setup

The token is optional for public repositories (anonymous limit: 60 requests/hour) but required for private repositories, code search, and write tools.

Create a fine-grained personal access token at GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens, restricted to only the repositories you want the assistant to see.

CapabilityRepository permission
Repos, contents, file reading, commitsContents: Read (Metadata: Read is implicit)
Issues and commentsIssues: Read
Pull requests, files, reviewsPull requests: Read
github_create_issue, github_add_issue_commentIssues: Read and write
github_create_branchContents: Read and write
github_create_pull_requestPull requests: Read and write

Start with read-only permissions. Add write permissions only if you enable write tools.

Public vs. authenticated: without a token only public data is visible and GitHub returns 404 for private resources. With a token you see exactly what the token can see, nothing more; GitMCP never bypasses GitHub's authorization.

Configuration

Environment variables (or a .env file in the working directory; copy .env.example):

VariableDefaultMeaning
GITHUB_TOKEN(none)Personal access token
GITHUB_API_URLhttps://api.github.comAPI base (GitHub Enterprise: https://HOST/api/v3)
GITHUB_API_VERSION2022-11-28X-GitHub-Api-Version header
GITHUB_REQUEST_TIMEOUT30Per-request timeout, seconds
GITHUB_MAX_RETRIES3Retries after the first attempt (0–10)
GITHUB_MAX_RATE_LIMIT_WAIT60Longest rate-limit wait GitMCP will sleep through, seconds
GITHUB_ENABLE_WRITE_TOOLSfalseRegister write tools
GITMCP_LOG_LEVELINFODEBUG/INFO/WARNING/ERROR (stderr)
GITMCP_MAX_FILE_BYTES200000Largest file github_get_file_contents returns
GITMCP_MAX_PATCH_CHARS8000Per-file diff truncation
GITMCP_MAX_COMMENT_CHARS4000Comment/review body truncation
GITMCP_MAX_RESPONSE_BYTES5000000Hard cap on any GitHub response body

Running the server

python -m gitmcp          # or simply: gitmcp

The server speaks MCP over stdio, so on its own it prints a "ready" line to stderr and waits. It is meant to be launched by an MCP client. To confirm it is discoverable and callable using a real MCP stdio client (from a clone of this repo):

python scripts/verify_tools.py                       # lists the tools
python scripts/verify_tools.py octocat/Hello-World   # also calls github_get_repository

Keep your token secret. Never paste it into chats, issues, screenshots or commit it. If a token is ever exposed, revoke it immediately at https://github.com/settings/personal-access-tokens and create a new one.

Claude Code

macOS / Linux / Git Bash:

claude mcp add gitmcp --scope user -e GITHUB_TOKEN="$GITHUB_TOKEN" -- python -m gitmcp

Windows PowerShell (Windows PowerShell 5.1 swallows the -- separator, so run through cmd):

$env:GITHUB_TOKEN = "your-token"       # type it in your own terminal only
cmd /c "claude mcp add gitmcp --scope user -e GITHUB_TOKEN=%GITHUB_TOKEN% -- python -m gitmcp"

Verify with claude mcp list (or /mcp inside Claude Code). If python is not the interpreter you installed GitMCP into, use its full path (where python / which python), e.g. C:\Users\you\AppData\Local\Programs\Python\Python312\python.exe.

You can also share a project-level .mcp.json, referencing each person's own environment variable (never write the token into the file):

{
  "mcpServers": {
    "gitmcp": {
      "command": "python",
      "args": ["-m", "gitmcp"],
      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
    }
  }
}

Claude Desktop

Edit claude_desktop_config.json (Windows: %APPDATA%\Claude\, macOS: ~/Library/Application Support/Claude/), then restart Claude Desktop:

{
  "mcpServers": {
    "gitmcp": {
      "command": "python",
      "args": ["-m", "gitmcp"],
      "env": {
        "GITHUB_TOKEN": "your-token",
        "GITHUB_ENABLE_WRITE_TOOLS": "false"
      }
    }
  }
}

If Claude Desktop cannot find python, set command to the full interpreter path, with doubled backslashes on Windows ("C:\\Users\\you\\AppData\\Local\\Programs\\Python\\Python312\\python.exe").

Using it with others

Each person runs their own local copy; no server is shared and tokens are never shared. Everyone does:

  1. python -m pip install git+https://github.com/wablevaibhav/gitmcp
  2. Creates their own fine-grained GitHub token (see below).
  3. Adds the server to their client using one of the configurations above.

Public repositories work without a token (60 requests/hour); private repositories, code search and write tools require one.

Installing the package does not make the server appear in a client by itself; the client must be configured as above. The server and command were verified with scripts/verify_tools.py (a real MCP stdio client). The Claude Code / Desktop snippets follow those products' documented formats.

Usage

Real output from python scripts/verify_tools.py octocat/Hello-World (an MCP client talking to the server over stdio, no token):

Connected. 14 tools:
  - github_search_repositories
  - github_get_repository
  - github_list_repository_contents
  - github_get_file_contents
  ...
is_error: False
{'full_name': 'octocat/Hello-World', 'owner': 'octocat', 'name': 'Hello-World',
 'visibility': 'public', 'stars': 3842, 'default_branch': 'master', ...}

Errors are short, typed and actionable (real output, anonymous):

github_get_repository  -> NotFoundError: Resource not found. Hint: Check owner, repo, path and ref.
                          Private resources also return 404 when the token lacks access.
                          No GITHUB_TOKEN is configured, so only public data is visible.
github_search_code     -> AuthenticationError: GitHub code search requires authentication.
                          Hint: Set GITHUB_TOKEN, and scope the query with repo:, org: or user:.

Tools

Every tool takes one object argument named params. All inputs reject unknown fields. List tools accept page (1–1000) and per_page (1–100, default 30) and return has_next_page. owner/repo/ref/path are validated (no traversal, no invalid ref characters).

ToolKey argumentsReturns
github_search_repositoriesquery, language, topic, min_stars, sort, order, pagingtotal count; items with full name, owner, description, URL, visibility, language, stars, default branch
github_get_repositoryowner, reposummary + license, forks, open issues, topics, clone/API URLs, timestamps
github_list_repository_contentsowner, repo, path, refentries: name, path, type, size, sha, web/API/download URLs
github_get_file_contentsowner, repo, path, refUTF-8 content, sha, size. Errors for directories, binary, > size limit
github_search_codequery (needs repo:/org:/user:), pagingitems: path, repository, URL, snippets. Requires token
github_list_issuesowner, repo, state, labels, assignee, include_pull_requests, pagingsummaries with is_pull_request flag
github_get_issueowner, repo, issue_numberdetails: body, labels, assignees, milestone, timestamps, URL
github_list_issue_commentsowner, repo, issue_number, pagingcomments (bodies truncated, flagged)
github_list_pull_requestsowner, repo, state, base, head (owner:branch), pagingPR summaries
github_get_pull_requestowner, repo, pull_numberbranches, SHAs, author, draft/merged/mergeable, size, reviewers
github_list_pull_request_filesowner, repo, pull_number, pagingfiles: status, +/−, patch (truncated, flagged)
github_list_pull_request_reviewsowner, repo, pull_number, pagingreviews: author, APPROVED/CHANGES_REQUESTED/…, body
github_list_commitsowner, repo, ref, path, author, pagingcommit summaries
github_get_commitowner, repo, refmessage, author, parents, stats, files (max 100, patches truncated)
github_create_issue ⚠owner, repo, title, body, labels, assigneesnumber, URL
github_add_issue_comment ⚠owner, repo, issue_number, bodycomment id, URL
github_create_branch ⚠owner, repo, branch, from_refnew ref, SHA
github_create_pull_request ⚠owner, repo, title, head, base, body, draftnumber, URL

⚠ Write tool: registered only when GITHUB_ENABLE_WRITE_TOOLS=true.

Example prompts

  • Repository exploration: "Look at octocat/Hello-World. What's the default branch, what's in the root, and what does the README say?"
  • Code search: "Find where parse_config is defined in acme/api." (needs a token)
  • Issue investigation: "List open issues labeled bug in acme/api, then summarize the discussion on the most-commented one."
  • PR review: "Review PR 482 in acme/api: show the metadata, the changed files and patches, existing reviews, and point out risky changes."
  • History: "Who last touched src/auth.py in acme/api and what did those commits change?"

Rate limits and error handling

SituationBehavior
429, or 403 with rate-limit signalsRetried, honoring Retry-After / x-ratelimit-reset. If the wait exceeds GITHUB_MAX_RATE_LIMIT_WAIT, fails immediately with RateLimitError stating the wait. Never sleeps past that limit
500/502/503/504, timeouts, connection errorsRetried up to GITHUB_MAX_RETRIES with exponential backoff and full jitter (0.5 s base, 8 s cap)
401AuthenticationError — no retry
403 (permission)AuthorizationError — no retry, hint names the permission to grant
404NotFoundError — no retry; hint notes private resources also return 404
400/409/422InputValidationError — no retry
Malformed / oversized / non-JSON bodyGitHubAPIError

Write requests (POST) are never replayed after a timeout or 5xx, since the write may have succeeded; they are only retried when GitHub rejected them with a rate limit.

Clients receive ErrorType: message Hint: .... Tracebacks, response bodies and tokens never reach the client.

Security

  • The token comes only from the environment, is held as a SecretStr, and is redacted from logs and errors (exact-match plus ghp_…/github_pat_…/Authorization: patterns).
  • Write tools are not registered unless GITHUB_ENABLE_WRITE_TOOLS=true, and each write service method refuses independently if the flag is off. GitHub still enforces the token's own permissions.
  • No shell execution, no arbitrary URL fetching, no GraphQL, no generic API proxy: every tool maps to a fixed REST endpoint and tool arguments are percent-encoded into the path.
  • File, patch, comment and total response sizes are capped; logs never contain source code or full responses.
  • Use a fine-grained token scoped to specific repositories, and set an expiry.
  • Streamable HTTP transport is intentionally not implemented; a network-exposed server would need its own authentication, which is out of scope for this release.

Development

From a development install:

pytest                       # unit + MCP tests (no token, no network)
ruff check . && ruff format --check .
mypy                         # strict, on src/

Optional live tests (read-only, skipped unless configured):

GITMCP_TEST_TOKEN=github_pat_xxx GITMCP_TEST_REPO=owner/repo pytest -m integration

Troubleshooting

SymptomLikely cause / fix
Client shows no toolsWrong Python in client config. Run python -m gitmcp with that exact interpreter; use its full path in the config
error: unknown option '-m' (PowerShell)PowerShell 5.1 drops --. Use the cmd /c "claude mcp add ..." form above
No module named gitmcpInstalled into a different Python/venv than the one the client launches
NotFoundError on a repo you can open in a browserPrivate repo and missing/under-scoped token
AuthorizationErrorToken lacks the permission in the table above or the repo isn't selected for the token
RateLimitError immediatelyReset is further away than GITHUB_MAX_RATE_LIMIT_WAIT; set a token or wait
github_search_code failsNeeds GITHUB_TOKEN and a repo:/org:/user: qualifier
Write tools missingIntentional: set GITHUB_ENABLE_WRITE_TOOLS=true and restart the client
invalid configuration on startA setting failed validation; field names are printed to stderr
Garbled protocol outputSomething wrote to stdout; use stderr for any debugging

Limitations and roadmap

  • Read tools return the first slice of a result (paging is explicit); there is no automatic follow-all-pages.
  • File contents are UTF-8 text only; binary files and files over GITMCP_MAX_FILE_BYTES are refused.
  • Code search depends on GitHub's search index and its limits (max 1000 results, query ≤ 256 chars).
  • github_get_commit caps files at 100 and does not page GitHub's commit-file pagination.
  • GitHub Actions, releases, review comments (inline), and Projects are not covered.
  • stdio only; planned: authenticated Streamable HTTP, conditional-request (ETag) caching, repository tree tool.
  • Python 3.14 and mcp 2.3 were used for development; the code targets Python ≥ 3.11.