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
@@ -0,0 +1,166 @@
1
+ # Best Practices for Creating MCP Applications
2
+
3
+ This guide provides best practices for developing new applications for the Universal MCP. We will use the `google_gemini` application as a reference to illustrate key concepts.
4
+
5
+ ## 1. Application Structure
6
+
7
+ Every application should be a class that inherits from `APIApplication`. This base class provides the necessary foundation for integration with the MCP.
8
+
9
+ ### Basic Class Definition
10
+
11
+ Your `app.py` file should contain a class definition similar to this:
12
+
13
+ ```python
14
+ from universal_mcp.applications.application import APIApplication
15
+ from universal_mcp.integrations import Integration
16
+
17
+ class YourApplicationName(APIApplication):
18
+ def __init__(self, integration: Integration = None, **kwargs) -> None:
19
+ super().__init__(name="your_application_name", integration=integration, **kwargs)
20
+ self._api_client = None # Lazy-loaded client
21
+
22
+ # ... your tool methods will go here
23
+ ```
24
+
25
+ - **`__init__`**: The constructor initializes the base application with a unique `name` (usually matching the folder name) and the `integration` object, which holds credentials.
26
+ - **`_api_client`**: It's a best practice to lazy-load your API client. Initialize it as `None` and create the client instance only when it's first needed.
27
+
28
+ ## 2. Authentication and API Clients
29
+
30
+ Credentials should never be hard-coded. They are managed through the `integration` object. Use a property to initialize your API client on its first use. This prevents unnecessary setup if the application is loaded but not used.
31
+
32
+ ```python
33
+ from google import genai
34
+
35
+ class GoogleGeminiApp(APIApplication):
36
+ # ... (init method)
37
+
38
+ @property
39
+ def genai_client(self) -> genai.Client:
40
+ if self._genai_client is not None:
41
+ return self._genai_client
42
+
43
+ credentials = self.integration.get_credentials()
44
+ api_key = (
45
+ credentials.get("api_key")
46
+ or credentials.get("API_KEY")
47
+ or credentials.get("apiKey")
48
+ )
49
+ if not api_key:
50
+ raise ValueError("API key not found in integration credentials")
51
+
52
+ self._genai_client = genai.Client(api_key=api_key)
53
+ return self._genai_client
54
+ ```
55
+
56
+ This `genai_client` property ensures the client is created only once, the first time a tool tries to access it.
57
+
58
+ ## 3. Defining Tools
59
+
60
+ Public methods within your application class are exposed as tools that the AI can use. For a method to be a valid tool, it must have a detailed docstring and proper type hints.
61
+
62
+ ### Docstrings: The AI's Guide
63
+
64
+ A well-written docstring is crucial. It's how the AI understands what your tool does, what inputs it needs, and what it returns.
65
+
66
+ ```python
67
+ async def generate_text(
68
+ self,
69
+ prompt: Annotated[str, "The prompt to generate text from"],
70
+ model: str = "gemini-2.5-flash",
71
+ ) -> str:
72
+ """Generates text using the Google Gemini model based on a given prompt.
73
+ This tool is suitable for various natural language processing tasks such as content generation, summarization, translation, and question answering.
74
+
75
+ Args:
76
+ prompt (str): The text prompt or instruction for the model to follow. For example: "Write a short story about a robot who learns to love."
77
+ model (str, optional): The Gemini model to use for text generation. Defaults to "gemini-2.5-flash".
78
+
79
+ Returns:
80
+ str: The generated text response from the Gemini model.
81
+
82
+ Raises:
83
+ ValueError: If the API key is not found in the integration credentials.
84
+ Exception: If the underlying client or API call fails.
85
+
86
+ Tags:
87
+ text, generate, llm, important
88
+ """
89
+ response = self.genai_client.models.generate_content(
90
+ contents=prompt, model=model
91
+ )
92
+ return response.text
93
+ ```
94
+
95
+ **Docstring Breakdown:**
96
+ 1. **Summary Line**: A single, concise sentence describing the tool's main function.
97
+ 2. **Detailed Description**: An optional paragraph providing more context, use cases, and why the tool is useful.
98
+ 3. **`Args`**: Describe each parameter. For clarity, use `typing.Annotated` to provide a short, user-friendly description directly in the function signature.
99
+ 4. **`Returns`**: Clearly describe the output of the function.
100
+ 5. **`Raises`**: List any potential exceptions the tool might raise.
101
+ 6. **`Tags`**: A comma-separated list of keywords that help in categorizing and discovering the tool. Use `important` for core tools.
102
+
103
+ ### Return Types
104
+
105
+ - **Simple Types**: For simple text or data, return standard Python types like `str`, `dict`, or `list`.
106
+
107
+ - **Files/Media (Images, Audio, etc.)**: When a tool generates content that should be treated as a file, return a specific dictionary structure. This allows the MCP to handle the data correctly (e.g., saving it to a file or displaying it).
108
+
109
+ The structure is:
110
+ ```json
111
+ {
112
+ "type": "image" | "audio" | "video" | "file",
113
+ "data": "<base64_encoded_string>",
114
+ "mime_type": "image/png",
115
+ "file_name": "suggested_filename.png"
116
+ }
117
+ ```
118
+
119
+ **Example from `generate_image`:**
120
+ ```python
121
+ return {
122
+ "type": "image",
123
+ "data": img_base64,
124
+ "mime_type": "image/png",
125
+ "file_name": file_name,
126
+ "text": text, # Optional: any accompanying text
127
+ }
128
+ ```
129
+
130
+ ## 4. Listing and Registering Tools
131
+
132
+ To make your tools available to the MCP, you must list them in the `list_tools` method.
133
+
134
+ ```python
135
+ class GoogleGeminiApp(APIApplication):
136
+ # ... (tool methods)
137
+
138
+ def list_tools(self):
139
+ return [
140
+ self.generate_text,
141
+ self.generate_image,
142
+ self.generate_audio,
143
+ ]
144
+ ```
145
+
146
+ This method should return a list of the function objects you want to expose.
147
+
148
+ ## 5. Local Testing
149
+
150
+ To facilitate rapid development and testing, include a runnable block in your `app.py`. This allows you to test your application's functionality directly without needing the full MCP environment.
151
+
152
+ ```python
153
+ if __name__ == "__main__":
154
+ import asyncio
155
+
156
+ async def test_google_gemini():
157
+ # NOTE: You may need to set up credentials locally for this to work
158
+ # e.g., os.environ["GOOGLE_API_KEY"] = "..."
159
+ app = GoogleGeminiApp()
160
+ response = await app.generate_text(
161
+ "Tell me a fun fact about the Roman Empire."
162
+ )
163
+ print(response)
164
+
165
+ asyncio.run(test_google_gemini())
166
+ ```
@@ -14,7 +14,6 @@ from pyairtable.api.types import (
14
14
  WritableFields,
15
15
  )
16
16
  from pyairtable.formulas import Formula, to_formula_str
17
-
18
17
  from universal_mcp.applications.application import APIApplication
19
18
  from universal_mcp.integrations import Integration
20
19
 
@@ -1,7 +1,6 @@
1
1
  from typing import Any
2
2
 
3
3
  from loguru import logger
4
-
5
4
  from universal_mcp.applications.application import APIApplication
6
5
  from universal_mcp.integrations import Integration
7
6
 
@@ -4,7 +4,6 @@ from typing import Any
4
4
 
5
5
  import boto3
6
6
  from botocore.exceptions import ClientError
7
-
8
7
  from universal_mcp.applications.application import BaseApplication
9
8
  from universal_mcp.integrations import Integration
10
9
 
@@ -49,7 +48,7 @@ class AwsS3App(BaseApplication):
49
48
  def list_buckets(self) -> list[str]:
50
49
  """
51
50
  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.
52
-
51
+
53
52
  Returns:
54
53
  List[str]: A list of bucket names.
55
54
  """
@@ -59,11 +58,11 @@ class AwsS3App(BaseApplication):
59
58
  def create_bucket(self, bucket_name: str, region: str | None = None) -> bool:
60
59
  """
61
60
  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.
62
-
61
+
63
62
  Args:
64
63
  bucket_name (str): The name of the bucket to create.
65
64
  region (str, optional): The region to create the bucket in.
66
-
65
+
67
66
  Returns:
68
67
  bool: True if the bucket was created successfully.
69
68
  Tags:
@@ -84,10 +83,10 @@ class AwsS3App(BaseApplication):
84
83
  def delete_bucket(self, bucket_name: str) -> bool:
85
84
  """
86
85
  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.
87
-
86
+
88
87
  Args:
89
88
  bucket_name (str): The name of the bucket to delete.
90
-
89
+
91
90
  Returns:
92
91
  bool: True if the bucket was deleted successfully.
93
92
  Tags:
@@ -102,10 +101,10 @@ class AwsS3App(BaseApplication):
102
101
  def get_bucket_policy(self, bucket_name: str) -> dict[str, Any]:
103
102
  """
104
103
  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.
105
-
104
+
106
105
  Args:
107
106
  bucket_name (str): The name of the S3 bucket.
108
-
107
+
109
108
  Returns:
110
109
  Dict[str, Any]: The bucket policy as a dictionary.
111
110
  Tags:
@@ -120,11 +119,11 @@ class AwsS3App(BaseApplication):
120
119
  def set_bucket_policy(self, bucket_name: str, policy: dict[str, Any]) -> bool:
121
120
  """
122
121
  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.
123
-
122
+
124
123
  Args:
125
124
  bucket_name (str): The name of the S3 bucket.
126
125
  policy (Dict[str, Any]): The bucket policy as a dictionary.
127
-
126
+
128
127
  Returns:
129
128
  bool: True if the policy was set successfully.
130
129
  Tags:
@@ -136,14 +135,16 @@ class AwsS3App(BaseApplication):
136
135
  except ClientError:
137
136
  return False
138
137
 
139
- def list_subdirectories(self, bucket_name: str, prefix: str | None = None) -> list[str]:
138
+ def list_subdirectories(
139
+ self, bucket_name: str, prefix: str | None = None
140
+ ) -> list[str]:
140
141
  """
141
142
  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.
142
-
143
+
143
144
  Args:
144
145
  bucket_name (str): The name of the S3 bucket.
145
146
  prefix (str, optional): The prefix to list folders under.
146
-
147
+
147
148
  Returns:
148
149
  List[str]: A list of folder prefixes.
149
150
  Tags:
@@ -168,12 +169,12 @@ class AwsS3App(BaseApplication):
168
169
  ) -> bool:
169
170
  """
170
171
  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.
171
-
172
+
172
173
  Args:
173
174
  bucket_name (str): The name of the S3 bucket.
174
175
  prefix_name (str): The name of the prefix to create.
175
176
  parent_prefix (str, optional): The parent prefix (folder path).
176
-
177
+
177
178
  Returns:
178
179
  bool: True if the prefix was created successfully.
179
180
  Tags:
@@ -189,11 +190,11 @@ class AwsS3App(BaseApplication):
189
190
  def list_objects(self, bucket_name: str, prefix: str) -> list[dict[str, Any]]:
190
191
  """
191
192
  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.
192
-
193
+
193
194
  Args:
194
195
  bucket_name (str): The name of the S3 bucket.
195
196
  prefix (str): The prefix (folder path) to list objects under.
196
-
197
+
197
198
  Returns:
198
199
  List[Dict[str, Any]]: A list of dictionaries containing object metadata.
199
200
  Tags:
@@ -222,13 +223,13 @@ class AwsS3App(BaseApplication):
222
223
  ) -> bool:
223
224
  """
224
225
  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.
225
-
226
+
226
227
  Args:
227
228
  bucket_name (str): The name of the S3 bucket.
228
229
  prefix (str): The prefix (folder path) where the object will be created.
229
230
  object_name (str): The name of the object to create.
230
231
  content (str): The content to write into the object.
231
-
232
+
232
233
  Returns:
233
234
  bool: True if the object was created successfully.
234
235
  Tags:
@@ -245,13 +246,13 @@ class AwsS3App(BaseApplication):
245
246
  ) -> bool:
246
247
  """
247
248
  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.
248
-
249
+
249
250
  Args:
250
251
  bucket_name (str): The name of the S3 bucket.
251
252
  prefix (str): The prefix (folder path) where the object will be created.
252
253
  object_name (str): The name of the object to create.
253
254
  base64_content (str): The base64-encoded content to upload.
254
-
255
+
255
256
  Returns:
256
257
  bool: True if the object was created successfully.
257
258
  Tags:
@@ -268,11 +269,11 @@ class AwsS3App(BaseApplication):
268
269
  def get_object_with_content(self, bucket_name: str, key: str) -> dict[str, Any]:
269
270
  """
270
271
  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.
271
-
272
+
272
273
  Args:
273
274
  bucket_name (str): The name of the S3 bucket.
274
275
  key (str): The key (path) to the object.
275
-
276
+
276
277
  Returns:
277
278
  Dict[str, Any]: A dictionary containing the object's name, content type, content (as text or base64), and size.
278
279
  Tags:
@@ -301,11 +302,11 @@ class AwsS3App(BaseApplication):
301
302
  def get_object_metadata(self, bucket_name: str, key: str) -> dict[str, Any]:
302
303
  """
303
304
  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.
304
-
305
+
305
306
  Args:
306
307
  bucket_name (str): The name of the S3 bucket.
307
308
  key (str): The key (path) to the object.
308
-
309
+
309
310
  Returns:
310
311
  Dict[str, Any]: A dictionary containing the object's metadata.
311
312
  Tags:
@@ -332,13 +333,13 @@ class AwsS3App(BaseApplication):
332
333
  ) -> bool:
333
334
  """
334
335
  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.
335
-
336
+
336
337
  Args:
337
338
  source_bucket (str): The source bucket name.
338
339
  source_key (str): The source object key.
339
340
  dest_bucket (str): The destination bucket name.
340
341
  dest_key (str): The destination object key.
341
-
342
+
342
343
  Returns:
343
344
  bool: True if the object was copied successfully.
344
345
  Tags:
@@ -358,13 +359,13 @@ class AwsS3App(BaseApplication):
358
359
  ) -> bool:
359
360
  """
360
361
  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.
361
-
362
+
362
363
  Args:
363
364
  source_bucket (str): The source bucket name.
364
365
  source_key (str): The source object key.
365
366
  dest_bucket (str): The destination bucket name.
366
367
  dest_key (str): The destination object key.
367
-
368
+
368
369
  Returns:
369
370
  bool: True if the object was moved successfully.
370
371
  Tags:
@@ -377,11 +378,11 @@ class AwsS3App(BaseApplication):
377
378
  def delete_single_object(self, bucket_name: str, key: str) -> bool:
378
379
  """
379
380
  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.
380
-
381
+
381
382
  Args:
382
383
  bucket_name (str): The name of the S3 bucket.
383
384
  key (str): The key (path) to the object to delete.
384
-
385
+
385
386
  Returns:
386
387
  bool: True if the object was deleted successfully.
387
388
  Tags:
@@ -396,11 +397,11 @@ class AwsS3App(BaseApplication):
396
397
  def delete_objects(self, bucket_name: str, keys: list[str]) -> dict[str, Any]:
397
398
  """
398
399
  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.
399
-
400
+
400
401
  Args:
401
402
  bucket_name (str): The name of the S3 bucket.
402
403
  keys (List[str]): List of object keys to delete.
403
-
404
+
404
405
  Returns:
405
406
  Dict[str, Any]: Results of the deletion operation.
406
407
  Tags:
@@ -427,13 +428,13 @@ class AwsS3App(BaseApplication):
427
428
  ) -> str:
428
429
  """
429
430
  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.
430
-
431
+
431
432
  Args:
432
433
  bucket_name (str): The name of the S3 bucket.
433
434
  key (str): The key (path) to the object.
434
435
  expiration (int): Time in seconds for the presigned URL to remain valid (default: 3600).
435
436
  http_method (str): HTTP method for the presigned URL (default: 'GET').
436
-
437
+
437
438
  Returns:
438
439
  str: The presigned URL or error message.
439
440
  Tags:
@@ -465,14 +466,14 @@ class AwsS3App(BaseApplication):
465
466
  ) -> list[dict[str, Any]]:
466
467
  """
467
468
  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.
468
-
469
+
469
470
  Args:
470
471
  bucket_name (str): The name of the S3 bucket.
471
472
  prefix (str): The prefix to search within.
472
473
  name_pattern (str): Pattern to match in object names (case-insensitive).
473
474
  min_size (int, optional): Minimum object size in bytes.
474
475
  max_size (int, optional): Maximum object size in bytes.
475
-
476
+
476
477
  Returns:
477
478
  List[Dict[str, Any]]: List of matching objects with metadata.
478
479
  Tags:
@@ -499,11 +500,11 @@ class AwsS3App(BaseApplication):
499
500
  def get_storage_summary(self, bucket_name: str, prefix: str = "") -> dict[str, Any]:
500
501
  """
501
502
  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').
502
-
503
+
503
504
  Args:
504
505
  bucket_name (str): The name of the S3 bucket.
505
506
  prefix (str): The prefix to calculate size for (default: entire bucket).
506
-
507
+
507
508
  Returns:
508
509
  Dict[str, Any]: Dictionary containing total size, object count, and human-readable size.
509
510
  Tags:
@@ -0,0 +1 @@
1
+ # Browser Use
File without changes
@@ -0,0 +1,71 @@
1
+ from typing import Annotated
2
+
3
+ from universal_mcp.applications.application import APIApplication
4
+ from universal_mcp.integrations import Integration
5
+
6
+ from browser_use_sdk import BrowserUse
7
+
8
+
9
+ class BrowserUseApp(APIApplication):
10
+ def __init__(self, integration: Integration | None = None, **kwargs) -> None:
11
+ super().__init__(name="browser_use", integration=integration, **kwargs)
12
+ self._browser_client = None
13
+
14
+ @property
15
+ def browser_client(self) -> BrowserUse:
16
+ if self._browser_client is not None:
17
+ return self._browser_client
18
+ if not self.integration:
19
+ raise ValueError("Integration is required but not provided")
20
+ credentials = self.integration.get_credentials()
21
+ api_key = (
22
+ credentials.get("api_key")
23
+ or credentials.get("API_KEY")
24
+ or credentials.get("apiKey")
25
+ )
26
+ if not api_key:
27
+ raise ValueError("API key not found in integration credentials")
28
+ self._browser_client = BrowserUse(api_key=api_key)
29
+ return self._browser_client
30
+
31
+ async def browser_task(
32
+ self,
33
+ task: Annotated[str, "What you want the browser to do"],
34
+ max_steps: Annotated[int, "Max actions to take (1-10, default: 8)"] = 8,
35
+ llm: Annotated[str, "The LLM to use for the task"] = "gpt-4.1",
36
+ ) -> dict:
37
+ """
38
+ Creates and runs a browser automation task.
39
+
40
+ Args:
41
+ task (str): The detailed description of the task for the browser to perform.
42
+ max_steps (int, optional): The maximum number of actions the browser can take. Defaults to 8.
43
+ llm (str, optional): The language model to use for interpreting the task. Defaults to "gpt-4.1".
44
+
45
+ Returns:
46
+ dict: The result of the completed task, including output and other metadata.
47
+ """
48
+ created_task = self.browser_client.tasks.create_task(
49
+ llm=llm, task=task, max_steps=max_steps
50
+ )
51
+ result = created_task.complete()
52
+ return result.to_dict()
53
+
54
+ async def get_browser_task_status(
55
+ self,
56
+ task_id: Annotated[str, "Task ID to check"],
57
+ ) -> dict:
58
+ """
59
+ Checks task progress with smart polling.
60
+
61
+ Args:
62
+ task_id (str): The ID of the task to check.
63
+
64
+ Returns:
65
+ dict: The current status and details of the task.
66
+ """
67
+ task = self.browser_client.tasks.get_task(task_id)
68
+ return task.to_dict()
69
+
70
+ def list_tools(self):
71
+ return [self.browser_task, self.get_browser_task_status]