universal-mcp-applications 0.1.21__py3-none-any.whl → 0.1.23__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.

Potentially problematic release.


This version of universal-mcp-applications might be problematic. Click here for more details.

Files changed (78) hide show
  1. universal_mcp/applications/BEST_PRACTICES.md +166 -0
  2. universal_mcp/applications/airtable/app.py +0 -1
  3. universal_mcp/applications/apollo/app.py +0 -1
  4. universal_mcp/applications/aws_s3/app.py +40 -39
  5. universal_mcp/applications/browser_use/README.md +1 -0
  6. universal_mcp/applications/browser_use/__init__.py +0 -0
  7. universal_mcp/applications/browser_use/app.py +71 -0
  8. universal_mcp/applications/calendly/app.py +125 -125
  9. universal_mcp/applications/canva/app.py +95 -99
  10. universal_mcp/applications/confluence/app.py +0 -1
  11. universal_mcp/applications/contentful/app.py +4 -5
  12. universal_mcp/applications/domain_checker/app.py +11 -15
  13. universal_mcp/applications/e2b/app.py +4 -4
  14. universal_mcp/applications/elevenlabs/app.py +18 -15
  15. universal_mcp/applications/exa/app.py +17 -17
  16. universal_mcp/applications/falai/app.py +28 -29
  17. universal_mcp/applications/file_system/app.py +9 -9
  18. universal_mcp/applications/firecrawl/app.py +36 -36
  19. universal_mcp/applications/fireflies/app.py +55 -56
  20. universal_mcp/applications/fpl/app.py +49 -50
  21. universal_mcp/applications/ghost_content/app.py +0 -1
  22. universal_mcp/applications/github/app.py +41 -43
  23. universal_mcp/applications/google_calendar/app.py +40 -39
  24. universal_mcp/applications/google_docs/app.py +168 -232
  25. universal_mcp/applications/google_drive/app.py +212 -215
  26. universal_mcp/applications/google_gemini/app.py +1 -5
  27. universal_mcp/applications/google_mail/app.py +91 -90
  28. universal_mcp/applications/google_searchconsole/app.py +29 -29
  29. universal_mcp/applications/google_sheet/app.py +115 -115
  30. universal_mcp/applications/hashnode/README.md +6 -3
  31. universal_mcp/applications/hashnode/app.py +174 -25
  32. universal_mcp/applications/http_tools/app.py +10 -11
  33. universal_mcp/applications/hubspot/__init__.py +1 -1
  34. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +36 -7
  35. universal_mcp/applications/hubspot/api_segments/crm_api.py +368 -368
  36. universal_mcp/applications/hubspot/api_segments/marketing_api.py +115 -115
  37. universal_mcp/applications/hubspot/app.py +131 -72
  38. universal_mcp/applications/jira/app.py +0 -1
  39. universal_mcp/applications/linkedin/app.py +20 -20
  40. universal_mcp/applications/markitdown/app.py +10 -5
  41. universal_mcp/applications/ms_teams/app.py +123 -123
  42. universal_mcp/applications/openai/app.py +40 -39
  43. universal_mcp/applications/outlook/app.py +32 -32
  44. universal_mcp/applications/perplexity/app.py +4 -4
  45. universal_mcp/applications/reddit/app.py +69 -70
  46. universal_mcp/applications/resend/app.py +116 -117
  47. universal_mcp/applications/rocketlane/app.py +0 -1
  48. universal_mcp/applications/scraper/__init__.py +1 -1
  49. universal_mcp/applications/scraper/app.py +80 -81
  50. universal_mcp/applications/serpapi/app.py +14 -14
  51. universal_mcp/applications/sharepoint/app.py +19 -20
  52. universal_mcp/applications/shopify/app.py +0 -1
  53. universal_mcp/applications/slack/app.py +48 -48
  54. universal_mcp/applications/tavily/app.py +4 -4
  55. universal_mcp/applications/twitter/api_segments/compliance_api.py +13 -15
  56. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +20 -20
  57. universal_mcp/applications/twitter/api_segments/dm_events_api.py +12 -12
  58. universal_mcp/applications/twitter/api_segments/likes_api.py +12 -12
  59. universal_mcp/applications/twitter/api_segments/lists_api.py +37 -39
  60. universal_mcp/applications/twitter/api_segments/spaces_api.py +24 -24
  61. universal_mcp/applications/twitter/api_segments/trends_api.py +4 -4
  62. universal_mcp/applications/twitter/api_segments/tweets_api.py +105 -105
  63. universal_mcp/applications/twitter/api_segments/usage_api.py +4 -4
  64. universal_mcp/applications/twitter/api_segments/users_api.py +136 -136
  65. universal_mcp/applications/twitter/app.py +6 -2
  66. universal_mcp/applications/unipile/app.py +90 -97
  67. universal_mcp/applications/whatsapp/app.py +53 -54
  68. universal_mcp/applications/whatsapp/audio.py +39 -35
  69. universal_mcp/applications/whatsapp/whatsapp.py +176 -154
  70. universal_mcp/applications/whatsapp_business/app.py +92 -92
  71. universal_mcp/applications/yahoo_finance/app.py +105 -63
  72. universal_mcp/applications/youtube/app.py +193 -196
  73. universal_mcp/applications/zenquotes/__init__.py +2 -0
  74. universal_mcp/applications/zenquotes/app.py +5 -5
  75. {universal_mcp_applications-0.1.21.dist-info → universal_mcp_applications-0.1.23.dist-info}/METADATA +2 -1
  76. {universal_mcp_applications-0.1.21.dist-info → universal_mcp_applications-0.1.23.dist-info}/RECORD +78 -74
  77. {universal_mcp_applications-0.1.21.dist-info → universal_mcp_applications-0.1.23.dist-info}/WHEEL +0 -0
  78. {universal_mcp_applications-0.1.21.dist-info → universal_mcp_applications-0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -10,11 +10,14 @@ This is automatically generated from OpenAPI schema for the HashnodeApp API.
10
10
  | Tool | Description |
11
11
  |------|-------------|
12
12
  | `publish_post` | Publishes a post to Hashnode using the GraphQL API. |
13
+ | `create_draft` | Creates a draft post on Hashnode. |
14
+ | `publish_draft` | Publishes a draft post. |
13
15
  | `get_me` | Fetches details about the authenticated user. |
14
16
  | `get_publication` | Fetches details about a publication by host or ID. Only one of host or publication_id should be provided. |
15
- | `get_post` | Fetches details of a single post by slug and hostname. |
16
- | `update_post` | Updates an existing post using the GraphQL API. |
17
+ | `list_posts` | Lists posts from a publication. |
18
+ | `get_post` | Fetches details of a single post by ID, or by slug and hostname. |
19
+ | `modify_post` | Modifies an existing post using the GraphQL API. |
17
20
  | `delete_post` | Deletes a post using the GraphQL API. |
18
21
  | `add_comment` | Adds a comment to a post using the GraphQL API. |
19
22
  | `delete_comment` | Deletes a comment using the GraphQL API. |
20
- | `get_user` | Fetches details about a user by username. |
23
+ | `get_user` | Fetches details about a user by username. |
@@ -1,5 +1,4 @@
1
1
  from gql import gql
2
-
3
2
  from universal_mcp.applications.application import GraphQLApplication
4
3
  from universal_mcp.integrations import Integration
5
4
 
@@ -69,14 +68,103 @@ class HashnodeApp(GraphQLApplication):
69
68
  variables["input"]["subtitle"] = subtitle
70
69
 
71
70
  if cover_image:
72
- variables["input"]["bannerImageOptions"] = {
73
- "url": cover_image,
74
- "potrait": False,
75
- }
71
+ variables["input"]["coverImageOptions"] = {"coverImageURL": cover_image}
76
72
 
77
73
  result = self.mutate(publish_post_mutation, variables)
78
74
  return result["publishPost"]["post"]["url"]
79
75
 
76
+ def create_draft(
77
+ self,
78
+ publication_id: str,
79
+ title: str,
80
+ content: str,
81
+ tags: list[str] = None,
82
+ slug: str = None,
83
+ subtitle: str = None,
84
+ cover_image: str = None,
85
+ ) -> dict:
86
+ """
87
+ Creates a draft post on Hashnode.
88
+
89
+ Args:
90
+ publication_id: The ID of the publication to create the draft in.
91
+ title: The title of the draft.
92
+ content: The markdown content of the draft.
93
+ tags: Optional list of tag names to add to the draft.
94
+ slug: Optional custom URL slug for the draft.
95
+ subtitle: Optional subtitle for the draft.
96
+ cover_image: Optional URL of the cover image for the draft.
97
+
98
+ Returns:
99
+ A dictionary containing the created draft's details, including its ID.
100
+
101
+ Raises:
102
+ GraphQLError: If the API request fails.
103
+
104
+ Tags:
105
+ create, draft, post, hashnode, api, important
106
+ """
107
+ create_draft_mutation = gql("""
108
+ mutation CreateDraft($input: CreateDraftInput!) {
109
+ createDraft(input: $input) {
110
+ draft {
111
+ id
112
+ slug
113
+ title
114
+ }
115
+ }
116
+ }
117
+ """)
118
+ variables = {
119
+ "input": {
120
+ "publicationId": publication_id,
121
+ "title": title,
122
+ "contentMarkdown": content,
123
+ }
124
+ }
125
+ if tags:
126
+ variables["input"]["tags"] = [
127
+ {"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags
128
+ ]
129
+ if slug:
130
+ variables["input"]["slug"] = slug
131
+ if subtitle:
132
+ variables["input"]["subtitle"] = subtitle
133
+ if cover_image:
134
+ variables["input"]["coverImageOptions"] = {"coverImageURL": cover_image}
135
+
136
+ result = self.mutate(create_draft_mutation, variables)
137
+ return result["createDraft"]["draft"]
138
+
139
+ def publish_draft(self, post_id: str) -> str:
140
+ """
141
+ Publishes a draft post.
142
+
143
+ Args:
144
+ post_id: The ID of the draft to publish.
145
+
146
+ Returns:
147
+ The URL of the published post.
148
+
149
+ Raises:
150
+ GraphQLError: If the API request fails.
151
+
152
+ Tags:
153
+ publish, draft, post, hashnode, api, important
154
+ """
155
+ publish_draft_mutation = gql("""
156
+ mutation PublishDraft($input: PublishDraftInput!) {
157
+ publishDraft(input: $input) {
158
+ post {
159
+ url
160
+ }
161
+ }
162
+ }
163
+ """)
164
+ variables = {"input": {"draftId": post_id}}
165
+ result = self.mutate(publish_draft_mutation, variables)
166
+ return result["publishDraft"]["post"]["url"]
167
+
80
168
  def get_me(self) -> dict:
81
169
  """
82
170
  Fetches details about the authenticated user.
@@ -190,25 +278,82 @@ class HashnodeApp(GraphQLApplication):
190
278
  result = self.query(get_publication_query, variables)
191
279
  return result.get("publication")
192
280
 
193
- def get_post(self, post_id: str) -> dict:
281
+ def list_posts(
282
+ self, publication_id: str, first: int = 10, after: str = None
283
+ ) -> dict:
194
284
  """
195
- Fetches details of a single post by slug and hostname.
285
+ Lists posts from a publication.
196
286
 
197
287
  Args:
198
- post_id: The ID of the post to fetch details for. This can be fetched by using get_publication method.
288
+ publication_id: The ID of the publication.
289
+ first: The number of posts to fetch. Defaults to 10.
290
+ after: The cursor for pagination.
291
+
292
+ Returns:
293
+ A dictionary containing the list of posts and pagination info.
294
+
295
+ Raises:
296
+ GraphQLError: If the API request fails.
297
+
298
+ Tags:
299
+ list, posts, hashnode, api, query, important
300
+ """
301
+ list_posts_query = gql("""
302
+ query Publication($id: ObjectId!, $first: Int!, $after: String) {
303
+ publication(id: $id) {
304
+ posts(first: $first, after: $after) {
305
+ edges {
306
+ node {
307
+ id
308
+ title
309
+ slug
310
+ url
311
+ }
312
+ cursor
313
+ }
314
+ pageInfo {
315
+ endCursor
316
+ hasNextPage
317
+ }
318
+ }
319
+ }
320
+ }
321
+ """)
322
+ variables = {"id": publication_id, "first": first}
323
+ if after:
324
+ variables["after"] = after
325
+ result = self.query(list_posts_query, variables)
326
+ return result.get("publication", {}).get("posts")
327
+
328
+ def get_post(
329
+ self, post_id: str = None, slug: str = None, hostname: str = None
330
+ ) -> dict:
331
+ """
332
+ Fetches details of a single post by ID, or by slug and hostname.
333
+
334
+ Args:
335
+ post_id: The ID of the post to fetch.
336
+ slug: The slug of the post.
337
+ hostname: The hostname of the publication the post belongs to.
199
338
 
200
339
  Returns:
201
340
  A dictionary containing post details.
202
341
 
203
342
  Raises:
204
343
  GraphQLError: If the API request fails.
344
+ ValueError: If neither post_id nor both slug and hostname are provided.
205
345
 
206
346
  Tags:
207
347
  get, post, hashnode, api, query, important
208
348
  """
349
+ if not post_id and not (slug and hostname):
350
+ raise ValueError(
351
+ "Either post_id or both slug and hostname must be provided."
352
+ )
353
+
209
354
  get_post_query = gql("""
210
- query Post($id: ID!) {
211
- post(id: $id) {
355
+ query Post($id: ID, $slug: String, $hostname: String) {
356
+ post(id: $id, slug: $slug, hostname: $hostname) {
212
357
  id
213
358
  slug
214
359
  previousSlugs
@@ -229,11 +374,17 @@ class HashnodeApp(GraphQLApplication):
229
374
  }
230
375
  }
231
376
  """)
232
- variables = {"id": post_id}
377
+ variables = {}
378
+ if post_id:
379
+ variables["id"] = post_id
380
+ else:
381
+ variables["slug"] = slug
382
+ variables["hostname"] = hostname
383
+
233
384
  result = self.query(get_post_query, variables)
234
385
  return result.get("post")
235
386
 
236
- def update_post(
387
+ def modify_post(
237
388
  self,
238
389
  post_id: str,
239
390
  title: str = None,
@@ -244,7 +395,7 @@ class HashnodeApp(GraphQLApplication):
244
395
  cover_image: str = None,
245
396
  ) -> str:
246
397
  """
247
- Updates an existing post using the GraphQL API.
398
+ Modifies an existing post using the GraphQL API.
248
399
 
249
400
  Args:
250
401
  post_id: The ID of the post to update.
@@ -262,7 +413,7 @@ class HashnodeApp(GraphQLApplication):
262
413
  GraphQLError: If the API request fails.
263
414
 
264
415
  Tags:
265
- update, post, hashnode, api
416
+ modify, post, hashnode, api, important
266
417
  """
267
418
  update_post_mutation = gql("""
268
419
  mutation UpdatePost($input: UpdatePostInput!) {
@@ -283,9 +434,7 @@ class HashnodeApp(GraphQLApplication):
283
434
  if title:
284
435
  variables["input"]["title"] = title
285
436
  if content:
286
- variables["input"]["contentMarkdown"] = (
287
- content # Assuming 'contentMarkdown' is the field name for content
288
- )
437
+ variables["input"]["contentMarkdown"] = content
289
438
  if tags:
290
439
  variables["input"]["tags"] = [
291
440
  {"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags
@@ -295,10 +444,7 @@ class HashnodeApp(GraphQLApplication):
295
444
  if subtitle:
296
445
  variables["input"]["subtitle"] = subtitle
297
446
  if cover_image:
298
- variables["input"]["bannerImageOptions"] = {
299
- "url": cover_image,
300
- "potrait": False, # Assuming 'potrait' is a required field for bannerImageOptions
301
- }
447
+ variables["input"]["coverImageOptions"] = {"coverImageURL": cover_image}
302
448
 
303
449
  result = self.mutate(update_post_mutation, variables)
304
450
  return result["updatePost"]["post"]["url"]
@@ -334,7 +480,7 @@ class HashnodeApp(GraphQLApplication):
334
480
  """)
335
481
  variables = {"input": {"id": post_id}}
336
482
  result = self.mutate(delete_post_mutation, variables)
337
- return result.get("deletePost", {}).get("id", "Post deleted successfully")
483
+ return result.get("removePost", {}).get("post", "Post deleted successfully")
338
484
 
339
485
  def add_comment(self, post_id: str, content: str) -> dict:
340
486
  """
@@ -406,8 +552,8 @@ class HashnodeApp(GraphQLApplication):
406
552
  variables = {"input": {"id": comment_id}}
407
553
  result = self.mutate(delete_comment_mutation, variables)
408
554
  return result.get("removeComment", {}).get(
409
- "id", "Comment deleted successfully"
410
- ) # Adjust based on actual API return
555
+ "comment", "Comment deleted successfully"
556
+ )
411
557
 
412
558
  def get_user(self, username: str) -> dict:
413
559
  """
@@ -445,10 +591,13 @@ class HashnodeApp(GraphQLApplication):
445
591
  def list_tools(self):
446
592
  return [
447
593
  self.publish_post,
594
+ self.create_draft,
595
+ self.publish_draft,
448
596
  self.get_me,
449
597
  self.get_publication,
598
+ self.list_posts,
450
599
  self.get_post,
451
- self.update_post,
600
+ self.modify_post,
452
601
  self.delete_post,
453
602
  self.add_comment,
454
603
  self.delete_comment,
@@ -1,6 +1,5 @@
1
1
  import httpx
2
2
  from loguru import logger
3
-
4
3
  from universal_mcp.applications.application import APIApplication
5
4
 
6
5
 
@@ -39,12 +38,12 @@ class HttpToolsApp(APIApplication):
39
38
  ):
40
39
  """
41
40
  Executes an HTTP GET request to a given URL with optional headers and query parameters. It handles HTTP errors by raising an exception and processes the response, returning parsed JSON or a dictionary with the raw text and status details if JSON is unavailable.
42
-
41
+
43
42
  Args:
44
43
  url (str): The URL to send the GET request to. Example: "https://api.example.com/data"
45
44
  headers (dict, optional): Optional HTTP headers to include in the request. Example: {"Authorization": "Bearer token"}
46
45
  query_params (dict, optional): Optional dictionary of query parameters to include in the request. Example: {"page": 1}
47
-
46
+
48
47
  Returns:
49
48
  dict: The JSON response from the GET request, or text if not JSON.
50
49
  Tags:
@@ -62,12 +61,12 @@ class HttpToolsApp(APIApplication):
62
61
  ):
63
62
  """
64
63
  Sends an HTTP POST request to a URL with an optional JSON body and headers. It returns the parsed JSON response or raw text if parsing fails and raises an exception for HTTP errors. It is used for creating new resources, unlike http_get which retrieves data.
65
-
64
+
66
65
  Args:
67
66
  url (str): The URL to send the POST request to. Example: "https://api.example.com/data"
68
67
  headers (dict, optional): Optional HTTP headers to include in the request. Example: {"Content-Type": "application/json"}
69
68
  body (dict, optional): Optional JSON body to include in the request. Example: {"name": "John"}
70
-
69
+
71
70
  Returns:
72
71
  dict: The JSON response from the POST request, or text if not JSON.
73
72
  Tags:
@@ -81,12 +80,12 @@ class HttpToolsApp(APIApplication):
81
80
  def http_put(self, url: str, headers: dict | None = None, body: dict | None = None):
82
81
  """
83
82
  Performs an HTTP PUT request to update or replace a resource at a specified URL. It accepts an optional JSON body and headers, raises an exception for error responses, and returns the parsed JSON response or a dictionary with the raw text and status details.
84
-
83
+
85
84
  Args:
86
85
  url (str): The URL to send the PUT request to. Example: "https://api.example.com/data/1"
87
86
  headers (dict, optional): Optional HTTP headers to include in the request. Example: {"Authorization": "Bearer token"}
88
87
  body (dict, optional): Optional JSON body to include in the request. Example: {"name": "Jane"}
89
-
88
+
90
89
  Returns:
91
90
  dict: The JSON response from the PUT request, or text if not JSON.
92
91
  Tags:
@@ -102,12 +101,12 @@ class HttpToolsApp(APIApplication):
102
101
  ):
103
102
  """
104
103
  Sends an HTTP DELETE request to a URL with optional headers and a JSON body. Raises an exception for HTTP error statuses and returns the parsed JSON response. If the response isn't JSON, it returns the text content, status code, and headers.
105
-
104
+
106
105
  Args:
107
106
  url (str): The URL to send the DELETE request to. Example: "https://api.example.com/data/1"
108
107
  headers (dict, optional): Optional HTTP headers to include in the request. Example: {"Authorization": "Bearer token"}
109
108
  body (dict, optional): Optional JSON body to include in the request. Example: {"reason": "obsolete"}
110
-
109
+
111
110
  Returns:
112
111
  dict: The JSON response from the DELETE request, or text if not JSON.
113
112
  Tags:
@@ -123,12 +122,12 @@ class HttpToolsApp(APIApplication):
123
122
  ):
124
123
  """
125
124
  Sends an HTTP PATCH request to apply partial modifications to a resource at a given URL. It accepts optional headers and a JSON body. It returns the parsed JSON response, or the raw text with status details if the response is not valid JSON.
126
-
125
+
127
126
  Args:
128
127
  url (str): The URL to send the PATCH request to. Example: "https://api.example.com/data/1"
129
128
  headers (dict, optional): Optional HTTP headers to include in the request. Example: {"Authorization": "Bearer token"}
130
129
  body (dict, optional): Optional JSON body to include in the request. Example: {"status": "active"}
131
-
130
+
132
131
  Returns:
133
132
  dict: The JSON response from the PATCH request, or text if not JSON.
134
133
  Tags:
@@ -1 +1 @@
1
- from .app import HubspotApp
1
+ from .app import HubspotApp
@@ -1,6 +1,6 @@
1
-
2
1
  from typing import Any
3
2
 
3
+
4
4
  class APISegmentBase:
5
5
  def __init__(self, main_app_client: Any):
6
6
  self.main_app_client = main_app_client
@@ -8,11 +8,41 @@ class APISegmentBase:
8
8
  def _get(self, url: str, params: dict = None, **kwargs):
9
9
  return self.main_app_client._get(url, params=params, **kwargs)
10
10
 
11
- def _post(self, url: str, data: Any = None, params: dict = None, content_type: str = None, files: Any = None, **kwargs):
12
- return self.main_app_client._post(url, data=data, params=params, content_type=content_type, files=files, **kwargs)
13
-
14
- def _put(self, url: str, data: Any = None, params: dict = None, content_type: str = None, files: Any = None, **kwargs):
15
- return self.main_app_client._put(url, data=data, params=params, content_type=content_type, files=files, **kwargs)
11
+ def _post(
12
+ self,
13
+ url: str,
14
+ data: Any = None,
15
+ params: dict = None,
16
+ content_type: str = None,
17
+ files: Any = None,
18
+ **kwargs,
19
+ ):
20
+ return self.main_app_client._post(
21
+ url,
22
+ data=data,
23
+ params=params,
24
+ content_type=content_type,
25
+ files=files,
26
+ **kwargs,
27
+ )
28
+
29
+ def _put(
30
+ self,
31
+ url: str,
32
+ data: Any = None,
33
+ params: dict = None,
34
+ content_type: str = None,
35
+ files: Any = None,
36
+ **kwargs,
37
+ ):
38
+ return self.main_app_client._put(
39
+ url,
40
+ data=data,
41
+ params=params,
42
+ content_type=content_type,
43
+ files=files,
44
+ **kwargs,
45
+ )
16
46
 
17
47
  def _patch(self, url: str, data: Any = None, params: dict = None, **kwargs):
18
48
  return self.main_app_client._patch(url, data=data, params=params, **kwargs)
@@ -22,4 +52,3 @@ class APISegmentBase:
22
52
 
23
53
  def _handle_response(self, response):
24
54
  return self.main_app_client._handle_response(response)
25
-