universal-mcp-applications 0.1.33__py3-none-any.whl → 0.1.39rc16__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 (119) hide show
  1. universal_mcp/applications/BEST_PRACTICES.md +1 -1
  2. universal_mcp/applications/ahrefs/app.py +92 -238
  3. universal_mcp/applications/airtable/app.py +36 -135
  4. universal_mcp/applications/apollo/app.py +124 -477
  5. universal_mcp/applications/asana/app.py +605 -1755
  6. universal_mcp/applications/aws_s3/app.py +63 -119
  7. universal_mcp/applications/bill/app.py +644 -2055
  8. universal_mcp/applications/box/app.py +1246 -4159
  9. universal_mcp/applications/braze/app.py +410 -1476
  10. universal_mcp/applications/browser_use/README.md +15 -1
  11. universal_mcp/applications/browser_use/__init__.py +1 -0
  12. universal_mcp/applications/browser_use/app.py +91 -26
  13. universal_mcp/applications/cal_com_v2/app.py +207 -625
  14. universal_mcp/applications/calendly/app.py +103 -242
  15. universal_mcp/applications/canva/app.py +75 -140
  16. universal_mcp/applications/clickup/app.py +331 -798
  17. universal_mcp/applications/coda/app.py +240 -520
  18. universal_mcp/applications/confluence/app.py +497 -1285
  19. universal_mcp/applications/contentful/app.py +40 -155
  20. universal_mcp/applications/crustdata/app.py +44 -123
  21. universal_mcp/applications/dialpad/app.py +451 -924
  22. universal_mcp/applications/digitalocean/app.py +2071 -6082
  23. universal_mcp/applications/domain_checker/app.py +3 -54
  24. universal_mcp/applications/e2b/app.py +17 -68
  25. universal_mcp/applications/elevenlabs/README.md +27 -3
  26. universal_mcp/applications/elevenlabs/app.py +741 -74
  27. universal_mcp/applications/exa/README.md +8 -4
  28. universal_mcp/applications/exa/app.py +415 -186
  29. universal_mcp/applications/falai/README.md +5 -7
  30. universal_mcp/applications/falai/app.py +156 -232
  31. universal_mcp/applications/figma/app.py +91 -175
  32. universal_mcp/applications/file_system/app.py +2 -13
  33. universal_mcp/applications/firecrawl/app.py +198 -176
  34. universal_mcp/applications/fireflies/app.py +59 -281
  35. universal_mcp/applications/fpl/app.py +92 -529
  36. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  37. universal_mcp/applications/fpl/utils/helper.py +25 -89
  38. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  39. universal_mcp/applications/ghost_content/app.py +70 -179
  40. universal_mcp/applications/github/app.py +30 -67
  41. universal_mcp/applications/gong/app.py +142 -302
  42. universal_mcp/applications/google_calendar/app.py +26 -78
  43. universal_mcp/applications/google_docs/README.md +15 -14
  44. universal_mcp/applications/google_docs/app.py +103 -206
  45. universal_mcp/applications/google_drive/app.py +194 -793
  46. universal_mcp/applications/google_gemini/app.py +68 -59
  47. universal_mcp/applications/google_mail/README.md +1 -0
  48. universal_mcp/applications/google_mail/app.py +93 -214
  49. universal_mcp/applications/google_searchconsole/app.py +25 -58
  50. universal_mcp/applications/google_sheet/README.md +2 -1
  51. universal_mcp/applications/google_sheet/app.py +226 -624
  52. universal_mcp/applications/google_sheet/helper.py +26 -53
  53. universal_mcp/applications/hashnode/app.py +57 -269
  54. universal_mcp/applications/heygen/README.md +10 -32
  55. universal_mcp/applications/heygen/app.py +339 -811
  56. universal_mcp/applications/http_tools/app.py +10 -32
  57. universal_mcp/applications/hubspot/README.md +1 -1
  58. universal_mcp/applications/hubspot/app.py +7508 -99
  59. universal_mcp/applications/jira/app.py +2419 -8334
  60. universal_mcp/applications/klaviyo/app.py +739 -1621
  61. universal_mcp/applications/linkedin/README.md +18 -1
  62. universal_mcp/applications/linkedin/app.py +729 -251
  63. universal_mcp/applications/mailchimp/app.py +696 -1851
  64. universal_mcp/applications/markitdown/app.py +8 -20
  65. universal_mcp/applications/miro/app.py +333 -815
  66. universal_mcp/applications/ms_teams/app.py +420 -1407
  67. universal_mcp/applications/neon/app.py +144 -250
  68. universal_mcp/applications/notion/app.py +38 -53
  69. universal_mcp/applications/onedrive/app.py +26 -48
  70. universal_mcp/applications/openai/app.py +43 -166
  71. universal_mcp/applications/outlook/README.md +22 -9
  72. universal_mcp/applications/outlook/app.py +403 -141
  73. universal_mcp/applications/perplexity/README.md +2 -1
  74. universal_mcp/applications/perplexity/app.py +161 -20
  75. universal_mcp/applications/pipedrive/app.py +1021 -3331
  76. universal_mcp/applications/posthog/app.py +272 -541
  77. universal_mcp/applications/reddit/app.py +65 -164
  78. universal_mcp/applications/resend/app.py +72 -139
  79. universal_mcp/applications/retell/app.py +23 -50
  80. universal_mcp/applications/rocketlane/app.py +252 -965
  81. universal_mcp/applications/scraper/app.py +114 -142
  82. universal_mcp/applications/semanticscholar/app.py +36 -78
  83. universal_mcp/applications/semrush/app.py +44 -78
  84. universal_mcp/applications/sendgrid/app.py +826 -1576
  85. universal_mcp/applications/sentry/app.py +444 -1079
  86. universal_mcp/applications/serpapi/app.py +44 -146
  87. universal_mcp/applications/sharepoint/app.py +27 -49
  88. universal_mcp/applications/shopify/app.py +1748 -4486
  89. universal_mcp/applications/shortcut/app.py +275 -536
  90. universal_mcp/applications/slack/app.py +43 -125
  91. universal_mcp/applications/spotify/app.py +206 -405
  92. universal_mcp/applications/supabase/app.py +174 -283
  93. universal_mcp/applications/tavily/app.py +2 -2
  94. universal_mcp/applications/trello/app.py +853 -2816
  95. universal_mcp/applications/twilio/app.py +27 -62
  96. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  97. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  98. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  99. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  100. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  101. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  102. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  103. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  104. universal_mcp/applications/whatsapp/app.py +35 -186
  105. universal_mcp/applications/whatsapp/audio.py +2 -6
  106. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  107. universal_mcp/applications/whatsapp_business/app.py +86 -299
  108. universal_mcp/applications/wrike/app.py +80 -153
  109. universal_mcp/applications/yahoo_finance/app.py +19 -65
  110. universal_mcp/applications/youtube/app.py +120 -306
  111. universal_mcp/applications/zenquotes/app.py +3 -3
  112. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/METADATA +4 -2
  113. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/RECORD +115 -119
  114. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/WHEEL +1 -1
  115. universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
  116. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
  117. universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
  118. universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
  119. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,6 @@
1
1
  import base64
2
2
  import json
3
3
  from typing import Any
4
-
5
4
  import boto3
6
5
  from botocore.exceptions import ClientError
7
6
  from universal_mcp.applications.application import BaseApplication
@@ -26,36 +25,34 @@ class AwsS3App(BaseApplication):
26
25
  self._client = client
27
26
  self.integration = integration
28
27
 
29
- @property
30
- def client(self):
28
+ async def get_client(self):
31
29
  """
32
30
  Lazily initializes and returns a cached Boto3 S3 client instance. It retrieves authentication credentials from the associated `integration` object. This property is the core mechanism used by all other methods in the class to interact with AWS S3, raising an error if the integration is not set.
33
31
  """
34
32
  if not self.integration:
35
33
  raise ValueError("Integration not initialized")
36
34
  if not self._client:
37
- credentials = self.integration.get_credentials()
35
+ credentials = await self.integration.get_credentials_async()
38
36
  credentials = {
39
- "aws_access_key_id": credentials.get("access_key_id")
40
- or credentials.get("username"),
41
- "aws_secret_access_key": credentials.get("secret_access_key")
42
- or credentials.get("password"),
37
+ "aws_access_key_id": credentials.get("access_key_id") or credentials.get("username"),
38
+ "aws_secret_access_key": credentials.get("secret_access_key") or credentials.get("password"),
43
39
  "region_name": credentials.get("region"),
44
40
  }
45
41
  self._client = boto3.client("s3", **credentials)
46
42
  return self._client
47
43
 
48
- def list_buckets(self) -> list[str]:
44
+ async def list_buckets(self) -> list[str]:
49
45
  """
50
46
  Retrieves all S3 buckets accessible by the configured AWS credentials. It calls the S3 API's list_buckets operation and processes the response to return a simple list containing just the names of the buckets.
51
47
 
52
48
  Returns:
53
49
  List[str]: A list of bucket names.
54
50
  """
55
- response = self.client.list_buckets()
51
+ client = await self.get_client()
52
+ response = await client.list_buckets()
56
53
  return [bucket["Name"] for bucket in response["Buckets"]]
57
54
 
58
- def create_bucket(self, bucket_name: str, region: str | None = None) -> bool:
55
+ async def create_bucket(self, bucket_name: str, region: str | None = None) -> bool:
59
56
  """
60
57
  Creates a new Amazon S3 bucket with a specified name and optional region. Returns `True` upon successful creation, or `False` if an AWS client error, such as a naming conflict or permission issue, occurs.
61
58
 
@@ -69,18 +66,16 @@ class AwsS3App(BaseApplication):
69
66
  important
70
67
  """
71
68
  try:
69
+ client = await self.get_client()
72
70
  if region:
73
- self.client.create_bucket(
74
- Bucket=bucket_name,
75
- CreateBucketConfiguration={"LocationConstraint": region},
76
- )
71
+ await client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": region})
77
72
  else:
78
- self.client.create_bucket(Bucket=bucket_name)
73
+ await client.create_bucket(Bucket=bucket_name)
79
74
  return True
80
75
  except ClientError:
81
76
  return False
82
77
 
83
- def delete_bucket(self, bucket_name: str) -> bool:
78
+ async def delete_bucket(self, bucket_name: str) -> bool:
84
79
  """
85
80
  Deletes a specified S3 bucket. The operation requires the bucket to be empty to succeed. It returns `True` if the bucket is successfully deleted and `False` if an error occurs, such as the bucket not being found or containing objects.
86
81
 
@@ -93,12 +88,13 @@ class AwsS3App(BaseApplication):
93
88
  important
94
89
  """
95
90
  try:
96
- self.client.delete_bucket(Bucket=bucket_name)
91
+ client = await self.get_client()
92
+ await client.delete_bucket(Bucket=bucket_name)
97
93
  return True
98
94
  except ClientError:
99
95
  return False
100
96
 
101
- def get_bucket_policy(self, bucket_name: str) -> dict[str, Any]:
97
+ async def get_bucket_policy(self, bucket_name: str) -> dict[str, Any]:
102
98
  """
103
99
  Retrieves the IAM resource policy for a specified S3 bucket, parsing the JSON string into a dictionary. If the operation fails due to permissions or a non-existent policy, it returns an error dictionary. This complements `put_bucket_policy`, which applies a new policy.
104
100
 
@@ -111,12 +107,13 @@ class AwsS3App(BaseApplication):
111
107
  important
112
108
  """
113
109
  try:
114
- response = self.client.get_bucket_policy(Bucket=bucket_name)
110
+ client = await self.get_client()
111
+ response = await client.get_bucket_policy(Bucket=bucket_name)
115
112
  return json.loads(response["Policy"])
116
113
  except ClientError as e:
117
114
  return {"error": str(e)}
118
115
 
119
- def set_bucket_policy(self, bucket_name: str, policy: dict[str, Any]) -> bool:
116
+ async def set_bucket_policy(self, bucket_name: str, policy: dict[str, Any]) -> bool:
120
117
  """
121
118
  Applies or replaces the access policy for a specified S3 bucket. The function accepts the policy as a dictionary, converts it to JSON, and assigns it to the bucket. This write operation is the counterpart to `get_bucket_policy` and returns `True` on success.
122
119
 
@@ -130,14 +127,13 @@ class AwsS3App(BaseApplication):
130
127
  important
131
128
  """
132
129
  try:
133
- self.client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy))
130
+ client = await self.get_client()
131
+ await client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy))
134
132
  return True
135
133
  except ClientError:
136
134
  return False
137
135
 
138
- def list_subdirectories(
139
- self, bucket_name: str, prefix: str | None = None
140
- ) -> list[str]:
136
+ async def list_subdirectories(self, bucket_name: str, prefix: str | None = None) -> list[str]:
141
137
  """
142
138
  Lists immediate subdirectories (common prefixes) within an S3 bucket. If a prefix is provided, it returns subdirectories under that path; otherwise, it lists top-level directories. This function specifically lists folders, distinguishing it from `list_objects`, which lists files.
143
139
 
@@ -150,23 +146,20 @@ class AwsS3App(BaseApplication):
150
146
  Tags:
151
147
  important
152
148
  """
153
- paginator = self.client.get_paginator("list_objects_v2")
149
+ paginator = (await self.get_client()).get_paginator("list_objects_v2")
154
150
  operation_parameters = {"Bucket": bucket_name}
155
151
  if prefix:
156
152
  operation_parameters["Prefix"] = prefix
157
153
  operation_parameters["Delimiter"] = "/"
158
154
  else:
159
155
  operation_parameters["Delimiter"] = "/"
160
-
161
156
  prefixes = []
162
157
  for page in paginator.paginate(**operation_parameters):
163
158
  for cp in page.get("CommonPrefixes", []):
164
159
  prefixes.append(cp.get("Prefix"))
165
160
  return prefixes
166
161
 
167
- def create_prefix(
168
- self, bucket_name: str, prefix_name: str, parent_prefix: str | None = None
169
- ) -> bool:
162
+ async def create_prefix(self, bucket_name: str, prefix_name: str, parent_prefix: str | None = None) -> bool:
170
163
  """
171
164
  Creates a prefix (folder) in an S3 bucket, optionally nested under a parent prefix. It simulates a directory by creating a zero-byte object with a key ending in a slash ('/'), distinguishing it from put_object, which uploads content.
172
165
 
@@ -184,10 +177,10 @@ class AwsS3App(BaseApplication):
184
177
  key = f"{parent_prefix.rstrip('/')}/{prefix_name}/"
185
178
  else:
186
179
  key = f"{prefix_name}/"
187
- self.client.put_object(Bucket=bucket_name, Key=key)
180
+ await (await self.get_client()).put_object(Bucket=bucket_name, Key=key)
188
181
  return True
189
182
 
190
- def list_objects(self, bucket_name: str, prefix: str) -> list[dict[str, Any]]:
183
+ async def list_objects(self, bucket_name: str, prefix: str) -> list[dict[str, Any]]:
191
184
  """
192
185
  Paginates through and lists all objects within a specified S3 bucket prefix. It returns a curated list of metadata for each object (key, name, size, last modified), excluding folder placeholders. This function specifically lists files, distinguishing it from `list_prefixes` which lists folders.
193
186
 
@@ -200,7 +193,8 @@ class AwsS3App(BaseApplication):
200
193
  Tags:
201
194
  important
202
195
  """
203
- paginator = self.client.get_paginator("list_objects_v2")
196
+ client = await self.get_client()
197
+ paginator = client.get_paginator("list_objects_v2")
204
198
  operation_parameters = {"Bucket": bucket_name, "Prefix": prefix}
205
199
  objects = []
206
200
  for page in paginator.paginate(**operation_parameters):
@@ -218,9 +212,7 @@ class AwsS3App(BaseApplication):
218
212
  )
219
213
  return objects
220
214
 
221
- def put_text_object(
222
- self, bucket_name: str, prefix: str, object_name: str, content: str
223
- ) -> bool:
215
+ async def put_text_object(self, bucket_name: str, prefix: str, object_name: str, content: str) -> bool:
224
216
  """
225
217
  Uploads string content to create an object in a specified S3 bucket and prefix. The content is UTF-8 encoded before being written. This method is for text, distinguishing it from `put_object_from_base64` which handles binary data.
226
218
 
@@ -236,14 +228,11 @@ class AwsS3App(BaseApplication):
236
228
  important
237
229
  """
238
230
  key = f"{prefix.rstrip('/')}/{object_name}" if prefix else object_name
239
- self.client.put_object(
240
- Bucket=bucket_name, Key=key, Body=content.encode("utf-8")
241
- )
231
+ client = await self.get_client()
232
+ await client.put_object(Bucket=bucket_name, Key=key, Body=content.encode("utf-8"))
242
233
  return True
243
234
 
244
- def put_object_from_base64(
245
- self, bucket_name: str, prefix: str, object_name: str, base64_content: str
246
- ) -> bool:
235
+ async def put_object_from_base64(self, bucket_name: str, prefix: str, object_name: str, base64_content: str) -> bool:
247
236
  """
248
237
  Decodes a base64 string into binary data and uploads it as an object to a specified S3 location. This method is designed for binary files, differentiating it from `put_object` which handles plain text content. Returns true on success.
249
238
 
@@ -261,12 +250,13 @@ class AwsS3App(BaseApplication):
261
250
  try:
262
251
  key = f"{prefix.rstrip('/')}/{object_name}" if prefix else object_name
263
252
  content = base64.b64decode(base64_content)
264
- self.client.put_object(Bucket=bucket_name, Key=key, Body=content)
253
+ client = await self.get_client()
254
+ await client.put_object(Bucket=bucket_name, Key=key, Body=content)
265
255
  return True
266
256
  except Exception:
267
257
  return False
268
258
 
269
- def get_object_with_content(self, bucket_name: str, key: str) -> dict[str, Any]:
259
+ async def get_object_with_content(self, bucket_name: str, key: str) -> dict[str, Any]:
270
260
  """
271
261
  Retrieves an S3 object's content. It decodes text files as UTF-8 or encodes binary files as base64 based on the object's key. This function downloads the full object body, unlike `get_object_metadata` which only fetches metadata without content, returning the content, name, size, and type.
272
262
 
@@ -280,26 +270,18 @@ class AwsS3App(BaseApplication):
280
270
  important
281
271
  """
282
272
  try:
283
- obj = self.client.get_object(Bucket=bucket_name, Key=key)
273
+ client = await self.get_client()
274
+ obj = await client.get_object(Bucket=bucket_name, Key=key)
284
275
  content = obj["Body"].read()
285
- is_text_file = key.lower().endswith(
286
- (".txt", ".csv", ".json", ".xml", ".html", ".md", ".js", ".css", ".py")
287
- )
276
+ is_text_file = key.lower().endswith((".txt", ".csv", ".json", ".xml", ".html", ".md", ".js", ".css", ".py"))
288
277
  content_dict = (
289
- {"content": content.decode("utf-8")}
290
- if is_text_file
291
- else {"content_base64": base64.b64encode(content).decode("ascii")}
278
+ {"content": content.decode("utf-8")} if is_text_file else {"content_base64": base64.b64encode(content).decode("ascii")}
292
279
  )
293
- return {
294
- "name": key.split("/")[-1],
295
- "content_type": "text" if is_text_file else "binary",
296
- **content_dict,
297
- "size": len(content),
298
- }
280
+ return {"name": key.split("/")[-1], "content_type": "text" if is_text_file else "binary", **content_dict, "size": len(content)}
299
281
  except ClientError as e:
300
282
  return {"error": str(e)}
301
283
 
302
- def get_object_metadata(self, bucket_name: str, key: str) -> dict[str, Any]:
284
+ async def get_object_metadata(self, bucket_name: str, key: str) -> dict[str, Any]:
303
285
  """
304
286
  Efficiently retrieves metadata for a specified S3 object, such as size and last modified date, without downloading its content. This function uses a HEAD request, making it faster than `get_object_content` for accessing object properties. Returns a dictionary of metadata or an error message on failure.
305
287
 
@@ -313,14 +295,13 @@ class AwsS3App(BaseApplication):
313
295
  important
314
296
  """
315
297
  try:
316
- response = self.client.head_object(Bucket=bucket_name, Key=key)
298
+ client = await self.get_client()
299
+ response = await client.head_object(Bucket=bucket_name, Key=key)
317
300
  return {
318
301
  "key": key,
319
302
  "name": key.split("/")[-1],
320
303
  "size": response.get("ContentLength", 0),
321
- "last_modified": response.get("LastModified", "").isoformat()
322
- if response.get("LastModified")
323
- else "",
304
+ "last_modified": response.get("LastModified", "").isoformat() if response.get("LastModified") else "",
324
305
  "content_type": response.get("ContentType", ""),
325
306
  "etag": response.get("ETag", ""),
326
307
  "metadata": response.get("Metadata", {}),
@@ -328,9 +309,7 @@ class AwsS3App(BaseApplication):
328
309
  except ClientError as e:
329
310
  return {"error": str(e)}
330
311
 
331
- def copy_object(
332
- self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str
333
- ) -> bool:
312
+ async def copy_object(self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str) -> bool:
334
313
  """
335
314
  Copies an S3 object from a specified source location to a destination, which can be in the same or a different bucket. Unlike `move_object`, the original object remains at the source after the copy operation. It returns `True` for a successful operation.
336
315
 
@@ -347,16 +326,12 @@ class AwsS3App(BaseApplication):
347
326
  """
348
327
  try:
349
328
  copy_source = {"Bucket": source_bucket, "Key": source_key}
350
- self.client.copy_object(
351
- CopySource=copy_source, Bucket=dest_bucket, Key=dest_key
352
- )
329
+ self.client.copy_object(CopySource=copy_source, Bucket=dest_bucket, Key=dest_key)
353
330
  return True
354
331
  except ClientError:
355
332
  return False
356
333
 
357
- def move_object(
358
- self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str
359
- ) -> bool:
334
+ async def move_object(self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str) -> bool:
360
335
  """
361
336
  Moves an S3 object from a source to a destination. This is achieved by first copying the object to the new location and subsequently deleting the original. The move can occur within the same bucket or between different ones, returning `True` on success.
362
337
 
@@ -371,11 +346,11 @@ class AwsS3App(BaseApplication):
371
346
  Tags:
372
347
  important
373
348
  """
374
- if self.copy_object(source_bucket, source_key, dest_bucket, dest_key):
349
+ if await self.copy_object(source_bucket, source_key, dest_bucket, dest_key):
375
350
  return self.delete_object(source_bucket, source_key)
376
351
  return False
377
352
 
378
- def delete_single_object(self, bucket_name: str, key: str) -> bool:
353
+ async def delete_single_object(self, bucket_name: str, key: str) -> bool:
379
354
  """
380
355
  Deletes a single, specified object from an S3 bucket using its key. Returns `True` if successful, otherwise `False`. For bulk deletions in a single request, the `delete_objects` function should be used instead.
381
356
 
@@ -394,7 +369,7 @@ class AwsS3App(BaseApplication):
394
369
  except ClientError:
395
370
  return False
396
371
 
397
- def delete_objects(self, bucket_name: str, keys: list[str]) -> dict[str, Any]:
372
+ async def delete_objects(self, bucket_name: str, keys: list[str]) -> dict[str, Any]:
398
373
  """
399
374
  Performs a bulk deletion of objects from a specified S3 bucket in a single request. Given a list of keys, it returns a dictionary detailing successful deletions and any errors. This method is the batch-processing counterpart to the singular `delete_object` function, designed for efficiency.
400
375
 
@@ -409,9 +384,8 @@ class AwsS3App(BaseApplication):
409
384
  """
410
385
  try:
411
386
  delete_dict = {"Objects": [{"Key": key} for key in keys]}
412
- response = self.client.delete_objects(
413
- Bucket=bucket_name, Delete=delete_dict
414
- )
387
+ client = await self.get_client()
388
+ response = await client.delete_objects(Bucket=bucket_name, Delete=delete_dict)
415
389
  return {
416
390
  "deleted": [obj.get("Key") for obj in response.get("Deleted", [])],
417
391
  "errors": [obj for obj in response.get("Errors", [])],
@@ -419,13 +393,7 @@ class AwsS3App(BaseApplication):
419
393
  except ClientError as e:
420
394
  return {"error": str(e)}
421
395
 
422
- def generate_presigned_url(
423
- self,
424
- bucket_name: str,
425
- key: str,
426
- expiration: int = 3600,
427
- http_method: str = "GET",
428
- ) -> str:
396
+ async def generate_presigned_url(self, bucket_name: str, key: str, expiration: int = 3600, http_method: str = "GET") -> str:
429
397
  """
430
398
  Generates a temporary, secure URL for a specific S3 object. This URL grants time-limited permissions for actions like GET, PUT, or DELETE, expiring after a defined period. It allows object access without sharing permanent AWS credentials.
431
399
 
@@ -441,28 +409,17 @@ class AwsS3App(BaseApplication):
441
409
  important
442
410
  """
443
411
  try:
444
- method_map = {
445
- "GET": "get_object",
446
- "PUT": "put_object",
447
- "DELETE": "delete_object",
448
- }
449
-
450
- response = self.client.generate_presigned_url(
451
- method_map.get(http_method.upper(), "get_object"),
452
- Params={"Bucket": bucket_name, "Key": key},
453
- ExpiresIn=expiration,
412
+ method_map = {"GET": "get_object", "PUT": "put_object", "DELETE": "delete_object"}
413
+ client = await self.get_client()
414
+ response = await client.generate_presigned_url(
415
+ method_map.get(http_method.upper(), "get_object"), Params={"Bucket": bucket_name, "Key": key}, ExpiresIn=expiration
454
416
  )
455
417
  return response
456
418
  except ClientError as e:
457
419
  return f"Error: {str(e)}"
458
420
 
459
- def search_objects(
460
- self,
461
- bucket_name: str,
462
- prefix: str = "",
463
- name_pattern: str = "",
464
- min_size: int | None = None,
465
- max_size: int | None = None,
421
+ async def search_objects(
422
+ self, bucket_name: str, prefix: str = "", name_pattern: str = "", min_size: int | None = None, max_size: int | None = None
466
423
  ) -> list[dict[str, Any]]:
467
424
  """
468
425
  Filters objects within an S3 bucket and prefix based on a name pattern and size range. It retrieves all objects via `list_objects` and then applies the specified criteria client-side, returning a refined list of matching objects and their metadata.
@@ -479,25 +436,19 @@ class AwsS3App(BaseApplication):
479
436
  Tags:
480
437
  important
481
438
  """
482
- all_objects = self.list_objects(bucket_name, prefix)
439
+ all_objects = await self.list_objects(bucket_name, prefix)
483
440
  filtered_objects = []
484
-
485
441
  for obj in all_objects:
486
- # Filter by name pattern
487
442
  if name_pattern and name_pattern.lower() not in obj["name"].lower():
488
443
  continue
489
-
490
- # Filter by size
491
444
  if min_size and obj["size"] < min_size:
492
445
  continue
493
446
  if max_size and obj["size"] > max_size:
494
447
  continue
495
-
496
448
  filtered_objects.append(obj)
497
-
498
449
  return filtered_objects
499
450
 
500
- def get_storage_summary(self, bucket_name: str, prefix: str = "") -> dict[str, Any]:
451
+ async def get_storage_summary(self, bucket_name: str, prefix: str = "") -> dict[str, Any]:
501
452
  """
502
453
  Calculates and returns statistics for an S3 bucket or prefix. The result includes the total number of objects, their combined size in bytes, and a human-readable string representation of the size (e.g., '15.2 MB').
503
454
 
@@ -510,11 +461,9 @@ class AwsS3App(BaseApplication):
510
461
  Tags:
511
462
  important
512
463
  """
513
- objects = self.list_objects(bucket_name, prefix)
514
- total_size = sum(obj["size"] for obj in objects)
464
+ objects = await self.list_objects(bucket_name, prefix)
465
+ total_size = sum((obj["size"] for obj in objects))
515
466
  object_count = len(objects)
516
-
517
- # Convert to human-readable format
518
467
  for unit in ["B", "KB", "MB", "GB", "TB"]:
519
468
  if total_size < 1024.0:
520
469
  human_size = f"{total_size:.2f} {unit}"
@@ -522,12 +471,7 @@ class AwsS3App(BaseApplication):
522
471
  total_size /= 1024.0
523
472
  else:
524
473
  human_size = f"{total_size:.2f} PB"
525
-
526
- return {
527
- "total_size_bytes": sum(obj["size"] for obj in objects),
528
- "human_readable_size": human_size,
529
- "object_count": object_count,
530
- }
474
+ return {"total_size_bytes": sum((obj["size"] for obj in objects)), "human_readable_size": human_size, "object_count": object_count}
531
475
 
532
476
  def list_tools(self):
533
477
  return [