# github-mcp
GitHub v3 REST API MCP (Model Context Protocol) server, generated by [mcpify](https://github.com/guercheLE/mcpify).
[](https://github.com/sponsors/guerchele)
Building and maintaining this took real ideation, time, design effort, and compute (including LLM usage) to get right. If it's useful to you, consider [sponsoring its development](https://github.com/sponsors/guerchele) β any amount helps keep it going. π
Exposes exactly 3 tools β `search`, `get`, `call` β backed by an embedded semantic database (`mcp_store.db`), so an LLM never needs the full API surface in context. Also exposes an MCP **prompts** capability: a master `github_workflow` menu plus 14 guided sub-workflow prompts that walk a calling LLM step by step through common multi-step GitHub tasks.
## Install
```bash
cargo build --release
```
This builds three binaries into `target/release/`: `github-mcp` (the CLI/server below), `github-mcp-populate-embeddings`, and `github-mcp-healthcheck`. Run `cargo install --path .` instead if you want `github-mcp` on your `PATH` so the commands below work without a `target/release/` prefix.
Prebuilt binaries for macOS, Linux, and Windows are attached to each [GitHub Release](https://github.com/guercheLE/github-mcp-rs/releases), along with a shell/PowerShell installer script.
Or install the published crate directly:
```bash
cargo install github-mcp
```
## Setup
```bash
cargo run -- setup
```
Interactively collects the API URL and the credentials your chosen auth method needs, then lets you persist them as a `.env` file, a local (`./github-mcp.config.yml`) or global (`~/.github-mcp/config.yml`) YAML config file, or a ready-to-run CLI invocation.
Supported auth methods: `pat` (personal access token, sent as a bearer token), `apiKey` (API key, sent as a header), `basic` (username/password).
## Configuration
| Env var | Purpose |
|---|---|
| `GITHUB_MCP_URL` | Base URL of the target API. |
| `GITHUB_MCP_TOKEN` / `GITHUB_MCP_API_KEY` | Overrides any stored credential for token/API-key auth β set either to authenticate without running `setup` first (checked before the OS keychain/encrypted-file fallback). |
| `GITHUB_MCP_USERNAME` / `GITHUB_MCP_PASSWORD` | Overrides any stored credential for basic auth β set both to authenticate without running `setup` first (checked before the OS keychain/encrypted-file fallback). |
| `GITHUB_MCP_LOG_LEVEL` | Log verbosity (`trace`/`debug`/`info`/`warn`/`error`). |
See `.env.example` for the full list of supported variables.
**GHES deployments must append `/api/v3` to `GITHUB_MCP_URL`** (e.g. `https://ghe.example.com/api/v3`). `api.github.com`/GHEC deployments need no suffix β use the bare host (e.g. `https://api.github.com`).
## Usage
### Terminal Client (default)
```bash
# 1. Semantic search over all 1,206 operations in the default GitHub.com store
github-mcp search "get an issue from a repository"
# 2. Inspect the exact method, path, and input/output schemas before calling
github-mcp get issues/get
# method: GET
# path: /repos/{owner}/{repo}/issues/{issue_number}
# 3. Path parameters are fields in one --args JSON object
github-mcp call issues/get --args '{"owner":"octocat","repo":"Hello-World","issue_number":1347}'
# Request payloads are nested under body in that same object
github-mcp call issues/create --args '{"owner":"octocat","repo":"Hello-World","body":{"title":"Found a bug","body":"The reproduction steps are...","labels":["bug"]}}'
```
`call` accepts one JSON object through `--args` (or `-a`), not arbitrary per-operation CLI flags. Use `get <operationId>` to see the accepted field names and which ones are required.
Other subcommands: `github-mcp test-connection` (verify the configured API URL/credentials are reachable), `github-mcp config` (print the resolved configuration, secrets redacted), `github-mcp version` (print the installed version), and `github-mcp versions` (list the API spec versions this project has a store for).
### Harness Server
```bash
github-mcp start # stdio transport (default)
github-mcp http --host 127.0.0.1 --port 3000 # HTTP transport
```
### Connect an MCP client
**stdio:** after running `github-mcp setup`, configure an MCP host to spawn the server. Include the connection settings printed by the wizard:
```json
{
"mcpServers": {
"github-mcp": {
"command": "github-mcp",
"args": ["start"],
"env": {
"GITHUB_MCP_URL": "<your target API URL>",
"GITHUB_MCP_AUTH_METHOD": "pat",
"GITHUB_MCP_API_VERSION": "gh-2026-03-10",
"GITHUB_MCP_TRANSPORT": "stdio"
}
}
}
}
```
Use the absolute executable path if `github-mcp` is not on the MCP host's `PATH`. The stdio server reads the connection settings from this `env` block and uses the credentials saved by `setup`.
**HTTP:** every request must carry its own `Authorization` header β HTTP transport intentionally does not fall back to credentials stored on the server:
```json
{
"mcpServers": {
"github-mcp": {
"url": "http://127.0.0.1:3000/mcp",
"headers": {
"Authorization": "<credential value>"
}
}
}
}
```
Keep the listener on localhost unless you have added appropriate network access controls and TLS in front of it.
### Guided workflow prompts
Beyond `search`/`get`/`call`, the server exposes an MCP **prompts** capability. Start with the master `github_workflow` prompt (optionally pass a `goal` argument describing what the user wants) β it presents the menu below and routes to the right sub-workflow, delegating the whole thing to an isolated sub-task when the calling environment supports it, so a multi-step workflow's full `search`/`get`/`call` trace doesn't have to live in the main conversation.
| Prompt | Covers |
|---|---|
| `github_workflow_pull_request` | Guided, multi-step pull request flow: the fork-vs-direct-branch decision, branch/commit/push, opening the PR, reviewers, and verifying checks/reviews before declaring it ready to merge. |
| `github_workflow_rulesets` | Guided setup of repository/org/enterprise rulesets β the mechanism that supersedes classic branch protection β including the evaluate-mode dry run before flipping enforcement on. |
| `github_workflow_environments_deployments` | Guided setup of deployment environments (protected or simple) and deployments, tracking the deployment status lifecycle and approval gates, plus a shorter GitHub Pages setup flow. |
| `github_workflow_repos` | Repository lifecycle (create, fork, transfer, archive, delete), branches and branch protection, tags, commits/git data, releases, topics/settings, webhooks. |
| `github_workflow_issues` | Issue lifecycle, labels, milestones, assignees, comments, reactions. |
| `github_workflow_actions_ci` | GitHub Actions workflows, runs, artifacts, secrets/variables, self-hosted runners, hosted compute, check-runs. |
| `github_workflow_orgs_teams` | Organizations, teams, enterprise teams/memberships, members, outside collaborators. |
| `github_workflow_security_suite` | Code scanning, secret scanning, code security configurations, Dependabot, security advisories, dependency graph, private registries. |
| `github_workflow_apps_auth_billing` | GitHub Apps/installations, OAuth apps, OIDC, billing, credentials, API insights. |
| `github_workflow_packages_migrations_gists` | Packages, import/export migrations, gists. |
| `github_workflow_codespaces_copilot` | Codespaces, Copilot, Copilot Spaces, agents/agent tasks, GitHub Classroom. |
| `github_workflow_projects` | Projects (v2), campaigns. |
| `github_workflow_users_activity` | User profile/keys/social graph, activity feed, starring/watching, notifications. |
| `github_workflow_meta_diagnostics` | Thin pointer to read-only utility signals: API meta, rate limits, code search, emojis, gitignore templates, licenses, code-of-conduct templates, markdown rendering. |
Every prompt describes GitHub operations only by what they do (e.g. "search for how to create a pull request"), never by a hardcoded operationId or an assumed response field β operation ids and response shapes genuinely differ across the `gh`/`ghec`/`ghes` deployments this server supports, so each prompt tells the calling LLM to confirm the current schema via `get` instead. See [docs/mcp-prompts-workflow-plan.md](docs/mcp-prompts-workflow-plan.md) for the full design rationale.
## Docker
```bash
# Stdio: the MCP client launches this one-off process and owns its stdin/stdout pipes
docker compose run --rm -T github-mcp
# HTTP: a long-running network endpoint published on http://localhost:3000
docker compose up github-mcp-http
```
Run these commands from the repository root. Docker Compose automatically discovers `docker-compose.yml`; `github-mcp` and `github-mcp-http` are service names inside that file, not filenames. Writing `docker compose -f docker-compose.yml ...` is equivalent, but `-f` is only needed when the file has another name or location, or when combining multiple Compose files.
Both services read configuration from a local `.env` file (copy `.env.example`) and persist credentials and configuration under `~/.github-mcp` on the host. For stdio, `-T` disables pseudo-TTY allocation so MCP JSON-RPC stays on raw stdin/stdout, and `--rm` removes the one-off container when the client exits.
Stdio is a process transport, not a listening service: the MCP client must start the server and communicate through that exact child process's stdin/stdout. This is useful when an MCP client is configured to launch `docker compose run --rm -T github-mcp`, in local scripts or CI that directly exchange MCP messages with the process, or in a custom image where your application launches the generated server's `start` subcommand as a child process. Merely putting the application and server in the same imageβor starting the stdio container separately with `docker compose up`βdoes not connect their streams. One stdio server process normally serves one client. Use HTTP when independently started applications, multiple clients, another container, or a remote machine need to connect over the network.
## Observability & Resilience
### Logging
Structured logs go to **stderr** (never stdout, which is reserved for MCP JSON-RPC frames on stdio transport): JSON by default, pretty-printed automatically when stderr is an interactive TTY (auto-detected β there's no separate flag for this). Level is controlled by `GITHUB_MCP_LOG_LEVEL` (default `info`), passed straight through to `tracing_subscriber::EnvFilter`, so directive syntax works too, e.g.:
```bash
GITHUB_MCP_LOG_LEVEL="github_mcp=debug,warn" github-mcp start
```
Secret redaction exists as a helper (`core::sanitizer::sanitize`, case-insensitive substring match on keys containing `password`/`token`/`secret`/`authorization`/`apikey`/`api_key`/`api-key`/`credential`), but today its only caller is `github-mcp config` (which prints the resolved config with those fields redacted). Request/response payloads aren't logged at all currently β the only `tracing` call sites are lifecycle/error events β so there's no in-flight redaction path exercised in normal operation yet.
### OpenTelemetry tracing
An OTLP/HTTP trace exporter (`core/otel.rs`) is built unconditionally at startup; if it fails to build, tracing export is silently skipped β there's no dedicated on/off switch in this app. It's *tracing only* (no OTel metrics exporter is wired up β see "Metrics" below). Point it at a collector with the OTLP SDK's own standard env vars (not `GITHUB_MCP_`-prefixed), which `opentelemetry-otlp` reads directly:
```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 github-mcp start
```
Defaults to `http://localhost:4318` if unset. `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`, `_PROTOCOL`, `_TIMEOUT`, and `_COMPRESSION` are also honored (standard OTLP conventions).
### Metrics
Separate from OTel: `GET /metrics` (HTTP transport only β not available over stdio) serves a minimal hand-rolled Prometheus-text counter store (`http/metrics.rs`). Today it only tracks one counter, `http_requests_total`:
```bash
curl http://127.0.0.1:3000/metrics
# http_requests_total 4
```
### Circuit breaker, retries, and rate limiting
Every outbound call to the target API (`services/api_client.rs`) passes through a rate limiter, then a circuit breaker, then the retry loop:
| Behavior | Configurable? | Knob | Default |
|---|---|---|---|
| Request timeout | Yes | `GITHUB_MCP_TIMEOUT_MS` / `timeout_ms` | 30000 ms |
| Retry attempts on request failure | Yes | `GITHUB_MCP_RETRY_ATTEMPTS` / `retry_attempts` | 3 (immediate retry, no backoff/jitter) |
| Rate limit | Partially | `GITHUB_MCP_RATE_LIMIT` / `rate_limit` | 100 calls; window is hardcoded to 1 second, not configurable |
| Circuit breaker | **No** | β (`CircuitBreaker::default()`) | opens after 5 consecutive failures, 30s before a half-open trial call |
("Knob" here means an env var or a matching key in `github-mcp.config.yml`/`~/.github-mcp/config.yml`/`/etc/github-mcp/config.yml` β see the config cascade in `core/config_manager.rs`.)
### Health checks
`GET /healthz` (HTTP transport only) reports the status of a `ComponentRegistry`, refreshed every 30 seconds with a 5-second per-check timeout by a `HealthCheckManager` β both intervals are hardcoded, not configurable. Today exactly one check is registered, `store` (can the active `mcp_store*.db` file be opened), marked critical:
```bash
curl http://127.0.0.1:3000/healthz
# {"status":"Healthy","components":1} # 503 + "Unhealthy" if the critical check is failing
```
Two related but distinct checks exist:
- `github-mcp-healthcheck` β the standalone binary wired into the Dockerfile's `HEALTHCHECK`; it only checks that the active store file exists and is readable on disk, and does not talk to a running server or `/healthz`.
- `github-mcp test-connection` β an on-demand CLI check that the *target API itself* is reachable with the configured credentials; unrelated to the periodic `/healthz` checks above.
### Credential storage
`github-mcp setup` writes credentials straight to the OS-native secret store via the `keyring` crate (macOS Keychain / Windows Credential Manager / Linux Secret Service), under service `github-mcp`, account `active-credentials`. If no OS keychain backend is available (e.g. no D-Bus secret-service daemon in a minimal container), it falls back automatically to an AES-256-GCM-encrypted file at `~/.github-mcp/credentials.enc` (`0600`, parent dir `0700` on Unix); the key is derived from `$HOME` plus the service name, so that file isn't portable to another machine.
The `GITHUB_MCP_TOKEN`/`GITHUB_MCP_API_KEY` (for token/API-key auth) and `GITHUB_MCP_USERNAME`/`GITHUB_MCP_PASSWORD` (for basic auth) env vars documented in `.env.example` are read directly by `AuthManager::credentials()` and take priority over the stored keychain/file credentials β useful for supplying credentials purely via environment (e.g. in a container) without ever running `setup`.
Credentials are never persisted into the `.env`/config-file output of `setup` itself; those files only carry non-secret settings, with credentials always going through the keychain/encrypted-file path.
## Testing
```bash
cargo test
```
## Coverage
```bash
bash scripts/coverage.sh # generates HTML and fails below 85% production-line coverage
```
The 85% gate counts executable production lines under `src/` and removes inline `#[cfg(test)]` module bodies from the LCOV denominator, so adding test code cannot inflate the result. The unfiltered annotated HTML remains useful for line-by-line analysis at `target/coverage/html/index.html`; the gate's machine-readable input is `target/coverage/production-lcov.info`. The command requires Python 3, `cargo-llvm-cov`, and the `llvm-tools-preview` Rust component.
## Profiling
```bash
bash scripts/profile.sh # clean CPU profiling via samply
bash scripts/profile-heap.sh # steady-state heap profiling via dhat-rs
```
CPU and heap profiling use separate builds: `scripts/profile.sh` deliberately profiles normal release binaries so DHAT allocation tracking cannot distort CPU samples, while `scripts/profile-heap.sh` starts DHAT collection only after its warmup search. CPU profiling records `profile/cold-start.json.gz` from a one-shot CLI search, then attaches to an already-initialized search harness and records `profile/warm-search.json.gz`; this keeps model initialization from being mistaken for steady-state request cost. Heap profiling defaults to 1 warmup and 5 measured searches, configurable with `PROFILE_HEAP_WARMUPS`, `PROFILE_HEAP_ITERATIONS`, and `PROFILE_QUERY`. Both scripts supply harmless URL/auth defaults when a generated checkout has not been configured because catalog search never calls the generated API. `profile/bottleneck-report.md` ranks coverage gaps and shows separate cold and warm CPU summaries. Requires [samply](https://github.com/mstange/samply) (`cargo install samply`).
## License
MIT β see [LICENSE](LICENSE).
---
Generated by mcpify β do not hand-edit generated files; re-run mcpify against an updated OpenAPI spec instead.