universal-mcp 0.1.7rc1__py3-none-any.whl → 0.1.8__py3-none-any.whl

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 (61) hide show
  1. universal_mcp/__init__.py +0 -2
  2. universal_mcp/analytics.py +75 -0
  3. universal_mcp/applications/ahrefs/README.md +76 -0
  4. universal_mcp/applications/ahrefs/app.py +2291 -0
  5. universal_mcp/applications/application.py +95 -5
  6. universal_mcp/applications/calendly/README.md +78 -0
  7. universal_mcp/applications/calendly/__init__.py +0 -0
  8. universal_mcp/applications/calendly/app.py +1195 -0
  9. universal_mcp/applications/coda/README.md +133 -0
  10. universal_mcp/applications/coda/__init__.py +0 -0
  11. universal_mcp/applications/coda/app.py +3671 -0
  12. universal_mcp/applications/e2b/app.py +14 -28
  13. universal_mcp/applications/figma/README.md +74 -0
  14. universal_mcp/applications/figma/__init__.py +0 -0
  15. universal_mcp/applications/figma/app.py +1261 -0
  16. universal_mcp/applications/firecrawl/app.py +38 -35
  17. universal_mcp/applications/github/app.py +127 -85
  18. universal_mcp/applications/google_calendar/app.py +62 -138
  19. universal_mcp/applications/google_docs/app.py +47 -52
  20. universal_mcp/applications/google_drive/app.py +119 -113
  21. universal_mcp/applications/google_mail/app.py +124 -50
  22. universal_mcp/applications/google_sheet/app.py +89 -91
  23. universal_mcp/applications/markitdown/app.py +9 -8
  24. universal_mcp/applications/notion/app.py +254 -134
  25. universal_mcp/applications/perplexity/app.py +13 -41
  26. universal_mcp/applications/reddit/app.py +94 -85
  27. universal_mcp/applications/resend/app.py +12 -13
  28. universal_mcp/applications/{serp → serpapi}/app.py +14 -25
  29. universal_mcp/applications/tavily/app.py +11 -18
  30. universal_mcp/applications/wrike/README.md +71 -0
  31. universal_mcp/applications/wrike/__init__.py +0 -0
  32. universal_mcp/applications/wrike/app.py +1372 -0
  33. universal_mcp/applications/youtube/README.md +82 -0
  34. universal_mcp/applications/youtube/__init__.py +0 -0
  35. universal_mcp/applications/youtube/app.py +1428 -0
  36. universal_mcp/applications/zenquotes/app.py +12 -2
  37. universal_mcp/exceptions.py +9 -2
  38. universal_mcp/integrations/__init__.py +24 -1
  39. universal_mcp/integrations/agentr.py +27 -4
  40. universal_mcp/integrations/integration.py +146 -32
  41. universal_mcp/logger.py +3 -56
  42. universal_mcp/servers/__init__.py +6 -14
  43. universal_mcp/servers/server.py +201 -146
  44. universal_mcp/stores/__init__.py +7 -2
  45. universal_mcp/stores/store.py +103 -40
  46. universal_mcp/tools/__init__.py +3 -0
  47. universal_mcp/tools/adapters.py +43 -0
  48. universal_mcp/tools/func_metadata.py +213 -0
  49. universal_mcp/tools/tools.py +342 -0
  50. universal_mcp/utils/docgen.py +325 -119
  51. universal_mcp/utils/docstring_parser.py +179 -0
  52. universal_mcp/utils/dump_app_tools.py +33 -23
  53. universal_mcp/utils/installation.py +201 -10
  54. universal_mcp/utils/openapi.py +229 -46
  55. {universal_mcp-0.1.7rc1.dist-info → universal_mcp-0.1.8.dist-info}/METADATA +9 -5
  56. universal_mcp-0.1.8.dist-info/RECORD +81 -0
  57. universal_mcp-0.1.7rc1.dist-info/RECORD +0 -58
  58. /universal_mcp/{utils/bridge.py → applications/ahrefs/__init__.py} +0 -0
  59. /universal_mcp/applications/{serp → serpapi}/README.md +0 -0
  60. {universal_mcp-0.1.7rc1.dist-info → universal_mcp-0.1.8.dist-info}/WHEEL +0 -0
  61. {universal_mcp-0.1.7rc1.dist-info → universal_mcp-0.1.8.dist-info}/entry_points.txt +0 -0
@@ -15,34 +15,11 @@ class FirecrawlApp(APIApplication):
15
15
 
16
16
  def __init__(self, integration: Integration | None = None) -> None:
17
17
  super().__init__(name="firecrawl", integration=integration)
18
- self.api_key: str | None = None
19
18
 
20
- def _set_api_key(self):
21
- """
22
- Ensures the API key is loaded from the integration.
23
- Raises ValueError if the integration or key is missing/misconfigured.
24
- """
25
- if self.api_key:
26
- return
27
-
28
- if not self.integration:
29
- raise ValueError(
30
- "Integration is None. Cannot retrieve Firecrawl API Key."
31
- )
32
-
33
- credentials = self.integration.get_credentials()
34
- if not credentials:
35
- raise ValueError(
36
- f"Failed to retrieve Firecrawl API Key using integration '{self.integration.name}'. "
37
- f"Check store configuration (e.g., ensure the correct source like environment variable is set)."
38
- )
39
-
40
- self.api_key = credentials
41
-
42
19
  def _get_client(self) -> FirecrawlApiClient:
43
20
  """Initializes and returns the Firecrawl client after ensuring API key is set."""
44
- self._set_api_key()
45
- return FirecrawlApiClient(api_key=self.api_key)
21
+ api_key = self.integration.get_credentials().get("api_key")
22
+ return FirecrawlApiClient(api_key=api_key)
46
23
 
47
24
  def scrape_url(
48
25
  self, url: str, params: dict[str, Any] | None = None
@@ -57,6 +34,9 @@ class FirecrawlApp(APIApplication):
57
34
  Returns:
58
35
  A dictionary containing the scraped data on success,
59
36
  or a string containing an error message on failure.
37
+
38
+ Tags:
39
+ scrape, important
60
40
  """
61
41
  try:
62
42
  client = self._get_client()
@@ -79,6 +59,9 @@ class FirecrawlApp(APIApplication):
79
59
  Returns:
80
60
  A dictionary containing the search results on success,
81
61
  or a string containing an error message on failure.
62
+
63
+ Tags:
64
+ search, important
82
65
  """
83
66
  try:
84
67
  client = self._get_client()
@@ -104,6 +87,9 @@ class FirecrawlApp(APIApplication):
104
87
  Returns:
105
88
  A dictionary containing the job initiation response on success,
106
89
  or a string containing an error message on failure.
90
+
91
+ Tags:
92
+ crawl, async_job, start
107
93
  """
108
94
  try:
109
95
  client = self._get_client()
@@ -125,12 +111,15 @@ class FirecrawlApp(APIApplication):
125
111
  Returns:
126
112
  A dictionary containing the job status details on success,
127
113
  or a string containing an error message on failure.
114
+
115
+ Tags:
116
+ crawl, async_job, status
128
117
  """
129
118
  try:
130
119
  client = self._get_client()
131
- status = client.check_crawl_status(id=job_id)
120
+ status = client.check_crawl_status(id=job_id)
132
121
  return status
133
-
122
+
134
123
  except Exception as e:
135
124
  return f"Error checking crawl status for job ID {job_id}: {type(e).__name__} - {e}"
136
125
 
@@ -144,17 +133,19 @@ class FirecrawlApp(APIApplication):
144
133
  Returns:
145
134
  A dictionary confirming the cancellation status on success,
146
135
  or a string containing an error message on failure.
136
+
137
+ Tags:
138
+ crawl, async_job, management, cancel
147
139
  """
148
140
  try:
149
141
  client = self._get_client()
150
142
  response = client.cancel_crawl(id=job_id)
151
-
143
+
152
144
  return response
153
145
 
154
- except Exception as e:
146
+ except Exception as e:
155
147
  return f"Error cancelling crawl job ID {job_id}: {type(e).__name__} - {e}"
156
148
 
157
-
158
149
  def start_batch_scrape(
159
150
  self,
160
151
  urls: list[str],
@@ -172,6 +163,9 @@ class FirecrawlApp(APIApplication):
172
163
  Returns:
173
164
  A dictionary containing the job initiation response on success,
174
165
  or a string containing an error message on failure.
166
+
167
+ Tags:
168
+ scrape, batch, async_job, start
175
169
  """
176
170
  try:
177
171
  client = self._get_client()
@@ -193,12 +187,15 @@ class FirecrawlApp(APIApplication):
193
187
  Returns:
194
188
  A dictionary containing the job status details on success,
195
189
  or a string containing an error message on failure.
190
+
191
+ Tags:
192
+ scrape, batch, async_job, status
196
193
  """
197
194
  try:
198
195
  client = self._get_client()
199
- status = client.check_batch_scrape_status(id=job_id)
196
+ status = client.check_batch_scrape_status(id=job_id)
200
197
  return status
201
-
198
+
202
199
  except Exception as e:
203
200
  return f"Error checking batch scrape status for job ID {job_id}: {type(e).__name__} - {e}"
204
201
 
@@ -219,8 +216,11 @@ class FirecrawlApp(APIApplication):
219
216
  Returns:
220
217
  A dictionary containing the job initiation response on success,
221
218
  or a string containing an error message on failure.
219
+
220
+ Tags:
221
+ extract, ai, async_job, start
222
222
  """
223
-
223
+
224
224
  try:
225
225
  client = self._get_client()
226
226
  response = client.async_extract(
@@ -241,12 +241,15 @@ class FirecrawlApp(APIApplication):
241
241
  Returns:
242
242
  A dictionary containing the job status details on success,
243
243
  or a string containing an error message on failure.
244
+
245
+ Tags:
246
+ extract, ai, async_job, status
244
247
  """
245
248
  try:
246
249
  client = self._get_client()
247
250
  status = client.get_extract_status(job_id=job_id)
248
251
  return status
249
-
252
+
250
253
  except Exception as e:
251
254
  return f"Error checking extraction status for job ID {job_id}: {type(e).__name__} - {e}"
252
255
 
@@ -262,4 +265,4 @@ class FirecrawlApp(APIApplication):
262
265
  self.check_batch_scrape_status,
263
266
  self.start_extract,
264
267
  self.check_extract_status,
265
- ]
268
+ ]
@@ -1,4 +1,4 @@
1
- from typing import Any
1
+ from typing import Any, ClassVar
2
2
 
3
3
  from loguru import logger
4
4
 
@@ -7,6 +7,8 @@ from universal_mcp.integrations import Integration
7
7
 
8
8
 
9
9
  class GithubApp(APIApplication):
10
+ APP_TAGS: ClassVar[list[str]] = ["developers-tools"]
11
+
10
12
  def __init__(self, integration: Integration) -> None:
11
13
  super().__init__(name="github", integration=integration)
12
14
  self.base_api_url = "https://api.github.com/repos"
@@ -23,18 +25,24 @@ class GithubApp(APIApplication):
23
25
  }
24
26
 
25
27
  def star_repository(self, repo_full_name: str) -> str:
26
- """Star a GitHub repository
28
+ """
29
+ Stars a GitHub repository using the GitHub API and returns a status message.
27
30
 
28
31
  Args:
29
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
32
+ repo_full_name: The full name of the repository in 'owner/repo' format (e.g., 'octocat/Hello-World')
30
33
 
31
34
  Returns:
35
+ A string message indicating whether the starring operation was successful, the repository was not found, or an error occurred
32
36
 
33
- A confirmation message
37
+ Raises:
38
+ RequestException: If there are network connectivity issues or API request failures
39
+ ValueError: If the repository name format is invalid
40
+
41
+ Tags:
42
+ star, github, api, action, social, repository, important
34
43
  """
35
44
  url = f"https://api.github.com/user/starred/{repo_full_name}"
36
45
  response = self._put(url, data={})
37
-
38
46
  if response.status_code == 204:
39
47
  return f"Successfully starred repository {repo_full_name}"
40
48
  elif response.status_code == 404:
@@ -44,23 +52,29 @@ class GithubApp(APIApplication):
44
52
  return f"Error starring repository: {response.text}"
45
53
 
46
54
  def list_commits(self, repo_full_name: str) -> str:
47
- """List recent commits for a GitHub repository
55
+ """
56
+ Retrieves and formats a list of recent commits from a GitHub repository
48
57
 
49
58
  Args:
50
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
59
+ repo_full_name: The full name of the repository in 'owner/repo' format
51
60
 
52
61
  Returns:
53
- A formatted list of recent commits
62
+ A formatted string containing the most recent 12 commits, including commit hash, message, and author
63
+
64
+ Raises:
65
+ requests.exceptions.HTTPError: When the GitHub API request fails (e.g., repository not found, rate limit exceeded)
66
+ requests.exceptions.RequestException: When network issues or other request-related problems occur
67
+
68
+ Tags:
69
+ list, read, commits, github, history, api, important
54
70
  """
55
71
  repo_full_name = repo_full_name.strip()
56
72
  url = f"{self.base_api_url}/{repo_full_name}/commits"
57
73
  response = self._get(url)
58
74
  response.raise_for_status()
59
-
60
75
  commits = response.json()
61
76
  if not commits:
62
77
  return f"No commits found for repository {repo_full_name}"
63
-
64
78
  result = f"Recent commits for {repo_full_name}:\n\n"
65
79
  for commit in commits[:12]: # Limit to 12 commits
66
80
  sha = commit.get("sha", "")[:7]
@@ -68,54 +82,63 @@ class GithubApp(APIApplication):
68
82
  author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
69
83
 
70
84
  result += f"- {sha}: {message} (by {author})\n"
71
-
72
85
  return result
73
86
 
74
87
  def list_branches(self, repo_full_name: str) -> str:
75
- """List branches for a GitHub repository
88
+ """
89
+ Lists all branches for a specified GitHub repository and returns them in a formatted string representation.
76
90
 
77
91
  Args:
78
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
92
+ repo_full_name: The full name of the repository in 'owner/repo' format (e.g., 'octocat/Hello-World')
79
93
 
80
94
  Returns:
81
- A formatted list of branches
95
+ A formatted string containing the list of branches, or a message indicating no branches were found
96
+
97
+ Raises:
98
+ HTTPError: When the GitHub API request fails (e.g., repository not found, authentication error)
99
+ RequestException: When there are network connectivity issues or API communication problems
100
+
101
+ Tags:
102
+ list, branches, github, read, api, repository, important
82
103
  """
83
104
  repo_full_name = repo_full_name.strip()
84
105
  url = f"{self.base_api_url}/{repo_full_name}/branches"
85
106
  response = self._get(url)
86
107
  response.raise_for_status()
87
-
88
108
  branches = response.json()
89
109
  if not branches:
90
110
  return f"No branches found for repository {repo_full_name}"
91
-
92
111
  result = f"Branches for {repo_full_name}:\n\n"
93
112
  for branch in branches:
94
113
  branch_name = branch.get("name", "Unknown")
95
114
  result += f"- {branch_name}\n"
96
-
97
115
  return result
98
116
 
99
117
  def list_pull_requests(self, repo_full_name: str, state: str = "open") -> str:
100
- """List pull requests for a GitHub repository
118
+ """
119
+ Retrieves and formats a list of pull requests for a specified GitHub repository.
101
120
 
102
121
  Args:
103
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
104
- state: The state of the pull requests to filter by (open, closed, or all)
122
+ repo_full_name: The full name of the repository in the format 'owner/repo' (e.g., 'tensorflow/tensorflow')
123
+ state: Filter for pull request state. Can be 'open', 'closed', or 'all'. Defaults to 'open'
105
124
 
106
125
  Returns:
107
- A formatted list of pull requests
126
+ A formatted string containing the list of pull requests, including PR number, title, author, and status. Returns a message if no pull requests are found.
127
+
128
+ Raises:
129
+ HTTPError: Raised when the GitHub API request fails (e.g., invalid repository name, rate limiting, or authentication issues)
130
+
131
+ Tags:
132
+ list, pull-request, github, api, read, important, fetch, query
108
133
  """
109
134
  repo_full_name = repo_full_name.strip()
110
135
  url = f"{self.base_api_url}/{repo_full_name}/pulls"
111
136
  params = {"state": state}
112
137
  response = self._get(url, params=params)
113
138
  response.raise_for_status()
114
-
115
139
  pull_requests = response.json()
116
140
  if not pull_requests:
117
141
  return f"No pull requests found for repository {repo_full_name} with state '{state}'"
118
-
119
142
  result = f"Pull requests for {repo_full_name} (State: {state}):\n\n"
120
143
  for pr in pull_requests:
121
144
  pr_title = pr.get("title", "No Title")
@@ -126,7 +149,6 @@ class GithubApp(APIApplication):
126
149
  result += (
127
150
  f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
128
151
  )
129
-
130
152
  return result
131
153
 
132
154
  def list_issues(
@@ -138,62 +160,72 @@ class GithubApp(APIApplication):
138
160
  per_page: int = 30,
139
161
  page: int = 1,
140
162
  ) -> list[dict[str, Any]]:
141
- """List issues for a GitHub repository
163
+ """
164
+ Retrieves a list of issues from a specified GitHub repository with optional filtering parameters.
142
165
 
143
166
  Args:
144
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
145
- state: State of issues to return (open, closed, all). Default: open
146
- assignee: Filter by assignee. Use 'none' for no assignee, '*' for any assignee
147
- labels: Comma-separated list of label names (e.g. "bug,ui,@high")
148
- per_page: The number of results per page (max 100)
149
- page: The page number of the results to fetch
167
+ repo_full_name: The full name of the repository in 'owner/repo' format
168
+ state: Filter issues by state ('open', 'closed', 'all'). Defaults to 'open'
169
+ assignee: Filter by assignee username. Use 'none' for unassigned issues, '*' for assigned issues
170
+ labels: Comma-separated string of label names to filter by (e.g., 'bug,ui,@high')
171
+ per_page: Number of results per page (max 100). Defaults to 30
172
+ page: Page number for pagination. Defaults to 1
150
173
 
151
174
  Returns:
152
- The complete GitHub API response
175
+ List of dictionaries containing issue data from the GitHub API response
176
+
177
+ Raises:
178
+ HTTPError: When the GitHub API request fails (e.g., invalid repository name, authentication failure)
179
+ RequestException: When there are network connectivity issues or other request-related problems
180
+
181
+ Tags:
182
+ list, issues, github, api, read, filter, pagination, important, project-management
153
183
  """
154
184
  repo_full_name = repo_full_name.strip()
155
185
  url = f"{self.base_api_url}/{repo_full_name}/issues"
156
-
157
186
  params = {"state": state, "per_page": per_page, "page": page}
158
-
159
187
  if assignee:
160
188
  params["assignee"] = assignee
161
189
  if labels:
162
190
  params["labels"] = labels
163
-
164
191
  response = self._get(url, params=params)
165
192
  response.raise_for_status()
166
193
  return response.json()
167
194
 
168
195
  def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
169
- """Get a specific pull request for a GitHub repository
196
+ """
197
+ Retrieves and formats detailed information about a specific GitHub pull request from a repository
170
198
 
171
199
  Args:
172
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
173
- pull_number: The number of the pull request to retrieve
200
+ repo_full_name: The full repository name in 'owner/repo' format (e.g., 'octocat/Hello-World')
201
+ pull_number: The numeric identifier of the pull request to retrieve
174
202
 
175
203
  Returns:
176
- A formatted string with pull request details
204
+ A formatted string containing pull request details including title, creator, status, and description
205
+
206
+ Raises:
207
+ HTTPError: Raised when the GitHub API request fails (e.g., invalid repository name, non-existent PR number, or authentication issues)
208
+ RequestException: Raised when there are network connectivity issues or other request-related problems
209
+
210
+ Tags:
211
+ get, read, github, pull-request, api, fetch, format, important
177
212
  """
178
213
  repo_full_name = repo_full_name.strip()
179
214
  url = f"{self.base_api_url}/{repo_full_name}/pulls/{pull_number}"
180
215
  response = self._get(url)
181
216
  response.raise_for_status()
182
-
183
217
  pr = response.json()
184
218
  pr_title = pr.get("title", "No Title")
185
219
  pr_number = pr.get("number", "Unknown")
186
220
  pr_state = pr.get("state", "Unknown")
187
221
  pr_user = pr.get("user", {}).get("login", "Unknown")
188
222
  pr_body = pr.get("body", "No description provided.")
189
-
190
223
  result = (
191
224
  f"Pull Request #{pr_number}: {pr_title}\n"
192
225
  f"Created by: {pr_user}\n"
193
226
  f"Status: {pr_state}\n"
194
227
  f"Description: {pr_body}\n"
195
228
  )
196
-
197
229
  return result
198
230
 
199
231
  def create_pull_request(
@@ -207,7 +239,8 @@ class GithubApp(APIApplication):
207
239
  maintainer_can_modify: bool = True,
208
240
  draft: bool = False,
209
241
  ) -> dict[str, Any]:
210
- """Create a new pull request for a GitHub repository
242
+ """
243
+ Creates a new pull request in a GitHub repository, optionally converting an existing issue into a pull request.
211
244
 
212
245
  Args:
213
246
  repo_full_name: The full name of the repository (e.g. 'owner/repo')
@@ -215,24 +248,28 @@ class GithubApp(APIApplication):
215
248
  base: The name of the branch you want the changes pulled into
216
249
  title: The title of the new pull request (required if issue is not specified)
217
250
  body: The contents of the pull request
218
- issue: An issue number to convert to a pull request. If specified, the issue's
219
- title, body, and comments will be used for the pull request
251
+ issue: An issue number to convert to a pull request. If specified, the issue's title, body, and comments will be used
220
252
  maintainer_can_modify: Indicates whether maintainers can modify the pull request
221
253
  draft: Indicates whether the pull request is a draft
222
254
 
223
255
  Returns:
224
- The complete GitHub API response
256
+ A dictionary containing the complete GitHub API response
257
+
258
+ Raises:
259
+ ValueError: Raised when neither 'title' nor 'issue' parameter is specified
260
+ HTTPError: Raised when the GitHub API request fails
261
+
262
+ Tags:
263
+ create, pull-request, github, api, write, important
225
264
  """
226
265
  repo_full_name = repo_full_name.strip()
227
266
  url = f"{self.base_api_url}/{repo_full_name}/pulls"
228
-
229
267
  pull_request_data = {
230
268
  "head": head,
231
269
  "base": base,
232
270
  "maintainer_can_modify": maintainer_can_modify,
233
271
  "draft": draft,
234
272
  }
235
-
236
273
  if issue is not None:
237
274
  pull_request_data["issue"] = issue
238
275
  else:
@@ -241,7 +278,6 @@ class GithubApp(APIApplication):
241
278
  pull_request_data["title"] = title
242
279
  if body is not None:
243
280
  pull_request_data["body"] = body
244
-
245
281
  response = self._post(url, pull_request_data)
246
282
  response.raise_for_status()
247
283
  return response.json()
@@ -249,25 +285,27 @@ class GithubApp(APIApplication):
249
285
  def create_issue(
250
286
  self, repo_full_name: str, title: str, body: str = "", labels=None
251
287
  ) -> str:
252
- """Create a new issue in a GitHub repository
288
+ """
289
+ Creates a new issue in a specified GitHub repository with a title, body content, and optional labels.
253
290
 
254
291
  Args:
255
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
292
+ repo_full_name: The full name of the repository in 'owner/repo' format
256
293
  title: The title of the issue
257
- body: The contents of the issue
258
- labels: Labels to associate with this issue. Enter as a comma-separated string
259
- (e.g. "bug,enhancement,documentation").
260
- NOTE: Only users with push access can set labels for new issues.
261
- Labels are silently dropped otherwise.
294
+ body: The contents/description of the issue (defaults to empty string)
295
+ labels: Labels to associate with the issue, as a comma-separated string or list. Only users with push access can set labels
262
296
 
263
297
  Returns:
264
- A confirmation message with the new issue details
298
+ A string containing a confirmation message with the issue number, title, and URL
299
+
300
+ Raises:
301
+ HTTPError: When the GitHub API request fails (e.g., invalid repository name, authentication issues, or insufficient permissions)
302
+
303
+ Tags:
304
+ create, issues, github, api, project-management, write, important
265
305
  """
266
306
  repo_full_name = repo_full_name.strip()
267
307
  url = f"{self.base_api_url}/{repo_full_name}/issues"
268
-
269
308
  issue_data = {"title": title, "body": body}
270
-
271
309
  if labels:
272
310
  if isinstance(labels, str):
273
311
  labels_list = [
@@ -276,14 +314,11 @@ class GithubApp(APIApplication):
276
314
  issue_data["labels"] = labels_list
277
315
  else:
278
316
  issue_data["labels"] = labels
279
-
280
317
  response = self._post(url, issue_data)
281
318
  response.raise_for_status()
282
-
283
319
  issue = response.json()
284
320
  issue_number = issue.get("number", "Unknown")
285
321
  issue_url = issue.get("html_url", "")
286
-
287
322
  return (
288
323
  f"Successfully created issue #{issue_number}:\n"
289
324
  f"Title: {title}\n"
@@ -293,31 +328,33 @@ class GithubApp(APIApplication):
293
328
  def list_repo_activities(
294
329
  self, repo_full_name: str, direction: str = "desc", per_page: int = 30
295
330
  ) -> str:
296
- """List activities for a GitHub repository
331
+ """
332
+ Retrieves and formats a list of activities for a specified GitHub repository.
297
333
 
298
334
  Args:
299
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
300
- direction: The direction to sort the results by (asc or desc). Default: desc
301
- per_page: The number of results per page (max 100). Default: 30
335
+ repo_full_name: The full name of the repository in 'owner/repo' format
336
+ direction: The sort direction for results ('asc' or 'desc'). Defaults to 'desc'
337
+ per_page: Number of activities to return per page (1-100). Defaults to 30
302
338
 
303
339
  Returns:
304
- A formatted list of repository activities
340
+ A formatted string containing a list of repository activities, including timestamps and actor names. Returns a 'No activities' message if no activities are found.
341
+
342
+ Raises:
343
+ HTTPError: Raised when the GitHub API request fails
344
+ ValueError: May be raised if repo_full_name is invalid or empty after stripping
345
+
346
+ Tags:
347
+ list, activity, github, read, events, api, query, format
305
348
  """
306
349
  repo_full_name = repo_full_name.strip()
307
350
  url = f"{self.base_api_url}/{repo_full_name}/activity"
308
-
309
- # Build query parameters
310
351
  params = {"direction": direction, "per_page": per_page}
311
-
312
352
  response = self._get(url, params=params)
313
353
  response.raise_for_status()
314
-
315
354
  activities = response.json()
316
355
  if not activities:
317
356
  return f"No activities found for repository {repo_full_name}"
318
-
319
357
  result = f"Repository activities for {repo_full_name}:\n\n"
320
-
321
358
  for activity in activities:
322
359
  # Extract common fields
323
360
  timestamp = activity.get("timestamp", "Unknown time")
@@ -327,7 +364,6 @@ class GithubApp(APIApplication):
327
364
 
328
365
  # Create a simple description of the activity
329
366
  result += f"- {actor_name} performed an activity at {timestamp}\n"
330
-
331
367
  return result
332
368
 
333
369
  def update_issue(
@@ -340,22 +376,29 @@ class GithubApp(APIApplication):
340
376
  state: str = None,
341
377
  state_reason: str = None,
342
378
  ) -> dict[str, Any]:
343
- """Update an issue in a GitHub repository
379
+ """
380
+ Updates an existing GitHub issue with specified parameters including title, body, assignee, state, and state reason.
344
381
 
345
382
  Args:
346
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
347
- issue_number: The number that identifies the issue
348
- title: The title of the issue
349
- body: The contents of the issue
350
- assignee: Username to assign to this issue
351
- state: State of the issue (open or closed)
352
- state_reason: Reason for state change (completed, not_planned, reopened, null)
383
+ repo_full_name: The full name of the repository in 'owner/repo' format
384
+ issue_number: The unique identifier number of the issue to update
385
+ title: The new title of the issue (optional)
386
+ body: The new content/description of the issue (optional)
387
+ assignee: GitHub username to assign to the issue (optional)
388
+ state: The desired state of the issue ('open' or 'closed') (optional)
389
+ state_reason: The reason for state change ('completed', 'not_planned', 'reopened', or null) (optional)
353
390
 
354
391
  Returns:
355
- The complete GitHub API response
392
+ A dictionary containing the complete GitHub API response with updated issue details
393
+
394
+ Raises:
395
+ HTTPError: Raised when the GitHub API request fails (e.g., invalid repository, non-existent issue, insufficient permissions)
396
+ RequestException: Raised when there's a network error or API connectivity issue
397
+
398
+ Tags:
399
+ github, issues, update, api, project-management, write, important
356
400
  """
357
401
  url = f"{self.base_api_url}/{repo_full_name}/issues/{issue_number}"
358
-
359
402
  update_data = {}
360
403
  if title is not None:
361
404
  update_data["title"] = title
@@ -367,7 +410,6 @@ class GithubApp(APIApplication):
367
410
  update_data["state"] = state
368
411
  if state_reason is not None:
369
412
  update_data["state_reason"] = state_reason
370
-
371
413
  response = self._patch(url, update_data)
372
414
  response.raise_for_status()
373
415
  return response.json()