github-mcp 0.5.0

GitHub v3 REST API MCP server, generated by mcpify.
Documentation
# github-mcp

GitHub v3 REST API MCP (Model Context Protocol) server, generated by [mcpify](https://github.com/guercheLE/mcpify).

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.

## 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.

## 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.