Skip to content

Commit 8ce62bc

Browse files
authored
feat: enhance GitHub integration with inline PR comment support (#41)
* feat: Update GitHub integration - Refactor GitHub API interaction module - Add support for new GitHub event type - Update documentation for GitHub integration setup * fix: ci workflow
1 parent c8e4b2f commit 8ce62bc

File tree

6 files changed

+112
-16
lines changed

6 files changed

+112
-16
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ on:
44
branches:
55
- main
66
paths:
7-
- "*.py"
7+
- "src/mcp_github/*.py"
88
- "Dockerfile"
99
- ".github/workflows/ci.yml"
1010
pull_request:
1111
branches:
1212
- main
1313
paths:
14-
- "*.py"
14+
- "src/mcp_github/*.py"
1515
- "Dockerfile"
1616
- ".github/workflows/ci.yml"
1717
workflow_dispatch:
@@ -56,7 +56,7 @@ jobs:
5656
lfs: 'true'
5757
- name: Set Tag Name
5858
run: |
59-
echo "TAG=$(echo ${{ github.head_ref || github.ref_name }})" >> $GITHUB_ENV
59+
echo "TAG=$(echo ${{ github.head_ref || github.ref_name }} | sed 's/[^a-zA-Z0-9.-]/-/g')" >> $GITHUB_ENV
6060
echo "DATE=v$(echo `date +'%Y.%m'`)" >> $GITHUB_ENV
6161
6262
- name: Run Trivy config vulnerability scanner

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ The toolset enables automated PR analysis, issue tracking, tagging and release m
1313
| PR Content Retrieval | `get_github_pr_content` | Fetch PR metadata including title, description, author, and state. |
1414
| PR Diff Analysis | `get_github_pr_diff` | Retrieve the diff/patch content showing file changes in the PR. |
1515
| PR Description Updates | `update_github_pr_description` | Update PR titles and descriptions with What/Why/How sections and file changes. |
16+
| PR General Comments | `add_github_pr_comment` | Add general discussion comments to pull requests. |
17+
| PR Inline Code Comments | `add_github_pr_inline_comment` | Add inline review comments to specific lines in PR files for code review. |
1618
| Issue Creation | `create_github_issue` | Create new issues with conventional commit prefixes (feat/fix/chore) and MCP label. |
1719
| Issue Updates | `update_github_issue` | Modify existing issues with new title, body, and state (open/closed). |
1820
| Tag Management | `create_github_tag` | Create new git tags with associated messages for versioning. |
@@ -80,11 +82,21 @@ cd mcp-github-pr-issue-analyser
8082
```
8183

8284
2. **Install dependencies:**
85+
86+
Launch MCP in `stdio` mode.
8387
```sh
84-
uv init
85-
uv venv
86-
uv pip install -r requirements.txt
88+
export GITHUB_TOKEN="<github-token>"
89+
uvx ./
8790
```
91+
92+
Alternatively, launch MCP in `sse` mode.
93+
```sh
94+
export GITHUB_TOKEN="<github-token>"
95+
export MCP_ENABLE_REMOTE=true
96+
uvx ./
97+
```
98+
> You can access it via `sse` i.e. `http(s)://localhost:8080/sse`
99+
88100
## Local Integration with Desktop LLMs
89101

90102
To add an MCP server to your desktop LLM such as Claude etc.., you need to add this section to the configuration file. The basic structure involves defining a server name and providing the command and any necessary arguments to run the server.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcp-github-pr-issue-analyser"
3-
version = "2.1.0"
3+
version = "2.2.1"
44
description = "MCP GitHub Issues Create/Update and PR Analyse"
55
readme = "README.md"
66
requires-python = ">=3.12"
@@ -27,5 +27,5 @@ testpaths = ["tests"]
2727
python_files = ["test_*.py"]
2828

2929
[build-system]
30-
requires = ["setuptools>=61.0","wheel"]
30+
requires = ["setuptools>=61.0", "wheel"]
3131
build-backend = "setuptools.build_meta"

src/mcp_github/github_integration.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,57 @@ def add_pr_comments(self, repo_owner: str, repo_name: str, pr_number: int, comme
184184
traceback.print_exc()
185185
return None
186186

187+
def add_inline_pr_comment(self, repo_owner: str, repo_name: str, pr_number: int, path: str, line: int, comment_body: str) -> Dict[str, Any]:
188+
"""
189+
Adds an inline review comment to a specific line in a pull request file.
190+
191+
Args:
192+
repo_owner (str): The owner of the repository.
193+
repo_name (str): The name of the repository.
194+
pr_number (int): The pull request number.
195+
path (str): The relative path to the file (e.g., 'src/main.py').
196+
line (int): The line number in the file to comment on.
197+
comment_body (str): The content of the review comment.
198+
199+
Returns:
200+
Dict[str, Any]: The JSON response from the GitHub API containing the comment data if successful.
201+
None: If an error occurs while adding the comment.
202+
203+
Error Handling:
204+
Logs an error message and prints the traceback if the request fails or an exception is raised.
205+
"""
206+
logging.info(f"Adding inline review comment to PR {repo_owner}/{repo_name}#{pr_number} on {path}:{line}")
207+
208+
# Construct the review comments URL
209+
review_comments_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls/{pr_number}/comments"
210+
211+
try:
212+
pr_url = self._get_pr_url(repo_owner, repo_name, pr_number)
213+
pr_response = requests.get(pr_url, headers=self._get_headers())
214+
pr_response.raise_for_status()
215+
pr_data = pr_response.json()
216+
commit_id = pr_data['head']['sha']
217+
218+
payload = {
219+
"body": comment_body,
220+
"commit_id": commit_id,
221+
"path": path,
222+
"line": line,
223+
"side": "RIGHT"
224+
}
225+
226+
response = requests.post(review_comments_url, headers=self._get_headers(), json=payload)
227+
response.raise_for_status()
228+
comment_data = response.json()
229+
230+
logging.info(f"Inline review comment added successfully")
231+
return comment_data
232+
233+
except Exception as e:
234+
logging.error(f"Error adding inline review comment: {str(e)}")
235+
traceback.print_exc()
236+
return None
237+
187238
def update_pr_description(self, repo_owner: str, repo_name: str, pr_number: int, new_title: str, new_description: str) -> Dict[str, Any]:
188239
"""
189240
Updates the title and description of a pull request on GitHub.

src/mcp_github/issues_pr_analyser.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ def __init__(self):
6565
self.ip = IP()
6666

6767
# Initialize MCP Server
68-
self.mcp = FastMCP("GitHub PR Analyse, Issue Create and Update")
68+
self.mcp = FastMCP(
69+
name="GitHub PR Analyse, Issue Create and Update",
70+
instruction="Use the tools to analyze GitHub PRs, create and update issues, and manage tags and releases.",
71+
)
6972
logging.info("MCP Server initialized")
7073

7174
# Register MCP tools
@@ -107,8 +110,8 @@ async def get_github_pr_diff(repo_owner: str, repo_name: str, pr_number: int) ->
107110
@self.mcp.tool()
108111
async def get_github_pr_content(repo_owner: str, repo_name: str, pr_number: int) -> Dict[str, Any]:
109112
"""
110-
First use get_pr_diff to fetch the diff of a specific pull request from a GitHub repository.
111-
Then, if you still need more context use get_pr_content to fetch the content of the pull request.
113+
Use get_pr_diff to fetch the diff of a specific pull request from a GitHub repository.
114+
If you still need more context, then use this to fetch the content of the pull request.
112115
Fetches the content of a GitHub pull request for a given repository and PR number.
113116
Args:
114117
repo_owner (str): The owner of the GitHub repository.
@@ -161,6 +164,36 @@ async def update_github_pr_description(repo_owner: str, repo_name: str, pr_numbe
161164
traceback.print_exc(file=sys.stderr)
162165
return error_msg
163166

167+
@self.mcp.tool()
168+
async def add_github_pr_inline_comment(repo_owner: str, repo_name: str, pr_number: int, path: str, line: int, comment_body: str) -> str:
169+
"""
170+
Adds an inline review comment to a specific line in a GitHub pull request file.
171+
Only comment if there is an issue with the code, otherwise do not comment.
172+
Args:
173+
repo_owner (str): The owner of the repository.
174+
repo_name (str): The name of the repository.
175+
pr_number (int): The pull request number to add the comment to.
176+
path (str): The relative path to the file (e.g., 'src/main.py').
177+
line (int): The line number in the file to comment on.
178+
comment_body (str): The content of the review comment.
179+
180+
Returns:
181+
str: A message indicating the result of the comment addition. Returns a success message if the comment is added successfully, or an error message if an exception occurs.
182+
183+
Error Handling:
184+
Catches and logs any exceptions that occur during the comment addition process. If an error is encountered, the error message is logged and returned.
185+
"""
186+
logging.info(f"Adding inline review comment to PR #{pr_number}")
187+
try:
188+
self.gi.add_inline_pr_comment(repo_owner, repo_name, pr_number, path, line, comment_body)
189+
logging.info(f"Successfully added inline review comment to PR #{pr_number}")
190+
return f"Successfully added inline review comment to PR #{pr_number}"
191+
except Exception as e:
192+
error_msg = f"Error adding inline review comment to PR: {str(e)}"
193+
logging.error(error_msg)
194+
traceback.print_exc(file=sys.stderr)
195+
return error_msg
196+
164197
@self.mcp.tool()
165198
async def add_github_pr_comment(repo_owner: str, repo_name: str, pr_number: int, comment: str) -> str:
166199
"""
@@ -197,7 +230,7 @@ async def create_github_issue(repo_owner: str, repo_name: str, title: str, body:
197230
Args:
198231
repo_owner (str): The owner of the GitHub repository.
199232
repo_name (str): The name of the GitHub repository.
200-
title (str): The title of the issue to be created.
233+
title (str): The title of the issue to be created, one of chore, fix, bug, feat, docs etc.
201234
body (str): The body content of the issue.
202235
labels (list[str]): A list of labels to assign to the issue.
203236
Returns:
@@ -224,7 +257,7 @@ async def update_github_issue(repo_owner: str, repo_name: str, issue_number: int
224257
repo_owner (str): The owner of the GitHub repository.
225258
repo_name (str): The name of the GitHub repository.
226259
issue_number (int): The number of the issue to update.
227-
title (str): The new title for the issue.
260+
title (str): The new title for the issue, one of chore, fix, bug, feat, docs etc.
228261
body (str): The new body content for the issue.
229262
labels (list[str]): A list of labels to assign to the issue.
230263
state (str): The state to set for the issue (e.g., 'open', 'closed').
@@ -340,10 +373,10 @@ def run(self):
340373
Logs any exceptions that occur during server execution and prints the traceback
341374
to standard error for debugging purposes.
342375
"""
343-
MCP_ENABLE_SSE = getenv("ENABLE_SSE", None)
376+
MCP_ENABLE_REMOTE = getenv("MCP_ENABLE_REMOTE", None)
344377
try:
345378
logging.info("Running MCP Server for GitHub PR Analysis.")
346-
if MCP_ENABLE_SSE:
379+
if MCP_ENABLE_REMOTE:
347380
self.mcp.run(transport='sse')
348381
else:
349382
self.mcp.run(transport='stdio')

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)