universal-mcp-applications 0.1.30__py3-none-any.whl → 0.1.36rc1__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 (105) hide show
  1. universal_mcp/applications/ahrefs/app.py +52 -198
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +111 -464
  4. universal_mcp/applications/asana/app.py +417 -1567
  5. universal_mcp/applications/aws_s3/app.py +33 -100
  6. universal_mcp/applications/bill/app.py +546 -1957
  7. universal_mcp/applications/box/app.py +1068 -3981
  8. universal_mcp/applications/braze/app.py +364 -1430
  9. universal_mcp/applications/browser_use/app.py +2 -8
  10. universal_mcp/applications/cal_com_v2/app.py +207 -625
  11. universal_mcp/applications/calendly/app.py +61 -200
  12. universal_mcp/applications/canva/app.py +45 -110
  13. universal_mcp/applications/clickup/app.py +207 -674
  14. universal_mcp/applications/coda/app.py +146 -426
  15. universal_mcp/applications/confluence/app.py +310 -1098
  16. universal_mcp/applications/contentful/app.py +36 -151
  17. universal_mcp/applications/crustdata/app.py +28 -107
  18. universal_mcp/applications/dialpad/app.py +283 -756
  19. universal_mcp/applications/digitalocean/app.py +1766 -5777
  20. universal_mcp/applications/domain_checker/app.py +3 -54
  21. universal_mcp/applications/e2b/app.py +14 -64
  22. universal_mcp/applications/elevenlabs/app.py +9 -47
  23. universal_mcp/applications/exa/app.py +6 -17
  24. universal_mcp/applications/falai/app.py +23 -100
  25. universal_mcp/applications/figma/app.py +53 -137
  26. universal_mcp/applications/file_system/app.py +2 -13
  27. universal_mcp/applications/firecrawl/app.py +51 -152
  28. universal_mcp/applications/fireflies/app.py +59 -281
  29. universal_mcp/applications/fpl/app.py +91 -528
  30. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  31. universal_mcp/applications/fpl/utils/helper.py +25 -89
  32. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  33. universal_mcp/applications/ghost_content/app.py +52 -161
  34. universal_mcp/applications/github/app.py +19 -56
  35. universal_mcp/applications/gong/app.py +88 -248
  36. universal_mcp/applications/google_calendar/app.py +16 -68
  37. universal_mcp/applications/google_docs/app.py +88 -188
  38. universal_mcp/applications/google_drive/app.py +140 -462
  39. universal_mcp/applications/google_gemini/app.py +12 -64
  40. universal_mcp/applications/google_mail/app.py +28 -157
  41. universal_mcp/applications/google_searchconsole/app.py +15 -48
  42. universal_mcp/applications/google_sheet/app.py +101 -578
  43. universal_mcp/applications/google_sheet/helper.py +10 -37
  44. universal_mcp/applications/hashnode/app.py +57 -269
  45. universal_mcp/applications/heygen/app.py +44 -122
  46. universal_mcp/applications/http_tools/app.py +10 -32
  47. universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
  48. universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
  49. universal_mcp/applications/hubspot/app.py +23 -87
  50. universal_mcp/applications/jira/app.py +2071 -7986
  51. universal_mcp/applications/klaviyo/app.py +494 -1376
  52. universal_mcp/applications/linkedin/README.md +9 -2
  53. universal_mcp/applications/linkedin/app.py +392 -212
  54. universal_mcp/applications/mailchimp/app.py +450 -1605
  55. universal_mcp/applications/markitdown/app.py +8 -20
  56. universal_mcp/applications/miro/app.py +217 -699
  57. universal_mcp/applications/ms_teams/app.py +64 -186
  58. universal_mcp/applications/neon/app.py +86 -192
  59. universal_mcp/applications/notion/app.py +21 -36
  60. universal_mcp/applications/onedrive/app.py +14 -36
  61. universal_mcp/applications/openai/app.py +42 -165
  62. universal_mcp/applications/outlook/app.py +16 -76
  63. universal_mcp/applications/perplexity/app.py +4 -19
  64. universal_mcp/applications/pipedrive/app.py +832 -3142
  65. universal_mcp/applications/posthog/app.py +163 -432
  66. universal_mcp/applications/reddit/app.py +40 -139
  67. universal_mcp/applications/resend/app.py +41 -107
  68. universal_mcp/applications/retell/app.py +14 -41
  69. universal_mcp/applications/rocketlane/app.py +221 -934
  70. universal_mcp/applications/scraper/README.md +7 -4
  71. universal_mcp/applications/scraper/app.py +216 -102
  72. universal_mcp/applications/semanticscholar/app.py +22 -64
  73. universal_mcp/applications/semrush/app.py +43 -77
  74. universal_mcp/applications/sendgrid/app.py +512 -1262
  75. universal_mcp/applications/sentry/app.py +271 -906
  76. universal_mcp/applications/serpapi/app.py +40 -143
  77. universal_mcp/applications/sharepoint/app.py +15 -37
  78. universal_mcp/applications/shopify/app.py +1551 -4287
  79. universal_mcp/applications/shortcut/app.py +155 -417
  80. universal_mcp/applications/slack/app.py +50 -101
  81. universal_mcp/applications/spotify/app.py +126 -325
  82. universal_mcp/applications/supabase/app.py +104 -213
  83. universal_mcp/applications/tavily/app.py +1 -1
  84. universal_mcp/applications/trello/app.py +693 -2656
  85. universal_mcp/applications/twilio/app.py +14 -50
  86. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  87. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  88. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  89. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  90. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  91. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  92. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  93. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  94. universal_mcp/applications/whatsapp/app.py +35 -186
  95. universal_mcp/applications/whatsapp/audio.py +2 -6
  96. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  97. universal_mcp/applications/whatsapp_business/app.py +70 -283
  98. universal_mcp/applications/wrike/app.py +45 -118
  99. universal_mcp/applications/yahoo_finance/app.py +19 -65
  100. universal_mcp/applications/youtube/app.py +75 -261
  101. universal_mcp/applications/zenquotes/app.py +2 -2
  102. {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,5 @@
1
1
  from collections.abc import Callable
2
2
  from typing import Any
3
-
4
3
  from loguru import logger
5
4
  from universal_mcp.applications.application import APIApplication
6
5
  from universal_mcp.integrations import Integration
@@ -26,8 +25,8 @@ class GhostContentApp(APIApplication):
26
25
  """
27
26
  super().__init__(name="ghost_content", integration=integration)
28
27
  self._base_url = None
29
- self._api_key = None # Cache the API key
30
- self._version = None # Cache the version
28
+ self._api_key = None
29
+ self._version = None
31
30
 
32
31
  @property
33
32
  def base_url(self) -> str:
@@ -39,13 +38,8 @@ class GhostContentApp(APIApplication):
39
38
  credentials = self.integration.get_credentials()
40
39
  ghost_url = credentials.get("url") or credentials.get("admin_domain")
41
40
  if not ghost_url:
42
- logger.error(
43
- "GhostContentApp: Missing 'url' or 'admin_domain' in integration credentials."
44
- )
45
- raise ValueError(
46
- "Integration credentials must include 'url' or 'admin_domain' for the Ghost site."
47
- )
48
-
41
+ logger.error("GhostContentApp: Missing 'url' or 'admin_domain' in integration credentials.")
42
+ raise ValueError("Integration credentials must include 'url' or 'admin_domain' for the Ghost site.")
49
43
  self._base_url = f"{ghost_url.rstrip('/')}/api/content/"
50
44
  logger.info(f"GhostContentApp: Constructed base URL as {self._base_url}")
51
45
  return self._base_url
@@ -70,18 +64,10 @@ class GhostContentApp(APIApplication):
70
64
  """
71
65
  if not self._api_key:
72
66
  credentials = self.integration.get_credentials()
73
- api_key = (
74
- credentials.get("key")
75
- or credentials.get("api_key")
76
- or credentials.get("API_KEY")
77
- )
67
+ api_key = credentials.get("key") or credentials.get("api_key") or credentials.get("API_KEY")
78
68
  if not api_key:
79
- logger.error(
80
- "GhostContentApp: Content API key ('key') not found in integration credentials."
81
- )
82
- raise ValueError(
83
- "Integration credentials must include the Ghost Content API 'key'."
84
- )
69
+ logger.error("GhostContentApp: Content API key ('key') not found in integration credentials.")
70
+ raise ValueError("Integration credentials must include the Ghost Content API 'key'.")
85
71
  self._api_key = api_key
86
72
  return self._api_key
87
73
 
@@ -95,10 +81,8 @@ class GhostContentApp(APIApplication):
95
81
  credentials = self.integration.get_credentials()
96
82
  version = credentials.get("api_version")
97
83
  if not version:
98
- logger.warning(
99
- "GhostContentApp: 'version' not found in integration credentials. Defaulting to 'v5.0'."
100
- )
101
- version = "v5.0" # Default to a common version if not specified
84
+ logger.warning("GhostContentApp: 'version' not found in integration credentials. Defaulting to 'v5.0'.")
85
+ version = "v5.0"
102
86
  self._version = version
103
87
  return self._version
104
88
 
@@ -107,37 +91,32 @@ class GhostContentApp(APIApplication):
107
91
  Get the headers for Ghost Content API requests.
108
92
  Overrides the base class method to include the `Accept-Version` header.
109
93
  """
110
- headers = super()._get_headers() # Get base headers (e.g., Content-Type)
111
-
112
- # Add the Accept-Version header as per Ghost Content API documentation
94
+ headers = super()._get_headers()
113
95
  headers["Accept-Version"] = self._get_version
114
- logger.debug(
115
- f"GhostContentApp: Using Accept-Version: {self._get_version} in headers."
116
- )
96
+ logger.debug(f"GhostContentApp: Using Accept-Version: {self._get_version} in headers.")
117
97
  return headers
118
98
 
119
99
  def _build_common_params(
120
100
  self,
121
101
  include: list[str] | None = None,
122
102
  fields: list[str] | None = None,
123
- filter: str | None = None, # Changed from filter_str to filter
103
+ filter: str | None = None,
124
104
  limit: int | None = None,
125
105
  order: str | None = None,
126
106
  page: int | None = None,
127
- formats: list[str] | None = None, # Specific to posts/pages for content format
128
- visibility: str | None = None, # Specific to posts/pages/tiers for visibility
107
+ formats: list[str] | None = None,
108
+ visibility: str | None = None,
129
109
  ) -> dict[str, Any]:
130
110
  """
131
111
  Helper to build common query parameters for Ghost Content API requests,
132
112
  including the mandatory API key.
133
113
  """
134
114
  params: dict[str, Any] = {"key": self._get_api_key}
135
-
136
115
  if include:
137
116
  params["include"] = ",".join(include)
138
117
  if fields:
139
118
  params["fields"] = ",".join(fields)
140
- if filter: # Use 'filter' here
119
+ if filter:
141
120
  params["filter"] = filter
142
121
  if limit is not None:
143
122
  params["limit"] = limit
@@ -151,8 +130,7 @@ class GhostContentApp(APIApplication):
151
130
  params["visibility"] = visibility
152
131
  return params
153
132
 
154
- # --- Posts Tools ---
155
- def browse_posts(
133
+ async def browse_posts(
156
134
  self,
157
135
  include: list[str] | None = None,
158
136
  fields: list[str] | None = None,
@@ -161,7 +139,7 @@ class GhostContentApp(APIApplication):
161
139
  page: int | None = None,
162
140
  order: str | None = None,
163
141
  formats: list[str] | None = None,
164
- ) -> dict[str, Any]: # Changed return type to Dict[str, Any]
142
+ ) -> dict[str, Any]:
165
143
  """
166
144
  Retrieves and browses posts from a data source based on provided parameters.
167
145
 
@@ -184,26 +162,15 @@ class GhostContentApp(APIApplication):
184
162
  browse, fetch, posts, management, important
185
163
  """
186
164
  url = f"{self.base_url}posts/"
187
- # Removed 'visibility' from params as it's not in the method signature
188
165
  params = self._build_common_params(
189
- include=include,
190
- fields=fields,
191
- filter=filter,
192
- limit=limit,
193
- page=page,
194
- order=order,
195
- formats=formats,
166
+ include=include, fields=fields, filter=filter, limit=limit, page=page, order=order, formats=formats
196
167
  )
197
168
  response = self._get(url, params=params)
198
169
  return response.json()
199
170
 
200
- def read_post_by_id(
201
- self,
202
- id: str,
203
- include: list[str] | None = None,
204
- fields: list[str] | None = None,
205
- formats: list[str] | None = None,
206
- ) -> dict[str, Any]: # Changed return type
171
+ async def read_post_by_id(
172
+ self, id: str, include: list[str] | None = None, fields: list[str] | None = None, formats: list[str] | None = None
173
+ ) -> dict[str, Any]:
207
174
  """
208
175
  Retrieves a post by its ID, optionally including additional data or specific fields.
209
176
 
@@ -223,19 +190,13 @@ class GhostContentApp(APIApplication):
223
190
  read, post, management
224
191
  """
225
192
  url = f"{self.base_url}posts/{id}/"
226
- params = self._build_common_params(
227
- include=include, fields=fields, formats=formats
228
- )
193
+ params = self._build_common_params(include=include, fields=fields, formats=formats)
229
194
  response = self._get(url, params=params)
230
195
  return response.json()
231
196
 
232
- def read_post_by_slug(
233
- self,
234
- slug: str,
235
- include: list[str] | None = None,
236
- fields: list[str] | None = None,
237
- formats: list[str] | None = None,
238
- ) -> dict[str, Any]: # Changed return type
197
+ async def read_post_by_slug(
198
+ self, slug: str, include: list[str] | None = None, fields: list[str] | None = None, formats: list[str] | None = None
199
+ ) -> dict[str, Any]:
239
200
  """
240
201
  Retrieves a post by its slug, with optional parameters to specify included data, select specific fields, or request particular data formats.
241
202
 
@@ -255,14 +216,11 @@ class GhostContentApp(APIApplication):
255
216
  read, post, fetch, management
256
217
  """
257
218
  url = f"{self.base_url}posts/slug/{slug}/"
258
- params = self._build_common_params(
259
- include=include, fields=fields, formats=formats
260
- )
219
+ params = self._build_common_params(include=include, fields=fields, formats=formats)
261
220
  response = self._get(url, params=params)
262
221
  return response.json()
263
222
 
264
- # --- Authors Tools ---
265
- def browse_authors(
223
+ async def browse_authors(
266
224
  self,
267
225
  include: list[str] | None = None,
268
226
  fields: list[str] | None = None,
@@ -270,7 +228,7 @@ class GhostContentApp(APIApplication):
270
228
  limit: int | None = None,
271
229
  page: int | None = None,
272
230
  order: str | None = None,
273
- ) -> dict[str, Any]: # Changed return type
231
+ ) -> dict[str, Any]:
274
232
  """
275
233
  Browse authors using various filtering and pagination options.
276
234
 
@@ -292,23 +250,11 @@ class GhostContentApp(APIApplication):
292
250
  list, management, important
293
251
  """
294
252
  url = f"{self.base_url}authors/"
295
- params = self._build_common_params(
296
- include=include,
297
- fields=fields,
298
- filter=filter,
299
- limit=limit,
300
- page=page,
301
- order=order,
302
- )
253
+ params = self._build_common_params(include=include, fields=fields, filter=filter, limit=limit, page=page, order=order)
303
254
  response = self._get(url, params=params)
304
255
  return response.json()
305
256
 
306
- def read_author_by_id(
307
- self,
308
- id: str,
309
- include: list[str] | None = None,
310
- fields: list[str] | None = None,
311
- ) -> dict[str, Any]: # Changed return type
257
+ async def read_author_by_id(self, id: str, include: list[str] | None = None, fields: list[str] | None = None) -> dict[str, Any]:
312
258
  """
313
259
  Read an author from the database by their unique ID.
314
260
 
@@ -331,12 +277,7 @@ class GhostContentApp(APIApplication):
331
277
  response = self._get(url, params=params)
332
278
  return response.json()
333
279
 
334
- def read_author_by_slug(
335
- self,
336
- slug: str,
337
- include: list[str] | None = None,
338
- fields: list[str] | None = None,
339
- ) -> dict[str, Any]: # Changed return type
280
+ async def read_author_by_slug(self, slug: str, include: list[str] | None = None, fields: list[str] | None = None) -> dict[str, Any]:
340
281
  """
341
282
  Retrieve an author's information by their slug.
342
283
 
@@ -359,8 +300,7 @@ class GhostContentApp(APIApplication):
359
300
  response = self._get(url, params=params)
360
301
  return response.json()
361
302
 
362
- # --- Tags Tools ---
363
- def browse_tags(
303
+ async def browse_tags(
364
304
  self,
365
305
  include: list[str] | None = None,
366
306
  fields: list[str] | None = None,
@@ -368,7 +308,7 @@ class GhostContentApp(APIApplication):
368
308
  limit: int | None = None,
369
309
  page: int | None = None,
370
310
  order: str | None = None,
371
- ) -> dict[str, Any]: # Changed return type
311
+ ) -> dict[str, Any]:
372
312
  """
373
313
  Browse and retrieve tags based on specified parameters.
374
314
 
@@ -390,23 +330,11 @@ class GhostContentApp(APIApplication):
390
330
  browse, tags, management, important
391
331
  """
392
332
  url = f"{self.base_url}tags/"
393
- params = self._build_common_params(
394
- include=include,
395
- fields=fields,
396
- filter=filter,
397
- limit=limit,
398
- page=page,
399
- order=order,
400
- )
333
+ params = self._build_common_params(include=include, fields=fields, filter=filter, limit=limit, page=page, order=order)
401
334
  response = self._get(url, params=params)
402
335
  return response.json()
403
336
 
404
- def read_tag_by_id(
405
- self,
406
- id: str,
407
- include: list[str] | None = None,
408
- fields: list[str] | None = None,
409
- ) -> dict[str, Any]: # Changed return type
337
+ async def read_tag_by_id(self, id: str, include: list[str] | None = None, fields: list[str] | None = None) -> dict[str, Any]:
410
338
  """
411
339
  Retrieves a tag's details by its unique identifier, optionally filtering by included and field sets.
412
340
 
@@ -429,12 +357,7 @@ class GhostContentApp(APIApplication):
429
357
  response = self._get(url, params=params)
430
358
  return response.json()
431
359
 
432
- def read_tag_by_slug(
433
- self,
434
- slug: str,
435
- include: list[str] | None = None,
436
- fields: list[str] | None = None,
437
- ) -> dict[str, Any]: # Changed return type
360
+ async def read_tag_by_slug(self, slug: str, include: list[str] | None = None, fields: list[str] | None = None) -> dict[str, Any]:
438
361
  """
439
362
  Retrieve tag information identified by a unique slug, with optional inclusion of related data and selective fields.
440
363
 
@@ -457,8 +380,7 @@ class GhostContentApp(APIApplication):
457
380
  response = self._get(url, params=params)
458
381
  return response.json()
459
382
 
460
- # --- Pages Tools ---
461
- def browse_pages(
383
+ async def browse_pages(
462
384
  self,
463
385
  include: list[str] | None = None,
464
386
  fields: list[str] | None = None,
@@ -467,7 +389,7 @@ class GhostContentApp(APIApplication):
467
389
  page: int | None = None,
468
390
  order: str | None = None,
469
391
  formats: list[str] | None = None,
470
- ) -> dict[str, Any]: # Changed return type
392
+ ) -> dict[str, Any]:
471
393
  """
472
394
  Retrieves a list of pages using optional filtering, pagination, and formatting parameters.
473
395
 
@@ -490,26 +412,15 @@ class GhostContentApp(APIApplication):
490
412
  browse, list, management, important
491
413
  """
492
414
  url = f"{self.base_url}pages/"
493
- # Removed 'visibility' from params as it's not in the method signature
494
415
  params = self._build_common_params(
495
- include=include,
496
- fields=fields,
497
- filter=filter,
498
- limit=limit,
499
- page=page,
500
- order=order,
501
- formats=formats,
416
+ include=include, fields=fields, filter=filter, limit=limit, page=page, order=order, formats=formats
502
417
  )
503
418
  response = self._get(url, params=params)
504
419
  return response.json()
505
420
 
506
- def read_page_by_id(
507
- self,
508
- id: str,
509
- include: list[str] | None = None,
510
- fields: list[str] | None = None,
511
- formats: list[str] | None = None,
512
- ) -> dict[str, Any]: # Changed return type
421
+ async def read_page_by_id(
422
+ self, id: str, include: list[str] | None = None, fields: list[str] | None = None, formats: list[str] | None = None
423
+ ) -> dict[str, Any]:
513
424
  """
514
425
  Read a page by ID, allowing for optional inclusion of additional data, specific fields, and formats.
515
426
 
@@ -529,19 +440,13 @@ class GhostContentApp(APIApplication):
529
440
  read, page, data-retrieval
530
441
  """
531
442
  url = f"{self.base_url}pages/{id}/"
532
- params = self._build_common_params(
533
- include=include, fields=fields, formats=formats
534
- )
443
+ params = self._build_common_params(include=include, fields=fields, formats=formats)
535
444
  response = self._get(url, params=params)
536
445
  return response.json()
537
446
 
538
- def read_page_by_slug(
539
- self,
540
- slug: str,
541
- include: list[str] | None = None,
542
- fields: list[str] | None = None,
543
- formats: list[str] | None = None,
544
- ) -> dict[str, Any]: # Changed return type
447
+ async def read_page_by_slug(
448
+ self, slug: str, include: list[str] | None = None, fields: list[str] | None = None, formats: list[str] | None = None
449
+ ) -> dict[str, Any]:
545
450
  """
546
451
  Retrieve a page's content and metadata by its slug identifier, optionally including related data, specific fields, and content formats.
547
452
 
@@ -561,14 +466,11 @@ class GhostContentApp(APIApplication):
561
466
  read, get, page, slug, http-request
562
467
  """
563
468
  url = f"{self.base_url}pages/slug/{slug}/"
564
- params = self._build_common_params(
565
- include=include, fields=fields, formats=formats
566
- )
469
+ params = self._build_common_params(include=include, fields=fields, formats=formats)
567
470
  response = self._get(url, params=params)
568
471
  return response.json()
569
472
 
570
- # --- Tiers Tool ---
571
- def browse_tiers(
473
+ async def browse_tiers(
572
474
  self,
573
475
  include: list[str] | None = None,
574
476
  fields: list[str] | None = None,
@@ -576,7 +478,7 @@ class GhostContentApp(APIApplication):
576
478
  limit: int | None = None,
577
479
  page: int | None = None,
578
480
  order: str | None = None,
579
- ) -> dict[str, Any]: # Changed return type
481
+ ) -> dict[str, Any]:
580
482
  """
581
483
  Browse tiers based on optional filters and pagination.
582
484
 
@@ -598,20 +500,11 @@ class GhostContentApp(APIApplication):
598
500
  browse, pagination, filter, management, important
599
501
  """
600
502
  url = f"{self.base_url}tiers/"
601
- # Removed 'visibility' from params as it's not in the method signature
602
- params = self._build_common_params(
603
- include=include,
604
- fields=fields,
605
- filter=filter,
606
- limit=limit,
607
- page=page,
608
- order=order,
609
- )
503
+ params = self._build_common_params(include=include, fields=fields, filter=filter, limit=limit, page=page, order=order)
610
504
  response = self._get(url, params=params)
611
505
  return response.json()
612
506
 
613
- # --- Settings Tool ---
614
- def browse_settings(self) -> dict[str, Any]: # Changed return type
507
+ async def browse_settings(self) -> dict[str, Any]:
615
508
  """
616
509
  Fetches site settings by making a GET request to the settings endpoint.
617
510
 
@@ -628,9 +521,7 @@ class GhostContentApp(APIApplication):
628
521
  fetch, settings, management, important
629
522
  """
630
523
  url = f"{self.base_url}settings/"
631
- params = (
632
- self._build_common_params()
633
- ) # Only the API key is needed for this endpoint via _build_common_params
524
+ params = self._build_common_params()
634
525
  response = self._get(url, params=params)
635
526
  return response.json()
636
527
 
@@ -1,5 +1,4 @@
1
1
  from typing import Any
2
-
3
2
  from loguru import logger
4
3
  from universal_mcp.applications.application import APIApplication
5
4
  from universal_mcp.integrations import Integration
@@ -17,12 +16,9 @@ class GithubApp(APIApplication):
17
16
  credentials = self.integration.get_credentials()
18
17
  if "headers" in credentials:
19
18
  return credentials["headers"]
20
- return {
21
- "Authorization": f"Bearer {credentials['access_token']}",
22
- "Accept": "application/vnd.github.v3+json",
23
- }
19
+ return {"Authorization": f"Bearer {credentials['access_token']}", "Accept": "application/vnd.github.v3+json"}
24
20
 
25
- def star_repository(self, repo_full_name: str) -> str:
21
+ async def star_repository(self, repo_full_name: str) -> str:
26
22
  """
27
23
  Stars a GitHub repository for the authenticated user. This user-centric action takes the full repository name ('owner/repo') and returns a simple string message confirming the outcome, unlike other functions that list or create repository content like issues or pull requests.
28
24
 
@@ -49,7 +45,7 @@ class GithubApp(APIApplication):
49
45
  logger.error(response.text)
50
46
  return f"Error starring repository: {response.text}"
51
47
 
52
- def list_recent_commits(self, repo_full_name: str) -> str:
48
+ async def list_recent_commits(self, repo_full_name: str) -> str:
53
49
  """
54
50
  Fetches and formats the 12 most recent commits from a repository. It returns a human-readable string summarizing each commit's hash, author, and message, providing a focused overview of recent code changes, unlike functions that list branches, issues, or pull requests.
55
51
 
@@ -74,15 +70,14 @@ class GithubApp(APIApplication):
74
70
  if not commits:
75
71
  return f"No commits found for repository {repo_full_name}"
76
72
  result = f"Recent commits for {repo_full_name}:\n\n"
77
- for commit in commits[:12]: # Limit to 12 commits
73
+ for commit in commits[:12]:
78
74
  sha = commit.get("sha", "")[:7]
79
75
  message = commit.get("commit", {}).get("message", "").split("\n")[0]
80
76
  author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
81
-
82
77
  result += f"- {sha}: {message} (by {author})\n"
83
78
  return result
84
79
 
85
- def list_branches(self, repo_full_name: str) -> str:
80
+ async def list_branches(self, repo_full_name: str) -> str:
86
81
  """
87
82
  Fetches all branches for a specified GitHub repository and formats them into a human-readable string. This method is distinct from others like `search_issues`, as it returns a formatted list for display rather than raw JSON data for programmatic use.
88
83
 
@@ -112,7 +107,7 @@ class GithubApp(APIApplication):
112
107
  result += f"- {branch_name}\n"
113
108
  return result
114
109
 
115
- def list_pull_requests(self, repo_full_name: str, state: str = "open") -> str:
110
+ async def list_pull_requests(self, repo_full_name: str, state: str = "open") -> str:
116
111
  """
117
112
  Fetches pull requests for a repository, filtered by state (e.g., 'open'). It returns a formatted string summarizing each PR's details, distinguishing it from `get_pull_request` (single PR) and `search_issues` (raw issue data).
118
113
 
@@ -143,20 +138,11 @@ class GithubApp(APIApplication):
143
138
  pr_number = pr.get("number", "Unknown")
144
139
  pr_state = pr.get("state", "Unknown")
145
140
  pr_user = pr.get("user", {}).get("login", "Unknown")
146
-
147
- result += (
148
- f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
149
- )
141
+ result += f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
150
142
  return result
151
143
 
152
- def search_issues(
153
- self,
154
- repo_full_name: str,
155
- state: str = "open",
156
- assignee: str = None,
157
- labels: str = None,
158
- per_page: int = 30,
159
- page: int = 1,
144
+ async def search_issues(
145
+ self, repo_full_name: str, state: str = "open", assignee: str = None, labels: str = None, per_page: int = 30, page: int = 1
160
146
  ) -> list[dict[str, Any]]:
161
147
  """
162
148
  Fetches issues from a GitHub repository using specified filters (state, assignee, labels) and pagination. It returns the raw API response as a list of dictionaries, providing detailed issue data for programmatic processing, distinct from other methods that return formatted strings.
@@ -190,7 +176,7 @@ class GithubApp(APIApplication):
190
176
  response.raise_for_status()
191
177
  return response.json()
192
178
 
193
- def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
179
+ async def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
194
180
  """
195
181
  Fetches a specific pull request from a repository using its unique number. It returns a human-readable string summarizing the PR's title, creator, status, and description, unlike `list_pull_requests` which retrieves a list of multiple PRs.
196
182
 
@@ -218,15 +204,10 @@ class GithubApp(APIApplication):
218
204
  pr_state = pr.get("state", "Unknown")
219
205
  pr_user = pr.get("user", {}).get("login", "Unknown")
220
206
  pr_body = pr.get("body", "No description provided.")
221
- result = (
222
- f"Pull Request #{pr_number}: {pr_title}\n"
223
- f"Created by: {pr_user}\n"
224
- f"Status: {pr_state}\n"
225
- f"Description: {pr_body}\n"
226
- )
207
+ result = f"Pull Request #{pr_number}: {pr_title}\nCreated by: {pr_user}\nStatus: {pr_state}\nDescription: {pr_body}\n"
227
208
  return result
228
209
 
229
- def create_pull_request(
210
+ async def create_pull_request(
230
211
  self,
231
212
  repo_full_name: str,
232
213
  head: str,
@@ -262,12 +243,7 @@ class GithubApp(APIApplication):
262
243
  """
263
244
  repo_full_name = repo_full_name.strip()
264
245
  url = f"{self.base_api_url}/{repo_full_name}/pulls"
265
- pull_request_data = {
266
- "head": head,
267
- "base": base,
268
- "maintainer_can_modify": maintainer_can_modify,
269
- "draft": draft,
270
- }
246
+ pull_request_data = {"head": head, "base": base, "maintainer_can_modify": maintainer_can_modify, "draft": draft}
271
247
  if issue is not None:
272
248
  pull_request_data["issue"] = issue
273
249
  else:
@@ -280,9 +256,7 @@ class GithubApp(APIApplication):
280
256
  response.raise_for_status()
281
257
  return response.json()
282
258
 
283
- def create_issue(
284
- self, repo_full_name: str, title: str, body: str = "", labels=None
285
- ) -> str:
259
+ async def create_issue(self, repo_full_name: str, title: str, body: str = "", labels=None) -> str:
286
260
  """
287
261
  Creates a new issue in a GitHub repository using a title, body, and optional labels. It returns a formatted confirmation string with the new issue's number and URL, differing from `update_issue` which modifies existing issues and `search_issues` which returns raw API data.
288
262
 
@@ -306,9 +280,7 @@ class GithubApp(APIApplication):
306
280
  issue_data = {"title": title, "body": body}
307
281
  if labels:
308
282
  if isinstance(labels, str):
309
- labels_list = [
310
- label.strip() for label in labels.split(",") if label.strip()
311
- ]
283
+ labels_list = [label.strip() for label in labels.split(",") if label.strip()]
312
284
  issue_data["labels"] = labels_list
313
285
  else:
314
286
  issue_data["labels"] = labels
@@ -317,15 +289,9 @@ class GithubApp(APIApplication):
317
289
  issue = response.json()
318
290
  issue_number = issue.get("number", "Unknown")
319
291
  issue_url = issue.get("html_url", "")
320
- return (
321
- f"Successfully created issue #{issue_number}:\n"
322
- f"Title: {title}\n"
323
- f"URL: {issue_url}"
324
- )
325
-
326
- def list_repo_activities(
327
- self, repo_full_name: str, direction: str = "desc", per_page: int = 30
328
- ) -> str:
292
+ return f"Successfully created issue #{issue_number}:\nTitle: {title}\nURL: {issue_url}"
293
+
294
+ async def list_repo_activities(self, repo_full_name: str, direction: str = "desc", per_page: int = 30) -> str:
329
295
  """
330
296
  Fetches recent events for a GitHub repository and formats them into a human-readable string. It summarizes activities with actors and timestamps, providing a general event feed, unlike other `list_*` functions which retrieve specific resources like commits or issues.
331
297
 
@@ -354,17 +320,14 @@ class GithubApp(APIApplication):
354
320
  return f"No activities found for repository {repo_full_name}"
355
321
  result = f"Repository activities for {repo_full_name}:\n\n"
356
322
  for activity in activities:
357
- # Extract common fields
358
323
  timestamp = activity.get("timestamp", "Unknown time")
359
324
  actor_name = "Unknown user"
360
325
  if "actor" in activity and activity["actor"]:
361
326
  actor_name = activity["actor"].get("login", "Unknown user")
362
-
363
- # Create a simple description of the activity
364
327
  result += f"- {actor_name} performed an activity at {timestamp}\n"
365
328
  return result
366
329
 
367
- def update_issue(
330
+ async def update_issue(
368
331
  self,
369
332
  repo_full_name: str,
370
333
  issue_number: int,