github-mcp-connector 0.2.0__tar.gz → 0.4.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.
- {github_mcp_connector-0.2.0/src/github_mcp_connector.egg-info → github_mcp_connector-0.4.0}/PKG-INFO +5 -2
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/README.md +4 -1
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/pyproject.toml +1 -1
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp/client.py +4 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp/server.py +87 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0/src/github_mcp_connector.egg-info}/PKG-INFO +5 -2
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/tests/test_server.py +115 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/LICENSE +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/setup.cfg +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp/__init__.py +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp/__main__.py +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp/config.py +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp_connector.egg-info/SOURCES.txt +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp_connector.egg-info/dependency_links.txt +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp_connector.egg-info/entry_points.txt +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp_connector.egg-info/requires.txt +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/src/github_mcp_connector.egg-info/top_level.txt +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/tests/test_client.py +0 -0
- {github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0}/tests/test_config.py +0 -0
{github_mcp_connector-0.2.0/src/github_mcp_connector.egg-info → github_mcp_connector-0.4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-mcp-connector
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A Model Context Protocol (MCP) connector for GitHub, for use with Claude.
|
|
5
5
|
Author: winnerlose2026
|
|
6
6
|
License: MIT
|
|
@@ -47,7 +47,7 @@ Enterprise Server.
|
|
|
47
47
|
- 📦 **Repositories** — metadata, branches, file contents, directory listings, your repo list
|
|
48
48
|
- 🧾 **Commits** — history, single-commit detail, and diffs between refs
|
|
49
49
|
- 🐛 **Issues** — list, read, create, comment, and edit/close
|
|
50
|
-
- 🔀 **Pull requests** — list, read, fetch diffs and
|
|
50
|
+
- 🔀 **Pull requests** — list, read, fetch diffs/changed files, open new PRs, and merge
|
|
51
51
|
- ⚙️ **Actions & releases** — list workflow runs and releases
|
|
52
52
|
- ✍️ **Repo writes** — create branches and commit files (gated by read-only mode)
|
|
53
53
|
- 🔒 **Read-only mode** — flip one env var to disable every write tool
|
|
@@ -76,10 +76,13 @@ Enterprise Server.
|
|
|
76
76
|
| `list_releases` | Releases for a repository | |
|
|
77
77
|
| `search_issues` | Search issues and PRs across GitHub | |
|
|
78
78
|
| `search_code` | Search code across GitHub | |
|
|
79
|
+
| `create_pull_request` | Open a new pull request (supports draft) | ✅ |
|
|
80
|
+
| `merge_pull_request` | Merge a PR (merge/squash/rebase) | ✅ |
|
|
79
81
|
| `create_issue` | Open a new issue | ✅ |
|
|
80
82
|
| `update_issue` | Edit/close/reopen an issue | ✅ |
|
|
81
83
|
| `add_issue_comment` | Comment on an issue or PR | ✅ |
|
|
82
84
|
| `create_branch` | Create a branch from a ref | ✅ |
|
|
85
|
+
| `delete_branch` | Delete a branch | ✅ |
|
|
83
86
|
| `create_or_update_file` | Commit a file (create or update) | ✅ |
|
|
84
87
|
|
|
85
88
|
Tools marked **Write** are disabled when `GITHUB_MCP_READ_ONLY` is set.
|
|
@@ -17,7 +17,7 @@ Enterprise Server.
|
|
|
17
17
|
- 📦 **Repositories** — metadata, branches, file contents, directory listings, your repo list
|
|
18
18
|
- 🧾 **Commits** — history, single-commit detail, and diffs between refs
|
|
19
19
|
- 🐛 **Issues** — list, read, create, comment, and edit/close
|
|
20
|
-
- 🔀 **Pull requests** — list, read, fetch diffs and
|
|
20
|
+
- 🔀 **Pull requests** — list, read, fetch diffs/changed files, open new PRs, and merge
|
|
21
21
|
- ⚙️ **Actions & releases** — list workflow runs and releases
|
|
22
22
|
- ✍️ **Repo writes** — create branches and commit files (gated by read-only mode)
|
|
23
23
|
- 🔒 **Read-only mode** — flip one env var to disable every write tool
|
|
@@ -46,10 +46,13 @@ Enterprise Server.
|
|
|
46
46
|
| `list_releases` | Releases for a repository | |
|
|
47
47
|
| `search_issues` | Search issues and PRs across GitHub | |
|
|
48
48
|
| `search_code` | Search code across GitHub | |
|
|
49
|
+
| `create_pull_request` | Open a new pull request (supports draft) | ✅ |
|
|
50
|
+
| `merge_pull_request` | Merge a PR (merge/squash/rebase) | ✅ |
|
|
49
51
|
| `create_issue` | Open a new issue | ✅ |
|
|
50
52
|
| `update_issue` | Edit/close/reopen an issue | ✅ |
|
|
51
53
|
| `add_issue_comment` | Comment on an issue or PR | ✅ |
|
|
52
54
|
| `create_branch` | Create a branch from a ref | ✅ |
|
|
55
|
+
| `delete_branch` | Delete a branch | ✅ |
|
|
53
56
|
| `create_or_update_file` | Commit a file (create or update) | ✅ |
|
|
54
57
|
|
|
55
58
|
Tools marked **Write** are disabled when `GITHUB_MCP_READ_ONLY` is set.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "github-mcp-connector"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "A Model Context Protocol (MCP) connector for GitHub, for use with Claude."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -136,6 +136,10 @@ class GitHubClient:
|
|
|
136
136
|
return None
|
|
137
137
|
return response.json()
|
|
138
138
|
|
|
139
|
+
async def delete(self, path: str) -> None:
|
|
140
|
+
# GitHub returns 204 No Content on a successful delete.
|
|
141
|
+
await self._request("DELETE", path)
|
|
142
|
+
|
|
139
143
|
|
|
140
144
|
def _extract_error_detail(response: httpx.Response) -> str:
|
|
141
145
|
"""Pull the most useful human-readable error message out of a response."""
|
|
@@ -695,6 +695,22 @@ async def create_branch(
|
|
|
695
695
|
}
|
|
696
696
|
|
|
697
697
|
|
|
698
|
+
@mcp.tool()
|
|
699
|
+
async def delete_branch(owner: str, repo: str, branch: str) -> dict[str, Any]:
|
|
700
|
+
"""Delete a branch from a repository.
|
|
701
|
+
|
|
702
|
+
`branch` is the branch name (e.g. `feature-x`, not `refs/heads/feature-x`).
|
|
703
|
+
This permanently removes the ref; it cannot delete the repository's default
|
|
704
|
+
branch (GitHub rejects that). Disabled in read-only mode; requires a token
|
|
705
|
+
with write access.
|
|
706
|
+
"""
|
|
707
|
+
_require_token()
|
|
708
|
+
_require_write()
|
|
709
|
+
async with GitHubClient(config) as gh:
|
|
710
|
+
await gh.delete(f"/repos/{owner}/{repo}/git/refs/heads/{branch}")
|
|
711
|
+
return {"deleted": True, "branch": branch}
|
|
712
|
+
|
|
713
|
+
|
|
698
714
|
@mcp.tool()
|
|
699
715
|
async def create_or_update_file(
|
|
700
716
|
owner: str,
|
|
@@ -748,6 +764,77 @@ async def create_or_update_file(
|
|
|
748
764
|
}
|
|
749
765
|
|
|
750
766
|
|
|
767
|
+
@mcp.tool()
|
|
768
|
+
async def create_pull_request(
|
|
769
|
+
owner: str,
|
|
770
|
+
repo: str,
|
|
771
|
+
title: str,
|
|
772
|
+
head: str,
|
|
773
|
+
base: str,
|
|
774
|
+
body: str | None = None,
|
|
775
|
+
draft: bool = False,
|
|
776
|
+
maintainer_can_modify: bool = True,
|
|
777
|
+
) -> dict[str, Any]:
|
|
778
|
+
"""Open a new pull request.
|
|
779
|
+
|
|
780
|
+
`head` is the branch with your changes (for a cross-fork PR use
|
|
781
|
+
`owner:branch`); `base` is the branch you want to merge into (e.g. `main`).
|
|
782
|
+
Set `draft=True` to open it as a draft. Disabled in read-only mode; requires
|
|
783
|
+
a token with write access to the repository.
|
|
784
|
+
"""
|
|
785
|
+
_require_token()
|
|
786
|
+
_require_write()
|
|
787
|
+
payload: dict[str, Any] = {
|
|
788
|
+
"title": title,
|
|
789
|
+
"head": head,
|
|
790
|
+
"base": base,
|
|
791
|
+
"draft": draft,
|
|
792
|
+
"maintainer_can_modify": maintainer_can_modify,
|
|
793
|
+
}
|
|
794
|
+
if body is not None:
|
|
795
|
+
payload["body"] = body
|
|
796
|
+
async with GitHubClient(config) as gh:
|
|
797
|
+
pull = await gh.post(f"/repos/{owner}/{repo}/pulls", json=payload)
|
|
798
|
+
summary = _summarize_pull(pull)
|
|
799
|
+
summary["body"] = pull.get("body")
|
|
800
|
+
return summary
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
@mcp.tool()
|
|
804
|
+
async def merge_pull_request(
|
|
805
|
+
owner: str,
|
|
806
|
+
repo: str,
|
|
807
|
+
pull_number: int,
|
|
808
|
+
merge_method: str = "merge",
|
|
809
|
+
commit_title: str | None = None,
|
|
810
|
+
commit_message: str | None = None,
|
|
811
|
+
) -> dict[str, Any]:
|
|
812
|
+
"""Merge a pull request.
|
|
813
|
+
|
|
814
|
+
`merge_method` is one of `merge` (merge commit), `squash`, or `rebase`.
|
|
815
|
+
`commit_title`/`commit_message` optionally override the merge commit text
|
|
816
|
+
(ignored for `rebase`). Fails if the PR isn't mergeable (conflicts, failing
|
|
817
|
+
required checks, or branch protection). Disabled in read-only mode; requires
|
|
818
|
+
a token with write access.
|
|
819
|
+
"""
|
|
820
|
+
_require_token()
|
|
821
|
+
_require_write()
|
|
822
|
+
payload: dict[str, Any] = {"merge_method": merge_method}
|
|
823
|
+
if commit_title is not None:
|
|
824
|
+
payload["commit_title"] = commit_title
|
|
825
|
+
if commit_message is not None:
|
|
826
|
+
payload["commit_message"] = commit_message
|
|
827
|
+
async with GitHubClient(config) as gh:
|
|
828
|
+
result = await gh.put(
|
|
829
|
+
f"/repos/{owner}/{repo}/pulls/{pull_number}/merge", json=payload
|
|
830
|
+
)
|
|
831
|
+
return {
|
|
832
|
+
"merged": result.get("merged"),
|
|
833
|
+
"sha": result.get("sha"),
|
|
834
|
+
"message": result.get("message"),
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
|
|
751
838
|
def main(argv: list[str] | None = None) -> None:
|
|
752
839
|
"""Console entry point. Selects the transport from CLI args/env."""
|
|
753
840
|
import argparse
|
{github_mcp_connector-0.2.0 → github_mcp_connector-0.4.0/src/github_mcp_connector.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-mcp-connector
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A Model Context Protocol (MCP) connector for GitHub, for use with Claude.
|
|
5
5
|
Author: winnerlose2026
|
|
6
6
|
License: MIT
|
|
@@ -47,7 +47,7 @@ Enterprise Server.
|
|
|
47
47
|
- 📦 **Repositories** — metadata, branches, file contents, directory listings, your repo list
|
|
48
48
|
- 🧾 **Commits** — history, single-commit detail, and diffs between refs
|
|
49
49
|
- 🐛 **Issues** — list, read, create, comment, and edit/close
|
|
50
|
-
- 🔀 **Pull requests** — list, read, fetch diffs and
|
|
50
|
+
- 🔀 **Pull requests** — list, read, fetch diffs/changed files, open new PRs, and merge
|
|
51
51
|
- ⚙️ **Actions & releases** — list workflow runs and releases
|
|
52
52
|
- ✍️ **Repo writes** — create branches and commit files (gated by read-only mode)
|
|
53
53
|
- 🔒 **Read-only mode** — flip one env var to disable every write tool
|
|
@@ -76,10 +76,13 @@ Enterprise Server.
|
|
|
76
76
|
| `list_releases` | Releases for a repository | |
|
|
77
77
|
| `search_issues` | Search issues and PRs across GitHub | |
|
|
78
78
|
| `search_code` | Search code across GitHub | |
|
|
79
|
+
| `create_pull_request` | Open a new pull request (supports draft) | ✅ |
|
|
80
|
+
| `merge_pull_request` | Merge a PR (merge/squash/rebase) | ✅ |
|
|
79
81
|
| `create_issue` | Open a new issue | ✅ |
|
|
80
82
|
| `update_issue` | Edit/close/reopen an issue | ✅ |
|
|
81
83
|
| `add_issue_comment` | Comment on an issue or PR | ✅ |
|
|
82
84
|
| `create_branch` | Create a branch from a ref | ✅ |
|
|
85
|
+
| `delete_branch` | Delete a branch | ✅ |
|
|
83
86
|
| `create_or_update_file` | Commit a file (create or update) | ✅ |
|
|
84
87
|
|
|
85
88
|
Tools marked **Write** are disabled when `GITHUB_MCP_READ_ONLY` is set.
|
|
@@ -379,3 +379,118 @@ async def test_create_or_update_file_creates_new_on_404(monkeypatch):
|
|
|
379
379
|
result = await server.create_or_update_file("o", "r", "new.md", "x", "add")
|
|
380
380
|
assert "sha" not in captured["body"] # no sha -> create
|
|
381
381
|
assert result["created"] is True
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
async def test_delete_branch_blocked_in_read_only(monkeypatch):
|
|
385
|
+
install_mock(monkeypatch, lambda r: httpx.Response(204), read_only=True)
|
|
386
|
+
with pytest.raises(GitHubError) as exc:
|
|
387
|
+
await server.delete_branch("o", "r", "feature")
|
|
388
|
+
assert exc.value.status_code == 403
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
async def test_delete_branch_calls_correct_ref(monkeypatch):
|
|
392
|
+
captured = {}
|
|
393
|
+
|
|
394
|
+
def handler(request):
|
|
395
|
+
captured["method"] = request.method
|
|
396
|
+
captured["path"] = request.url.path
|
|
397
|
+
return httpx.Response(204)
|
|
398
|
+
|
|
399
|
+
install_mock(monkeypatch, handler)
|
|
400
|
+
result = await server.delete_branch("o", "r", "feature-x")
|
|
401
|
+
assert captured["method"] == "DELETE"
|
|
402
|
+
assert captured["path"] == "/repos/o/r/git/refs/heads/feature-x"
|
|
403
|
+
assert result == {"deleted": True, "branch": "feature-x"}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
async def test_delete_branch_surfaces_api_error(monkeypatch):
|
|
407
|
+
def handler(request):
|
|
408
|
+
return httpx.Response(422, json={"message": "Reference does not exist"})
|
|
409
|
+
|
|
410
|
+
install_mock(monkeypatch, handler)
|
|
411
|
+
with pytest.raises(GitHubError) as exc:
|
|
412
|
+
await server.delete_branch("o", "r", "missing")
|
|
413
|
+
assert exc.value.status_code == 422
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
async def test_create_pull_request_blocked_in_read_only(monkeypatch):
|
|
417
|
+
install_mock(monkeypatch, lambda r: httpx.Response(200, json={}),
|
|
418
|
+
read_only=True)
|
|
419
|
+
with pytest.raises(GitHubError) as exc:
|
|
420
|
+
await server.create_pull_request("o", "r", "t", "feature", "main")
|
|
421
|
+
assert exc.value.status_code == 403
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
async def test_create_pull_request_posts_payload(monkeypatch):
|
|
425
|
+
captured = {}
|
|
426
|
+
|
|
427
|
+
def handler(request):
|
|
428
|
+
import json
|
|
429
|
+
assert request.method == "POST"
|
|
430
|
+
assert request.url.path == "/repos/o/r/pulls"
|
|
431
|
+
captured["body"] = json.loads(request.content)
|
|
432
|
+
return httpx.Response(
|
|
433
|
+
201,
|
|
434
|
+
json={
|
|
435
|
+
"number": 12,
|
|
436
|
+
"title": "t",
|
|
437
|
+
"state": "open",
|
|
438
|
+
"draft": True,
|
|
439
|
+
"body": "desc",
|
|
440
|
+
"head": {"ref": "feature"},
|
|
441
|
+
"base": {"ref": "main"},
|
|
442
|
+
},
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
install_mock(monkeypatch, handler)
|
|
446
|
+
result = await server.create_pull_request(
|
|
447
|
+
"o", "r", "t", "feature", "main", body="desc", draft=True
|
|
448
|
+
)
|
|
449
|
+
assert captured["body"]["title"] == "t"
|
|
450
|
+
assert captured["body"]["head"] == "feature"
|
|
451
|
+
assert captured["body"]["base"] == "main"
|
|
452
|
+
assert captured["body"]["draft"] is True
|
|
453
|
+
assert captured["body"]["body"] == "desc"
|
|
454
|
+
assert result["number"] == 12
|
|
455
|
+
assert result["draft"] is True
|
|
456
|
+
assert result["body"] == "desc"
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
async def test_merge_pull_request_blocked_in_read_only(monkeypatch):
|
|
460
|
+
install_mock(monkeypatch, lambda r: httpx.Response(200, json={}),
|
|
461
|
+
read_only=True)
|
|
462
|
+
with pytest.raises(GitHubError) as exc:
|
|
463
|
+
await server.merge_pull_request("o", "r", 1)
|
|
464
|
+
assert exc.value.status_code == 403
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
async def test_merge_pull_request_puts_payload(monkeypatch):
|
|
468
|
+
captured = {}
|
|
469
|
+
|
|
470
|
+
def handler(request):
|
|
471
|
+
import json
|
|
472
|
+
assert request.method == "PUT"
|
|
473
|
+
assert request.url.path == "/repos/o/r/pulls/5/merge"
|
|
474
|
+
captured["body"] = json.loads(request.content)
|
|
475
|
+
return httpx.Response(
|
|
476
|
+
200, json={"merged": True, "sha": "abc", "message": "Pull Request successfully merged"}
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
install_mock(monkeypatch, handler)
|
|
480
|
+
result = await server.merge_pull_request(
|
|
481
|
+
"o", "r", 5, merge_method="squash", commit_title="T"
|
|
482
|
+
)
|
|
483
|
+
assert captured["body"]["merge_method"] == "squash"
|
|
484
|
+
assert captured["body"]["commit_title"] == "T"
|
|
485
|
+
assert result["merged"] is True
|
|
486
|
+
assert result["sha"] == "abc"
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
async def test_merge_pull_request_surfaces_conflict(monkeypatch):
|
|
490
|
+
def handler(request):
|
|
491
|
+
return httpx.Response(405, json={"message": "Pull Request is not mergeable"})
|
|
492
|
+
|
|
493
|
+
install_mock(monkeypatch, handler)
|
|
494
|
+
with pytest.raises(GitHubError) as exc:
|
|
495
|
+
await server.merge_pull_request("o", "r", 5)
|
|
496
|
+
assert exc.value.status_code == 405
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|