universal-mcp 0.1.7rc2__py3-none-any.whl → 0.1.8rc2__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/applications/application.py +6 -5
- universal_mcp/applications/calendly/README.md +78 -0
- universal_mcp/applications/calendly/app.py +954 -0
- universal_mcp/applications/e2b/app.py +18 -12
- universal_mcp/applications/firecrawl/app.py +28 -1
- universal_mcp/applications/github/app.py +150 -107
- universal_mcp/applications/google_calendar/app.py +72 -137
- universal_mcp/applications/google_docs/app.py +35 -15
- universal_mcp/applications/google_drive/app.py +84 -55
- universal_mcp/applications/google_mail/app.py +143 -53
- universal_mcp/applications/google_sheet/app.py +61 -38
- universal_mcp/applications/markitdown/app.py +12 -11
- universal_mcp/applications/notion/app.py +199 -89
- universal_mcp/applications/perplexity/app.py +17 -15
- universal_mcp/applications/reddit/app.py +110 -101
- universal_mcp/applications/resend/app.py +14 -7
- universal_mcp/applications/{serp → serpapi}/app.py +14 -7
- universal_mcp/applications/tavily/app.py +13 -10
- universal_mcp/applications/wrike/README.md +71 -0
- universal_mcp/applications/wrike/__init__.py +0 -0
- universal_mcp/applications/wrike/app.py +1044 -0
- universal_mcp/applications/youtube/README.md +82 -0
- universal_mcp/applications/youtube/__init__.py +0 -0
- universal_mcp/applications/youtube/app.py +986 -0
- universal_mcp/applications/zenquotes/app.py +13 -3
- universal_mcp/exceptions.py +8 -2
- universal_mcp/integrations/__init__.py +15 -1
- universal_mcp/integrations/integration.py +132 -27
- universal_mcp/servers/__init__.py +6 -15
- universal_mcp/servers/server.py +208 -149
- universal_mcp/stores/__init__.py +7 -2
- universal_mcp/stores/store.py +103 -42
- universal_mcp/tools/__init__.py +3 -0
- universal_mcp/tools/adapters.py +40 -0
- universal_mcp/tools/func_metadata.py +214 -0
- universal_mcp/tools/tools.py +285 -0
- universal_mcp/utils/docgen.py +277 -123
- universal_mcp/utils/docstring_parser.py +156 -0
- universal_mcp/utils/openapi.py +149 -40
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8rc2.dist-info}/METADATA +8 -4
- universal_mcp-0.1.8rc2.dist-info/RECORD +71 -0
- universal_mcp-0.1.7rc2.dist-info/RECORD +0 -58
- /universal_mcp/{utils/bridge.py → applications/calendly/__init__.py} +0 -0
- /universal_mcp/applications/{serp → serpapi}/README.md +0 -0
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8rc2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8rc2.dist-info}/entry_points.txt +0 -0
@@ -45,48 +45,48 @@ class RedditApp(APIApplication):
|
|
45
45
|
def get_subreddit_posts(
|
46
46
|
self, subreddit: str, limit: int = 5, timeframe: str = "day"
|
47
47
|
) -> str:
|
48
|
-
"""
|
49
|
-
|
48
|
+
"""
|
49
|
+
Retrieves and formats top posts from a specified subreddit within a given timeframe using the Reddit API
|
50
|
+
|
50
51
|
Args:
|
51
|
-
subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/'
|
52
|
-
limit: The maximum number of posts to return (default: 5, max: 100)
|
53
|
-
timeframe: The time period for top posts. Valid options: 'hour', 'day', 'week', 'month', 'year', 'all' (default: 'day')
|
54
|
-
|
52
|
+
subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/' prefix
|
53
|
+
limit: The maximum number of posts to return (default: 5, max: 100)
|
54
|
+
timeframe: The time period for top posts. Valid options: 'hour', 'day', 'week', 'month', 'year', 'all' (default: 'day')
|
55
|
+
|
55
56
|
Returns:
|
56
|
-
A formatted string
|
57
|
+
A formatted string containing a numbered list of top posts, including titles, authors, scores, and URLs, or an error message if the request fails
|
58
|
+
|
59
|
+
Raises:
|
60
|
+
RequestException: When the HTTP request to the Reddit API fails
|
61
|
+
JSONDecodeError: When the API response contains invalid JSON
|
62
|
+
|
63
|
+
Tags:
|
64
|
+
fetch, reddit, api, list, social-media, important, read-only
|
57
65
|
"""
|
58
66
|
valid_timeframes = ["hour", "day", "week", "month", "year", "all"]
|
59
67
|
if timeframe not in valid_timeframes:
|
60
68
|
return f"Error: Invalid timeframe '{timeframe}'. Please use one of: {', '.join(valid_timeframes)}"
|
61
|
-
|
62
69
|
if not 1 <= limit <= 100:
|
63
70
|
return (
|
64
71
|
f"Error: Invalid limit '{limit}'. Please use a value between 1 and 100."
|
65
72
|
)
|
66
|
-
|
67
73
|
url = f"{self.base_api_url}/r/{subreddit}/top"
|
68
74
|
params = {"limit": limit, "t": timeframe}
|
69
|
-
|
70
75
|
logger.info(
|
71
76
|
f"Requesting top {limit} posts from r/{subreddit} for timeframe '{timeframe}'"
|
72
77
|
)
|
73
78
|
response = self._get(url, params=params)
|
74
|
-
|
75
79
|
data = response.json()
|
76
|
-
|
77
80
|
if "error" in data:
|
78
81
|
logger.error(
|
79
82
|
f"Reddit API error: {data['error']} - {data.get('message', '')}"
|
80
83
|
)
|
81
84
|
return f"Error from Reddit API: {data['error']} - {data.get('message', '')}"
|
82
|
-
|
83
85
|
posts = data.get("data", {}).get("children", [])
|
84
|
-
|
85
86
|
if not posts:
|
86
87
|
return (
|
87
88
|
f"No top posts found in r/{subreddit} for the timeframe '{timeframe}'."
|
88
89
|
)
|
89
|
-
|
90
90
|
result_lines = [
|
91
91
|
f"Top {len(posts)} posts from r/{subreddit} (timeframe: {timeframe}):\n"
|
92
92
|
]
|
@@ -100,31 +100,36 @@ class RedditApp(APIApplication):
|
|
100
100
|
|
101
101
|
result_lines.append(f'{i + 1}. "{title}" by u/{author} (Score: {score})')
|
102
102
|
result_lines.append(f" Link: {full_url}")
|
103
|
-
|
104
103
|
return "\n".join(result_lines)
|
105
104
|
|
106
105
|
def search_subreddits(
|
107
106
|
self, query: str, limit: int = 5, sort: str = "relevance"
|
108
107
|
) -> str:
|
109
|
-
"""
|
110
|
-
|
108
|
+
"""
|
109
|
+
Searches Reddit for subreddits matching a given query string and returns a formatted list of results including subreddit names, subscriber counts, and descriptions.
|
110
|
+
|
111
111
|
Args:
|
112
|
-
query: The text to search for in subreddit names and descriptions
|
113
|
-
limit: The maximum number of subreddits to return (default: 5
|
114
|
-
sort: The order of results
|
115
|
-
|
112
|
+
query: The text to search for in subreddit names and descriptions
|
113
|
+
limit: The maximum number of subreddits to return, between 1 and 100 (default: 5)
|
114
|
+
sort: The order of results, either 'relevance' or 'activity' (default: 'relevance')
|
115
|
+
|
116
116
|
Returns:
|
117
|
-
A formatted string
|
117
|
+
A formatted string containing a list of matching subreddits with their names, subscriber counts, and descriptions, or an error message if the search fails or parameters are invalid
|
118
|
+
|
119
|
+
Raises:
|
120
|
+
RequestException: When the HTTP request to Reddit's API fails
|
121
|
+
JSONDecodeError: When the API response contains invalid JSON
|
122
|
+
|
123
|
+
Tags:
|
124
|
+
search, important, reddit, api, query, format, list, validation
|
118
125
|
"""
|
119
126
|
valid_sorts = ["relevance", "activity"]
|
120
127
|
if sort not in valid_sorts:
|
121
128
|
return f"Error: Invalid sort option '{sort}'. Please use one of: {', '.join(valid_sorts)}"
|
122
|
-
|
123
129
|
if not 1 <= limit <= 100:
|
124
130
|
return (
|
125
131
|
f"Error: Invalid limit '{limit}'. Please use a value between 1 and 100."
|
126
132
|
)
|
127
|
-
|
128
133
|
url = f"{self.base_api_url}/subreddits/search"
|
129
134
|
params = {
|
130
135
|
"q": query,
|
@@ -133,25 +138,19 @@ class RedditApp(APIApplication):
|
|
133
138
|
# Optionally include NSFW results? Defaulting to false for safety.
|
134
139
|
# "include_over_18": "false"
|
135
140
|
}
|
136
|
-
|
137
141
|
logger.info(
|
138
142
|
f"Searching for subreddits matching '{query}' (limit: {limit}, sort: {sort})"
|
139
143
|
)
|
140
144
|
response = self._get(url, params=params)
|
141
|
-
|
142
145
|
data = response.json()
|
143
|
-
|
144
146
|
if "error" in data:
|
145
147
|
logger.error(
|
146
148
|
f"Reddit API error during subreddit search: {data['error']} - {data.get('message', '')}"
|
147
149
|
)
|
148
150
|
return f"Error from Reddit API during search: {data['error']} - {data.get('message', '')}"
|
149
|
-
|
150
151
|
subreddits = data.get("data", {}).get("children", [])
|
151
|
-
|
152
152
|
if not subreddits:
|
153
153
|
return f"No subreddits found matching the query '{query}'."
|
154
|
-
|
155
154
|
result_lines = [
|
156
155
|
f"Found {len(subreddits)} subreddits matching '{query}' (sorted by {sort}):\n"
|
157
156
|
]
|
@@ -173,28 +172,31 @@ class RedditApp(APIApplication):
|
|
173
172
|
)
|
174
173
|
if description:
|
175
174
|
result_lines.append(f" Description: {description}")
|
176
|
-
|
177
175
|
return "\n".join(result_lines)
|
178
176
|
|
179
177
|
def get_post_flairs(self, subreddit: str):
|
180
|
-
"""
|
181
|
-
|
178
|
+
"""
|
179
|
+
Retrieves a list of available post flairs for a specified subreddit using the Reddit API.
|
180
|
+
|
182
181
|
Args:
|
183
|
-
subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/'
|
184
|
-
|
182
|
+
subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/' prefix
|
183
|
+
|
185
184
|
Returns:
|
186
|
-
A list of dictionaries containing flair details, or
|
185
|
+
A list of dictionaries containing flair details if flairs exist, or a string message indicating no flairs are available
|
186
|
+
|
187
|
+
Raises:
|
188
|
+
RequestException: When the API request fails or network connectivity issues occur
|
189
|
+
JSONDecodeError: When the API response contains invalid JSON data
|
190
|
+
|
191
|
+
Tags:
|
192
|
+
fetch, get, reddit, flair, api, read-only
|
187
193
|
"""
|
188
|
-
|
189
194
|
url = f"{self.base_api_url}/r/{subreddit}/api/link_flair_v2"
|
190
|
-
|
191
195
|
logger.info(f"Fetching post flairs for subreddit: r/{subreddit}")
|
192
196
|
response = self._get(url)
|
193
|
-
|
194
197
|
flairs = response.json()
|
195
198
|
if not flairs:
|
196
199
|
return f"No post flairs available for r/{subreddit}."
|
197
|
-
|
198
200
|
return flairs
|
199
201
|
|
200
202
|
def create_post(
|
@@ -206,32 +208,32 @@ class RedditApp(APIApplication):
|
|
206
208
|
url: str = None,
|
207
209
|
flair_id: str = None,
|
208
210
|
):
|
209
|
-
"""
|
210
|
-
|
211
|
+
"""
|
212
|
+
Creates a new Reddit post in a specified subreddit with support for text posts, link posts, and image posts
|
213
|
+
|
211
214
|
Args:
|
212
|
-
subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/'
|
213
|
-
title: The title of the post
|
214
|
-
kind: The type of post; either 'self' (text post) or 'link' (link or image post)
|
215
|
-
text: The text content of the post; required if kind is 'self'
|
216
|
-
url: The URL of the link or image; required if kind is 'link'.
|
217
|
-
|
218
|
-
|
219
|
-
Note that .gif support can be inconsistent.
|
220
|
-
flair_id: The ID of the flair to assign to the post.
|
221
|
-
|
215
|
+
subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/'
|
216
|
+
title: The title of the post
|
217
|
+
kind: The type of post; either 'self' (text post) or 'link' (link or image post)
|
218
|
+
text: The text content of the post; required if kind is 'self'
|
219
|
+
url: The URL of the link or image; required if kind is 'link'. Must end with valid image extension for image posts
|
220
|
+
flair_id: The ID of the flair to assign to the post
|
221
|
+
|
222
222
|
Returns:
|
223
|
-
The JSON response from the Reddit API, or an error message as a string
|
224
|
-
|
223
|
+
The JSON response from the Reddit API, or an error message as a string if the API returns an error
|
224
|
+
|
225
|
+
Raises:
|
226
|
+
ValueError: Raised when kind is invalid or when required parameters (text for self posts, url for link posts) are missing
|
227
|
+
|
228
|
+
Tags:
|
229
|
+
create, post, social-media, reddit, api, important
|
225
230
|
"""
|
226
|
-
|
227
231
|
if kind not in ["self", "link"]:
|
228
232
|
raise ValueError("Invalid post kind. Must be one of 'self' or 'link'.")
|
229
|
-
|
230
233
|
if kind == "self" and not text:
|
231
234
|
raise ValueError("Text content is required for text posts.")
|
232
235
|
if kind == "link" and not url:
|
233
236
|
raise ValueError("URL is required for link posts (including images).")
|
234
|
-
|
235
237
|
data = {
|
236
238
|
"sr": subreddit,
|
237
239
|
"title": title,
|
@@ -241,13 +243,10 @@ class RedditApp(APIApplication):
|
|
241
243
|
"flair_id": flair_id,
|
242
244
|
}
|
243
245
|
data = {k: v for k, v in data.items() if v is not None}
|
244
|
-
|
245
246
|
url_api = f"{self.base_api_url}/api/submit"
|
246
247
|
logger.info(f"Submitting a new post to r/{subreddit}")
|
247
248
|
response = self._post(url_api, data=data)
|
248
249
|
response_json = response.json()
|
249
|
-
|
250
|
-
# Check for Reddit API errors in the response
|
251
250
|
if (
|
252
251
|
response_json
|
253
252
|
and "json" in response_json
|
@@ -259,27 +258,27 @@ class RedditApp(APIApplication):
|
|
259
258
|
[f"{code}: {message}" for code, message in errors]
|
260
259
|
)
|
261
260
|
return f"Reddit API error: {error_message}"
|
262
|
-
|
263
261
|
return response_json
|
264
262
|
|
265
263
|
def get_comment_by_id(self, comment_id: str) -> dict:
|
266
264
|
"""
|
267
|
-
|
268
|
-
|
265
|
+
Retrieves a specific Reddit comment using its unique identifier.
|
266
|
+
|
269
267
|
Args:
|
270
|
-
comment_id: The full unique
|
271
|
-
|
268
|
+
comment_id: The full unique identifier of the comment (prefixed with 't1_', e.g., 't1_abcdef')
|
269
|
+
|
272
270
|
Returns:
|
273
|
-
A dictionary containing the comment data,
|
271
|
+
A dictionary containing the comment data including attributes like author, body, score, etc. If the comment is not found, returns a dictionary with an error message.
|
272
|
+
|
273
|
+
Raises:
|
274
|
+
HTTPError: When the Reddit API request fails due to network issues or invalid authentication
|
275
|
+
JSONDecodeError: When the API response cannot be parsed as valid JSON
|
276
|
+
|
277
|
+
Tags:
|
278
|
+
retrieve, get, reddit, comment, api, fetch, single-item, important
|
274
279
|
"""
|
275
|
-
|
276
|
-
# Define the endpoint URL
|
277
280
|
url = f"https://oauth.reddit.com/api/info.json?id={comment_id}"
|
278
|
-
|
279
|
-
# Make the GET request to the Reddit API
|
280
|
-
|
281
281
|
response = self._get(url)
|
282
|
-
|
283
282
|
data = response.json()
|
284
283
|
comments = data.get("data", {}).get("children", [])
|
285
284
|
if comments:
|
@@ -289,72 +288,82 @@ class RedditApp(APIApplication):
|
|
289
288
|
|
290
289
|
def post_comment(self, parent_id: str, text: str) -> dict:
|
291
290
|
"""
|
292
|
-
|
293
|
-
|
291
|
+
Posts a comment to a Reddit post or comment using the Reddit API
|
292
|
+
|
294
293
|
Args:
|
295
|
-
parent_id: The full ID of the parent comment or post (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
|
296
|
-
text: The text content of the comment
|
297
|
-
|
294
|
+
parent_id: The full ID of the parent comment or post (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
|
295
|
+
text: The text content of the comment to be posted
|
296
|
+
|
298
297
|
Returns:
|
299
|
-
A dictionary containing the
|
298
|
+
A dictionary containing the Reddit API response with details about the posted comment
|
299
|
+
|
300
|
+
Raises:
|
301
|
+
RequestException: If the API request fails or returns an error status code
|
302
|
+
JSONDecodeError: If the API response cannot be parsed as JSON
|
303
|
+
|
304
|
+
Tags:
|
305
|
+
post, comment, social, reddit, api, important
|
300
306
|
"""
|
301
|
-
|
302
307
|
url = f"{self.base_api_url}/api/comment"
|
303
308
|
data = {
|
304
309
|
"parent": parent_id,
|
305
310
|
"text": text,
|
306
311
|
}
|
307
|
-
|
308
312
|
logger.info(f"Posting comment to {parent_id}")
|
309
313
|
response = self._post(url, data=data)
|
310
|
-
|
311
314
|
return response.json()
|
312
315
|
|
313
316
|
def edit_content(self, content_id: str, text: str) -> dict:
|
314
317
|
"""
|
315
|
-
|
316
|
-
|
318
|
+
Edits the text content of an existing Reddit post or comment using the Reddit API
|
319
|
+
|
317
320
|
Args:
|
318
|
-
content_id: The full ID of the content to edit (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
|
319
|
-
text: The new text content
|
320
|
-
|
321
|
+
content_id: The full ID of the content to edit (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
|
322
|
+
text: The new text content to replace the existing content
|
323
|
+
|
321
324
|
Returns:
|
322
|
-
A dictionary containing the
|
325
|
+
A dictionary containing the API response with details about the edited content
|
326
|
+
|
327
|
+
Raises:
|
328
|
+
RequestException: When the API request fails or network connectivity issues occur
|
329
|
+
ValueError: When invalid content_id format or empty text is provided
|
330
|
+
|
331
|
+
Tags:
|
332
|
+
edit, update, content, reddit, api, important
|
323
333
|
"""
|
324
|
-
|
325
334
|
url = f"{self.base_api_url}/api/editusertext"
|
326
335
|
data = {
|
327
336
|
"thing_id": content_id,
|
328
337
|
"text": text,
|
329
338
|
}
|
330
|
-
|
331
339
|
logger.info(f"Editing content {content_id}")
|
332
340
|
response = self._post(url, data=data)
|
333
|
-
|
334
341
|
return response.json()
|
335
342
|
|
336
343
|
def delete_content(self, content_id: str) -> dict:
|
337
344
|
"""
|
338
|
-
|
339
|
-
|
345
|
+
Deletes a specified Reddit post or comment using the Reddit API.
|
346
|
+
|
340
347
|
Args:
|
341
|
-
content_id: The full ID of the content to delete (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
|
342
|
-
|
348
|
+
content_id: The full ID of the content to delete (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
|
349
|
+
|
343
350
|
Returns:
|
344
|
-
A dictionary containing
|
351
|
+
A dictionary containing a success message with the deleted content ID
|
352
|
+
|
353
|
+
Raises:
|
354
|
+
HTTPError: When the API request fails or returns an error status code
|
355
|
+
RequestException: When there are network connectivity issues or API communication problems
|
356
|
+
|
357
|
+
Tags:
|
358
|
+
delete, content-management, api, reddit, important
|
345
359
|
"""
|
346
|
-
|
347
360
|
url = f"{self.base_api_url}/api/del"
|
348
361
|
data = {
|
349
362
|
"id": content_id,
|
350
363
|
}
|
351
|
-
|
352
364
|
logger.info(f"Deleting content {content_id}")
|
353
365
|
response = self._post(url, data=data)
|
354
366
|
response.raise_for_status()
|
355
|
-
|
356
|
-
# Reddit's delete endpoint returns an empty response on success.
|
357
|
-
# We'll just return a success message.
|
358
367
|
return {"message": f"Content {content_id} deleted successfully."}
|
359
368
|
|
360
369
|
def list_tools(self):
|
@@ -25,15 +25,22 @@ class ResendApp(APIApplication):
|
|
25
25
|
}
|
26
26
|
|
27
27
|
def send_email(self, to: str, subject: str, content: str) -> str:
|
28
|
-
"""
|
29
|
-
|
28
|
+
"""
|
29
|
+
Sends an email using the Resend API with specified recipient, subject, and content
|
30
|
+
|
30
31
|
Args:
|
31
|
-
to:
|
32
|
-
subject:
|
33
|
-
content:
|
34
|
-
|
32
|
+
to: Email address of the recipient
|
33
|
+
subject: Subject line of the email
|
34
|
+
content: Main body text content of the email
|
35
|
+
|
35
36
|
Returns:
|
36
|
-
|
37
|
+
String message confirming successful email delivery ('Sent Successfully')
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
ValueError: Raised when no valid credentials are found for the API
|
41
|
+
|
42
|
+
Tags:
|
43
|
+
send, email, api, communication, important
|
37
44
|
"""
|
38
45
|
credentials = self.integration.get_credentials()
|
39
46
|
if not credentials:
|
@@ -5,7 +5,7 @@ from serpapi import SerpApiClient as SerpApiSearch
|
|
5
5
|
from universal_mcp.applications.application import APIApplication
|
6
6
|
|
7
7
|
|
8
|
-
class
|
8
|
+
class SerpapiApp(APIApplication):
|
9
9
|
def __init__(self, **kwargs):
|
10
10
|
super().__init__(name="serpapi", **kwargs)
|
11
11
|
self.api_key: str | None = None
|
@@ -35,13 +35,21 @@ class SerpApp(APIApplication):
|
|
35
35
|
logger.info("SERP API Key successfully retrieved via integration.")
|
36
36
|
|
37
37
|
async def search(self, params: dict[str, any] = None) -> str:
|
38
|
-
"""
|
39
|
-
|
38
|
+
"""
|
39
|
+
Performs an asynchronous search using the SerpApi service and returns formatted search results.
|
40
|
+
|
40
41
|
Args:
|
41
|
-
params: Dictionary of engine-specific parameters (e.g., {
|
42
|
-
|
42
|
+
params: Dictionary of engine-specific parameters (e.g., {'q': 'Coffee', 'engine': 'google_light', 'location': 'Austin, TX'}). Defaults to None.
|
43
|
+
|
43
44
|
Returns:
|
44
|
-
A formatted string
|
45
|
+
A formatted string containing search results with titles, links, and snippets, or an error message if the search fails.
|
46
|
+
|
47
|
+
Raises:
|
48
|
+
httpx.HTTPStatusError: Raised when the API request fails due to HTTP errors (401 for invalid API key, 429 for rate limiting)
|
49
|
+
Exception: Raised for general errors such as network issues or invalid parameters
|
50
|
+
|
51
|
+
Tags:
|
52
|
+
search, async, web-scraping, api, serpapi, important
|
45
53
|
"""
|
46
54
|
if params is None:
|
47
55
|
params = {}
|
@@ -51,7 +59,6 @@ class SerpApp(APIApplication):
|
|
51
59
|
"engine": "google_light", # Fastest engine by default
|
52
60
|
**params, # Include any additional parameters
|
53
61
|
}
|
54
|
-
|
55
62
|
try:
|
56
63
|
search = SerpApiSearch(params)
|
57
64
|
data = search.get_dict()
|
@@ -28,13 +28,21 @@ class TavilyApp(APIApplication):
|
|
28
28
|
}
|
29
29
|
|
30
30
|
def search(self, query: str) -> str:
|
31
|
-
"""
|
32
|
-
|
31
|
+
"""
|
32
|
+
Performs a web search using Tavily's search API and returns either a direct answer or a summary of top results.
|
33
|
+
|
33
34
|
Args:
|
34
|
-
query: The search query
|
35
|
-
|
35
|
+
query: The search query string to be processed by Tavily's search engine
|
36
|
+
|
36
37
|
Returns:
|
37
|
-
|
38
|
+
A string containing either a direct answer from Tavily's AI or a formatted summary of the top 3 search results, with each result containing the title and snippet
|
39
|
+
|
40
|
+
Raises:
|
41
|
+
ValueError: When authentication credentials are invalid or missing (via validate() method)
|
42
|
+
HTTPError: When the API request fails or returns an error response
|
43
|
+
|
44
|
+
Tags:
|
45
|
+
search, ai, web, query, important, api-client, text-processing
|
38
46
|
"""
|
39
47
|
self.validate()
|
40
48
|
url = f"{self.base_url}/search"
|
@@ -50,18 +58,13 @@ class TavilyApp(APIApplication):
|
|
50
58
|
"include_domains": [],
|
51
59
|
"exclude_domains": [],
|
52
60
|
}
|
53
|
-
|
54
61
|
response = self._post(url, payload)
|
55
62
|
result = response.json()
|
56
|
-
|
57
63
|
if "answer" in result:
|
58
64
|
return result["answer"]
|
59
|
-
|
60
|
-
# Fallback to combining top results if no direct answer
|
61
65
|
summaries = []
|
62
66
|
for item in result.get("results", [])[:3]:
|
63
67
|
summaries.append(f"• {item['title']}: {item['snippet']}")
|
64
|
-
|
65
68
|
return "\n".join(summaries)
|
66
69
|
|
67
70
|
def list_tools(self):
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
# Wrike MCP Server
|
3
|
+
|
4
|
+
An MCP Server for the Wrike API.
|
5
|
+
|
6
|
+
## Supported Integrations
|
7
|
+
|
8
|
+
- AgentR
|
9
|
+
- API Key (Coming Soon)
|
10
|
+
- OAuth (Coming Soon)
|
11
|
+
|
12
|
+
## Tools
|
13
|
+
|
14
|
+
This is automatically generated from OpenAPI schema for the Wrike API.
|
15
|
+
|
16
|
+
## Supported Integrations
|
17
|
+
|
18
|
+
This tool can be integrated with any service that supports HTTP requests.
|
19
|
+
|
20
|
+
## Tool List
|
21
|
+
|
22
|
+
| Tool | Description |
|
23
|
+
|------|-------------|
|
24
|
+
| get_contacts | Retrieves a list of contacts from the server, with optional filtering and field selection. |
|
25
|
+
| get_contacts_by_contactid | Retrieves contact information for a specific contact ID, optionally returning only specified fields. |
|
26
|
+
| put_contacts_by_contactid | Updates an existing contact by contact ID with provided details such as metadata, billing and cost rates, job role, custom fields, or additional fields. |
|
27
|
+
| get_users_by_userid | Retrieves user information for a given user ID from the API endpoint. |
|
28
|
+
| put_users_by_userid | Updates a user's profile information by user ID using a PUT request. |
|
29
|
+
| get_groups | Retrieves a list of groups from the API, applying optional filtering and pagination parameters. |
|
30
|
+
| post_groups | Creates a new group with the specified title and optional details, sending a POST request to the groups endpoint. |
|
31
|
+
| get_groups_by_groupid | Retrieves details for a specific group by its group ID, optionally returning only specified fields. |
|
32
|
+
| put_groups_by_groupid | Updates an existing group identified by groupId with new properties and membership changes via a PUT request. |
|
33
|
+
| delete_groups_by_groupid | Deletes a group resource identified by the provided groupId using an HTTP DELETE request. |
|
34
|
+
| put_groups_bulk | Updates multiple group memberships in bulk by sending a PUT request with the given members data. |
|
35
|
+
| get_invitations | Retrieves all invitations from the server using a GET request. |
|
36
|
+
| post_invitations | Sends an invitation email to a user with optional details such as name, role, and custom message. |
|
37
|
+
| put_invitations_by_invitationid | Updates an existing invitation by invitation ID with optional fields such as resend, role, external, and user type ID. |
|
38
|
+
| delete_invitations_by_invitationid | Deletes an invitation specified by its invitation ID. |
|
39
|
+
| get_a_ccount | Retrieves account information from the API, optionally including only specified fields. |
|
40
|
+
| put_a_ccount | Sends a PUT request to update or create an account with the provided metadata and returns the server response as a JSON object. |
|
41
|
+
| get_workflows | Retrieves all workflows from the server using a GET request. |
|
42
|
+
| post_workflows | Creates a new workflow by sending a POST request to the workflows endpoint. |
|
43
|
+
| put_workflows_by_workflowid | Updates an existing workflow by workflow ID with optional name, hidden status, and request body data. |
|
44
|
+
| get_customfields | Retrieves all custom fields from the API and returns them as a parsed JSON object. |
|
45
|
+
| post_customfields | Creates a custom field by sending a POST request with the specified parameters to the customfields endpoint and returns the created field's data. |
|
46
|
+
| get_customfields_by_customfieldid | Retrieves details for a custom field by its unique identifier from the API. |
|
47
|
+
| put_customfields_by_customfieldid | Updates a custom field specified by its ID with the provided parameters using an HTTP PUT request. |
|
48
|
+
| delete_customfields_by_customfieldid | Deletes a custom field resource identified by its custom field ID. |
|
49
|
+
| get_folders | Retrieves a list of folders from the API, supporting extensive filtering, pagination, and field selection. |
|
50
|
+
| get_folders_by_folderid_folders | Retrieves subfolders of a specified folder, applying optional filters and pagination parameters. |
|
51
|
+
| post_folders_by_folderid_folders | Creates a new subfolder within a specified folder by folder ID, with configurable attributes such as title, description, sharing, metadata, and permissions. |
|
52
|
+
| delete_folders_by_folderid | Deletes a folder resource identified by its folder ID via an HTTP DELETE request. |
|
53
|
+
| put_folders_by_folderid | Updates a folder's properties and relationships by folder ID using a PUT request. |
|
54
|
+
| get_tasks | Retrieves tasks from the API with optional filtering, sorting, pagination, and field selection parameters. |
|
55
|
+
| get_tasks_by_taskid | Retrieves a task by its ID from the remote service, optionally returning only specified fields. |
|
56
|
+
| put_tasks_by_taskid | Updates the properties and relationships of a task specified by its ID, applying the given changes and returning the updated task data as a JSON object. |
|
57
|
+
| delete_tasks_by_taskid | Deletes a task identified by the given task ID via an HTTP DELETE request and returns the response as a JSON object. |
|
58
|
+
| post_folders_by_folderid_tasks | Creates a new task within a specified folder by folder ID, with configurable attributes such as title, description, status, importance, dates, assigned users, metadata, custom fields, and other options. |
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
## Usage
|
63
|
+
|
64
|
+
- Login to AgentR
|
65
|
+
- Follow the quickstart guide to setup MCP Server for your client
|
66
|
+
- Visit Apps Store and enable the Wrike app
|
67
|
+
- Restart the MCP Server
|
68
|
+
|
69
|
+
### Local Development
|
70
|
+
|
71
|
+
- Follow the README to test with the local MCP Server
|
File without changes
|