Skip to content

Commit 2fc8a6d

Browse files
authored
feat(tools): add two new GraphQL tools (#242)
* chore(deps): combine dependabot dependency updates Batch update 5 dependencies from open dependabot PRs (#234-#237, #239): - authlib 1.7.0 -> 1.7.1 (PR #239) - cryptography 47.0.0 -> 48.0.0 (PR #234) - jsonschema-path 0.4.5 -> 0.4.6 (PR #237) - pydantic-settings 2.14.0 -> 2.14.1 (PR #235) - uv 0.11.12 -> 0.11.13 (PR #236) Note: pydantic-core (PR #238) excluded - bumping it independently conflicts with pydantic==2.13.3 which pins pydantic-core==2.46.3 exactly. * feat(tools): add get_pr_linked_issues and get_pr_status_checks Two new GraphQL-backed tools that close genuine agent blind spots: - get_pr_linked_issues: queries closingIssuesReferences to return the authoritative list of issues that auto-close on merge. More reliable than text-parsing "Closes #N" keywords from the PR body, and picks up issues linked via the GitHub UI. - get_pr_status_checks: queries check suites and legacy commit status from the PR HEAD commit. Derives an overall "passing/failing/pending/ unknown" state so agents can make a merge decision without asking the user whether CI is green. Both auto-register as MCP tools via the existing inspect.getmembers() mechanism. Also fix pre-existing ruff formatting issues in auth.py, tests/__init__.py, and tests/test_auth.py. * refactor(tools): raise ToolError on failure, add ToolAnnotations, remove IP tools - Replace all return {"status": "error"} patterns with raise ToolError so agents see failures as errors rather than successful results with error payloads; merge_pr preserves the GitHub API message before raising - Add ToolAnnotations to the registration layer: readOnlyHint=True on all read-only tools so Claude skips confirmation prompts; destructiveHint=True on merge_pr - Remove get_ipv4_info and get_ipv6_info (IPIntegration, ip_integration.py, ip-lookup skill, IPInfoError) - no genuine value in a GitHub-focused MCP - Remove traceback import from github_integration (now unused) * refactor(tools): apply ToolAnnotations directly to methods via decorators Replace the centralised _TOOL_ANNOTATIONS lookup dict in issues_pr_analyser.py with _read_only/_destructive decorator helpers in github_integration.py that stamp a _mcp_annotations attribute directly on each method. * fix(quality): resolve duplicate heading and reduce cyclomatic complexity Rename duplicate '### Features' heading in README to '### Tool Categories'. Extract status-set literals in _derive_overall to module-level frozensets and replace comprehension conditionals with set-difference, reducing cyclomatic complexity from 13 to 7. * refactor(tools): split _derive_overall into focused boolean helpers Replace module-level frozenset constants with _has_failing_checks and _has_pending_checks private methods, restoring inline local sets and reducing _derive_overall cyclomatic complexity from 10 to 6. * feat(tools): add @_write annotations to all write-operation tools Eleven public methods were registered without ToolAnnotations, leaving MCP clients unable to classify them as read/write. Adds a _write() decorator (readOnlyHint=False) and applies it to add_pr_comments, add_inline_pr_comment, update_pr_description, create_pr, create_issue, update_pr_branch, update_issue, update_reviews, update_assignees, create_tag, and create_release. * fix(annotation): add correct hints to mr merge
1 parent 7a95ec6 commit 2fc8a6d

10 files changed

Lines changed: 396 additions & 309 deletions

File tree

README.md

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ The toolset enables automated PR analysis, issue tracking, tagging and release m
1414
|------------------------------------------|---------------------------------------------------------------------------------------------------|
1515
| Analyse GitHub Pull Requests and fetch diffs | Retrieve the diff/patch for any PR in a repository. |
1616
| Fetch content and metadata for specific PRs | Get PR title, description, author, timestamps, and state. |
17+
| Fetch linked issues for a PR | Get the issues that will auto-close when a PR is merged, via GraphQL `closingIssuesReferences`. |
18+
| Fetch CI status checks for a PR | Get check run conclusions and legacy commit status for a PR's HEAD commit. |
1719
| Create Pull Requests | Open new PRs with title, body, head/base branch, and draft option. |
1820
| Update PR title and description | Change the title and body of any PR. |
1921
| Merge Pull Requests | Merge a PR using merge, squash, or rebase method. |
@@ -26,7 +28,6 @@ The toolset enables automated PR analysis, issue tracking, tagging and release m
2628
| Create tags and releases | Tag repository commits and publish releases with changelogs. |
2729
| Search GitHub Users | Retrieve user profile information via GraphQL. |
2830
| Get User Activity | Fetch commit, PR, issue, and review contributions with org/repo/date filtering. |
29-
| Retrieve IPv4 and IPv6 information | Get public IP address details for both IPv4 and IPv6. |
3031

3132
## Requirements
3233

@@ -62,66 +63,66 @@ Two auth modes are supported. The active mode is selected automatically from env
6263
## Architecture Diagram
6364

6465
```ascii
65-
+------------------------+
66-
| |
67-
| MCP Client/User |
68-
| |
69-
+------------------------+
70-
|
71-
| (stdio/http)
72-
v
73-
+------------------------+
74-
| Auth Layer +-->+------------------------+
75-
| (auth.py) | | OAuth Token Store |
76-
| | | MemoryStore (default) |
77-
| stdio : no auth | | RedisStore |
78-
| http : APIKeyVerifier | | (REDIS_HOST_PORT set)|
79-
| oauth : GitHub OAuth2 | | redis:// / rediss:// |
80-
| (DCR + token proxy) | +------------------------+
81-
+------------------------+ |
82-
| v
83-
| +----------+
84-
| | Redis |
85-
| +----------+
86-
v
87-
+--------------------+ +------------------------+
88-
| | | PRIssueAnalyser |
89-
| IP Integration | <------------| (FastMCP Server) |
90-
| (ipinfo.io) | | |
91-
+--------------------+ +------------------------+
92-
|
93-
| (API calls)
94-
v
95-
+------------------------+
96-
| GitHub Integration |
97-
+------------------------+
98-
|
99-
+-------------------+-------------------+
100-
| (REST API) | (GraphQL API)
101-
v v
102-
+-----------------------------------+ +-----------------------------+
103-
| | | |
104-
+-------------+ +--------------+ +------+ | User Search & Activity |
105-
| GitHub PRs | |GitHub Issues | | Tags/| | (contributions, profile) |
106-
| & Reviews | | | |Rels | | |
107-
+-------------+ +--------------+ +------+ +-----------------------------+
66+
+------------------------+
67+
| |
68+
| MCP Client/User |
69+
| |
70+
+------------------------+
71+
|
72+
| (stdio/http)
73+
v
74+
+------------------------+
75+
| Auth Layer +-->+------------------------+
76+
| (auth.py) | | OAuth Token Store |
77+
| | | MemoryStore (default) |
78+
| stdio : no auth | | RedisStore |
79+
| http : APIKeyVerifier | | (REDIS_HOST_PORT set)|
80+
| oauth : GitHub OAuth2 | | redis:// / rediss:// |
81+
| (DCR + token proxy) | +------------------------+
82+
+------------------------+ |
83+
| v
84+
| +----------+
85+
| | Redis |
86+
| +----------+
87+
v
88+
+------------------------+
89+
| PRIssueAnalyser |
90+
| (FastMCP Server) |
91+
+------------------------+
92+
|
93+
| (API calls)
94+
v
95+
+------------------------+
96+
| GitHub Integration |
97+
+------------------------+
98+
|
99+
+-------------------+-------------------+
100+
| (REST API) | (GraphQL API)
101+
v v
102+
+---------------------------------------+ +-----------------------------+
103+
| | | |
104+
| PRs (diff, content, status, linked | | User Search & Activity |
105+
| issues, reviews, comments, merge) | | (contributions, profile) |
106+
| | | |
107+
| Issues (create, update, list, assign) | | PR Linked Issues |
108+
| | | PR Status Checks |
109+
| Tags and Releases | | |
110+
+---------------------------------------+ +-----------------------------+
108111
```
109112

110-
### Features:
113+
### Tool Categories
111114

112-
1. PR Management: Fetch, analyse, create, merge, review, and update
115+
1. PR Management: Fetch diffs, content, linked issues, CI status - create, review, merge, and update
113116
2. Issue Tracking: Create, update, list, and assign
114117
3. Release Management: Tags and releases
115118
4. User Search: Profile lookup and activity tracking via GraphQL
116-
5. Network Info: IPv4/IPv6 details
117119

118-
### Main Flows:
120+
### Main Flows
119121

120-
- PRIssueAnalyser: Main MCP server handling tool registration and requests
122+
- MCP Client: Interacts via stdio or streamable HTTP
121123
- Auth Layer: Selects APIKeyVerifier (static token) or GitHub OAuth2 provider; token state in MemoryStore or RedisStore
122-
- GitHub Integration: Manages all GitHub API interactions (REST + GraphQL)
123-
- IP Integration: Handles IPv4/IPv6 information retrieval
124-
- MCP Client: Interacts via stdio or streamable HTTP (http)
124+
- PRIssueAnalyser: FastMCP server - handles tool registration and request routing
125+
- GitHub Integration: All GitHub API calls (REST v3 + GraphQL v4)
125126

126127
## Local Installation
127128

src/mcp_github/auth.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ def _parse_redis_db(path: str) -> int:
103103
"""Parse the database index from a Redis URI path component."""
104104
db_path = path.lstrip("/")
105105
if db_path and not db_path.isdigit():
106-
raise ValueError(
107-
f"Invalid Redis database in URI: {db_path!r} (must be a non-negative integer)"
108-
)
106+
raise ValueError(f"Invalid Redis database in URI: {db_path!r} (must be a non-negative integer)")
109107
return int(db_path) if db_path else 0
110108

111109

src/mcp_github/exceptions.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,3 @@ def __init__(self, message: str = "Validation failed.", response_body: dict | No
113113
response_body=response_body,
114114
code="VALIDATION_ERROR",
115115
)
116-
117-
118-
class IPInfoError(MCPGitHubError):
119-
"""IP info service error."""
120-
121-
def __init__(self, message: str, url: str | None = None):
122-
"""Initialize IPInfoError."""
123-
super().__init__(message, code="IP_INFO_ERROR")
124-
self.url = url

0 commit comments

Comments
 (0)