universal-mcp 0.1.8rc2__py3-none-any.whl → 0.1.8rc4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_mcp/__init__.py +0 -2
 - universal_mcp/analytics.py +75 -0
 - universal_mcp/applications/ahrefs/README.md +76 -0
 - universal_mcp/applications/ahrefs/__init__.py +0 -0
 - universal_mcp/applications/ahrefs/app.py +2291 -0
 - universal_mcp/applications/application.py +94 -5
 - universal_mcp/applications/calendly/app.py +412 -171
 - universal_mcp/applications/coda/README.md +133 -0
 - universal_mcp/applications/coda/__init__.py +0 -0
 - universal_mcp/applications/coda/app.py +3671 -0
 - universal_mcp/applications/e2b/app.py +8 -35
 - universal_mcp/applications/figma/README.md +74 -0
 - universal_mcp/applications/figma/__init__.py +0 -0
 - universal_mcp/applications/figma/app.py +1261 -0
 - universal_mcp/applications/firecrawl/app.py +3 -33
 - universal_mcp/applications/github/app.py +41 -42
 - universal_mcp/applications/google_calendar/app.py +20 -31
 - universal_mcp/applications/google_docs/app.py +21 -46
 - universal_mcp/applications/google_drive/app.py +53 -76
 - universal_mcp/applications/google_mail/app.py +40 -56
 - universal_mcp/applications/google_sheet/app.py +43 -68
 - universal_mcp/applications/markitdown/app.py +4 -4
 - universal_mcp/applications/notion/app.py +93 -83
 - universal_mcp/applications/perplexity/app.py +4 -38
 - universal_mcp/applications/reddit/app.py +32 -32
 - universal_mcp/applications/resend/app.py +4 -22
 - universal_mcp/applications/serpapi/app.py +6 -32
 - universal_mcp/applications/tavily/app.py +4 -24
 - universal_mcp/applications/wrike/app.py +565 -237
 - universal_mcp/applications/youtube/app.py +625 -183
 - universal_mcp/applications/zenquotes/app.py +3 -3
 - universal_mcp/exceptions.py +1 -0
 - universal_mcp/integrations/__init__.py +11 -2
 - universal_mcp/integrations/agentr.py +27 -4
 - universal_mcp/integrations/integration.py +14 -6
 - universal_mcp/logger.py +3 -56
 - universal_mcp/servers/__init__.py +2 -1
 - universal_mcp/servers/server.py +73 -77
 - universal_mcp/stores/store.py +5 -3
 - universal_mcp/tools/__init__.py +1 -1
 - universal_mcp/tools/adapters.py +4 -1
 - universal_mcp/tools/func_metadata.py +5 -6
 - universal_mcp/tools/tools.py +108 -51
 - universal_mcp/utils/docgen.py +121 -69
 - universal_mcp/utils/docstring_parser.py +44 -21
 - universal_mcp/utils/dump_app_tools.py +33 -23
 - universal_mcp/utils/installation.py +199 -8
 - universal_mcp/utils/openapi.py +121 -47
 - {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc4.dist-info}/METADATA +2 -2
 - universal_mcp-0.1.8rc4.dist-info/RECORD +81 -0
 - universal_mcp-0.1.8rc2.dist-info/RECORD +0 -71
 - {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc4.dist-info}/WHEEL +0 -0
 - {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc4.dist-info}/entry_points.txt +0 -0
 
| 
         @@ -47,19 +47,19 @@ class RedditApp(APIApplication): 
     | 
|
| 
       47 
47 
     | 
    
         
             
                ) -> str:
         
     | 
| 
       48 
48 
     | 
    
         
             
                    """
         
     | 
| 
       49 
49 
     | 
    
         
             
                    Retrieves and formats top posts from a specified subreddit within a given timeframe using the Reddit API
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       51 
51 
     | 
    
         
             
                    Args:
         
     | 
| 
       52 
52 
     | 
    
         
             
                        subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/' prefix
         
     | 
| 
       53 
53 
     | 
    
         
             
                        limit: The maximum number of posts to return (default: 5, max: 100)
         
     | 
| 
       54 
54 
     | 
    
         
             
                        timeframe: The time period for top posts. Valid options: 'hour', 'day', 'week', 'month', 'year', 'all' (default: 'day')
         
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
       56 
56 
     | 
    
         
             
                    Returns:
         
     | 
| 
       57 
57 
     | 
    
         
             
                        A formatted string containing a numbered list of top posts, including titles, authors, scores, and URLs, or an error message if the request fails
         
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
       59 
59 
     | 
    
         
             
                    Raises:
         
     | 
| 
       60 
60 
     | 
    
         
             
                        RequestException: When the HTTP request to the Reddit API fails
         
     | 
| 
       61 
61 
     | 
    
         
             
                        JSONDecodeError: When the API response contains invalid JSON
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
       63 
63 
     | 
    
         
             
                    Tags:
         
     | 
| 
       64 
64 
     | 
    
         
             
                        fetch, reddit, api, list, social-media, important, read-only
         
     | 
| 
       65 
65 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -107,19 +107,19 @@ class RedditApp(APIApplication): 
     | 
|
| 
       107 
107 
     | 
    
         
             
                ) -> str:
         
     | 
| 
       108 
108 
     | 
    
         
             
                    """
         
     | 
| 
       109 
109 
     | 
    
         
             
                    Searches Reddit for subreddits matching a given query string and returns a formatted list of results including subreddit names, subscriber counts, and descriptions.
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
       111 
111 
     | 
    
         
             
                    Args:
         
     | 
| 
       112 
112 
     | 
    
         
             
                        query: The text to search for in subreddit names and descriptions
         
     | 
| 
       113 
113 
     | 
    
         
             
                        limit: The maximum number of subreddits to return, between 1 and 100 (default: 5)
         
     | 
| 
       114 
114 
     | 
    
         
             
                        sort: The order of results, either 'relevance' or 'activity' (default: 'relevance')
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
       116 
116 
     | 
    
         
             
                    Returns:
         
     | 
| 
       117 
117 
     | 
    
         
             
                        A formatted string containing a list of matching subreddits with their names, subscriber counts, and descriptions, or an error message if the search fails or parameters are invalid
         
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
       119 
119 
     | 
    
         
             
                    Raises:
         
     | 
| 
       120 
120 
     | 
    
         
             
                        RequestException: When the HTTP request to Reddit's API fails
         
     | 
| 
       121 
121 
     | 
    
         
             
                        JSONDecodeError: When the API response contains invalid JSON
         
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
       123 
123 
     | 
    
         
             
                    Tags:
         
     | 
| 
       124 
124 
     | 
    
         
             
                        search, important, reddit, api, query, format, list, validation
         
     | 
| 
       125 
125 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -177,17 +177,17 @@ class RedditApp(APIApplication): 
     | 
|
| 
       177 
177 
     | 
    
         
             
                def get_post_flairs(self, subreddit: str):
         
     | 
| 
       178 
178 
     | 
    
         
             
                    """
         
     | 
| 
       179 
179 
     | 
    
         
             
                    Retrieves a list of available post flairs for a specified subreddit using the Reddit API.
         
     | 
| 
       180 
     | 
    
         
            -
             
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
       181 
181 
     | 
    
         
             
                    Args:
         
     | 
| 
       182 
182 
     | 
    
         
             
                        subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/' prefix
         
     | 
| 
       183 
     | 
    
         
            -
             
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
       184 
184 
     | 
    
         
             
                    Returns:
         
     | 
| 
       185 
185 
     | 
    
         
             
                        A list of dictionaries containing flair details if flairs exist, or a string message indicating no flairs are available
         
     | 
| 
       186 
     | 
    
         
            -
             
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
       187 
187 
     | 
    
         
             
                    Raises:
         
     | 
| 
       188 
188 
     | 
    
         
             
                        RequestException: When the API request fails or network connectivity issues occur
         
     | 
| 
       189 
189 
     | 
    
         
             
                        JSONDecodeError: When the API response contains invalid JSON data
         
     | 
| 
       190 
     | 
    
         
            -
             
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
       191 
191 
     | 
    
         
             
                    Tags:
         
     | 
| 
       192 
192 
     | 
    
         
             
                        fetch, get, reddit, flair, api, read-only
         
     | 
| 
       193 
193 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -210,7 +210,7 @@ class RedditApp(APIApplication): 
     | 
|
| 
       210 
210 
     | 
    
         
             
                ):
         
     | 
| 
       211 
211 
     | 
    
         
             
                    """
         
     | 
| 
       212 
212 
     | 
    
         
             
                    Creates a new Reddit post in a specified subreddit with support for text posts, link posts, and image posts
         
     | 
| 
       213 
     | 
    
         
            -
             
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
       214 
214 
     | 
    
         
             
                    Args:
         
     | 
| 
       215 
215 
     | 
    
         
             
                        subreddit: The name of the subreddit (e.g., 'python', 'worldnews') without the 'r/'
         
     | 
| 
       216 
216 
     | 
    
         
             
                        title: The title of the post
         
     | 
| 
         @@ -218,13 +218,13 @@ class RedditApp(APIApplication): 
     | 
|
| 
       218 
218 
     | 
    
         
             
                        text: The text content of the post; required if kind is 'self'
         
     | 
| 
       219 
219 
     | 
    
         
             
                        url: The URL of the link or image; required if kind is 'link'. Must end with valid image extension for image posts
         
     | 
| 
       220 
220 
     | 
    
         
             
                        flair_id: The ID of the flair to assign to the post
         
     | 
| 
       221 
     | 
    
         
            -
             
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
       222 
222 
     | 
    
         
             
                    Returns:
         
     | 
| 
       223 
223 
     | 
    
         
             
                        The JSON response from the Reddit API, or an error message as a string if the API returns an error
         
     | 
| 
       224 
     | 
    
         
            -
             
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
       225 
225 
     | 
    
         
             
                    Raises:
         
     | 
| 
       226 
226 
     | 
    
         
             
                        ValueError: Raised when kind is invalid or when required parameters (text for self posts, url for link posts) are missing
         
     | 
| 
       227 
     | 
    
         
            -
             
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
       228 
228 
     | 
    
         
             
                    Tags:
         
     | 
| 
       229 
229 
     | 
    
         
             
                        create, post, social-media, reddit, api, important
         
     | 
| 
       230 
230 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -263,17 +263,17 @@ class RedditApp(APIApplication): 
     | 
|
| 
       263 
263 
     | 
    
         
             
                def get_comment_by_id(self, comment_id: str) -> dict:
         
     | 
| 
       264 
264 
     | 
    
         
             
                    """
         
     | 
| 
       265 
265 
     | 
    
         
             
                    Retrieves a specific Reddit comment using its unique identifier.
         
     | 
| 
       266 
     | 
    
         
            -
             
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
       267 
267 
     | 
    
         
             
                    Args:
         
     | 
| 
       268 
268 
     | 
    
         
             
                        comment_id: The full unique identifier of the comment (prefixed with 't1_', e.g., 't1_abcdef')
         
     | 
| 
       269 
     | 
    
         
            -
             
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
       270 
270 
     | 
    
         
             
                    Returns:
         
     | 
| 
       271 
271 
     | 
    
         
             
                        A dictionary containing the comment data including attributes like author, body, score, etc. If the comment is not found, returns a dictionary with an error message.
         
     | 
| 
       272 
     | 
    
         
            -
             
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
       273 
273 
     | 
    
         
             
                    Raises:
         
     | 
| 
       274 
274 
     | 
    
         
             
                        HTTPError: When the Reddit API request fails due to network issues or invalid authentication
         
     | 
| 
       275 
275 
     | 
    
         
             
                        JSONDecodeError: When the API response cannot be parsed as valid JSON
         
     | 
| 
       276 
     | 
    
         
            -
             
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
       277 
277 
     | 
    
         
             
                    Tags:
         
     | 
| 
       278 
278 
     | 
    
         
             
                        retrieve, get, reddit, comment, api, fetch, single-item, important
         
     | 
| 
       279 
279 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -289,18 +289,18 @@ class RedditApp(APIApplication): 
     | 
|
| 
       289 
289 
     | 
    
         
             
                def post_comment(self, parent_id: str, text: str) -> dict:
         
     | 
| 
       290 
290 
     | 
    
         
             
                    """
         
     | 
| 
       291 
291 
     | 
    
         
             
                    Posts a comment to a Reddit post or comment using the Reddit API
         
     | 
| 
       292 
     | 
    
         
            -
             
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
       293 
293 
     | 
    
         
             
                    Args:
         
     | 
| 
       294 
294 
     | 
    
         
             
                        parent_id: The full ID of the parent comment or post (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
         
     | 
| 
       295 
295 
     | 
    
         
             
                        text: The text content of the comment to be posted
         
     | 
| 
       296 
     | 
    
         
            -
             
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
       297 
297 
     | 
    
         
             
                    Returns:
         
     | 
| 
       298 
298 
     | 
    
         
             
                        A dictionary containing the Reddit API response with details about the posted comment
         
     | 
| 
       299 
     | 
    
         
            -
             
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
       300 
300 
     | 
    
         
             
                    Raises:
         
     | 
| 
       301 
301 
     | 
    
         
             
                        RequestException: If the API request fails or returns an error status code
         
     | 
| 
       302 
302 
     | 
    
         
             
                        JSONDecodeError: If the API response cannot be parsed as JSON
         
     | 
| 
       303 
     | 
    
         
            -
             
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
       304 
304 
     | 
    
         
             
                    Tags:
         
     | 
| 
       305 
305 
     | 
    
         
             
                        post, comment, social, reddit, api, important
         
     | 
| 
       306 
306 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -316,18 +316,18 @@ class RedditApp(APIApplication): 
     | 
|
| 
       316 
316 
     | 
    
         
             
                def edit_content(self, content_id: str, text: str) -> dict:
         
     | 
| 
       317 
317 
     | 
    
         
             
                    """
         
     | 
| 
       318 
318 
     | 
    
         
             
                    Edits the text content of an existing Reddit post or comment using the Reddit API
         
     | 
| 
       319 
     | 
    
         
            -
             
     | 
| 
      
 319 
     | 
    
         
            +
             
     | 
| 
       320 
320 
     | 
    
         
             
                    Args:
         
     | 
| 
       321 
321 
     | 
    
         
             
                        content_id: The full ID of the content to edit (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
         
     | 
| 
       322 
322 
     | 
    
         
             
                        text: The new text content to replace the existing content
         
     | 
| 
       323 
     | 
    
         
            -
             
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
       324 
324 
     | 
    
         
             
                    Returns:
         
     | 
| 
       325 
325 
     | 
    
         
             
                        A dictionary containing the API response with details about the edited content
         
     | 
| 
       326 
     | 
    
         
            -
             
     | 
| 
      
 326 
     | 
    
         
            +
             
     | 
| 
       327 
327 
     | 
    
         
             
                    Raises:
         
     | 
| 
       328 
328 
     | 
    
         
             
                        RequestException: When the API request fails or network connectivity issues occur
         
     | 
| 
       329 
329 
     | 
    
         
             
                        ValueError: When invalid content_id format or empty text is provided
         
     | 
| 
       330 
     | 
    
         
            -
             
     | 
| 
      
 330 
     | 
    
         
            +
             
     | 
| 
       331 
331 
     | 
    
         
             
                    Tags:
         
     | 
| 
       332 
332 
     | 
    
         
             
                        edit, update, content, reddit, api, important
         
     | 
| 
       333 
333 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -343,17 +343,17 @@ class RedditApp(APIApplication): 
     | 
|
| 
       343 
343 
     | 
    
         
             
                def delete_content(self, content_id: str) -> dict:
         
     | 
| 
       344 
344 
     | 
    
         
             
                    """
         
     | 
| 
       345 
345 
     | 
    
         
             
                    Deletes a specified Reddit post or comment using the Reddit API.
         
     | 
| 
       346 
     | 
    
         
            -
             
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
       347 
347 
     | 
    
         
             
                    Args:
         
     | 
| 
       348 
348 
     | 
    
         
             
                        content_id: The full ID of the content to delete (e.g., 't3_abc123' for a post, 't1_def456' for a comment)
         
     | 
| 
       349 
     | 
    
         
            -
             
     | 
| 
      
 349 
     | 
    
         
            +
             
     | 
| 
       350 
350 
     | 
    
         
             
                    Returns:
         
     | 
| 
       351 
351 
     | 
    
         
             
                        A dictionary containing a success message with the deleted content ID
         
     | 
| 
       352 
     | 
    
         
            -
             
     | 
| 
      
 352 
     | 
    
         
            +
             
     | 
| 
       353 
353 
     | 
    
         
             
                    Raises:
         
     | 
| 
       354 
354 
     | 
    
         
             
                        HTTPError: When the API request fails or returns an error status code
         
     | 
| 
       355 
355 
     | 
    
         
             
                        RequestException: When there are network connectivity issues or API communication problems
         
     | 
| 
       356 
     | 
    
         
            -
             
     | 
| 
      
 356 
     | 
    
         
            +
             
     | 
| 
       357 
357 
     | 
    
         
             
                    Tags:
         
     | 
| 
       358 
358 
     | 
    
         
             
                        delete, content-management, api, reddit, important
         
     | 
| 
       359 
359 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -5,40 +5,22 @@ from universal_mcp.integrations import Integration 
     | 
|
| 
       5 
5 
     | 
    
         
             
            class ResendApp(APIApplication):
         
     | 
| 
       6 
6 
     | 
    
         
             
                def __init__(self, integration: Integration) -> None:
         
     | 
| 
       7 
7 
     | 
    
         
             
                    super().__init__(name="resend", integration=integration)
         
     | 
| 
       8 
     | 
    
         
            -
                    self.api_key = None
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
                def _get_headers(self):
         
     | 
| 
       11 
     | 
    
         
            -
                    if not self.api_key:
         
     | 
| 
       12 
     | 
    
         
            -
                        credentials = self.integration.get_credentials()
         
     | 
| 
       13 
     | 
    
         
            -
                        if not credentials:
         
     | 
| 
       14 
     | 
    
         
            -
                            raise ValueError("No credentials found")
         
     | 
| 
       15 
     | 
    
         
            -
                        api_key = (
         
     | 
| 
       16 
     | 
    
         
            -
                            credentials.get("api_key")
         
     | 
| 
       17 
     | 
    
         
            -
                            or credentials.get("API_KEY")
         
     | 
| 
       18 
     | 
    
         
            -
                            or credentials.get("apiKey")
         
     | 
| 
       19 
     | 
    
         
            -
                        )
         
     | 
| 
       20 
     | 
    
         
            -
                        if not api_key:
         
     | 
| 
       21 
     | 
    
         
            -
                            raise ValueError("No API key found")
         
     | 
| 
       22 
     | 
    
         
            -
                        self.api_key = api_key
         
     | 
| 
       23 
     | 
    
         
            -
                    return {
         
     | 
| 
       24 
     | 
    
         
            -
                        "Authorization": f"Bearer {self.api_key}",
         
     | 
| 
       25 
     | 
    
         
            -
                    }
         
     | 
| 
       26 
8 
     | 
    
         | 
| 
       27 
9 
     | 
    
         
             
                def send_email(self, to: str, subject: str, content: str) -> str:
         
     | 
| 
       28 
10 
     | 
    
         
             
                    """
         
     | 
| 
       29 
11 
     | 
    
         
             
                    Sends an email using the Resend API with specified recipient, subject, and content
         
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
       31 
13 
     | 
    
         
             
                    Args:
         
     | 
| 
       32 
14 
     | 
    
         
             
                        to: Email address of the recipient
         
     | 
| 
       33 
15 
     | 
    
         
             
                        subject: Subject line of the email
         
     | 
| 
       34 
16 
     | 
    
         
             
                        content: Main body text content of the email
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       36 
18 
     | 
    
         
             
                    Returns:
         
     | 
| 
       37 
19 
     | 
    
         
             
                        String message confirming successful email delivery ('Sent Successfully')
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
       39 
21 
     | 
    
         
             
                    Raises:
         
     | 
| 
       40 
22 
     | 
    
         
             
                        ValueError: Raised when no valid credentials are found for the API
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
       42 
24 
     | 
    
         
             
                    Tags:
         
     | 
| 
       43 
25 
     | 
    
         
             
                        send, email, api, communication, important
         
     | 
| 
       44 
26 
     | 
    
         
             
                    """
         
     | 
| 
         @@ -1,5 +1,4 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import httpx
         
     | 
| 
       2 
     | 
    
         
            -
            from loguru import logger
         
     | 
| 
       3 
2 
     | 
    
         
             
            from serpapi import SerpApiClient as SerpApiSearch
         
     | 
| 
       4 
3 
     | 
    
         | 
| 
       5 
4 
     | 
    
         
             
            from universal_mcp.applications.application import APIApplication
         
     | 
| 
         @@ -8,54 +7,29 @@ from universal_mcp.applications.application import APIApplication 
     | 
|
| 
       8 
7 
     | 
    
         
             
            class SerpapiApp(APIApplication):
         
     | 
| 
       9 
8 
     | 
    
         
             
                def __init__(self, **kwargs):
         
     | 
| 
       10 
9 
     | 
    
         
             
                    super().__init__(name="serpapi", **kwargs)
         
     | 
| 
       11 
     | 
    
         
            -
                    self.api_key: str | None = None
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                def _set_api_key(self):
         
     | 
| 
       14 
     | 
    
         
            -
                    if self.api_key is not None:
         
     | 
| 
       15 
     | 
    
         
            -
                        return
         
     | 
| 
       16 
     | 
    
         
            -
                    if not self.integration:
         
     | 
| 
       17 
     | 
    
         
            -
                        raise ValueError("Integration is None. Cannot retrieve SERP API Key.")
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                    credentials = self.integration.get_credentials()
         
     | 
| 
       20 
     | 
    
         
            -
                    if not credentials:
         
     | 
| 
       21 
     | 
    
         
            -
                        raise ValueError(
         
     | 
| 
       22 
     | 
    
         
            -
                            f"Failed to retrieve SERP API Key using integration '{self.integration.name}'. "
         
     | 
| 
       23 
     | 
    
         
            -
                            f"Check store configuration (e.g., ensure the correct environment variable is set)."
         
     | 
| 
       24 
     | 
    
         
            -
                        )
         
     | 
| 
       25 
     | 
    
         
            -
                    api_key = (
         
     | 
| 
       26 
     | 
    
         
            -
                        credentials.get("api_key")
         
     | 
| 
       27 
     | 
    
         
            -
                        or credentials.get("API_KEY")
         
     | 
| 
       28 
     | 
    
         
            -
                        or credentials.get("apiKey")
         
     | 
| 
       29 
     | 
    
         
            -
                    )
         
     | 
| 
       30 
     | 
    
         
            -
                    if not api_key:
         
     | 
| 
       31 
     | 
    
         
            -
                        raise ValueError(
         
     | 
| 
       32 
     | 
    
         
            -
                            f"Invalid credential format received for SERP API Key via integration '{self.integration.name}'. "
         
     | 
| 
       33 
     | 
    
         
            -
                        )
         
     | 
| 
       34 
     | 
    
         
            -
                    self.api_key = api_key
         
     | 
| 
       35 
     | 
    
         
            -
                    logger.info("SERP API Key successfully retrieved via integration.")
         
     | 
| 
       36 
10 
     | 
    
         | 
| 
       37 
11 
     | 
    
         
             
                async def search(self, params: dict[str, any] = None) -> str:
         
     | 
| 
       38 
12 
     | 
    
         
             
                    """
         
     | 
| 
       39 
13 
     | 
    
         
             
                    Performs an asynchronous search using the SerpApi service and returns formatted search results.
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       41 
15 
     | 
    
         
             
                    Args:
         
     | 
| 
       42 
16 
     | 
    
         
             
                        params: Dictionary of engine-specific parameters (e.g., {'q': 'Coffee', 'engine': 'google_light', 'location': 'Austin, TX'}). Defaults to None.
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       44 
18 
     | 
    
         
             
                    Returns:
         
     | 
| 
       45 
19 
     | 
    
         
             
                        A formatted string containing search results with titles, links, and snippets, or an error message if the search fails.
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
       47 
21 
     | 
    
         
             
                    Raises:
         
     | 
| 
       48 
22 
     | 
    
         
             
                        httpx.HTTPStatusError: Raised when the API request fails due to HTTP errors (401 for invalid API key, 429 for rate limiting)
         
     | 
| 
       49 
23 
     | 
    
         
             
                        Exception: Raised for general errors such as network issues or invalid parameters
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       51 
25 
     | 
    
         
             
                    Tags:
         
     | 
| 
       52 
26 
     | 
    
         
             
                        search, async, web-scraping, api, serpapi, important
         
     | 
| 
       53 
27 
     | 
    
         
             
                    """
         
     | 
| 
       54 
28 
     | 
    
         
             
                    if params is None:
         
     | 
| 
       55 
29 
     | 
    
         
             
                        params = {}
         
     | 
| 
       56 
     | 
    
         
            -
                    self. 
     | 
| 
      
 30 
     | 
    
         
            +
                    api_key = self.integration.get_credentials().get("api_key")
         
     | 
| 
       57 
31 
     | 
    
         
             
                    params = {
         
     | 
| 
       58 
     | 
    
         
            -
                        "api_key":  
     | 
| 
      
 32 
     | 
    
         
            +
                        "api_key": api_key,
         
     | 
| 
       59 
33 
     | 
    
         
             
                        "engine": "google_light",  # Fastest engine by default
         
     | 
| 
       60 
34 
     | 
    
         
             
                        **params,  # Include any additional parameters
         
     | 
| 
       61 
35 
     | 
    
         
             
                    }
         
     | 
| 
         @@ -7,44 +7,24 @@ class TavilyApp(APIApplication): 
     | 
|
| 
       7 
7 
     | 
    
         
             
                    name = "tavily"
         
     | 
| 
       8 
8 
     | 
    
         
             
                    self.base_url = "https://api.tavily.com"
         
     | 
| 
       9 
9 
     | 
    
         
             
                    super().__init__(name=name, integration=integration)
         
     | 
| 
       10 
     | 
    
         
            -
                    self.api_key = None
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                def _get_headers(self):
         
     | 
| 
       13 
     | 
    
         
            -
                    if not self.api_key:
         
     | 
| 
       14 
     | 
    
         
            -
                        credentials = self.integration.get_credentials()
         
     | 
| 
       15 
     | 
    
         
            -
                        if not credentials:
         
     | 
| 
       16 
     | 
    
         
            -
                            raise ValueError("No credentials found")
         
     | 
| 
       17 
     | 
    
         
            -
                        api_key = (
         
     | 
| 
       18 
     | 
    
         
            -
                            credentials.get("api_key")
         
     | 
| 
       19 
     | 
    
         
            -
                            or credentials.get("API_KEY")
         
     | 
| 
       20 
     | 
    
         
            -
                            or credentials.get("apiKey")
         
     | 
| 
       21 
     | 
    
         
            -
                        )
         
     | 
| 
       22 
     | 
    
         
            -
                        if not api_key:
         
     | 
| 
       23 
     | 
    
         
            -
                            raise ValueError("No API key found")
         
     | 
| 
       24 
     | 
    
         
            -
                        self.api_key = api_key
         
     | 
| 
       25 
     | 
    
         
            -
                    return {
         
     | 
| 
       26 
     | 
    
         
            -
                        "Authorization": f"Bearer {self.api_key}",
         
     | 
| 
       27 
     | 
    
         
            -
                        "Content-Type": "application/json",
         
     | 
| 
       28 
     | 
    
         
            -
                    }
         
     | 
| 
       29 
10 
     | 
    
         | 
| 
       30 
11 
     | 
    
         
             
                def search(self, query: str) -> str:
         
     | 
| 
       31 
12 
     | 
    
         
             
                    """
         
     | 
| 
       32 
13 
     | 
    
         
             
                    Performs a web search using Tavily's search API and returns either a direct answer or a summary of top results.
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       34 
15 
     | 
    
         
             
                    Args:
         
     | 
| 
       35 
16 
     | 
    
         
             
                        query: The search query string to be processed by Tavily's search engine
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       37 
18 
     | 
    
         
             
                    Returns:
         
     | 
| 
       38 
19 
     | 
    
         
             
                        A string containing either a direct answer from Tavily's AI or a formatted summary of the top 3 search results, with each result containing the title and snippet
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
       40 
21 
     | 
    
         
             
                    Raises:
         
     | 
| 
       41 
22 
     | 
    
         
             
                        ValueError: When authentication credentials are invalid or missing (via validate() method)
         
     | 
| 
       42 
23 
     | 
    
         
             
                        HTTPError: When the API request fails or returns an error response
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       44 
25 
     | 
    
         
             
                    Tags:
         
     | 
| 
       45 
26 
     | 
    
         
             
                        search, ai, web, query, important, api-client, text-processing
         
     | 
| 
       46 
27 
     | 
    
         
             
                    """
         
     | 
| 
       47 
     | 
    
         
            -
                    self.validate()
         
     | 
| 
       48 
28 
     | 
    
         
             
                    url = f"{self.base_url}/search"
         
     | 
| 
       49 
29 
     | 
    
         
             
                    payload = {
         
     | 
| 
       50 
30 
     | 
    
         
             
                        "query": query,
         
     |