mcp-github-crunchtools 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.specify/memory/constitution.md +14 -5
- mcp_github_crunchtools-1.3.0/.specify/specs/002-request-token-merge-gate/spec.md +132 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/CHANGELOG.md +33 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/CLAUDE.md +10 -1
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/Containerfile +1 -1
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/PKG-INFO +68 -10
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/README.md +67 -9
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/SECURITY.md +86 -33
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/pyproject.toml +1 -1
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/server.json +2 -2
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/__init__.py +5 -2
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/client.py +71 -9
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/config.py +23 -11
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/errors.py +22 -4
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/server.py +18 -7
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/pull_requests.py +274 -15
- mcp_github_crunchtools-1.3.0/tests/test_merge_gate.py +584 -0
- mcp_github_crunchtools-1.3.0/tests/test_request_token.py +325 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/tests/test_tools.py +0 -61
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/uv.lock +1 -1
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.gatehouse-ignore +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/dependabot.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/ashigaru.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/ci.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/constitution.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/container.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/dependabot-automerge.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/gatehouse-retriage.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/gatehouse.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/publish.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.github/workflows/security.yml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.gitignore +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.pre-commit-config.yaml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.specify/specs/000-baseline/spec.md +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.specify/specs/001-pr-merge-logs-release/spec.md +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.specify/templates/plan-template.md +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.specify/templates/spec-template.md +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/LICENSE +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/gourmand-exceptions.toml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/gourmand.toml +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/__main__.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/models.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/__init__.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/actions.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/files.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/issues.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/releases.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/src/mcp_github_crunchtools/tools/search.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/tests/__init__.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/tests/conftest.py +0 -0
- {mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/tests/test_validation.py +0 -0
{mcp_github_crunchtools-1.2.0 → mcp_github_crunchtools-1.3.0}/.specify/memory/constitution.md
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# mcp-github-crunchtools Constitution
|
|
2
2
|
|
|
3
|
-
> **Version:** 1.
|
|
3
|
+
> **Version:** 1.3.0
|
|
4
4
|
> **Ratified:** 2026-06-14
|
|
5
|
-
> **Amended:** 2026-10-
|
|
5
|
+
> **Amended:** 2026-10-14
|
|
6
6
|
> **Status:** Active
|
|
7
7
|
> **Inherits:** [crunchtools/constitution](https://github.com/crunchtools/constitution) v1.24.0
|
|
8
8
|
> **Profile:** MCP Server
|
|
@@ -15,9 +15,17 @@ restated here.
|
|
|
15
15
|
|
|
16
16
|
## Security Model Specifics
|
|
17
17
|
|
|
18
|
-
- **Credentials:** `GITHUB_TOKEN
|
|
19
|
-
|
|
20
|
-
`
|
|
18
|
+
- **Credentials:** `GITHUB_TOKEN`, held as `SecretStr`, read from the
|
|
19
|
+
environment only; required unless `GITHUB_ACCEPT_REQUEST_TOKEN` is on, in
|
|
20
|
+
which case an HTTP request's `Authorization: Bearer <token>` is used for
|
|
21
|
+
that request (strict format, malformed refused, never stored on the shared
|
|
22
|
+
client). `GitHubApiError` scrubs both tokens from messages, and
|
|
23
|
+
`Config.__repr__()`/`__str__()` never expose the token.
|
|
24
|
+
- **Merge gate:** `merge_pull_request` merges only when every required
|
|
25
|
+
status check of the base branch (rulesets and classic protection) passes
|
|
26
|
+
on the head commit, pinned to that head; it fails closed when checks
|
|
27
|
+
cannot be read, and when none are required unless the caller passes
|
|
28
|
+
`allow_no_required_checks=True`.
|
|
21
29
|
- **Input limits:** Pydantic models with `extra="forbid"`; issue and PR
|
|
22
30
|
states are allowlisted (`open`, `closed`, `all`), as are merge methods
|
|
23
31
|
(`merge`, `squash`, `rebase`); owner and repo names are
|
|
@@ -73,3 +81,4 @@ responses in `TestClientErrorHandling`.
|
|
|
73
81
|
| 1.0.1 | 2026-09-25 | Inherit constitution v1.17.0 (Gatehouse gates) |
|
|
74
82
|
| 1.1.0 | 2026-10-02 | Manifest under constitution v1.18.0: profile restatement removed, mcp-github specifics kept; `..` rule moved from file paths to git refs to match the code |
|
|
75
83
|
| 1.2.0 | 2026-10-12 | Seven tools for pull requests, Actions runs and logs, and releases (spec 001): redirect handling, result caps, merge-method and SHA validation, releases tool group |
|
|
84
|
+
| 1.3.0 | 2026-10-14 | Per-request token (`GITHUB_ACCEPT_REQUEST_TOKEN`) and the merge gate (spec 002) |
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Specification: Per-Request Token and Merge Gate
|
|
2
|
+
|
|
3
|
+
> **Spec ID:** 002-request-token-merge-gate
|
|
4
|
+
> **Status:** Implemented
|
|
5
|
+
> **Version:** 1.3.0
|
|
6
|
+
> **Author:** crunchtools.com
|
|
7
|
+
> **Date:** 2026-10-14
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Two changes, both about whose authority a call carries.
|
|
12
|
+
|
|
13
|
+
1. **Per-request token.** GitHub's hosted MCP server takes the caller's
|
|
14
|
+
personal access token as `Authorization: Bearer <token>` on every HTTP
|
|
15
|
+
request and acts with it. Behind an opt-in, this server does the same, so
|
|
16
|
+
it can stand in for GitHub's server without the client changing.
|
|
17
|
+
2. **Merge gate.** The token an agent merges with may belong to an
|
|
18
|
+
organization owner, and owners can bypass ruleset-required checks: GitHub
|
|
19
|
+
would merge a pull request whose checks failed. `merge_pull_request_tool`
|
|
20
|
+
now enforces the required checks itself.
|
|
21
|
+
|
|
22
|
+
No tools are added; the count stays at 25.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Per-Request Token
|
|
27
|
+
|
|
28
|
+
| Setting | Incoming `Authorization` | Token used |
|
|
29
|
+
|---------|-------------------------|------------|
|
|
30
|
+
| `GITHUB_ACCEPT_REQUEST_TOKEN` off (default) | ignored | `GITHUB_TOKEN` (required) |
|
|
31
|
+
| on, HTTP request with `Bearer <token>` | used | the request's token |
|
|
32
|
+
| on, HTTP request without the header, or stdio | — | `GITHUB_TOKEN` |
|
|
33
|
+
| on, no header and no `GITHUB_TOKEN` | — | none: error before any API call |
|
|
34
|
+
| on, header present but malformed | refused | none: error, not forwarded |
|
|
35
|
+
|
|
36
|
+
- The header is read with `fastmcp.server.dependencies.get_http_headers(include={"authorization"})`,
|
|
37
|
+
which returns `{}` outside an HTTP request.
|
|
38
|
+
- Accepted form: `(?i:bearer) ` followed by 1 to 1024 characters in
|
|
39
|
+
`\x21`–`\x7e`, matched in full.
|
|
40
|
+
- The token is chosen in `GitHubClient._send` for each call and passed as
|
|
41
|
+
that call's header. The shared `httpx.AsyncClient` has an `Authorization`
|
|
42
|
+
default only when `GITHUB_TOKEN` is set.
|
|
43
|
+
- `errors.request_token` (a `ContextVar`) holds the request's token so that
|
|
44
|
+
`GitHubApiError` scrubs it; each MCP request runs in its own task context.
|
|
45
|
+
- The job-log second hop (`get_redirected_tail`) still sends no token.
|
|
46
|
+
|
|
47
|
+
## Merge Gate
|
|
48
|
+
|
|
49
|
+
Before `PUT /repos/{owner}/{repo}/pulls/{n}/merge`:
|
|
50
|
+
|
|
51
|
+
| Step | Request | Refuses when |
|
|
52
|
+
|------|---------|--------------|
|
|
53
|
+
| 1 | `GET .../pulls/{n}` | caller `sha` is not the head; head or base missing |
|
|
54
|
+
| 2a | `GET .../rules/branches/{base}` (paged) | — |
|
|
55
|
+
| 2b | `GET .../branches/{base}` (`protection.required_status_checks`) | any error, or `protected` with no `protection` shown |
|
|
56
|
+
| 3 | — | no required checks, unless `allow_no_required_checks=True` |
|
|
57
|
+
| 4 | `GET .../commits/{head}/check-runs` and `.../status` (paged) | a required check is missing, pending or failed |
|
|
58
|
+
| 5 | `GET .../pulls/{n}` again | head or base changed since step 1 |
|
|
59
|
+
| 6 | `PUT .../merge` with `sha` = head | (GitHub: 405, 409) |
|
|
60
|
+
|
|
61
|
+
- A required check is `(context, app id or None)`. Rulesets give
|
|
62
|
+
`integration_id`; classic protection gives `checks[].app_id`, where -1
|
|
63
|
+
means any, or bare `contexts`.
|
|
64
|
+
- The most recent check run of the context's name (highest id) decides; it
|
|
65
|
+
passes when completed with `success`, `skipped` or `neutral`. With an app
|
|
66
|
+
id, only that app's runs count. With no app id and no run, the commit
|
|
67
|
+
status of that context must be `success`.
|
|
68
|
+
- Each list is read up to 10 pages of 100; more refuses the merge.
|
|
69
|
+
- Refusals raise `MergeRefusedError` naming every offending check.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Security Considerations
|
|
74
|
+
|
|
75
|
+
- Token mix-up between concurrent callers: nothing request-specific is
|
|
76
|
+
stored on the shared client or in a global.
|
|
77
|
+
- A malformed header never falls back to `GITHUB_TOKEN`: a caller that
|
|
78
|
+
meant to use its own token does not silently act as the server.
|
|
79
|
+
- The gate fails closed on unreadable protection, missing checks, oversized
|
|
80
|
+
lists and branches without required checks.
|
|
81
|
+
- Required reviews and other rules are not re-enforced; the bypass still
|
|
82
|
+
applies to them.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Module Changes
|
|
87
|
+
|
|
88
|
+
| File | Changes |
|
|
89
|
+
|------|---------|
|
|
90
|
+
| `config.py` | `GITHUB_ACCEPT_REQUEST_TOKEN`; `token` may be None |
|
|
91
|
+
| `client.py` | `_incoming_token`, `_auth_headers`; conditional default `Authorization` |
|
|
92
|
+
| `errors.py` | `request_token`, `scrub_tokens`, `MergeRefusedError` |
|
|
93
|
+
| `tools/pull_requests.py` | Merge gate helpers; `allow_no_required_checks` |
|
|
94
|
+
| `server.py` | `allow_no_required_checks` on `merge_pull_request_tool`; docstring |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Testing Requirements
|
|
99
|
+
|
|
100
|
+
- [x] stdio uses the env token; option off ignores the header (well-formed
|
|
101
|
+
or not); option on prefers the header; header absent falls back
|
|
102
|
+
- [x] Request-token-only instance: no default `Authorization` on the client
|
|
103
|
+
- [x] No token at all: error and no HTTP request
|
|
104
|
+
- [x] Malformed headers refused and not sent: empty, no scheme, other
|
|
105
|
+
scheme, extra spaces, tab, control, DEL, non-ASCII, over-long
|
|
106
|
+
- [x] Request token scrubbed from API and transport errors
|
|
107
|
+
- [x] Five concurrent requests with different tokens each send their own
|
|
108
|
+
(real httpx client over a mock transport)
|
|
109
|
+
- [x] Gate: all green merges pinned to head; failing, pending, missing;
|
|
110
|
+
newest status of a repeated context decides; unreadable rules, check
|
|
111
|
+
runs, statuses or PR never merge; the MCP tool forwards the flag;
|
|
112
|
+
head or base moving during the gate is refused;
|
|
113
|
+
status pending/failure/error; most recent run decides; app-bound
|
|
114
|
+
rule; no checks refused and allowed by flag; flag does not waive
|
|
115
|
+
existing checks; `sha` mismatch; protection 403; classic `checks`
|
|
116
|
+
and `contexts`; rulesets and classic combined; branch name encoded;
|
|
117
|
+
check runs, statuses and rules paginated; page cap refuses
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Dependencies
|
|
122
|
+
|
|
123
|
+
- Depends on: 001-pr-merge-logs-release
|
|
124
|
+
- Blocks: none
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Changelog
|
|
129
|
+
|
|
130
|
+
| Version | Date | Changes |
|
|
131
|
+
|---------|------|---------|
|
|
132
|
+
| 1.3.0 | 2026-10-14 | Implemented |
|
|
@@ -6,6 +6,39 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.3.0] - 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `GITHUB_ACCEPT_REQUEST_TOKEN` (default off): over HTTP, act with the token
|
|
14
|
+
in each request's `Authorization: Bearer <token>` header, the way GitHub's
|
|
15
|
+
hosted MCP server takes it, falling back to `GITHUB_TOKEN`. With it on,
|
|
16
|
+
`GITHUB_TOKEN` is optional. A malformed header is refused, never
|
|
17
|
+
forwarded; a call with no token at all fails before reaching GitHub.
|
|
18
|
+
stdio is unchanged.
|
|
19
|
+
- `merge_pull_request_tool` takes `allow_no_required_checks` (default
|
|
20
|
+
False).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `merge_pull_request_tool` enforces the base branch's required status
|
|
25
|
+
checks itself before merging: from rulesets and classic branch
|
|
26
|
+
protection, every required check must have a passing check run (success,
|
|
27
|
+
skipped or neutral) or a successful status on the head commit, or the
|
|
28
|
+
merge is refused with each offending check named. A branch with no
|
|
29
|
+
required checks is refused unless `allow_no_required_checks=True`, and a
|
|
30
|
+
branch protection that cannot be read is refused. Classic protection is
|
|
31
|
+
read from the branch itself, so no Administration permission is needed.
|
|
32
|
+
The merge is always pinned to the checked head SHA. It now needs Pull
|
|
33
|
+
requests: read and Commit statuses: read as well as Contents: write.
|
|
34
|
+
|
|
35
|
+
### Security
|
|
36
|
+
|
|
37
|
+
- An organization owner's token can bypass required checks on GitHub's
|
|
38
|
+
side; the merge gate closes that for this tool.
|
|
39
|
+
- The request token is scrubbed from error messages like `GITHUB_TOKEN`,
|
|
40
|
+
and is passed per call, never stored on the shared HTTP client.
|
|
41
|
+
|
|
9
42
|
## [1.2.0] - 2026-10-12
|
|
10
43
|
|
|
11
44
|
### Added
|
|
@@ -44,7 +44,8 @@ claude mcp add mcp-github-crunchtools \
|
|
|
44
44
|
|
|
45
45
|
| Variable | Required | Default | Description |
|
|
46
46
|
|----------|----------|---------|-------------|
|
|
47
|
-
| `GITHUB_TOKEN` | Yes | — | GitHub Personal Access Token |
|
|
47
|
+
| `GITHUB_TOKEN` | Yes, unless `GITHUB_ACCEPT_REQUEST_TOKEN` is on | — | GitHub Personal Access Token |
|
|
48
|
+
| `GITHUB_ACCEPT_REQUEST_TOKEN` | No | off | Over HTTP, use each request's `Authorization: Bearer` token (falls back to `GITHUB_TOKEN`) |
|
|
48
49
|
| `GITHUB_API_URL` | No | `https://api.github.com` | API base URL (set for GHES) |
|
|
49
50
|
| `GITHUB_DEFAULT_ORG` | No | — | Default owner when a tool omits `owner` |
|
|
50
51
|
| `SSL_CERT_FILE` | No | — | Custom CA bundle for self-hosted instances |
|
|
@@ -68,6 +69,14 @@ fresh run (via `workflow_dispatch`) and is the way to *force* a build —
|
|
|
68
69
|
`rerun_*` only re-run an existing run and GitHub rejects runs older than 30
|
|
69
70
|
days.
|
|
70
71
|
|
|
72
|
+
`merge_pull_request_tool` enforces the base branch's required checks itself
|
|
73
|
+
(rulesets and classic protection, fail closed) before it merges, pinned to
|
|
74
|
+
the checked head SHA: an org owner's token can bypass the rules on GitHub's
|
|
75
|
+
side. Do not weaken that gate.
|
|
76
|
+
|
|
77
|
+
The per-request token goes into one call's headers and the `request_token`
|
|
78
|
+
context variable in `errors.py`; never onto the shared httpx client.
|
|
79
|
+
|
|
71
80
|
`get_failed_job_logs_tool` is the one tool that talks to a host other than the
|
|
72
81
|
API: GitHub redirects a job log to a storage URL. `GitHubClient.get_redirected_tail`
|
|
73
82
|
follows it with a separate client that has no token. Keep it that way.
|
|
@@ -27,7 +27,7 @@ FROM quay.io/hummingbird/python:latest
|
|
|
27
27
|
|
|
28
28
|
# Labels for container metadata
|
|
29
29
|
LABEL name="mcp-github-crunchtools" \
|
|
30
|
-
version="1.
|
|
30
|
+
version="1.3.0" \
|
|
31
31
|
summary="Secure MCP server for GitHub issues, pull requests, Actions, releases, files, and search" \
|
|
32
32
|
description="A security-focused MCP server for GitHub built on Red Hat UBI" \
|
|
33
33
|
maintainer="crunchtools.com" \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-github-crunchtools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Secure MCP server for GitHub issues, pull requests, Actions, releases, files, and search
|
|
5
5
|
Author: crunchtools.com
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -86,7 +86,7 @@ permission the tool needs (see [Token permissions](#token-permissions)).
|
|
|
86
86
|
| `create_pull_request_tool` | Open a pull request, optionally as a draft (*write*) | Pull requests: write |
|
|
87
87
|
| `update_pull_request_tool` | Edit, close or reopen a pull request; does not merge (*write*) | Pull requests: write |
|
|
88
88
|
| `reply_to_review_comment_tool` | Reply in the thread of a diff comment (*write*) | Pull requests: write |
|
|
89
|
-
| `merge_pull_request_tool` | Merge, squash or rebase a pull request,
|
|
89
|
+
| `merge_pull_request_tool` | Merge, squash or rebase a pull request, only once the base branch's required checks are green on its head (see [The merge gate](#the-merge-gate)) (*write*) | Contents: write; Pull requests and Commit statuses: read |
|
|
90
90
|
|
|
91
91
|
### Actions (6 tools)
|
|
92
92
|
|
|
@@ -162,12 +162,39 @@ podman run -e GITHUB_TOKEN=your_token \
|
|
|
162
162
|
|
|
163
163
|
| Variable | Required | Default | Description |
|
|
164
164
|
|----------|----------|---------|-------------|
|
|
165
|
-
| `GITHUB_TOKEN` | Yes | — | GitHub Personal Access Token |
|
|
165
|
+
| `GITHUB_TOKEN` | Yes, unless `GITHUB_ACCEPT_REQUEST_TOKEN` is on | — | GitHub Personal Access Token |
|
|
166
|
+
| `GITHUB_ACCEPT_REQUEST_TOKEN` | No | off | `true`, `1` or `yes`: over HTTP, act with the token in each request's `Authorization: Bearer` header (see [Per-request token](#per-request-token)) |
|
|
166
167
|
| `GITHUB_API_URL` | No | `https://api.github.com` | API base URL (set for GHES) |
|
|
167
168
|
| `GITHUB_DEFAULT_ORG` | No | — | Default owner when a tool omits `owner` |
|
|
168
169
|
| `SSL_CERT_FILE` | No | — | Custom CA bundle path, for self-hosted GHES with an internal CA |
|
|
169
170
|
| `GITHUB_SSL_VERIFY` | No | `true` | Set `false` to disable TLS verification (not recommended) |
|
|
170
171
|
|
|
172
|
+
### Per-request token
|
|
173
|
+
|
|
174
|
+
GitHub's own hosted MCP server takes the caller's token on every HTTP
|
|
175
|
+
request as `Authorization: Bearer <token>` and acts with it. With
|
|
176
|
+
`GITHUB_ACCEPT_REQUEST_TOKEN=true` this server does the same, so a client
|
|
177
|
+
configured for GitHub's server can point here instead with the same header.
|
|
178
|
+
|
|
179
|
+
- Over the `streamable-http` or `sse` transport, a request carrying
|
|
180
|
+
`Authorization: Bearer <token>` is sent to GitHub with that token, for
|
|
181
|
+
that request only. Concurrent requests with different tokens each use
|
|
182
|
+
their own; the token is never stored on the shared client.
|
|
183
|
+
- A request without the header falls back to `GITHUB_TOKEN`. With the option
|
|
184
|
+
on, `GITHUB_TOKEN` is optional: an instance without it serves only
|
|
185
|
+
requests that bring a token, and refuses the others before calling GitHub
|
|
186
|
+
("No GitHub token for this request").
|
|
187
|
+
- The header must be exactly `Bearer <token>` (scheme in any case, one
|
|
188
|
+
space, a token of printable ASCII with no spaces). Anything else is
|
|
189
|
+
refused, never forwarded, even when `GITHUB_TOKEN` is set.
|
|
190
|
+
- The request token is scrubbed from error messages like `GITHUB_TOKEN` is.
|
|
191
|
+
- With the option off (the default) an incoming Authorization header is
|
|
192
|
+
ignored, exactly as before. stdio has no request headers and always uses
|
|
193
|
+
`GITHUB_TOKEN`.
|
|
194
|
+
|
|
195
|
+
The option makes the server act as whoever sends a token. Expose the HTTP
|
|
196
|
+
port only to callers you would hand that token to anyway, behind TLS.
|
|
197
|
+
|
|
171
198
|
### Creating a GitHub Personal Access Token
|
|
172
199
|
|
|
173
200
|
1. **Navigate to token settings**
|
|
@@ -192,11 +219,11 @@ For a fine-grained personal access token, the repository permissions are:
|
|
|
192
219
|
| Metadata | every tool (GitHub grants it with any other permission) | — |
|
|
193
220
|
| Contents | `get_file_content_tool`, `list_repo_tree_tool` | `merge_pull_request_tool`, `create_release_tool` |
|
|
194
221
|
| Issues | `list_issues_tool`, `get_issue_tool` | `create_issue_tool`, `update_issue_tool`, `create_issue_comment_tool` |
|
|
195
|
-
| Pull requests | `list_pull_requests_tool`, `get_pull_request_tool`, `get_pull_request_diff_tool`, `get_pull_request_checks_tool`, `list_pull_request_comments_tool` | `create_pull_request_tool`, `update_pull_request_tool`, `reply_to_review_comment_tool`, `create_issue_comment_tool` on a PR |
|
|
222
|
+
| Pull requests | `merge_pull_request_tool`, `list_pull_requests_tool`, `get_pull_request_tool`, `get_pull_request_diff_tool`, `get_pull_request_checks_tool`, `list_pull_request_comments_tool` | `create_pull_request_tool`, `update_pull_request_tool`, `reply_to_review_comment_tool`, `create_issue_comment_tool` on a PR |
|
|
196
223
|
| Actions | `list_workflow_runs_tool`, `get_workflow_run_tool`, `get_failed_job_logs_tool` | `trigger_workflow_tool`, `rerun_workflow_run_tool`, `rerun_failed_jobs_tool` |
|
|
197
|
-
| Commit statuses | `get_pull_request_checks_tool` | — |
|
|
224
|
+
| Commit statuses | `get_pull_request_checks_tool`, `merge_pull_request_tool` | — |
|
|
198
225
|
|
|
199
|
-
`get_pull_request_checks_tool`
|
|
226
|
+
`get_pull_request_checks_tool` and `merge_pull_request_tool` read check-runs, and GitHub's permission table
|
|
200
227
|
for fine-grained tokens lists no permission for that endpoint. It is readable
|
|
201
228
|
on public repositories; on a private repository use a classic token or a
|
|
202
229
|
GitHub App token. The search tools likewise have no permission of their own:
|
|
@@ -212,9 +239,35 @@ Three things worth knowing before granting write:
|
|
|
212
239
|
`.github/workflows/`, relative to the default branch, additionally needs
|
|
213
240
|
Workflows: write; without it GitHub answers 404. Expect the same of a merge
|
|
214
241
|
that brings workflow changes in.
|
|
215
|
-
- **
|
|
216
|
-
|
|
217
|
-
|
|
242
|
+
- **The merge gate checks required status checks itself**, below. Other
|
|
243
|
+
branch rules (required reviews, for instance) are left to GitHub, which
|
|
244
|
+
answers 405 when it refuses; a token that may bypass the rules gets past
|
|
245
|
+
them.
|
|
246
|
+
|
|
247
|
+
### The merge gate
|
|
248
|
+
|
|
249
|
+
An organization owner, or anyone on a ruleset's bypass list, can merge past
|
|
250
|
+
failing required checks: GitHub merges a red pull request for such a token.
|
|
251
|
+
So `merge_pull_request_tool` does not leave the checks to GitHub. Before it
|
|
252
|
+
merges it:
|
|
253
|
+
|
|
254
|
+
1. reads the pull request's base branch and head SHA, and refuses if `sha`
|
|
255
|
+
was given and the head has moved;
|
|
256
|
+
2. collects the base branch's required status checks from its active
|
|
257
|
+
rulesets and its classic branch protection, read from the branch itself
|
|
258
|
+
so no Administration permission is needed. An error reading either, or a
|
|
259
|
+
protected branch whose protection is not shown, refuses the merge rather
|
|
260
|
+
than count as none;
|
|
261
|
+
3. refuses if no check is required, unless `allow_no_required_checks=True`;
|
|
262
|
+
4. requires, for every required check on the head commit, a completed check
|
|
263
|
+
run of that name concluding `success`, `skipped` or `neutral` (the most
|
|
264
|
+
recent run of the name decides), or a commit status of `success`. A rule
|
|
265
|
+
that names an app accepts only that app's check runs. A missing, pending
|
|
266
|
+
or failed check refuses the merge, and the error lists each one and why;
|
|
267
|
+
5. reads the pull request again and refuses if its head or base changed
|
|
268
|
+
meanwhile (GitHub's merge call pins the head but not the base), then
|
|
269
|
+
merges with the head SHA pinned, so a later push makes GitHub refuse
|
|
270
|
+
(409) rather than merge code nobody checked.
|
|
218
271
|
|
|
219
272
|
With a classic token, `repo` covers all of the above (`public_repo` for public
|
|
220
273
|
repositories only), plus `workflow` for the workflow-file case.
|
|
@@ -282,6 +335,7 @@ Assistant: [calls list_pull_request_comments_tool with pull_number=5]
|
|
|
282
335
|
Assistant: [calls reply_to_review_comment_tool with comment_id=..., body="fixed in abc1234"]
|
|
283
336
|
Assistant: [calls get_pull_request_checks_tool with pull_number=5]
|
|
284
337
|
Assistant: [calls merge_pull_request_tool with merge_method="squash", sha=<head_sha from the checks>]
|
|
338
|
+
(the tool checks the required checks again itself before merging)
|
|
285
339
|
```
|
|
286
340
|
|
|
287
341
|
### Read a File
|
|
@@ -307,7 +361,9 @@ This server was designed with security as a primary concern. See [SECURITY.md](S
|
|
|
307
361
|
1. **Token Protection**
|
|
308
362
|
- Stored as SecretStr (never accidentally logged)
|
|
309
363
|
- Environment variable only (never in files or args)
|
|
310
|
-
-
|
|
364
|
+
- Optionally, a per-request token from the HTTP `Authorization` header,
|
|
365
|
+
used for that request only and never stored on the shared client
|
|
366
|
+
- Both sanitized from all error messages
|
|
311
367
|
|
|
312
368
|
2. **Input Validation**
|
|
313
369
|
- Pydantic models for write inputs
|
|
@@ -322,6 +378,8 @@ This server was designed with security as a primary concern. See [SECURITY.md](S
|
|
|
322
378
|
- Response size limits (10MB)
|
|
323
379
|
- Job-log downloads follow GitHub's redirect without the token, over
|
|
324
380
|
HTTPS only, and keep just the tail (20MB download limit)
|
|
381
|
+
- Merges only when the required checks are green on the head commit,
|
|
382
|
+
even for a token GitHub would let bypass them
|
|
325
383
|
|
|
326
384
|
4. **Automated CVE Scanning**
|
|
327
385
|
- GitHub Actions scan dependencies
|
|
@@ -60,7 +60,7 @@ permission the tool needs (see [Token permissions](#token-permissions)).
|
|
|
60
60
|
| `create_pull_request_tool` | Open a pull request, optionally as a draft (*write*) | Pull requests: write |
|
|
61
61
|
| `update_pull_request_tool` | Edit, close or reopen a pull request; does not merge (*write*) | Pull requests: write |
|
|
62
62
|
| `reply_to_review_comment_tool` | Reply in the thread of a diff comment (*write*) | Pull requests: write |
|
|
63
|
-
| `merge_pull_request_tool` | Merge, squash or rebase a pull request,
|
|
63
|
+
| `merge_pull_request_tool` | Merge, squash or rebase a pull request, only once the base branch's required checks are green on its head (see [The merge gate](#the-merge-gate)) (*write*) | Contents: write; Pull requests and Commit statuses: read |
|
|
64
64
|
|
|
65
65
|
### Actions (6 tools)
|
|
66
66
|
|
|
@@ -136,12 +136,39 @@ podman run -e GITHUB_TOKEN=your_token \
|
|
|
136
136
|
|
|
137
137
|
| Variable | Required | Default | Description |
|
|
138
138
|
|----------|----------|---------|-------------|
|
|
139
|
-
| `GITHUB_TOKEN` | Yes | — | GitHub Personal Access Token |
|
|
139
|
+
| `GITHUB_TOKEN` | Yes, unless `GITHUB_ACCEPT_REQUEST_TOKEN` is on | — | GitHub Personal Access Token |
|
|
140
|
+
| `GITHUB_ACCEPT_REQUEST_TOKEN` | No | off | `true`, `1` or `yes`: over HTTP, act with the token in each request's `Authorization: Bearer` header (see [Per-request token](#per-request-token)) |
|
|
140
141
|
| `GITHUB_API_URL` | No | `https://api.github.com` | API base URL (set for GHES) |
|
|
141
142
|
| `GITHUB_DEFAULT_ORG` | No | — | Default owner when a tool omits `owner` |
|
|
142
143
|
| `SSL_CERT_FILE` | No | — | Custom CA bundle path, for self-hosted GHES with an internal CA |
|
|
143
144
|
| `GITHUB_SSL_VERIFY` | No | `true` | Set `false` to disable TLS verification (not recommended) |
|
|
144
145
|
|
|
146
|
+
### Per-request token
|
|
147
|
+
|
|
148
|
+
GitHub's own hosted MCP server takes the caller's token on every HTTP
|
|
149
|
+
request as `Authorization: Bearer <token>` and acts with it. With
|
|
150
|
+
`GITHUB_ACCEPT_REQUEST_TOKEN=true` this server does the same, so a client
|
|
151
|
+
configured for GitHub's server can point here instead with the same header.
|
|
152
|
+
|
|
153
|
+
- Over the `streamable-http` or `sse` transport, a request carrying
|
|
154
|
+
`Authorization: Bearer <token>` is sent to GitHub with that token, for
|
|
155
|
+
that request only. Concurrent requests with different tokens each use
|
|
156
|
+
their own; the token is never stored on the shared client.
|
|
157
|
+
- A request without the header falls back to `GITHUB_TOKEN`. With the option
|
|
158
|
+
on, `GITHUB_TOKEN` is optional: an instance without it serves only
|
|
159
|
+
requests that bring a token, and refuses the others before calling GitHub
|
|
160
|
+
("No GitHub token for this request").
|
|
161
|
+
- The header must be exactly `Bearer <token>` (scheme in any case, one
|
|
162
|
+
space, a token of printable ASCII with no spaces). Anything else is
|
|
163
|
+
refused, never forwarded, even when `GITHUB_TOKEN` is set.
|
|
164
|
+
- The request token is scrubbed from error messages like `GITHUB_TOKEN` is.
|
|
165
|
+
- With the option off (the default) an incoming Authorization header is
|
|
166
|
+
ignored, exactly as before. stdio has no request headers and always uses
|
|
167
|
+
`GITHUB_TOKEN`.
|
|
168
|
+
|
|
169
|
+
The option makes the server act as whoever sends a token. Expose the HTTP
|
|
170
|
+
port only to callers you would hand that token to anyway, behind TLS.
|
|
171
|
+
|
|
145
172
|
### Creating a GitHub Personal Access Token
|
|
146
173
|
|
|
147
174
|
1. **Navigate to token settings**
|
|
@@ -166,11 +193,11 @@ For a fine-grained personal access token, the repository permissions are:
|
|
|
166
193
|
| Metadata | every tool (GitHub grants it with any other permission) | — |
|
|
167
194
|
| Contents | `get_file_content_tool`, `list_repo_tree_tool` | `merge_pull_request_tool`, `create_release_tool` |
|
|
168
195
|
| Issues | `list_issues_tool`, `get_issue_tool` | `create_issue_tool`, `update_issue_tool`, `create_issue_comment_tool` |
|
|
169
|
-
| Pull requests | `list_pull_requests_tool`, `get_pull_request_tool`, `get_pull_request_diff_tool`, `get_pull_request_checks_tool`, `list_pull_request_comments_tool` | `create_pull_request_tool`, `update_pull_request_tool`, `reply_to_review_comment_tool`, `create_issue_comment_tool` on a PR |
|
|
196
|
+
| Pull requests | `merge_pull_request_tool`, `list_pull_requests_tool`, `get_pull_request_tool`, `get_pull_request_diff_tool`, `get_pull_request_checks_tool`, `list_pull_request_comments_tool` | `create_pull_request_tool`, `update_pull_request_tool`, `reply_to_review_comment_tool`, `create_issue_comment_tool` on a PR |
|
|
170
197
|
| Actions | `list_workflow_runs_tool`, `get_workflow_run_tool`, `get_failed_job_logs_tool` | `trigger_workflow_tool`, `rerun_workflow_run_tool`, `rerun_failed_jobs_tool` |
|
|
171
|
-
| Commit statuses | `get_pull_request_checks_tool` | — |
|
|
198
|
+
| Commit statuses | `get_pull_request_checks_tool`, `merge_pull_request_tool` | — |
|
|
172
199
|
|
|
173
|
-
`get_pull_request_checks_tool`
|
|
200
|
+
`get_pull_request_checks_tool` and `merge_pull_request_tool` read check-runs, and GitHub's permission table
|
|
174
201
|
for fine-grained tokens lists no permission for that endpoint. It is readable
|
|
175
202
|
on public repositories; on a private repository use a classic token or a
|
|
176
203
|
GitHub App token. The search tools likewise have no permission of their own:
|
|
@@ -186,9 +213,35 @@ Three things worth knowing before granting write:
|
|
|
186
213
|
`.github/workflows/`, relative to the default branch, additionally needs
|
|
187
214
|
Workflows: write; without it GitHub answers 404. Expect the same of a merge
|
|
188
215
|
that brings workflow changes in.
|
|
189
|
-
- **
|
|
190
|
-
|
|
191
|
-
|
|
216
|
+
- **The merge gate checks required status checks itself**, below. Other
|
|
217
|
+
branch rules (required reviews, for instance) are left to GitHub, which
|
|
218
|
+
answers 405 when it refuses; a token that may bypass the rules gets past
|
|
219
|
+
them.
|
|
220
|
+
|
|
221
|
+
### The merge gate
|
|
222
|
+
|
|
223
|
+
An organization owner, or anyone on a ruleset's bypass list, can merge past
|
|
224
|
+
failing required checks: GitHub merges a red pull request for such a token.
|
|
225
|
+
So `merge_pull_request_tool` does not leave the checks to GitHub. Before it
|
|
226
|
+
merges it:
|
|
227
|
+
|
|
228
|
+
1. reads the pull request's base branch and head SHA, and refuses if `sha`
|
|
229
|
+
was given and the head has moved;
|
|
230
|
+
2. collects the base branch's required status checks from its active
|
|
231
|
+
rulesets and its classic branch protection, read from the branch itself
|
|
232
|
+
so no Administration permission is needed. An error reading either, or a
|
|
233
|
+
protected branch whose protection is not shown, refuses the merge rather
|
|
234
|
+
than count as none;
|
|
235
|
+
3. refuses if no check is required, unless `allow_no_required_checks=True`;
|
|
236
|
+
4. requires, for every required check on the head commit, a completed check
|
|
237
|
+
run of that name concluding `success`, `skipped` or `neutral` (the most
|
|
238
|
+
recent run of the name decides), or a commit status of `success`. A rule
|
|
239
|
+
that names an app accepts only that app's check runs. A missing, pending
|
|
240
|
+
or failed check refuses the merge, and the error lists each one and why;
|
|
241
|
+
5. reads the pull request again and refuses if its head or base changed
|
|
242
|
+
meanwhile (GitHub's merge call pins the head but not the base), then
|
|
243
|
+
merges with the head SHA pinned, so a later push makes GitHub refuse
|
|
244
|
+
(409) rather than merge code nobody checked.
|
|
192
245
|
|
|
193
246
|
With a classic token, `repo` covers all of the above (`public_repo` for public
|
|
194
247
|
repositories only), plus `workflow` for the workflow-file case.
|
|
@@ -256,6 +309,7 @@ Assistant: [calls list_pull_request_comments_tool with pull_number=5]
|
|
|
256
309
|
Assistant: [calls reply_to_review_comment_tool with comment_id=..., body="fixed in abc1234"]
|
|
257
310
|
Assistant: [calls get_pull_request_checks_tool with pull_number=5]
|
|
258
311
|
Assistant: [calls merge_pull_request_tool with merge_method="squash", sha=<head_sha from the checks>]
|
|
312
|
+
(the tool checks the required checks again itself before merging)
|
|
259
313
|
```
|
|
260
314
|
|
|
261
315
|
### Read a File
|
|
@@ -281,7 +335,9 @@ This server was designed with security as a primary concern. See [SECURITY.md](S
|
|
|
281
335
|
1. **Token Protection**
|
|
282
336
|
- Stored as SecretStr (never accidentally logged)
|
|
283
337
|
- Environment variable only (never in files or args)
|
|
284
|
-
-
|
|
338
|
+
- Optionally, a per-request token from the HTTP `Authorization` header,
|
|
339
|
+
used for that request only and never stored on the shared client
|
|
340
|
+
- Both sanitized from all error messages
|
|
285
341
|
|
|
286
342
|
2. **Input Validation**
|
|
287
343
|
- Pydantic models for write inputs
|
|
@@ -296,6 +352,8 @@ This server was designed with security as a primary concern. See [SECURITY.md](S
|
|
|
296
352
|
- Response size limits (10MB)
|
|
297
353
|
- Job-log downloads follow GitHub's redirect without the token, over
|
|
298
354
|
HTTPS only, and keep just the tail (20MB download limit)
|
|
355
|
+
- Merges only when the required checks are green on the head commit,
|
|
356
|
+
even for a token GitHub would let bypass them
|
|
299
357
|
|
|
300
358
|
4. **Automated CVE Scanning**
|
|
301
359
|
- GitHub Actions scan dependencies
|