universal-mcp 0.1.7rc2__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.
- universal_mcp/__init__.py +0 -2
- universal_mcp/analytics.py +75 -0
- universal_mcp/applications/ahrefs/README.md +76 -0
- universal_mcp/applications/ahrefs/app.py +2291 -0
- universal_mcp/applications/application.py +95 -5
- universal_mcp/applications/calendly/README.md +78 -0
- universal_mcp/applications/calendly/__init__.py +0 -0
- universal_mcp/applications/calendly/app.py +1195 -0
- universal_mcp/applications/coda/README.md +133 -0
- universal_mcp/applications/coda/__init__.py +0 -0
- universal_mcp/applications/coda/app.py +3671 -0
- universal_mcp/applications/e2b/app.py +14 -35
- universal_mcp/applications/figma/README.md +74 -0
- universal_mcp/applications/figma/__init__.py +0 -0
- universal_mcp/applications/figma/app.py +1261 -0
- universal_mcp/applications/firecrawl/app.py +29 -32
- universal_mcp/applications/github/app.py +127 -85
- universal_mcp/applications/google_calendar/app.py +62 -138
- universal_mcp/applications/google_docs/app.py +47 -52
- universal_mcp/applications/google_drive/app.py +119 -113
- universal_mcp/applications/google_mail/app.py +124 -50
- universal_mcp/applications/google_sheet/app.py +89 -91
- universal_mcp/applications/markitdown/app.py +9 -8
- universal_mcp/applications/notion/app.py +254 -134
- universal_mcp/applications/perplexity/app.py +13 -45
- universal_mcp/applications/reddit/app.py +94 -85
- universal_mcp/applications/resend/app.py +12 -23
- universal_mcp/applications/{serp → serpapi}/app.py +14 -33
- universal_mcp/applications/tavily/app.py +11 -28
- universal_mcp/applications/wrike/README.md +71 -0
- universal_mcp/applications/wrike/__init__.py +0 -0
- universal_mcp/applications/wrike/app.py +1372 -0
- universal_mcp/applications/youtube/README.md +82 -0
- universal_mcp/applications/youtube/__init__.py +0 -0
- universal_mcp/applications/youtube/app.py +1428 -0
- universal_mcp/applications/zenquotes/app.py +12 -2
- universal_mcp/exceptions.py +9 -2
- universal_mcp/integrations/__init__.py +24 -1
- universal_mcp/integrations/agentr.py +27 -4
- universal_mcp/integrations/integration.py +143 -30
- universal_mcp/logger.py +3 -56
- universal_mcp/servers/__init__.py +6 -14
- universal_mcp/servers/server.py +201 -146
- universal_mcp/stores/__init__.py +7 -2
- universal_mcp/stores/store.py +103 -40
- universal_mcp/tools/__init__.py +3 -0
- universal_mcp/tools/adapters.py +43 -0
- universal_mcp/tools/func_metadata.py +213 -0
- universal_mcp/tools/tools.py +342 -0
- universal_mcp/utils/docgen.py +325 -119
- universal_mcp/utils/docstring_parser.py +179 -0
- universal_mcp/utils/dump_app_tools.py +33 -23
- universal_mcp/utils/installation.py +199 -8
- universal_mcp/utils/openapi.py +229 -46
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8.dist-info}/METADATA +9 -5
- universal_mcp-0.1.8.dist-info/RECORD +81 -0
- universal_mcp-0.1.7rc2.dist-info/RECORD +0 -58
- /universal_mcp/{utils/bridge.py → applications/ahrefs/__init__.py} +0 -0
- /universal_mcp/applications/{serp → serpapi}/README.md +0 -0
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8.dist-info}/entry_points.txt +0 -0
@@ -15,41 +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
|
-
|
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("Integration is None. Cannot retrieve Firecrawl API Key.")
|
30
|
-
|
31
|
-
credentials = self.integration.get_credentials()
|
32
|
-
if not credentials:
|
33
|
-
raise ValueError(
|
34
|
-
f"Failed to retrieve Firecrawl API Key using integration '{self.integration.name}'. "
|
35
|
-
f"Check store configuration (e.g., ensure the correct source like environment variable is set)."
|
36
|
-
)
|
37
|
-
api_key = (
|
38
|
-
credentials.get("api_key")
|
39
|
-
or credentials.get("API_KEY")
|
40
|
-
or credentials.get("apiKey")
|
41
|
-
)
|
42
|
-
if not api_key:
|
43
|
-
raise ValueError(
|
44
|
-
f"Failed to retrieve Firecrawl API Key using integration '{self.integration.name}'. "
|
45
|
-
f"Check store configuration (e.g., ensure the correct environment variable is set)."
|
46
|
-
)
|
47
|
-
self.api_key = api_key
|
48
18
|
|
49
19
|
def _get_client(self) -> FirecrawlApiClient:
|
50
20
|
"""Initializes and returns the Firecrawl client after ensuring API key is set."""
|
51
|
-
self.
|
52
|
-
return FirecrawlApiClient(api_key=
|
21
|
+
api_key = self.integration.get_credentials().get("api_key")
|
22
|
+
return FirecrawlApiClient(api_key=api_key)
|
53
23
|
|
54
24
|
def scrape_url(
|
55
25
|
self, url: str, params: dict[str, Any] | None = None
|
@@ -64,6 +34,9 @@ class FirecrawlApp(APIApplication):
|
|
64
34
|
Returns:
|
65
35
|
A dictionary containing the scraped data on success,
|
66
36
|
or a string containing an error message on failure.
|
37
|
+
|
38
|
+
Tags:
|
39
|
+
scrape, important
|
67
40
|
"""
|
68
41
|
try:
|
69
42
|
client = self._get_client()
|
@@ -86,6 +59,9 @@ class FirecrawlApp(APIApplication):
|
|
86
59
|
Returns:
|
87
60
|
A dictionary containing the search results on success,
|
88
61
|
or a string containing an error message on failure.
|
62
|
+
|
63
|
+
Tags:
|
64
|
+
search, important
|
89
65
|
"""
|
90
66
|
try:
|
91
67
|
client = self._get_client()
|
@@ -111,6 +87,9 @@ class FirecrawlApp(APIApplication):
|
|
111
87
|
Returns:
|
112
88
|
A dictionary containing the job initiation response on success,
|
113
89
|
or a string containing an error message on failure.
|
90
|
+
|
91
|
+
Tags:
|
92
|
+
crawl, async_job, start
|
114
93
|
"""
|
115
94
|
try:
|
116
95
|
client = self._get_client()
|
@@ -132,6 +111,9 @@ class FirecrawlApp(APIApplication):
|
|
132
111
|
Returns:
|
133
112
|
A dictionary containing the job status details on success,
|
134
113
|
or a string containing an error message on failure.
|
114
|
+
|
115
|
+
Tags:
|
116
|
+
crawl, async_job, status
|
135
117
|
"""
|
136
118
|
try:
|
137
119
|
client = self._get_client()
|
@@ -151,6 +133,9 @@ class FirecrawlApp(APIApplication):
|
|
151
133
|
Returns:
|
152
134
|
A dictionary confirming the cancellation status on success,
|
153
135
|
or a string containing an error message on failure.
|
136
|
+
|
137
|
+
Tags:
|
138
|
+
crawl, async_job, management, cancel
|
154
139
|
"""
|
155
140
|
try:
|
156
141
|
client = self._get_client()
|
@@ -178,6 +163,9 @@ class FirecrawlApp(APIApplication):
|
|
178
163
|
Returns:
|
179
164
|
A dictionary containing the job initiation response on success,
|
180
165
|
or a string containing an error message on failure.
|
166
|
+
|
167
|
+
Tags:
|
168
|
+
scrape, batch, async_job, start
|
181
169
|
"""
|
182
170
|
try:
|
183
171
|
client = self._get_client()
|
@@ -199,6 +187,9 @@ class FirecrawlApp(APIApplication):
|
|
199
187
|
Returns:
|
200
188
|
A dictionary containing the job status details on success,
|
201
189
|
or a string containing an error message on failure.
|
190
|
+
|
191
|
+
Tags:
|
192
|
+
scrape, batch, async_job, status
|
202
193
|
"""
|
203
194
|
try:
|
204
195
|
client = self._get_client()
|
@@ -225,6 +216,9 @@ class FirecrawlApp(APIApplication):
|
|
225
216
|
Returns:
|
226
217
|
A dictionary containing the job initiation response on success,
|
227
218
|
or a string containing an error message on failure.
|
219
|
+
|
220
|
+
Tags:
|
221
|
+
extract, ai, async_job, start
|
228
222
|
"""
|
229
223
|
|
230
224
|
try:
|
@@ -247,6 +241,9 @@ class FirecrawlApp(APIApplication):
|
|
247
241
|
Returns:
|
248
242
|
A dictionary containing the job status details on success,
|
249
243
|
or a string containing an error message on failure.
|
244
|
+
|
245
|
+
Tags:
|
246
|
+
extract, ai, async_job, status
|
250
247
|
"""
|
251
248
|
try:
|
252
249
|
client = self._get_client()
|
@@ -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
|
-
"""
|
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
|
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
|
-
|
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
|
-
"""
|
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
|
59
|
+
repo_full_name: The full name of the repository in 'owner/repo' format
|
51
60
|
|
52
61
|
Returns:
|
53
|
-
A formatted
|
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
|
-
"""
|
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
|
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
|
-
"""
|
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
|
104
|
-
state:
|
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
|
-
"""
|
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
|
145
|
-
state:
|
146
|
-
assignee: Filter by assignee. Use 'none' for
|
147
|
-
labels: Comma-separated
|
148
|
-
per_page:
|
149
|
-
page:
|
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
|
-
|
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
|
-
"""
|
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
|
173
|
-
pull_number: The
|
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
|
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
|
-
"""
|
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
|
-
|
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
|
-
"""
|
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
|
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
|
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
|
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
|
-
"""
|
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
|
300
|
-
direction: The direction
|
301
|
-
per_page:
|
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
|
-
"""
|
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
|
347
|
-
issue_number: The number
|
348
|
-
title: The title of the issue
|
349
|
-
body: The
|
350
|
-
assignee:
|
351
|
-
state:
|
352
|
-
state_reason:
|
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
|
-
|
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()
|