github-agent 0.30.0__tar.gz → 0.33.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 (63) hide show
  1. {github_agent-0.30.0 → github_agent-0.33.0}/PKG-INFO +3 -3
  2. {github_agent-0.30.0 → github_agent-0.33.0}/README.md +1 -1
  3. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/agent_server.py +1 -1
  4. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_base.py +60 -3
  5. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/github_input_models.py +7 -2
  6. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_action.py +72 -13
  7. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_branch.py +26 -5
  8. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_collaborator.py +21 -5
  9. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_commit.py +13 -2
  10. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_content.py +16 -4
  11. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_issue.py +29 -7
  12. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_org.py +45 -15
  13. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_pull.py +33 -8
  14. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_release.py +34 -9
  15. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_repo.py +75 -13
  16. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/mcp_search.py +12 -3
  17. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp_server.py +342 -89
  18. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent.egg-info/PKG-INFO +3 -3
  19. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent.egg-info/SOURCES.txt +2 -0
  20. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent.egg-info/requires.txt +1 -1
  21. {github_agent-0.30.0 → github_agent-0.33.0}/pyproject.toml +2 -2
  22. github_agent-0.33.0/tests/test_action_discovery.py +87 -0
  23. github_agent-0.33.0/tests/test_hardening.py +118 -0
  24. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_mcp_coverage.py +35 -42
  25. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_pages.py +1 -0
  26. {github_agent-0.30.0 → github_agent-0.33.0}/LICENSE +0 -0
  27. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/__init__.py +0 -0
  28. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/__main__.py +0 -0
  29. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/__init__.py +0 -0
  30. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_branches.py +0 -0
  31. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_commits.py +0 -0
  32. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_contents.py +0 -0
  33. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_issues.py +0 -0
  34. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_orgs.py +0 -0
  35. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_pages.py +0 -0
  36. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_pulls.py +0 -0
  37. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_releases.py +0 -0
  38. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_repos.py +0 -0
  39. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_search.py +0 -0
  40. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api/api_client_workflows.py +0 -0
  41. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/api_client.py +0 -0
  42. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/auth.py +0 -0
  43. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/github_response_models.py +0 -0
  44. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp/__init__.py +0 -0
  45. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent/mcp_config.json +0 -0
  46. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent.egg-info/dependency_links.txt +0 -0
  47. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent.egg-info/entry_points.txt +0 -0
  48. {github_agent-0.30.0 → github_agent-0.33.0}/github_agent.egg-info/top_level.txt +0 -0
  49. {github_agent-0.30.0 → github_agent-0.33.0}/scripts/security_sanitizer.py +0 -0
  50. {github_agent-0.30.0 → github_agent-0.33.0}/scripts/validate_a2a_agent.py +0 -0
  51. {github_agent-0.30.0 → github_agent-0.33.0}/scripts/verify_api_integration.py +0 -0
  52. {github_agent-0.30.0 → github_agent-0.33.0}/setup.cfg +0 -0
  53. {github_agent-0.30.0 → github_agent-0.33.0}/tests/conftest.py +0 -0
  54. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_api_client_endpoints.py +0 -0
  55. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_api_validation.py +0 -0
  56. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_auth_client_edge_cases.py +0 -0
  57. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_concept_parity.py +0 -0
  58. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_github_agent_api_brute_force_coverage.py +0 -0
  59. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_github_agent_brute_force_coverage.py +0 -0
  60. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_init_dynamics.py +0 -0
  61. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_model_fields.py +0 -0
  62. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_org_crud.py +0 -0
  63. {github_agent-0.30.0 → github_agent-0.33.0}/tests/test_startup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-agent
3
- Version: 0.30.0
3
+ Version: 0.33.0
4
4
  Summary: GitHub Agent for MCP
5
5
  Author-email: Audel Rouhi <knucklessg1@gmail.com>
6
6
  License: MIT
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: <3.15,>=3.11
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: agent-utilities[agent,logfire]>=0.48.0
15
+ Requires-Dist: agent-utilities[agent,logfire]>=0.51.0
16
16
  Provides-Extra: test
17
17
  Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
18
18
  Requires-Dist: pytest; extra == "test"
@@ -41,7 +41,7 @@ Dynamic: license-file
41
41
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/github-agent)
42
42
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/github-agent)
43
43
 
44
- *Version: 0.30.0*
44
+ *Version: 0.33.0*
45
45
 
46
46
  > **Documentation** — Installation, deployment, and usage across the MCP, API, and
47
47
  > CLI interfaces, including the integrated A2A agent server, are maintained in the
@@ -20,7 +20,7 @@
20
20
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/github-agent)
21
21
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/github-agent)
22
22
 
23
- *Version: 0.30.0*
23
+ *Version: 0.33.0*
24
24
 
25
25
  > **Documentation** — Installation, deployment, and usage across the MCP, API, and
26
26
  > CLI interfaces, including the integrated A2A agent server, are maintained in the
@@ -4,7 +4,7 @@ import os
4
4
  import sys
5
5
  import warnings
6
6
 
7
- __version__ = "0.30.0"
7
+ __version__ = "0.33.0"
8
8
 
9
9
  logging.basicConfig(
10
10
  level=logging.INFO,
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env python
2
2
  import logging
3
+ import os
3
4
  import re
5
+ from collections.abc import Mapping
4
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
5
7
  from typing import TypeVar
6
8
 
@@ -18,6 +20,52 @@ logger = get_logger(__name__)
18
20
  T = TypeVar("T", bound=BaseModel)
19
21
 
20
22
 
23
+ def _default_timeout() -> tuple[float, float]:
24
+ """(connect, read) timeout for every outbound request, env-overridable.
25
+
26
+ A bounded timeout stops a single slow GitHub response from hanging an async
27
+ handler up to the gateway's 300s and wedging the whole server for every
28
+ concurrent caller (the failure mode behind stuck in-flight calls and child
29
+ restarts). Override with GITHUB_HTTP_CONNECT_TIMEOUT / GITHUB_HTTP_READ_TIMEOUT.
30
+ """
31
+ connect = float(os.getenv("GITHUB_HTTP_CONNECT_TIMEOUT", "10"))
32
+ read = float(os.getenv("GITHUB_HTTP_READ_TIMEOUT", "30"))
33
+ return (connect, read)
34
+
35
+
36
+ class _TimeoutAdapter(requests.adapters.HTTPAdapter):
37
+ """HTTPAdapter that applies a default timeout when a request sets none.
38
+
39
+ Mounted on the session rather than subclassing Session, so test suites that
40
+ replace ``requests.Session`` with a mock are unaffected (``mount`` is a no-op
41
+ on a mock) while real requests still get a bounded timeout.
42
+ """
43
+
44
+ def __init__(self, timeout: tuple[float, float], *args, **kwargs):
45
+ self._timeout = timeout
46
+ super().__init__(*args, **kwargs)
47
+
48
+ def send(
49
+ self,
50
+ request: requests.PreparedRequest,
51
+ stream: bool = False,
52
+ timeout: None | float | tuple[float, float] | tuple[float, None] = None,
53
+ verify: bool | str = True,
54
+ cert: None | bytes | str | tuple[bytes | str, bytes | str] = None,
55
+ proxies: Mapping[str, str] | None = None,
56
+ ) -> requests.Response:
57
+ if timeout is None:
58
+ timeout = self._timeout
59
+ return super().send(
60
+ request,
61
+ stream=stream,
62
+ timeout=timeout,
63
+ verify=verify,
64
+ cert=cert,
65
+ proxies=proxies,
66
+ )
67
+
68
+
21
69
  class BaseApiClient:
22
70
  def __init__(
23
71
  self,
@@ -36,6 +84,9 @@ class BaseApiClient:
36
84
  raise MissingParameterError
37
85
 
38
86
  self._session = requests.Session()
87
+ _adapter = _TimeoutAdapter(_default_timeout())
88
+ self._session.mount("https://", _adapter)
89
+ self._session.mount("http://", _adapter)
39
90
  self.url = url.rstrip("/")
40
91
  self.headers = {
41
92
  "Accept": "application/vnd.github+json",
@@ -119,10 +170,16 @@ class BaseApiClient:
119
170
 
120
171
  total_pages = self._get_total_pages(response)
121
172
 
122
- max_pages = getattr(model, "max_pages", total_pages)
123
- if not max_pages or max_pages == 0 or max_pages > total_pages:
173
+ # Bounded by default: an unset max_pages fetches only the first page so a
174
+ # single list call can't pull an entire org/run-history (the cause of
175
+ # multi-hundred-KB responses and gateway timeouts). Callers opt into more
176
+ # by passing max_pages (use max_pages<=0 to mean "all pages").
177
+ max_pages = getattr(model, "max_pages", None)
178
+ if max_pages is None:
179
+ max_pages = 1
180
+ elif max_pages <= 0 or max_pages > total_pages:
124
181
  max_pages = total_pages
125
- model.max_pages = total_pages # type: ignore[attr-defined]
182
+ model.max_pages = max_pages # type: ignore[attr-defined]
126
183
 
127
184
  if max_pages > 1:
128
185
  with ThreadPoolExecutor(max_workers=5) as executor:
@@ -14,10 +14,15 @@ class BaseModelWrapper(BaseModel):
14
14
  """
15
15
 
16
16
  max_pages: int | None = Field(
17
- description="Max amount of pages to retrieve", default=None
17
+ description=(
18
+ "Max number of pages to retrieve. Unset (default) fetches only the "
19
+ "first page to keep responses small; pass a number for more, or "
20
+ "max_pages<=0 to fetch all pages."
21
+ ),
22
+ default=None,
18
23
  )
19
24
  page: int | None = Field(description="Pagination page", default=1)
20
- per_page: int | None = Field(description="Results per page", default=100)
25
+ per_page: int | None = Field(description="Results per page (1-100)", default=30)
21
26
  api_parameters: dict[str, Any] = Field(
22
27
  description="API Parameters", default_factory=dict
23
28
  )
@@ -3,12 +3,44 @@
3
3
  Auto-generated from mcp_server.py during ecosystem standardization.
4
4
  """
5
5
 
6
+ from agent_utilities.mcp_utilities import resolve_action, run_blocking
6
7
  from fastmcp import Context, FastMCP
7
8
  from fastmcp.dependencies import Depends
8
9
  from pydantic import Field
9
10
 
10
11
  from github_agent.auth import get_client
11
12
 
13
+ #: Exact keys dropped by _slim (pure hypermedia/noise, never semantic data).
14
+ _SLIM_DROP_EXACT = {"_links", "url", "node_id"}
15
+
16
+ #: Valid workflow actions for the shared ``resolve_action`` discovery helper.
17
+ WORKFLOW_ACTIONS = (
18
+ "list_workflows",
19
+ "list_runs",
20
+ "get_run",
21
+ "trigger_dispatch",
22
+ "rerun",
23
+ "cancel",
24
+ "delete_run",
25
+ )
26
+
27
+
28
+ def _slim(obj):
29
+ """Recursively drop hypermedia ``*_url`` hrefs and ``_links`` noise.
30
+
31
+ Mirror of ``mcp_server._slim``; see that module for rationale.
32
+ """
33
+ if isinstance(obj, list):
34
+ return [_slim(item) for item in obj]
35
+ if isinstance(obj, dict):
36
+ return {
37
+ k: _slim(v)
38
+ for k, v in obj.items()
39
+ if k not in _SLIM_DROP_EXACT
40
+ and not (k.endswith("_url") and k != "html_url")
41
+ }
42
+ return obj
43
+
12
44
 
13
45
  def register_action_tools(mcp: FastMCP):
14
46
  @mcp.tool(tags={"actions"})
@@ -24,7 +56,15 @@ def register_action_tools(mcp: FastMCP):
24
56
  default=None, description="MCP context for progress reporting"
25
57
  ),
26
58
  ) -> dict:
27
- """Manage GitHub actions workflows and runs."""
59
+ """Manage GitHub actions workflows and runs.
60
+
61
+ list_runs params (via params_json): owner, repo, and optional filters
62
+ applied server-side to keep results small — status (e.g. failure,
63
+ success, completed, in_progress), branch, per_page (1-100, default 30),
64
+ max_pages (default 1 page; max_pages<=0 = all pages), slim (default
65
+ true: drops hypermedia *_url/_links noise, keeps html_url and all data).
66
+ Prefer status=failure + branch=<default> for a CI-health sweep.
67
+ """
28
68
  if ctx:
29
69
  await ctx.info("Executing github_actions action...")
30
70
  import json
@@ -35,6 +75,12 @@ def register_action_tools(mcp: FastMCP):
35
75
  return {"status": 400, "error": f"Invalid params_json: {e}", "data": None}
36
76
 
37
77
  kwargs = {k: v for k, v in kwargs.items() if v is not None}
78
+ slim = kwargs.pop("slim", True)
79
+
80
+ resolved = resolve_action(action, WORKFLOW_ACTIONS, service="github-agent")
81
+ if isinstance(resolved, dict):
82
+ return resolved
83
+ action = resolved
38
84
 
39
85
  try:
40
86
  if action == "list_workflows":
@@ -46,18 +92,21 @@ def register_action_tools(mcp: FastMCP):
46
92
  "error": "Missing required 'owner' or 'repo' parameter",
47
93
  "data": None,
48
94
  }
49
- response = client.get_workflows(owner=owner, repo=repo)
95
+ response = await run_blocking(
96
+ client.get_workflows, owner=owner, repo=repo
97
+ )
50
98
  return {
51
99
  "status": 200,
52
100
  "message": "Workflows retrieved successfully",
53
101
  "data": [w.model_dump() for w in response.data],
54
102
  }
55
103
  elif action == "list_runs":
56
- response = client.get_workflow_runs(**kwargs)
104
+ response = await run_blocking(client.get_workflow_runs, **kwargs)
105
+ data = [r.model_dump() for r in response.data]
57
106
  return {
58
107
  "status": 200,
59
108
  "message": "Workflow runs retrieved successfully",
60
- "data": [r.model_dump() for r in response.data],
109
+ "data": _slim(data) if slim else data,
61
110
  }
62
111
  elif action == "get_run":
63
112
  owner = kwargs.get("owner")
@@ -69,8 +118,8 @@ def register_action_tools(mcp: FastMCP):
69
118
  "error": "Missing required 'owner', 'repo', or 'run_id' parameter",
70
119
  "data": None,
71
120
  }
72
- response = client.get_workflow_run(
73
- owner=owner, repo=repo, run_id=int(run_id)
121
+ response = await run_blocking(
122
+ client.get_workflow_run, owner=owner, repo=repo, run_id=int(run_id)
74
123
  )
75
124
  return {
76
125
  "status": 200,
@@ -89,7 +138,8 @@ def register_action_tools(mcp: FastMCP):
89
138
  "error": "Missing 'owner', 'repo', 'workflow_id', or 'ref' parameter",
90
139
  "data": None,
91
140
  }
92
- response = client.trigger_workflow_dispatch(
141
+ response = await run_blocking(
142
+ client.trigger_workflow_dispatch,
93
143
  owner=owner,
94
144
  repo=repo,
95
145
  workflow_id=workflow_id,
@@ -111,8 +161,11 @@ def register_action_tools(mcp: FastMCP):
111
161
  "error": "Missing 'owner', 'repo', or 'run_id' parameter",
112
162
  "data": None,
113
163
  }
114
- response = client.rerun_workflow_run(
115
- owner=owner, repo=repo, run_id=int(run_id)
164
+ response = await run_blocking(
165
+ client.rerun_workflow_run,
166
+ owner=owner,
167
+ repo=repo,
168
+ run_id=int(run_id),
116
169
  )
117
170
  return {
118
171
  "status": 200,
@@ -129,8 +182,11 @@ def register_action_tools(mcp: FastMCP):
129
182
  "error": "Missing 'owner', 'repo', or 'run_id' parameter",
130
183
  "data": None,
131
184
  }
132
- response = client.cancel_workflow_run(
133
- owner=owner, repo=repo, run_id=int(run_id)
185
+ response = await run_blocking(
186
+ client.cancel_workflow_run,
187
+ owner=owner,
188
+ repo=repo,
189
+ run_id=int(run_id),
134
190
  )
135
191
  return {
136
192
  "status": 200,
@@ -147,8 +203,11 @@ def register_action_tools(mcp: FastMCP):
147
203
  "error": "Missing 'owner', 'repo', or 'run_id' parameter",
148
204
  "data": None,
149
205
  }
150
- response = client.delete_workflow_run(
151
- owner=owner, repo=repo, run_id=int(run_id)
206
+ response = await run_blocking(
207
+ client.delete_workflow_run,
208
+ owner=owner,
209
+ repo=repo,
210
+ run_id=int(run_id),
152
211
  )
153
212
  return {
154
213
  "status": 200,
@@ -3,12 +3,24 @@
3
3
  Auto-generated from mcp_server.py during ecosystem standardization.
4
4
  """
5
5
 
6
+ from agent_utilities.mcp_utilities import resolve_action, run_blocking
6
7
  from fastmcp import Context, FastMCP
7
8
  from fastmcp.dependencies import Depends
8
9
  from pydantic import Field
9
10
 
10
11
  from github_agent.auth import get_client
11
12
 
13
+ #: Valid branch actions for the shared ``resolve_action`` discovery helper.
14
+ BRANCH_ACTIONS = (
15
+ "list",
16
+ "get",
17
+ "create",
18
+ "delete",
19
+ "get_protection",
20
+ "update_protection",
21
+ "delete_protection",
22
+ )
23
+
12
24
 
13
25
  def register_branch_tools(mcp: FastMCP):
14
26
  @mcp.tool(tags={"branches"})
@@ -36,9 +48,14 @@ def register_branch_tools(mcp: FastMCP):
36
48
 
37
49
  kwargs = {k: v for k, v in kwargs.items() if v is not None}
38
50
 
51
+ resolved = resolve_action(action, BRANCH_ACTIONS, service="github-agent")
52
+ if isinstance(resolved, dict):
53
+ return resolved
54
+ action = resolved
55
+
39
56
  try:
40
57
  if action == "list":
41
- response = client.get_branches(**kwargs)
58
+ response = await run_blocking(client.get_branches, **kwargs)
42
59
  return {
43
60
  "status": 200,
44
61
  "message": "Branches retrieved successfully",
@@ -54,7 +71,9 @@ def register_branch_tools(mcp: FastMCP):
54
71
  "error": "Missing 'owner', 'repo', or 'branch' parameter",
55
72
  "data": None,
56
73
  }
57
- response = client.get_branch(owner=owner, repo=repo, branch=branch)
74
+ response = await run_blocking(
75
+ client.get_branch, owner=owner, repo=repo, branch=branch
76
+ )
58
77
  return {
59
78
  "status": 200,
60
79
  "message": "Branch retrieved successfully",
@@ -71,8 +90,8 @@ def register_branch_tools(mcp: FastMCP):
71
90
  "error": "Missing 'owner', 'repo', 'branch', or 'ref' parameter",
72
91
  "data": None,
73
92
  }
74
- response = client.create_branch(
75
- owner=owner, repo=repo, branch=branch, ref=ref
93
+ response = await run_blocking(
94
+ client.create_branch, owner=owner, repo=repo, branch=branch, ref=ref
76
95
  )
77
96
  return {
78
97
  "status": 201,
@@ -89,7 +108,9 @@ def register_branch_tools(mcp: FastMCP):
89
108
  "error": "Missing 'owner', 'repo', or 'branch' parameter",
90
109
  "data": None,
91
110
  }
92
- response = client.delete_branch(owner=owner, repo=repo, branch=branch)
111
+ response = await run_blocking(
112
+ client.delete_branch, owner=owner, repo=repo, branch=branch
113
+ )
93
114
  return {
94
115
  "status": 200,
95
116
  "message": "Branch deleted successfully",
@@ -3,12 +3,16 @@
3
3
  Auto-generated from mcp_server.py during ecosystem standardization.
4
4
  """
5
5
 
6
+ from agent_utilities.mcp_utilities import resolve_action, run_blocking
6
7
  from fastmcp import Context, FastMCP
7
8
  from fastmcp.dependencies import Depends
8
9
  from pydantic import Field
9
10
 
10
11
  from github_agent.auth import get_client
11
12
 
13
+ #: Valid collaborator actions for the shared ``resolve_action`` discovery helper.
14
+ COLLABORATOR_ACTIONS = ("list", "add", "remove")
15
+
12
16
 
13
17
  def register_collaborator_tools(mcp: FastMCP):
14
18
  @mcp.tool(tags={"collaborators"})
@@ -36,9 +40,14 @@ def register_collaborator_tools(mcp: FastMCP):
36
40
 
37
41
  kwargs = {k: v for k, v in kwargs.items() if v is not None}
38
42
 
43
+ resolved = resolve_action(action, COLLABORATOR_ACTIONS, service="github-agent")
44
+ if isinstance(resolved, dict):
45
+ return resolved
46
+ action = resolved
47
+
39
48
  try:
40
49
  if action == "list":
41
- response = client.get_collaborators(**kwargs)
50
+ response = await run_blocking(client.get_collaborators, **kwargs)
42
51
  return {
43
52
  "status": 200,
44
53
  "message": "Collaborators retrieved successfully",
@@ -55,8 +64,12 @@ def register_collaborator_tools(mcp: FastMCP):
55
64
  "error": "Missing 'owner', 'repo', or 'username' parameter",
56
65
  "data": None,
57
66
  }
58
- response = client.add_collaborator(
59
- owner=owner, repo=repo, username=username, permission=permission
67
+ response = await run_blocking(
68
+ client.add_collaborator,
69
+ owner=owner,
70
+ repo=repo,
71
+ username=username,
72
+ permission=permission,
60
73
  )
61
74
  return {
62
75
  "status": 200,
@@ -73,8 +86,11 @@ def register_collaborator_tools(mcp: FastMCP):
73
86
  "error": "Missing 'owner', 'repo', or 'username' parameter",
74
87
  "data": None,
75
88
  }
76
- response = client.remove_collaborator(
77
- owner=owner, repo=repo, username=username
89
+ response = await run_blocking(
90
+ client.remove_collaborator,
91
+ owner=owner,
92
+ repo=repo,
93
+ username=username,
78
94
  )
79
95
  return {
80
96
  "status": 200,
@@ -3,12 +3,16 @@
3
3
  Auto-generated from mcp_server.py during ecosystem standardization.
4
4
  """
5
5
 
6
+ from agent_utilities.mcp_utilities import resolve_action, run_blocking
6
7
  from fastmcp import Context, FastMCP
7
8
  from fastmcp.dependencies import Depends
8
9
  from pydantic import Field
9
10
 
10
11
  from github_agent.auth import get_client
11
12
 
13
+ #: Valid commit actions for the shared ``resolve_action`` discovery helper.
14
+ COMMIT_ACTIONS = ("list", "get")
15
+
12
16
 
13
17
  def register_commit_tools(mcp: FastMCP):
14
18
  @mcp.tool(tags={"commits"})
@@ -36,9 +40,14 @@ def register_commit_tools(mcp: FastMCP):
36
40
 
37
41
  kwargs = {k: v for k, v in kwargs.items() if v is not None}
38
42
 
43
+ resolved = resolve_action(action, COMMIT_ACTIONS, service="github-agent")
44
+ if isinstance(resolved, dict):
45
+ return resolved
46
+ action = resolved
47
+
39
48
  try:
40
49
  if action == "list":
41
- response = client.get_commits(**kwargs)
50
+ response = await run_blocking(client.get_commits, **kwargs)
42
51
  return {
43
52
  "status": 200,
44
53
  "message": "Commits retrieved successfully",
@@ -54,7 +63,9 @@ def register_commit_tools(mcp: FastMCP):
54
63
  "error": "Missing 'owner', 'repo', or 'sha' parameter",
55
64
  "data": None,
56
65
  }
57
- response = client.get_commit(owner=owner, repo=repo, sha=sha)
66
+ response = await run_blocking(
67
+ client.get_commit, owner=owner, repo=repo, sha=sha
68
+ )
58
69
  return {
59
70
  "status": 200,
60
71
  "message": "Commit retrieved successfully",
@@ -3,12 +3,16 @@
3
3
  Auto-generated from mcp_server.py during ecosystem standardization.
4
4
  """
5
5
 
6
+ from agent_utilities.mcp_utilities import resolve_action, run_blocking
6
7
  from fastmcp import Context, FastMCP
7
8
  from fastmcp.dependencies import Depends
8
9
  from pydantic import Field
9
10
 
10
11
  from github_agent.auth import get_client
11
12
 
13
+ #: Valid content actions for the shared ``resolve_action`` discovery helper.
14
+ CONTENT_ACTIONS = ("get", "create", "update", "delete")
15
+
12
16
 
13
17
  def register_content_tools(mcp: FastMCP):
14
18
  @mcp.tool(tags={"contents"})
@@ -36,9 +40,14 @@ def register_content_tools(mcp: FastMCP):
36
40
 
37
41
  kwargs = {k: v for k, v in kwargs.items() if v is not None}
38
42
 
43
+ resolved = resolve_action(action, CONTENT_ACTIONS, service="github-agent")
44
+ if isinstance(resolved, dict):
45
+ return resolved
46
+ action = resolved
47
+
39
48
  try:
40
49
  if action == "get":
41
- response = client.get_contents(**kwargs)
50
+ response = await run_blocking(client.get_contents, **kwargs)
42
51
  if isinstance(response.data, list):
43
52
  data = [item.model_dump() for item in response.data]
44
53
  else:
@@ -60,7 +69,8 @@ def register_content_tools(mcp: FastMCP):
60
69
  "error": "Missing 'owner', 'repo', 'path', 'message', or 'content' parameter",
61
70
  "data": None,
62
71
  }
63
- response = client.create_content(
72
+ response = await run_blocking(
73
+ client.create_content,
64
74
  owner=owner,
65
75
  repo=repo,
66
76
  path=path,
@@ -93,7 +103,8 @@ def register_content_tools(mcp: FastMCP):
93
103
  "error": "Missing 'owner', 'repo', 'path', 'message', 'content', or 'sha' parameter",
94
104
  "data": None,
95
105
  }
96
- response = client.update_content(
106
+ response = await run_blocking(
107
+ client.update_content,
97
108
  owner=owner,
98
109
  repo=repo,
99
110
  path=path,
@@ -119,7 +130,8 @@ def register_content_tools(mcp: FastMCP):
119
130
  "error": "Missing 'owner', 'repo', 'path', 'message', or 'sha' parameter",
120
131
  "data": None,
121
132
  }
122
- response = client.delete_content(
133
+ response = await run_blocking(
134
+ client.delete_content,
123
135
  owner=owner,
124
136
  repo=repo,
125
137
  path=path,
@@ -3,12 +3,16 @@
3
3
  Auto-generated from mcp_server.py during ecosystem standardization.
4
4
  """
5
5
 
6
+ from agent_utilities.mcp_utilities import resolve_action, run_blocking
6
7
  from fastmcp import Context, FastMCP
7
8
  from fastmcp.dependencies import Depends
8
9
  from pydantic import Field
9
10
 
10
11
  from github_agent.auth import get_client
11
12
 
13
+ #: Valid issue actions for the shared ``resolve_action`` discovery helper.
14
+ ISSUE_ACTIONS = ("list", "get", "create", "update")
15
+
12
16
 
13
17
  def register_issue_tools(mcp: FastMCP):
14
18
  @mcp.tool(tags={"issues"})
@@ -24,7 +28,14 @@ def register_issue_tools(mcp: FastMCP):
24
28
  default=None, description="MCP context for progress reporting"
25
29
  ),
26
30
  ) -> dict:
27
- """Manage GitHub issues."""
31
+ """Manage GitHub issues.
32
+
33
+ list params (via params_json): owner, repo, and optional filters applied
34
+ server-side — state (open/closed/all), labels, assignee, since, per_page
35
+ (1-100, default 30), max_pages (default 1 page; max_pages<=0 = all pages).
36
+ Note: the list/search APIs return PRs alongside issues — a returned item
37
+ with a 'pull_request' field is a PR, not an issue.
38
+ """
28
39
  if ctx:
29
40
  await ctx.info("Executing github_issues action...")
30
41
  import json
@@ -36,9 +47,14 @@ def register_issue_tools(mcp: FastMCP):
36
47
 
37
48
  kwargs = {k: v for k, v in kwargs.items() if v is not None}
38
49
 
50
+ resolved = resolve_action(action, ISSUE_ACTIONS, service="github-agent")
51
+ if isinstance(resolved, dict):
52
+ return resolved
53
+ action = resolved
54
+
39
55
  try:
40
56
  if action == "list":
41
- response = client.get_issues(**kwargs)
57
+ response = await run_blocking(client.get_issues, **kwargs)
42
58
  return {
43
59
  "status": 200,
44
60
  "message": "Issues retrieved successfully",
@@ -54,7 +70,9 @@ def register_issue_tools(mcp: FastMCP):
54
70
  "error": "Missing 'owner', 'repo', or 'number' parameter",
55
71
  "data": None,
56
72
  }
57
- response = client.get_issue(owner=owner, repo=repo, number=int(number))
73
+ response = await run_blocking(
74
+ client.get_issue, owner=owner, repo=repo, number=int(number)
75
+ )
58
76
  return {
59
77
  "status": 200,
60
78
  "message": "Issue retrieved successfully",
@@ -70,8 +88,8 @@ def register_issue_tools(mcp: FastMCP):
70
88
  "error": "Missing 'owner', 'repo', or 'title' parameter",
71
89
  "data": None,
72
90
  }
73
- response = client.create_issue(
74
- owner=owner, repo=repo, title=title, **kwargs
91
+ response = await run_blocking(
92
+ client.create_issue, owner=owner, repo=repo, title=title, **kwargs
75
93
  )
76
94
  return {
77
95
  "status": 201,
@@ -88,8 +106,12 @@ def register_issue_tools(mcp: FastMCP):
88
106
  "error": "Missing 'owner', 'repo', or 'number' parameter",
89
107
  "data": None,
90
108
  }
91
- response = client.update_issue(
92
- owner=owner, repo=repo, number=int(number), **kwargs
109
+ response = await run_blocking(
110
+ client.update_issue,
111
+ owner=owner,
112
+ repo=repo,
113
+ number=int(number),
114
+ **kwargs,
93
115
  )
94
116
  return {
95
117
  "status": 200,