github-mcp-connector 0.7.0__tar.gz → 0.8.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 (19) hide show
  1. {github_mcp_connector-0.7.0/src/github_mcp_connector.egg-info → github_mcp_connector-0.8.0}/PKG-INFO +3 -2
  2. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/README.md +2 -1
  3. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/pyproject.toml +1 -1
  4. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp/server.py +93 -0
  5. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0/src/github_mcp_connector.egg-info}/PKG-INFO +3 -2
  6. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/tests/test_server.py +54 -0
  7. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/LICENSE +0 -0
  8. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/setup.cfg +0 -0
  9. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp/__init__.py +0 -0
  10. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp/__main__.py +0 -0
  11. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp/client.py +0 -0
  12. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp/config.py +0 -0
  13. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp_connector.egg-info/SOURCES.txt +0 -0
  14. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp_connector.egg-info/dependency_links.txt +0 -0
  15. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp_connector.egg-info/entry_points.txt +0 -0
  16. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp_connector.egg-info/requires.txt +0 -0
  17. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/src/github_mcp_connector.egg-info/top_level.txt +0 -0
  18. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/tests/test_client.py +0 -0
  19. {github_mcp_connector-0.7.0 → github_mcp_connector-0.8.0}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-mcp-connector
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: A Model Context Protocol (MCP) connector for GitHub, for use with Claude.
5
5
  Author: winnerlose2026
6
6
  License: MIT
@@ -51,7 +51,7 @@ Enterprise Server.
51
51
  - 🧾 **Commits** — history, single-commit detail, and diffs between refs
52
52
  - 🐛 **Issues** — list, read, create, comment, read comments, edit/close, label, assign
53
53
  - 🔀 **Pull requests** — list, read, diffs/files, comments, open, update, merge, and review
54
- - ⚙️ **Actions & releases** — list runs/jobs, read failed-job logs, re-run/dispatch workflows, commit statuses & check runs, list/create releases
54
+ - ⚙️ **Actions & releases** — list runs/jobs, read failed-job logs, re-run/dispatch/**schedule** workflows, commit statuses & check runs, list/create releases
55
55
  - 🔐 **Security** — secret-scanning, code-scanning, and Dependabot alerts
56
56
  - 🔔 **Notifications** — list and mark read across all your repos
57
57
  - ✍️ **Repo writes** — create branches, commit files, manage labels/assignees, create gists (gated by read-only mode)
@@ -101,6 +101,7 @@ Enterprise Server.
101
101
  | `add_pull_request_review_comment` | Inline comment on a PR diff line | ✅ |
102
102
  | `rerun_workflow_run` | Re-run a workflow run (or just failed jobs) | ✅ |
103
103
  | `trigger_workflow` | Dispatch a workflow_dispatch run | ✅ |
104
+ | `create_scheduled_workflow` | Commit a cron-scheduled workflow ("do X later") | ✅ |
104
105
  | `create_release` | Create a release (and its tag) | ✅ |
105
106
  | `create_issue` | Open a new issue | ✅ |
106
107
  | `update_issue` | Edit/close/reopen an issue | ✅ |
@@ -21,7 +21,7 @@ Enterprise Server.
21
21
  - 🧾 **Commits** — history, single-commit detail, and diffs between refs
22
22
  - 🐛 **Issues** — list, read, create, comment, read comments, edit/close, label, assign
23
23
  - 🔀 **Pull requests** — list, read, diffs/files, comments, open, update, merge, and review
24
- - ⚙️ **Actions & releases** — list runs/jobs, read failed-job logs, re-run/dispatch workflows, commit statuses & check runs, list/create releases
24
+ - ⚙️ **Actions & releases** — list runs/jobs, read failed-job logs, re-run/dispatch/**schedule** workflows, commit statuses & check runs, list/create releases
25
25
  - 🔐 **Security** — secret-scanning, code-scanning, and Dependabot alerts
26
26
  - 🔔 **Notifications** — list and mark read across all your repos
27
27
  - ✍️ **Repo writes** — create branches, commit files, manage labels/assignees, create gists (gated by read-only mode)
@@ -71,6 +71,7 @@ Enterprise Server.
71
71
  | `add_pull_request_review_comment` | Inline comment on a PR diff line | ✅ |
72
72
  | `rerun_workflow_run` | Re-run a workflow run (or just failed jobs) | ✅ |
73
73
  | `trigger_workflow` | Dispatch a workflow_dispatch run | ✅ |
74
+ | `create_scheduled_workflow` | Commit a cron-scheduled workflow ("do X later") | ✅ |
74
75
  | `create_release` | Create a release (and its tag) | ✅ |
75
76
  | `create_issue` | Open a new issue | ✅ |
76
77
  | `update_issue` | Edit/close/reopen an issue | ✅ |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "github-mcp-connector"
7
- version = "0.7.0"
7
+ version = "0.8.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"
@@ -12,6 +12,7 @@ variable ``GITHUB_MCP_READ_ONLY`` is truthy.
12
12
  from __future__ import annotations
13
13
 
14
14
  import base64
15
+ import re
15
16
  from typing import Any
16
17
  from urllib.parse import quote
17
18
 
@@ -1628,6 +1629,98 @@ async def create_gist(
1628
1629
  return _summarize_gist(gist)
1629
1630
 
1630
1631
 
1632
+ @mcp.tool()
1633
+ async def create_scheduled_workflow(
1634
+ owner: str,
1635
+ repo: str,
1636
+ name: str,
1637
+ cron: str,
1638
+ run: str,
1639
+ runs_on: str = "ubuntu-latest",
1640
+ branch: str | None = None,
1641
+ filename: str | None = None,
1642
+ message: str | None = None,
1643
+ ) -> dict[str, Any]:
1644
+ """Schedule recurring work by committing a cron GitHub Actions workflow.
1645
+
1646
+ This is GitHub's native "do X later / on a schedule": it creates (or updates)
1647
+ a workflow file at `.github/workflows/<filename>` that runs `run` (a shell
1648
+ script) on the `cron` schedule, and also adds a `workflow_dispatch` trigger
1649
+ so you can run it on demand. `cron` is standard 5-field UTC cron
1650
+ (e.g. "0 9 * * 1" = 09:00 UTC every Monday).
1651
+
1652
+ IMPORTANT: GitHub only fires `schedule` triggers from a repository's DEFAULT
1653
+ branch, so `branch` defaults to it — committing to another branch will run on
1654
+ demand but never on the schedule. `filename` defaults to a slug of `name`.
1655
+ Disabled in read-only mode; requires write access (and the token needs the
1656
+ `workflow` scope to add workflow files).
1657
+ """
1658
+ _require_token()
1659
+ _require_write()
1660
+ if len(cron.split()) != 5:
1661
+ raise GitHubError(
1662
+ 400,
1663
+ "CRON",
1664
+ f"/repos/{owner}/{repo}",
1665
+ f"`cron` must be a 5-field cron expression (got: {cron!r}).",
1666
+ )
1667
+ slug = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") or "scheduled"
1668
+ fname = filename or f"{slug}.yml"
1669
+ path = f".github/workflows/{fname}"
1670
+ indented_run = "\n".join(" " + line for line in run.splitlines())
1671
+ content = (
1672
+ f"name: {name}\n"
1673
+ "on:\n"
1674
+ " schedule:\n"
1675
+ f' - cron: "{cron}"\n'
1676
+ " workflow_dispatch: {}\n"
1677
+ "permissions:\n"
1678
+ " contents: read\n"
1679
+ "jobs:\n"
1680
+ " scheduled:\n"
1681
+ f" runs-on: {runs_on}\n"
1682
+ " steps:\n"
1683
+ " - uses: actions/checkout@v4\n"
1684
+ " - name: Run scheduled command\n"
1685
+ " run: |\n"
1686
+ f"{indented_run}\n"
1687
+ )
1688
+ encoded = base64.b64encode(content.encode("utf-8")).decode("ascii")
1689
+ commit_message = message or f"Add scheduled workflow: {name}"
1690
+ async with GitHubClient(config) as gh:
1691
+ if branch is None:
1692
+ repo_data = await gh.get(f"/repos/{owner}/{repo}")
1693
+ branch = repo_data.get("default_branch")
1694
+ payload: dict[str, Any] = {
1695
+ "message": commit_message,
1696
+ "content": encoded,
1697
+ "branch": branch,
1698
+ }
1699
+ # If the workflow file already exists on this branch, update it.
1700
+ try:
1701
+ existing = await gh.get(
1702
+ f"/repos/{owner}/{repo}/contents/{path}", params={"ref": branch}
1703
+ )
1704
+ if isinstance(existing, dict) and existing.get("sha"):
1705
+ payload["sha"] = existing.get("sha")
1706
+ except GitHubError as exc:
1707
+ if exc.status_code != 404:
1708
+ raise
1709
+ result = await gh.put(f"/repos/{owner}/{repo}/contents/{path}", json=payload)
1710
+ commit = result.get("commit", {})
1711
+ content_info = result.get("content", {})
1712
+ return {
1713
+ "path": content_info.get("path", path),
1714
+ "branch": branch,
1715
+ "cron": cron,
1716
+ "runs_on": runs_on,
1717
+ "commit_sha": commit.get("sha"),
1718
+ "html_url": content_info.get("html_url"),
1719
+ "note": "Scheduled runs fire only from the default branch and start "
1720
+ "from the next cron tick; use trigger_workflow to run it now.",
1721
+ }
1722
+
1723
+
1631
1724
  def main(argv: list[str] | None = None) -> None:
1632
1725
  """Console entry point. Selects the transport from CLI args/env."""
1633
1726
  import argparse
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-mcp-connector
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: A Model Context Protocol (MCP) connector for GitHub, for use with Claude.
5
5
  Author: winnerlose2026
6
6
  License: MIT
@@ -51,7 +51,7 @@ Enterprise Server.
51
51
  - 🧾 **Commits** — history, single-commit detail, and diffs between refs
52
52
  - 🐛 **Issues** — list, read, create, comment, read comments, edit/close, label, assign
53
53
  - 🔀 **Pull requests** — list, read, diffs/files, comments, open, update, merge, and review
54
- - ⚙️ **Actions & releases** — list runs/jobs, read failed-job logs, re-run/dispatch workflows, commit statuses & check runs, list/create releases
54
+ - ⚙️ **Actions & releases** — list runs/jobs, read failed-job logs, re-run/dispatch/**schedule** workflows, commit statuses & check runs, list/create releases
55
55
  - 🔐 **Security** — secret-scanning, code-scanning, and Dependabot alerts
56
56
  - 🔔 **Notifications** — list and mark read across all your repos
57
57
  - ✍️ **Repo writes** — create branches, commit files, manage labels/assignees, create gists (gated by read-only mode)
@@ -101,6 +101,7 @@ Enterprise Server.
101
101
  | `add_pull_request_review_comment` | Inline comment on a PR diff line | ✅ |
102
102
  | `rerun_workflow_run` | Re-run a workflow run (or just failed jobs) | ✅ |
103
103
  | `trigger_workflow` | Dispatch a workflow_dispatch run | ✅ |
104
+ | `create_scheduled_workflow` | Commit a cron-scheduled workflow ("do X later") | ✅ |
104
105
  | `create_release` | Create a release (and its tag) | ✅ |
105
106
  | `create_issue` | Open a new issue | ✅ |
106
107
  | `update_issue` | Edit/close/reopen an issue | ✅ |
@@ -1056,3 +1056,57 @@ async def test_get_repository_tree_errors_without_default_branch(monkeypatch):
1056
1056
  with pytest.raises(GitHubError) as exc:
1057
1057
  await server.get_repository_tree("o", "r")
1058
1058
  assert exc.value.status_code == 404
1059
+
1060
+
1061
+ async def test_create_scheduled_workflow_read_only(monkeypatch):
1062
+ install_mock(monkeypatch, lambda r: httpx.Response(200, json={}),
1063
+ read_only=True)
1064
+ with pytest.raises(GitHubError) as exc:
1065
+ await server.create_scheduled_workflow("o", "r", "Nightly", "0 9 * * *",
1066
+ "echo hi")
1067
+ assert exc.value.status_code == 403
1068
+
1069
+
1070
+ async def test_create_scheduled_workflow_bad_cron(monkeypatch):
1071
+ install_mock(monkeypatch, lambda r: httpx.Response(200, json={}))
1072
+ with pytest.raises(GitHubError) as exc:
1073
+ await server.create_scheduled_workflow("o", "r", "x", "not cron", "echo")
1074
+ assert exc.value.status_code == 400
1075
+
1076
+
1077
+ async def test_create_scheduled_workflow_commits_yaml(monkeypatch):
1078
+ import base64
1079
+ captured = {}
1080
+
1081
+ def handler(request):
1082
+ import json
1083
+ p = request.url.path
1084
+ if request.method == "GET" and p == "/repos/o/r":
1085
+ return httpx.Response(200, json={"default_branch": "main"})
1086
+ if request.method == "GET" and p.startswith("/repos/o/r/contents/"):
1087
+ return httpx.Response(404, json={"message": "Not Found"})
1088
+ if request.method == "PUT" and p == "/repos/o/r/contents/.github/workflows/nightly-backup.yml":
1089
+ captured["body"] = json.loads(request.content)
1090
+ captured["path"] = p
1091
+ return httpx.Response(
1092
+ 201,
1093
+ json={"content": {"path": ".github/workflows/nightly-backup.yml",
1094
+ "sha": "s", "html_url": "u"},
1095
+ "commit": {"sha": "c"}},
1096
+ )
1097
+ raise AssertionError(f"unexpected {request.method} {p}")
1098
+
1099
+ install_mock(monkeypatch, handler)
1100
+ result = await server.create_scheduled_workflow(
1101
+ "o", "r", "Nightly Backup", "0 9 * * 1", "echo hi\nrun-backup.sh"
1102
+ )
1103
+ body = captured["body"]
1104
+ assert body["branch"] == "main" # defaulted to default branch
1105
+ assert "sha" not in body # new file
1106
+ yaml = base64.b64decode(body["content"]).decode()
1107
+ assert "schedule:" in yaml
1108
+ assert 'cron: "0 9 * * 1"' in yaml
1109
+ assert "workflow_dispatch: {}" in yaml
1110
+ assert "run-backup.sh" in yaml
1111
+ assert result["cron"] == "0 9 * * 1"
1112
+ assert result["branch"] == "main"