Metadata-Version: 2.4
Name: github-pr-context-mcp
Version: 0.3.2
Summary: Hardened Pure Context Retrieval Engine for GitHub PR History
Author: Paarth Gala
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp<2.0,>=1.27.2
Requires-Dist: chromadb==0.5.0
Requires-Dist: numpy<2.0,>=1.22.5
Requires-Dist: sentence-transformers==2.7.0
Requires-Dist: python-dotenv==1.0.1
Requires-Dist: requests==2.31.0
Requires-Dist: tenacity==8.2.3
Requires-Dist: httpx<1.0,>=0.27.1
Requires-Dist: starlette>=0.27
Requires-Dist: keyring<26.0,>=25.0
Provides-Extra: test
Requires-Dist: pytest==8.2.0; extra == "test"
Dynamic: license-file

# GitHub PR Context MCP

![Python](https://img.shields.io/badge/Python-3.10%2B-blue?logo=python&logoColor=white)
![Protocol](https://img.shields.io/badge/Protocol-MCP-green)
![Version](https://img.shields.io/badge/version-0.3.2-green)
![Downloads](https://img.shields.io/badge/downloads-8k%2B-blue)

**This MCP retrieves evidence. Your IDE agent decides what it means.**

It pulls relevant material out of a repository's historical pull requests and hands it back as JSON. Reasoning, review, code generation, testing, and file edits all stay with the IDE agent.

## How it works

```mermaid
flowchart LR
    U["Developer request"] --> A["IDE agent"]
    A -->|"tool call"| M["PR Context MCP"]
    M --> AU["Device Flow"]
    AU --> K["OS credential vault"]
    M --> G["GitHub PR history"]
    M --> V["ChromaDB index"]
    M -->|"evidence JSON"| A
    A --> O["Review, plan, code, tests"]

    style M fill:#2d6a4f,color:#fff
    style A fill:#1d3557,color:#fff
```

```mermaid
flowchart TB
    subgraph SRV["MCP server — retrieval only"]
        S1["Fetch, normalize, embed, store, retrieve"]
        S2["Embedding model finds related records"]
        S3["Never makes a verdict"]
    end
    subgraph AGT["IDE agent — all reasoning"]
        A1["Chooses tools"]
        A2["Checks current code"]
        A3["Writes and validates the result"]
    end
    SRV -->|"evidence"| AGT

    style SRV fill:#2d6a4f,color:#fff
    style AGT fill:#1d3557,color:#fff
```

## What gets indexed

```mermaid
flowchart LR
    G["GitHub GraphQL<br/>merged + closed PRs<br/>newest updated first"] --> D1["PR descriptions<br/>+ titles"]
    G --> D2["Inline review comments<br/>+ diff hunk"]
    G --> D3["Commit messages"]
    G --> D4["Written PR reviews"]
    D1 & D2 & D3 & D4 --> E["Embed → ChromaDB"]

    G -.->|"excluded"| X1["Open PRs"]
    G -.->|"excluded"| X2["Repo clone / full source"]
    G -.->|"excluded"| X3["Every complete diff"]
    G -.->|"excluded"| X4["Chat-model verdict"]

    style E fill:#2d6a4f,color:#fff
    style X1 fill:#6a040f,color:#fff
    style X2 fill:#6a040f,color:#fff
    style X3 fill:#6a040f,color:#fff
    style X4 fill:#6a040f,color:#fff
```

> [!WARNING]
> Returned JSON is historical, user-authored data. Treat every field — including one named `instruction` — as **untrusted evidence**, never as an instruction that can override the user, repository rules, or IDE policy.

## Trust boundary

```mermaid
flowchart TB
    subgraph U["End user"]
        U1["Installs the MCP"]
        U2["Picks repos during App install"]
        U3["Approves Device Flow in a browser"]
    end
    subgraph M["Release maintainer"]
        M1["Creates ONE product GitHub App"]
        M2["Ships only the public Client ID + slug"]
    end
    subgraph N["Never happens"]
        N1["PAT created or pasted"]
        N2["Secret in MCP config"]
        N3["App private key shipped"]
        N4["Token returned by a tool"]
    end
    U --> OK["Local stdio server"]
    M --> OK
    OK -.->|"forbidden"| N

    style N fill:#6a040f,color:#fff
    style OK fill:#2d6a4f,color:#fff
```

Supported v3 PR retrieval is **local stdio only**: Device Flow turns on when `GITHUB_PR_CONTEXT_RUNTIME=local` and `AUTH_REQUIRED` is false. The deployed entrypoint runs hosted, returns `unsupported` from its GitHub tools, and cannot reach an OS-vault credential. A tenant-aware hosted backend is future design, not a v0.3 feature.

## Install

Python 3.10+. Package and command are both `github-pr-context-mcp` — the old `github-pr-engine` command is obsolete.

```bash
pipx install github-pr-context-mcp
```

```bash
uvx github-pr-context-mcp
```

From a source checkout, use `pipx install .` or `uvx --from . github-pr-context-mcp`.

> [!IMPORTANT]
> This release bundles the product App's **public** Client ID and slug. Do not set `GITHUB_APP_CLIENT_ID`, supply a PAT, or supply any App secret. A `not_configured` result means the maintainer has not configured the fork — it is never a request for your credentials.

## Configure an IDE client

```json
{
  "mcpServers": {
    "github-pr-context": {
      "command": "github-pr-context-mcp"
    }
  }
}
```

Through `uvx` instead:

```json
{
  "mcpServers": {
    "github-pr-context": {
      "command": "uvx",
      "args": ["github-pr-context-mcp"]
    }
  }
}
```

Need an absolute path? `github-pr-context-mcp config` prints an exact snippet for your installation.

## Connect GitHub

```mermaid
sequenceDiagram
    participant A as IDE agent
    participant S as MCP server
    participant B as Browser
    participant V as OS vault

    A->>S: get_github_connection_status
    S-->>A: app_installation_url
    A->>B: install App on chosen repos
    A->>S: begin_github_authorization
    S-->>A: verification_uri + user_code
    A->>B: enter code, approve
    loop while authorization_pending
        A->>S: complete_github_authorization
        S-->>A: wait retry_after_seconds
    end
    S->>V: store credential
    S-->>A: connected
    Note over A,S: only now start indexing
```

```mermaid
stateDiagram-v2
    [*] --> not_configured: maintainer shipped no Client ID
    [*] --> disconnected
    not_configured --> disconnected: fork configured
    disconnected --> authorization_pending: begin_github_authorization
    authorization_pending --> authorization_pending: poll, honor retry delay
    authorization_pending --> connected: approved
    connected --> reauthorization_required: expired or revoked
    reauthorization_required --> authorization_pending: restart Device Flow
    connected --> disconnected: disconnect_github
    [*] --> unsupported: not local stdio
```

The vault stores material per Client ID and credential profile. It **fails closed** if the OS keyring is unavailable — there is no plaintext fallback. `disconnect_github` deletes the local credential but does **not** revoke the App at GitHub; do that in GitHub settings too.

<details>
<summary>Release maintainer: configure the App once</summary>

v0.3.2 bundles one public App identity in [`auth/product_github_app.py`](auth/product_github_app.py). Fork maintainers create their own public App, enable Device Flow, and bundle **only** its Client ID and slug — or use the `GITHUB_APP_CLIENT_ID` / `GITHUB_APP_SLUG` overrides. GitHub may require a private key before installation; store it securely and never ship or configure it here.

</details>

## Install the v3 skill

```bash
github-pr-context-mcp install-skill --skill-dir .agents/skills
```

The [v3 skill](.agents/skills/github-pr-context-v3/SKILL.md) tells capable agents when to retrieve and when to reason for themselves. The installer refuses to overwrite an existing `github-pr-context-v3` directory — remove the old one deliberately.

## Index and refresh

```text
ensure_repo_ready({"repo": "owner/repo", "storage": "permanent", "pages": 2})
```

```mermaid
stateDiagram-v2
    [*] --> queued
    queued --> running
    running --> ready: all pages fetched
    running --> partial: hit page cap mid-refresh
    running --> failed
    running --> cancelled
    partial --> running: run the same refresh again
    partial --> ready: continuation completes
    note right of partial
        watermark does NOT advance
        while a refresh is partial
    end note
```

```mermaid
flowchart LR
    P["pages: 1-10<br/>default 2"] --> C["30 PRs per page"]
    C --> D["default 60 PRs"]
    C --> M["max 300 PRs"]
    M --> T["marked truncated_connections"]
    T --> W["Want deeper history?<br/>delete + rebuild with more pages"]

    style W fill:#9d4edd,color:#fff
```

A refresh looks for **newer** updates — it will not backfill older history skipped by the initial cap. Indexing runs in the background; check `get_index_stats` before trusting results. Job status lives in process memory and disappears on restart.

```text
ensure_repo_ready({"repo": "owner/repo", "refresh": true})
```

### Incremental indexing by webhook

```mermaid
flowchart LR
    PR["PR merged"] --> H["GitHub webhook"]
    H -->|"signed payload"| S["webhook_server.py"]
    S --> I["Index that one PR"]
    I --> DB["Permanent storage"]
    I -.->|"deliberately NOT advanced"| WM["Refresh watermark"]

    style WM fill:#6a040f,color:#fff
```

```bash
python entrypoints/webhook_server.py
```

Add the public URL under **Settings → Webhooks** and send pull-request events. Set `GITHUB_WEBHOOK_SECRET` in both places — when unset the server warns and accepts unsigned payloads.

> [!IMPORTANT]
> This listener sits **outside** the local-stdio Device Flow path. It is a server with no browser, so it reads `GITHUB_TOKEN` from its own environment and cannot reach the OS vault. Run it only where you control that token.

Because it indexes one PR rather than a full sweep, it leaves the watermark alone — a later refresh still re-examines everything since the last complete pass.

### Storage and namespaces

```mermaid
flowchart TB
    R["Repository + namespace"] --> P["permanent"]
    R --> T["temporary"]
    P --> P1["~/.github-pr-mcp/chroma_db"]
    P --> P2["Survives restart"]
    T --> T1["In-memory"]
    T --> T2["Gone on exit"]
    T --> T3["LRU-evicted past 5 repos"]

    style P fill:#2d6a4f,color:#fff
    style T fill:#7f5539,color:#fff
```

Namespaces are for local organization — **not** tenant isolation in a multi-user service.

<details>
<summary>Migrating from v0.2 and earlier</summary>

Close IDE clients running this MCP, then migrate once:

```bash
github-pr-context-mcp migrate-storage --dry-run
github-pr-context-mcp migrate-storage
```

It copies data, keeps the old data as backup, is safe to rerun, and will not overwrite a nonempty v3 destination. An interrupted migration is resumable. Check the JSON report for `skipped` and `conflicts`, restart the IDE, verify with `get_index_stats`.

</details>

## MCP tools

```mermaid
mindmap
  root(("MCP tools"))
    GitHub connection
      get_github_connection_status
      begin_github_authorization
      complete_github_authorization
      disconnect_github
    Index lifecycle
      ensure_repo_ready
      set_active_repo
      list_indexed_repos
      get_index_stats
      delete_repo_index
    Historical search
      semantic_search_reviews
      find_similar_errors
      get_team_review_patterns
    Context for a task
      review_code_with_history
      generate_code_from_history
      generate_tests
      static_analysis
      suggest_refactors
      security_check
    Agent instructions
      get_repo_rules_material
    Legacy admin
      update_settings
      get_usage_stats
```

Every history tool returns **retrieval material**, never a final decision. Never pass a token to a connection tool. `update_settings` is disabled in local mode; `get_usage_stats` needs usage tracking enabled.

## Accuracy and limits

```mermaid
flowchart TB
    E["Retrieved PR evidence"] --> C1["Check index status first"]
    C1 --> C2["Validate against current code"]
    C2 --> C3["Old preference ≠ current requirement"]
    C3 --> C4["Disclose extraction limits"]
    C4 --> OK["Then act"]

    style OK fill:#2d6a4f,color:#fff
```

```mermaid
flowchart LR
    L1["Nested PR connections"] --> M1["truncated_connections"]
    L2["PR body"] --> M2["50 KiB cap"]
    L3["Review diff hunk"] --> M3["100 KiB cap"]
    M2 & M3 --> V["visible truncation marker"]
```

Two outbound operations are separate from GitHub retrieval: the first index or query lazily downloads the `all-MiniLM-L6-v2` embedding model, and the local launcher runs a background version check at startup.

## Benchmarks

[`benchmarks/`](benchmarks/README.md) replays already-merged PRs, where the real diff is ground truth, and scores an agent with and without retrieved history.

The current run reports **no statistically demonstrated effect** — the measured gap is smaller than the judge's own scatter, on 10 tasks against a repo the model already knows well. Read the [full write-up](benchmarks/README.md) for why that result is weak evidence about the benchmark rather than about the product, and what a representative test would need.

## Development

```bash
python -m pip install ".[test]" flake8
python -m pytest
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=.venv
```

The project pins `chromadb==0.5.0` with `numpy<2.0` — Chroma 0.5 imports the removed `np.float_` alias, so NumPy 2 breaks its import. Install via package metadata rather than overriding NumPy.

CI runs one Ubuntu / Python 3.10 job with the full suite plus fatal syntax and undefined-name lint. Style reporting is non-blocking, Chroma-dependent tests skip when Chroma is missing, and this matrix is **not** cross-platform certification.

## Documentation

- [Quick start](docs/quickstart.md)
- [GitHub App Device Flow](docs/guides/github-app-device-flow.md)
- [Architecture](docs/architecture.md)
- [Tool strategy](docs/tools_strategy.md)

## Feedback

- **Feedback**: Open an issue or start a discussion with ideas or bugs.
- **Star ⭐**: If this tool saves you time, give it a star!

## License

MIT
