mcp-github-crunchtools 1.3.2__tar.gz → 1.5.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.
Files changed (54) hide show
  1. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/CHANGELOG.md +33 -0
  2. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/CLAUDE.md +3 -3
  3. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/Containerfile +1 -1
  4. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/PKG-INFO +27 -13
  5. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/README.md +26 -12
  6. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/pyproject.toml +1 -1
  7. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/server.json +2 -2
  8. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/__init__.py +1 -1
  9. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/errors.py +1 -0
  10. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/server.py +42 -3
  11. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/__init__.py +2 -0
  12. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/issues.py +57 -3
  13. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/pull_requests.py +188 -29
  14. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/tests/test_merge_gate.py +288 -4
  15. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/tests/test_tools.py +102 -3
  16. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/uv.lock +1 -1
  17. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.gatehouse-ignore +0 -0
  18. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  19. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  20. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  21. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/dependabot.yml +0 -0
  22. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/ashigaru.yml +0 -0
  23. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/ci.yml +0 -0
  24. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/constitution.yml +0 -0
  25. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/container.yml +0 -0
  26. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/dependabot-automerge.yml +0 -0
  27. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/gatehouse-retriage.yml +0 -0
  28. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/gatehouse.yml +0 -0
  29. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/publish.yml +0 -0
  30. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.github/workflows/security.yml +0 -0
  31. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.gitignore +0 -0
  32. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.pre-commit-config.yaml +0 -0
  33. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.specify/memory/constitution.md +0 -0
  34. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.specify/specs/000-baseline/spec.md +0 -0
  35. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.specify/specs/001-pr-merge-logs-release/spec.md +0 -0
  36. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.specify/specs/002-request-token-merge-gate/spec.md +0 -0
  37. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.specify/templates/plan-template.md +0 -0
  38. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/.specify/templates/spec-template.md +0 -0
  39. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/LICENSE +0 -0
  40. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/SECURITY.md +0 -0
  41. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/gourmand-exceptions.toml +0 -0
  42. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/gourmand.toml +0 -0
  43. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/__main__.py +0 -0
  44. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/client.py +0 -0
  45. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/config.py +0 -0
  46. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/models.py +0 -0
  47. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/actions.py +0 -0
  48. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/files.py +0 -0
  49. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/releases.py +0 -0
  50. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/src/mcp_github_crunchtools/tools/search.py +0 -0
  51. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/tests/__init__.py +0 -0
  52. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/tests/conftest.py +0 -0
  53. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/tests/test_request_token.py +0 -0
  54. {mcp_github_crunchtools-1.3.2 → mcp_github_crunchtools-1.5.0}/tests/test_validation.py +0 -0
@@ -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.5.0] - 2026-10-20
10
+
11
+ ### Added
12
+
13
+ - `merge_pull_request_tool` and `get_pull_request_checks_tool` work with a
14
+ fine-grained token. GitHub cannot grant one the Checks permission, so
15
+ check-runs answers 403; the tools then read the head commit's Actions jobs
16
+ (Actions: read), each of which is a check run with the same id and name.
17
+ Only Actions-reported checks are visible that way, so the merge gate
18
+ refuses a required check that names another app, or that accepts any app
19
+ and has no commit status. Both results carry `checks_source`.
20
+
21
+ ### Fixed
22
+
23
+ - `get_pull_request_checks_tool` reads every page of check runs, not just the
24
+ first, up to the merge gate's page limit; past it the verdict says
25
+ `truncated` and is not `ready_to_merge`.
26
+
27
+ ## [1.4.0] - 2026-10-17
28
+
29
+ ### Added
30
+
31
+ - `list_issue_comments_tool`: the comments on an issue or a pull request
32
+ conversation, oldest first, one page at a time, with id, author,
33
+ author_association, created_at, updated_at, body and html_url (#45).
34
+
35
+ ### Fixed
36
+
37
+ - `list_pull_request_comments_tool` called on a plain issue returned GitHub's
38
+ 403 "Resource not accessible by personal access token", which reads like a
39
+ token scope problem. It now says the number is an issue and points to
40
+ `list_issue_comments_tool` (#45).
41
+
9
42
  ## [1.3.2] - 2026-10-17
10
43
 
11
44
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  # Claude Code Instructions
2
2
 
3
- Secure MCP server for the GitHub REST API with 25 tools across issues, pull requests, Actions, releases, files, and search. Works with github.com and GitHub Enterprise Server.
3
+ Secure MCP server for the GitHub REST API with 26 tools across issues, pull requests, Actions, releases, files, and search. Works with github.com and GitHub Enterprise Server.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -51,11 +51,11 @@ claude mcp add mcp-github-crunchtools \
51
51
  | `SSL_CERT_FILE` | No | — | Custom CA bundle for self-hosted instances |
52
52
  | `GITHUB_SSL_VERIFY` | No | `true` | Set `false` to disable SSL verification |
53
53
 
54
- ## Available Tools (25)
54
+ ## Available Tools (26)
55
55
 
56
56
  | Category | Tools |
57
57
  |----------|-------|
58
- | Issues | `list_issues_tool`, `get_issue_tool`, `create_issue_tool`, `create_issue_comment_tool`, `update_issue_tool` |
58
+ | Issues | `list_issues_tool`, `get_issue_tool`, `list_issue_comments_tool`, `create_issue_tool`, `create_issue_comment_tool`, `update_issue_tool` |
59
59
  | 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`, `merge_pull_request_tool` |
60
60
  | Files | `get_file_content_tool`, `list_repo_tree_tool` |
61
61
  | Search | `search_code_tool`, `search_issues_tool` |
@@ -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.3.2" \
30
+ version="1.5.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.2
3
+ Version: 1.5.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
@@ -60,7 +60,7 @@ The container image is built on the [Hummingbird Python base image](https://quay
60
60
 
61
61
  ## Features
62
62
 
63
- 25 tools. Tools marked *write* change something on GitHub; the others only
63
+ 26 tools. Tools marked *write* change something on GitHub; the others only
64
64
  read and publish `readOnlyHint`. The last column is the fine-grained token
65
65
  permission the tool needs (see [Token permissions](#token-permissions)).
66
66
 
@@ -70,6 +70,7 @@ permission the tool needs (see [Token permissions](#token-permissions)).
70
70
  |------|--------------|------------|
71
71
  | `list_issues_tool` | List issues for a repository (pull requests excluded) | Issues: read |
72
72
  | `get_issue_tool` | Get a single issue by number | Issues: read |
73
+ | `list_issue_comments_tool` | Comments on an issue or a pull request conversation, oldest first | Issues: read, or Pull requests: read for a PR |
73
74
  | `create_issue_tool` | Open an issue (*write*) | Issues: write |
74
75
  | `create_issue_comment_tool` | Comment on an issue or pull request (*write*) | Issues: write, or Pull requests: write for a PR |
75
76
  | `update_issue_tool` | Edit, close or reopen an issue (*write*) | Issues: write |
@@ -81,12 +82,12 @@ permission the tool needs (see [Token permissions](#token-permissions)).
81
82
  | `list_pull_requests_tool` | List pull requests for a repository | Pull requests: read |
82
83
  | `get_pull_request_tool` | Get a single pull request by number | Pull requests: read |
83
84
  | `get_pull_request_diff_tool` | Get the unified diff for a pull request | Pull requests: read |
84
- | `get_pull_request_checks_tool` | CI verdict from check-runs and commit statuses; skipped is not failed | Pull requests: read, Commit statuses: read (and see the note under Token permissions) |
85
+ | `get_pull_request_checks_tool` | CI verdict from check-runs and commit statuses; skipped is not failed | Pull requests: read, Commit statuses: read, Actions: read (and see the note under Token permissions) |
85
86
  | `list_pull_request_comments_tool` | Conversation comments, diff comments and reviews in one list, each tagged with its kind | Pull requests: read |
86
87
  | `create_pull_request_tool` | Open a pull request, optionally as a draft (*write*) | Pull requests: write |
87
88
  | `update_pull_request_tool` | Edit, close or reopen a pull request; does not merge (*write*) | Pull requests: write |
88
89
  | `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, 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
+ | `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, Commit statuses and Actions: read |
90
91
 
91
92
  ### Actions (6 tools)
92
93
 
@@ -123,7 +124,10 @@ permission the tool needs (see [Token permissions](#token-permissions)).
123
124
 
124
125
  - `list_pull_request_comments_tool` reads each of its three kinds oldest
125
126
  first, up to 300 items per kind. `truncated` and `truncated_kinds` say when
126
- newer items were left out.
127
+ newer items were left out. Called on a plain issue it says so; use
128
+ `list_issue_comments_tool` there.
129
+ - `list_issue_comments_tool` returns one page (up to 100 comments); follow
130
+ `pagination.next_page` for more.
127
131
  - `get_workflow_run_tool` lists up to 300 jobs; `jobs_truncated` says when the
128
132
  run has more.
129
133
  - `get_failed_job_logs_tool` returns at most 1000 lines per job (200 by
@@ -218,16 +222,25 @@ For a fine-grained personal access token, the repository permissions are:
218
222
  |------------|--------------------|---------------------|
219
223
  | Metadata | every tool (GitHub grants it with any other permission) | — |
220
224
  | Contents | `get_file_content_tool`, `list_repo_tree_tool` | `merge_pull_request_tool`, `create_release_tool` |
221
- | Issues | `list_issues_tool`, `get_issue_tool` | `create_issue_tool`, `update_issue_tool`, `create_issue_comment_tool` |
225
+ | Issues | `list_issues_tool`, `get_issue_tool`, `list_issue_comments_tool` | `create_issue_tool`, `update_issue_tool`, `create_issue_comment_tool` |
222
226
  | 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 |
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` |
227
+ | Actions | `list_workflow_runs_tool`, `get_workflow_run_tool`, `get_failed_job_logs_tool`, `get_pull_request_checks_tool`, `merge_pull_request_tool` | `trigger_workflow_tool`, `rerun_workflow_run_tool`, `rerun_failed_jobs_tool` |
224
228
  | Commit statuses | `get_pull_request_checks_tool`, `merge_pull_request_tool` | — |
225
229
 
226
- `get_pull_request_checks_tool` and `merge_pull_request_tool` read check-runs, and GitHub's permission table
227
- for fine-grained tokens lists no permission for that endpoint. It is readable
228
- on public repositories; on a private repository use a classic token or a
229
- GitHub App token. The search tools likewise have no permission of their own:
230
- they return what the token can already read.
230
+ `get_pull_request_checks_tool` and `merge_pull_request_tool` read check-runs,
231
+ and GitHub's permission table for fine-grained tokens has no permission for
232
+ that endpoint: a fine-grained token gets 403 on a private repository. Both
233
+ tools then read the head commit's GitHub Actions jobs instead (Actions: read).
234
+ Every Actions job is a check run with the same id and name, so the verdict
235
+ and the merge gate are the same for checks GitHub Actions reports. Checks from
236
+ any other app are not visible that way, so the merge gate refuses a required
237
+ check that names another app, or that accepts any app and has no commit
238
+ status, as missing. `checks_source` in each result says which was read, and
239
+ the checks verdict then carries a `note` saying other apps' checks are not
240
+ visible. Only the newest run of each workflow and event is read, and a commit
241
+ with runs of more than 30 workflows is not judged.
242
+
243
+ The search tools have no permission of their own: they return what the token can already read.
231
244
 
232
245
  Three things worth knowing before granting write:
233
246
 
@@ -262,7 +275,8 @@ merges it:
262
275
  4. requires, for every required check on the head commit, a completed check
263
276
  run of that name concluding `success`, `skipped` or `neutral` (the most
264
277
  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
278
+ that names an app accepts only that app's check runs. A token that cannot
279
+ read check-runs is judged on the Actions jobs (see above). A missing, pending
266
280
  or failed check refuses the merge, and the error lists each one and why;
267
281
  5. reads the pull request again and refuses if its head or base changed
268
282
  meanwhile (GitHub's merge call pins the head but not the base), then
@@ -34,7 +34,7 @@ The container image is built on the [Hummingbird Python base image](https://quay
34
34
 
35
35
  ## Features
36
36
 
37
- 25 tools. Tools marked *write* change something on GitHub; the others only
37
+ 26 tools. Tools marked *write* change something on GitHub; the others only
38
38
  read and publish `readOnlyHint`. The last column is the fine-grained token
39
39
  permission the tool needs (see [Token permissions](#token-permissions)).
40
40
 
@@ -44,6 +44,7 @@ permission the tool needs (see [Token permissions](#token-permissions)).
44
44
  |------|--------------|------------|
45
45
  | `list_issues_tool` | List issues for a repository (pull requests excluded) | Issues: read |
46
46
  | `get_issue_tool` | Get a single issue by number | Issues: read |
47
+ | `list_issue_comments_tool` | Comments on an issue or a pull request conversation, oldest first | Issues: read, or Pull requests: read for a PR |
47
48
  | `create_issue_tool` | Open an issue (*write*) | Issues: write |
48
49
  | `create_issue_comment_tool` | Comment on an issue or pull request (*write*) | Issues: write, or Pull requests: write for a PR |
49
50
  | `update_issue_tool` | Edit, close or reopen an issue (*write*) | Issues: write |
@@ -55,12 +56,12 @@ permission the tool needs (see [Token permissions](#token-permissions)).
55
56
  | `list_pull_requests_tool` | List pull requests for a repository | Pull requests: read |
56
57
  | `get_pull_request_tool` | Get a single pull request by number | Pull requests: read |
57
58
  | `get_pull_request_diff_tool` | Get the unified diff for a pull request | Pull requests: read |
58
- | `get_pull_request_checks_tool` | CI verdict from check-runs and commit statuses; skipped is not failed | Pull requests: read, Commit statuses: read (and see the note under Token permissions) |
59
+ | `get_pull_request_checks_tool` | CI verdict from check-runs and commit statuses; skipped is not failed | Pull requests: read, Commit statuses: read, Actions: read (and see the note under Token permissions) |
59
60
  | `list_pull_request_comments_tool` | Conversation comments, diff comments and reviews in one list, each tagged with its kind | Pull requests: read |
60
61
  | `create_pull_request_tool` | Open a pull request, optionally as a draft (*write*) | Pull requests: write |
61
62
  | `update_pull_request_tool` | Edit, close or reopen a pull request; does not merge (*write*) | Pull requests: write |
62
63
  | `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, 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
+ | `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, Commit statuses and Actions: read |
64
65
 
65
66
  ### Actions (6 tools)
66
67
 
@@ -97,7 +98,10 @@ permission the tool needs (see [Token permissions](#token-permissions)).
97
98
 
98
99
  - `list_pull_request_comments_tool` reads each of its three kinds oldest
99
100
  first, up to 300 items per kind. `truncated` and `truncated_kinds` say when
100
- newer items were left out.
101
+ newer items were left out. Called on a plain issue it says so; use
102
+ `list_issue_comments_tool` there.
103
+ - `list_issue_comments_tool` returns one page (up to 100 comments); follow
104
+ `pagination.next_page` for more.
101
105
  - `get_workflow_run_tool` lists up to 300 jobs; `jobs_truncated` says when the
102
106
  run has more.
103
107
  - `get_failed_job_logs_tool` returns at most 1000 lines per job (200 by
@@ -192,16 +196,25 @@ For a fine-grained personal access token, the repository permissions are:
192
196
  |------------|--------------------|---------------------|
193
197
  | Metadata | every tool (GitHub grants it with any other permission) | — |
194
198
  | Contents | `get_file_content_tool`, `list_repo_tree_tool` | `merge_pull_request_tool`, `create_release_tool` |
195
- | Issues | `list_issues_tool`, `get_issue_tool` | `create_issue_tool`, `update_issue_tool`, `create_issue_comment_tool` |
199
+ | Issues | `list_issues_tool`, `get_issue_tool`, `list_issue_comments_tool` | `create_issue_tool`, `update_issue_tool`, `create_issue_comment_tool` |
196
200
  | 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 |
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` |
201
+ | Actions | `list_workflow_runs_tool`, `get_workflow_run_tool`, `get_failed_job_logs_tool`, `get_pull_request_checks_tool`, `merge_pull_request_tool` | `trigger_workflow_tool`, `rerun_workflow_run_tool`, `rerun_failed_jobs_tool` |
198
202
  | Commit statuses | `get_pull_request_checks_tool`, `merge_pull_request_tool` | — |
199
203
 
200
- `get_pull_request_checks_tool` and `merge_pull_request_tool` read check-runs, and GitHub's permission table
201
- for fine-grained tokens lists no permission for that endpoint. It is readable
202
- on public repositories; on a private repository use a classic token or a
203
- GitHub App token. The search tools likewise have no permission of their own:
204
- they return what the token can already read.
204
+ `get_pull_request_checks_tool` and `merge_pull_request_tool` read check-runs,
205
+ and GitHub's permission table for fine-grained tokens has no permission for
206
+ that endpoint: a fine-grained token gets 403 on a private repository. Both
207
+ tools then read the head commit's GitHub Actions jobs instead (Actions: read).
208
+ Every Actions job is a check run with the same id and name, so the verdict
209
+ and the merge gate are the same for checks GitHub Actions reports. Checks from
210
+ any other app are not visible that way, so the merge gate refuses a required
211
+ check that names another app, or that accepts any app and has no commit
212
+ status, as missing. `checks_source` in each result says which was read, and
213
+ the checks verdict then carries a `note` saying other apps' checks are not
214
+ visible. Only the newest run of each workflow and event is read, and a commit
215
+ with runs of more than 30 workflows is not judged.
216
+
217
+ The search tools have no permission of their own: they return what the token can already read.
205
218
 
206
219
  Three things worth knowing before granting write:
207
220
 
@@ -236,7 +249,8 @@ merges it:
236
249
  4. requires, for every required check on the head commit, a completed check
237
250
  run of that name concluding `success`, `skipped` or `neutral` (the most
238
251
  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
252
+ that names an app accepts only that app's check runs. A token that cannot
253
+ read check-runs is judged on the Actions jobs (see above). A missing, pending
240
254
  or failed check refuses the merge, and the error lists each one and why;
241
255
  5. reads the pull request again and refuses if its head or base changed
242
256
  meanwhile (GitHub's merge call pins the head but not the base), then
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-github-crunchtools"
3
- version = "1.3.2"
3
+ version = "1.5.0"
4
4
  description = "Secure MCP server for GitHub issues, pull requests, Actions, releases, files, and search"
5
5
  requires-python = ">=3.10"
6
6
  license = "AGPL-3.0-or-later"
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/crunchtools/mcp-github",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.3.0",
10
+ "version": "1.5.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "mcp-github-crunchtools",
15
- "version": "1.3.0",
15
+ "version": "1.5.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
@@ -29,7 +29,7 @@ import argparse
29
29
 
30
30
  from .server import mcp
31
31
 
32
- __version__ = "1.3.2"
32
+ __version__ = "1.5.0"
33
33
  __all__ = ["main", "mcp"]
34
34
 
35
35
 
@@ -44,6 +44,7 @@ class GitHubApiError(UserError):
44
44
 
45
45
  def __init__(self, code: int, message: str) -> None:
46
46
  super().__init__(f"GitHub API error {code}: {scrub_tokens(message)}")
47
+ self.status_code = code
47
48
 
48
49
 
49
50
  class NotFoundError(UserError):
@@ -21,6 +21,7 @@ from .tools import (
21
21
  get_pull_request_checks,
22
22
  get_pull_request_diff,
23
23
  get_workflow_run,
24
+ list_issue_comments,
24
25
  list_issues,
25
26
  list_pull_request_comments,
26
27
  list_pull_requests,
@@ -45,7 +46,7 @@ READ_ONLY = {"readOnlyHint": True}
45
46
 
46
47
  mcp = FastMCP(
47
48
  name="mcp-github",
48
- version="1.3.2",
49
+ version="1.5.0",
49
50
  instructions=(
50
51
  "Secure MCP server for GitHub repositories: issues, pull requests "
51
52
  "(open, review discussion, diffs, CI checks, merge), Actions runs and "
@@ -106,6 +107,40 @@ async def get_issue_tool(
106
107
  return await get_issue(owner=owner, repo=repo, issue_number=issue_number)
107
108
 
108
109
 
110
+ @mcp.tool(annotations=READ_ONLY)
111
+ async def list_issue_comments_tool(
112
+ repo: str,
113
+ issue_number: Annotated[int, Field(ge=1)],
114
+ owner: str | None = None,
115
+ per_page: int = 30,
116
+ page: int = 1,
117
+ ) -> dict[str, Any]:
118
+ """List the comments on a GitHub issue or pull request conversation, oldest first.
119
+
120
+ Works for plain issues, which list_pull_request_comments_tool does not.
121
+ On a pull request it returns only the conversation comments; diff
122
+ comments and reviews come from list_pull_request_comments_tool.
123
+
124
+ Args:
125
+ repo: Repository name
126
+ issue_number: Issue or pull request number
127
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
128
+ per_page: Results per page, max 100 (default: 30)
129
+ page: Page number (default: 1)
130
+
131
+ Returns:
132
+ {"issue_number", "items", "pagination"?}; each item carries id,
133
+ author, author_association, created_at, updated_at, body, html_url
134
+ """
135
+ return await list_issue_comments(
136
+ owner=owner,
137
+ repo=repo,
138
+ issue_number=issue_number,
139
+ per_page=per_page,
140
+ page=page,
141
+ )
142
+
143
+
109
144
  @mcp.tool()
110
145
  async def create_issue_tool(
111
146
  repo: str,
@@ -270,7 +305,9 @@ async def get_pull_request_checks_tool(
270
305
  failing, pending, or skipped. SKIPPED checks are NOT failures. Use the
271
306
  returned ``ready_to_merge`` boolean as the signal for whether the PR is
272
307
  clear to merge (True only when nothing is failing or pending and the PR
273
- is not known-unmergeable).
308
+ is not known-unmergeable). A token that cannot read check-runs (any
309
+ fine-grained token) gets the Actions jobs instead; ``checks_source`` says
310
+ which, and only Actions-reported checks are then visible.
274
311
 
275
312
  Args:
276
313
  repo: Repository name
@@ -372,7 +409,9 @@ async def merge_pull_request_tool(
372
409
  head; when branch protection cannot be read; and when the base branch
373
410
  requires no checks at all, unless ``allow_no_required_checks`` is True.
374
411
  Passing check runs conclude success, skipped or neutral. The merge is
375
- pinned to the head SHA that was checked.
412
+ pinned to the head SHA that was checked. A token that cannot read
413
+ check-runs (any fine-grained token) is judged on the Actions jobs, and a
414
+ required check not reported by GitHub Actions is refused as missing.
376
415
 
377
416
  Args:
378
417
  repo: Repository name
@@ -16,6 +16,7 @@ from .issues import (
16
16
  create_issue,
17
17
  create_issue_comment,
18
18
  get_issue,
19
+ list_issue_comments,
19
20
  list_issues,
20
21
  update_issue,
21
22
  )
@@ -45,6 +46,7 @@ __all__ = [
45
46
  "get_pull_request_checks",
46
47
  "get_pull_request_diff",
47
48
  "get_workflow_run",
49
+ "list_issue_comments",
48
50
  "list_issues",
49
51
  "list_pull_request_comments",
50
52
  "list_pull_requests",
@@ -88,6 +88,62 @@ async def get_issue(
88
88
  return await client.get(f"/repos/{owner}/{repo}/issues/{issue_number}")
89
89
 
90
90
 
91
+ async def list_issue_comments(
92
+ owner: str | None,
93
+ repo: str,
94
+ issue_number: int,
95
+ per_page: int = 30,
96
+ page: int = 1,
97
+ ) -> dict[str, Any]:
98
+ """List the comments on an issue or a pull request conversation, oldest first.
99
+
100
+ Diff comments and reviews on a pull request are not included; use
101
+ list_pull_request_comments for those.
102
+
103
+ Args:
104
+ owner: Repository owner (defaults to GITHUB_DEFAULT_ORG if unset)
105
+ repo: Repository name
106
+ issue_number: Issue or pull request number
107
+ per_page: Results per page (max 100)
108
+ page: Page number
109
+
110
+ Returns:
111
+ {"issue_number", "items", "pagination"?}; each item carries id,
112
+ author, author_association, created_at, updated_at, body, html_url
113
+ """
114
+ owner = resolve_owner(owner)
115
+ repo = validate_name(repo, "repo")
116
+ issue_number = validate_positive_int(issue_number, "issue_number")
117
+
118
+ client = get_client()
119
+ result = await client.get(
120
+ f"/repos/{owner}/{repo}/issues/{issue_number}/comments",
121
+ params={
122
+ "per_page": clamp_per_page(per_page),
123
+ "page": validate_positive_int(page, "page"),
124
+ },
125
+ )
126
+
127
+ response: dict[str, Any] = {
128
+ "issue_number": issue_number,
129
+ "items": [
130
+ {
131
+ "id": raw.get("id"),
132
+ "author": (raw.get("user") or {}).get("login"),
133
+ "author_association": raw.get("author_association"),
134
+ "created_at": raw.get("created_at"),
135
+ "updated_at": raw.get("updated_at"),
136
+ "body": raw.get("body") or "",
137
+ "html_url": raw.get("html_url"),
138
+ }
139
+ for raw in result.get("items", [])
140
+ ],
141
+ }
142
+ if "pagination" in result:
143
+ response["pagination"] = result["pagination"]
144
+ return response
145
+
146
+
91
147
  async def create_issue(
92
148
  owner: str | None,
93
149
  repo: str,
@@ -169,9 +225,7 @@ async def update_issue(
169
225
  json_data["state"] = state
170
226
  if state_reason is not None:
171
227
  if state_reason not in ("completed", "not_planned", "reopened"):
172
- raise ValidationError(
173
- "state_reason must be 'completed', 'not_planned', or 'reopened'"
174
- )
228
+ raise ValidationError("state_reason must be 'completed', 'not_planned', or 'reopened'")
175
229
  json_data["state_reason"] = state_reason
176
230
  if title is not None:
177
231
  json_data["title"] = title