universal-mcp-applications 0.1.39rc8__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 (45) hide show
  1. universal_mcp/applications/BEST_PRACTICES.md +1 -1
  2. universal_mcp/applications/airtable/app.py +13 -13
  3. universal_mcp/applications/apollo/app.py +2 -2
  4. universal_mcp/applications/aws_s3/app.py +30 -19
  5. universal_mcp/applications/browser_use/app.py +10 -7
  6. universal_mcp/applications/contentful/app.py +4 -4
  7. universal_mcp/applications/crustdata/app.py +2 -2
  8. universal_mcp/applications/e2b/app.py +3 -4
  9. universal_mcp/applications/elevenlabs/README.md +27 -3
  10. universal_mcp/applications/elevenlabs/app.py +753 -48
  11. universal_mcp/applications/exa/app.py +18 -11
  12. universal_mcp/applications/falai/README.md +5 -7
  13. universal_mcp/applications/falai/app.py +160 -159
  14. universal_mcp/applications/firecrawl/app.py +14 -15
  15. universal_mcp/applications/ghost_content/app.py +4 -4
  16. universal_mcp/applications/github/app.py +2 -2
  17. universal_mcp/applications/gong/app.py +2 -2
  18. universal_mcp/applications/google_docs/README.md +15 -14
  19. universal_mcp/applications/google_docs/app.py +5 -4
  20. universal_mcp/applications/google_gemini/app.py +61 -17
  21. universal_mcp/applications/google_sheet/README.md +2 -1
  22. universal_mcp/applications/google_sheet/app.py +55 -0
  23. universal_mcp/applications/heygen/README.md +10 -32
  24. universal_mcp/applications/heygen/app.py +350 -744
  25. universal_mcp/applications/klaviyo/app.py +2 -2
  26. universal_mcp/applications/linkedin/README.md +14 -2
  27. universal_mcp/applications/linkedin/app.py +411 -38
  28. universal_mcp/applications/ms_teams/app.py +420 -1285
  29. universal_mcp/applications/notion/app.py +2 -2
  30. universal_mcp/applications/openai/app.py +1 -1
  31. universal_mcp/applications/perplexity/app.py +6 -7
  32. universal_mcp/applications/reddit/app.py +4 -4
  33. universal_mcp/applications/resend/app.py +31 -32
  34. universal_mcp/applications/rocketlane/app.py +2 -2
  35. universal_mcp/applications/scraper/app.py +51 -21
  36. universal_mcp/applications/semrush/app.py +1 -1
  37. universal_mcp/applications/serpapi/app.py +8 -7
  38. universal_mcp/applications/shopify/app.py +5 -7
  39. universal_mcp/applications/shortcut/app.py +3 -2
  40. universal_mcp/applications/slack/app.py +2 -2
  41. universal_mcp/applications/twilio/app.py +14 -13
  42. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/METADATA +1 -1
  43. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/RECORD +45 -45
  44. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/WHEEL +0 -0
  45. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/licenses/LICENSE +0 -0
@@ -27,8 +27,7 @@ class FirecrawlApp(APIApplication):
27
27
  if FirecrawlApiClient is None:
28
28
  logger.warning("Firecrawl SDK is not available. Firecrawl tools will not function.")
29
29
 
30
- @property
31
- def firecrawl_api_key(self) -> str:
30
+ async def get_firecrawl_api_key(self) -> str:
32
31
  """
33
32
  A property that lazily retrieves and caches the Firecrawl API key from the configured integration. On first access, it fetches credentials and raises a `NotAuthorizedError` if the key is unobtainable, ensuring all subsequent API calls within the application are properly authenticated before execution.
34
33
  """
@@ -37,7 +36,7 @@ class FirecrawlApp(APIApplication):
37
36
  logger.error(f"{self.name.capitalize()} App: Integration not configured.")
38
37
  raise NotAuthorizedError(f"Integration not configured for {self.name.capitalize()} App. Cannot retrieve API key.")
39
38
  try:
40
- credentials = self.integration.get_credentials()
39
+ credentials = await self.integration.get_credentials_async()
41
40
  except NotAuthorizedError as e:
42
41
  logger.error(f"{self.name.capitalize()} App: Authorization error when fetching credentials: {e.message}")
43
42
  raise
@@ -65,7 +64,7 @@ class FirecrawlApp(APIApplication):
65
64
  assert self._firecrawl_api_key is not None
66
65
  return self._firecrawl_api_key
67
66
 
68
- def _get_client(self) -> AsyncFirecrawl:
67
+ async def get_firecrawl_client(self) -> AsyncFirecrawl:
69
68
  """
70
69
  Initializes and returns the Firecrawl client after ensuring API key is set.
71
70
  Raises NotAuthorizedError if API key cannot be obtained or SDK is not installed.
@@ -73,7 +72,7 @@ class FirecrawlApp(APIApplication):
73
72
  if FirecrawlApiClient is None:
74
73
  logger.error("Firecrawl SDK (firecrawl-py) is not available.")
75
74
  raise ToolError("Firecrawl SDK (firecrawl-py) is not installed or failed to import.")
76
- current_api_key = self.firecrawl_api_key
75
+ current_api_key = await self.get_firecrawl_api_key()
77
76
  return FirecrawlApiClient(api_key=current_api_key)
78
77
 
79
78
  def _handle_firecrawl_exception(self, e: Exception, operation_desc: str) -> str:
@@ -161,7 +160,7 @@ class FirecrawlApp(APIApplication):
161
160
  """
162
161
  logger.info(f"Attempting to scrape URL: {url} with schema: {schema is not None}, prompt: {prompt is not None}")
163
162
  try:
164
- client = self._get_client()
163
+ client = await self.get_firecrawl_client()
165
164
 
166
165
  # Construct formats if schema or prompt is provided (V2 structured output)
167
166
  if schema or prompt:
@@ -212,7 +211,7 @@ class FirecrawlApp(APIApplication):
212
211
  """
213
212
  logger.info(f"Attempting Firecrawl search for query: {query}")
214
213
  try:
215
- client = self._get_client()
214
+ client = await self.get_firecrawl_client()
216
215
  response = await client.search(query=query)
217
216
  logger.info(f"Successfully performed Firecrawl search for query: {query}")
218
217
  return self._to_serializable(response)
@@ -250,7 +249,7 @@ class FirecrawlApp(APIApplication):
250
249
  """
251
250
  logger.info(f"Attempting to start Firecrawl crawl for URL: {url} with limit: {limit}")
252
251
  try:
253
- client = self._get_client()
252
+ client = await self.get_firecrawl_client()
254
253
  response = await client.start_crawl(url=url, limit=limit, scrape_options=scrape_options)
255
254
  job_id = response.id
256
255
  logger.info(f"Successfully started Firecrawl crawl for URL {url}, Job ID: {job_id}")
@@ -282,7 +281,7 @@ class FirecrawlApp(APIApplication):
282
281
  """
283
282
  logger.info(f"Attempting to check Firecrawl crawl status for job ID: {job_id}")
284
283
  try:
285
- client = self._get_client()
284
+ client = await self.get_firecrawl_client()
286
285
  status = await client.get_crawl_status(job_id=job_id)
287
286
  logger.info(f"Successfully checked Firecrawl crawl status for job ID: {job_id}")
288
287
  return self._to_serializable(status)
@@ -314,7 +313,7 @@ class FirecrawlApp(APIApplication):
314
313
  """
315
314
  logger.info(f"Attempting to cancel Firecrawl crawl job ID: {job_id}")
316
315
  try:
317
- client = self._get_client()
316
+ client = await self.get_firecrawl_client()
318
317
  response = await client.cancel_crawl(crawl_id=job_id)
319
318
  logger.info(f"Successfully issued cancel command for Firecrawl crawl job ID: {job_id}")
320
319
  return self._to_serializable(response)
@@ -345,7 +344,7 @@ class FirecrawlApp(APIApplication):
345
344
  """
346
345
  logger.info(f"Attempting to start Firecrawl batch scrape for {len(urls)} URLs.")
347
346
  try:
348
- client = self._get_client()
347
+ client = await self.get_firecrawl_client()
349
348
  response = await client.start_batch_scrape(urls=urls)
350
349
  logger.info(f"Successfully started Firecrawl batch scrape for {len(urls)} URLs.")
351
350
  return self._to_serializable(response)
@@ -376,7 +375,7 @@ class FirecrawlApp(APIApplication):
376
375
  """
377
376
  logger.info(f"Attempting to check Firecrawl batch scrape status for job ID: {job_id}")
378
377
  try:
379
- client = self._get_client()
378
+ client = await self.get_firecrawl_client()
380
379
  status = await client.get_batch_scrape_status(job_id=job_id)
381
380
  logger.info(f"Successfully checked Firecrawl batch scrape status for job ID: {job_id}")
382
381
  return self._to_serializable(status)
@@ -448,7 +447,7 @@ class FirecrawlApp(APIApplication):
448
447
  f"Attempting quick web extraction for {len(urls)} URLs with prompt: {prompt is not None}, schema: {schema is not None}."
449
448
  )
450
449
  try:
451
- client = self._get_client()
450
+ client = await self.get_firecrawl_client()
452
451
  response = await client.extract(
453
452
  urls=urls, prompt=prompt, schema=schema, system_prompt=system_prompt, allow_external_links=allow_external_links
454
453
  )
@@ -488,7 +487,7 @@ class FirecrawlApp(APIApplication):
488
487
  """
489
488
  logger.info(f"Attempting to check Firecrawl extraction status for job ID: {job_id}")
490
489
  try:
491
- client = self._get_client()
490
+ client = await self.get_firecrawl_client()
492
491
  status = await client.get_extract_status(job_id=job_id)
493
492
  logger.info(f"Successfully checked Firecrawl extraction status for job ID: {job_id}")
494
493
  return self._to_serializable(status)
@@ -520,7 +519,7 @@ class FirecrawlApp(APIApplication):
520
519
  """
521
520
  logger.info(f"Attempting to map site: {url} with limit: {limit}")
522
521
  try:
523
- client = self._get_client()
522
+ client = await self.get_firecrawl_client()
524
523
  # client.map signature (async): (url, search=None, ignoreSitemap=None, includeSubdomains=None, limit=None)
525
524
  # We expose url and limit for now, maybe more if needed later.
526
525
  response = await client.map(url=url, limit=limit)
@@ -21,7 +21,7 @@ class GhostContentApp(APIApplication):
21
21
  and Content API key.
22
22
  It is expected that the integration provides 'url' (e.g.,
23
23
  "https://your-ghost-site.com") and 'key' (the Content API key)
24
- via `integration.get_credentials()`.
24
+ via `integration.get_credentials_async()`.
25
25
  """
26
26
  super().__init__(name="ghost_content", integration=integration)
27
27
  self._base_url = None
@@ -35,7 +35,7 @@ class GhostContentApp(APIApplication):
35
35
  This is constructed from the integration's credentials.
36
36
  """
37
37
  if not self._base_url:
38
- credentials = self.integration.get_credentials()
38
+ credentials = await self.integration.get_credentials_async()
39
39
  ghost_url = credentials.get("url") or credentials.get("admin_domain")
40
40
  if not ghost_url:
41
41
  logger.error("GhostContentApp: Missing 'url' or 'admin_domain' in integration credentials.")
@@ -63,7 +63,7 @@ class GhostContentApp(APIApplication):
63
63
  Caches the key after the first retrieval.
64
64
  """
65
65
  if not self._api_key:
66
- credentials = self.integration.get_credentials()
66
+ credentials = await self.integration.get_credentials_async()
67
67
  api_key = credentials.get("key") or credentials.get("api_key") or credentials.get("API_KEY")
68
68
  if not api_key:
69
69
  logger.error("GhostContentApp: Content API key ('key') not found in integration credentials.")
@@ -78,7 +78,7 @@ class GhostContentApp(APIApplication):
78
78
  Caches the version after the first retrieval.
79
79
  """
80
80
  if not self._version:
81
- credentials = self.integration.get_credentials()
81
+ credentials = await self.integration.get_credentials_async()
82
82
  version = credentials.get("api_version")
83
83
  if not version:
84
84
  logger.warning("GhostContentApp: 'version' not found in integration credentials. Defaulting to 'v5.0'.")
@@ -10,10 +10,10 @@ class GithubApp(APIApplication):
10
10
  self.base_api_url = "https://api.github.com/repos"
11
11
  self.base_url = "https://api.github.com"
12
12
 
13
- def _get_headers(self):
13
+ async def _aget_headers(self):
14
14
  if not self.integration:
15
15
  raise ValueError("Integration not configured")
16
- credentials = self.integration.get_credentials()
16
+ credentials = await self.integration.get_credentials_async()
17
17
  if "headers" in credentials:
18
18
  return credentials["headers"]
19
19
  return {"Authorization": f"Bearer {credentials['access_token']}", "Accept": "application/vnd.github.v3+json"}
@@ -9,8 +9,8 @@ class GongApp(APIApplication):
9
9
  super().__init__(name="gong", integration=integration, **kwargs)
10
10
  self.base_url = "https://api.gong.io"
11
11
 
12
- def _get_headers(self) -> dict[str, str]:
13
- credentials = self.integration.get_credentials()
12
+ async def _aget_headers(self) -> dict[str, str]:
13
+ credentials = await self.integration.get_credentials_async()
14
14
  api_key = credentials.get("api_key")
15
15
  secret = credentials.get("secret")
16
16
  api_key_b64 = b64encode(f"{api_key}:{secret}".encode()).decode()
@@ -9,17 +9,18 @@ This is automatically generated from OpenAPI schema for the GoogleDocsApp API.
9
9
 
10
10
  | Tool | Description |
11
11
  |------|-------------|
12
- | `create_document` | Creates a blank Google Document with a specified title using a POST request to the Google Docs API. This is the primary creation method, returning the document's metadata, including the ID required by functions like `get_document` or `insert_text` to perform subsequent operations on the new file. |
13
- | `get_document` | Retrieves the complete content and metadata for a specific Google Document using its unique ID. This function performs a GET request to the API, returning the full JSON response. It's the primary read operation, contrasting with `create_document` which creates new documents. |
14
- | `insert_text` | Inserts a text string at a specified index within an existing Google Document using the `batchUpdate` API. This function adds new textual content, distinguishing it from functions that insert non-text elements like tables or apply formatting (`apply_text_style`) to existing content. |
15
- | `apply_text_style` | Applies character-level formatting such as bold, italic, font size, color, and hyperlinks to a specified text range in a document. This function modifies text appearance, distinguishing it from `update_paragraph_style`, which handles block-level formatting like alignment and headings. |
16
- | `delete_content_range` | Deletes content within a specified index range in a Google Document via the batchUpdate API. It removes any content based on location, distinguishing it from functions like `delete_header` or `delete_paragraph_bullets`, which remove specific structures or styles instead. |
17
- | `insert_table` | Inserts a table with specified row and column dimensions at a given index in a Google Document. This function uses the `batchUpdate` API for precise placement, allowing the table to be added to the document's body, a header, or a footer, returning the API's response. |
18
- | `create_footer` | Creates a footer of a specified type in a Google Document via the batch update API. The footer can optionally be associated with a specific section break, enabling distinct footers for different document sections, distinguishing it from the `create_header` and `create_footnote` functions. |
19
- | `create_footnote` | Creates a footnote via the batch update API, inserting a numbered reference at a specified index or a segment's end. This function adds an in-body citation, distinguishing it from `create_footer` which creates a content block at the bottom of the page. |
20
- | `delete_footer` | Deletes a specific footer from a Google Document using its unique ID via a `batchUpdate` request. This operation, the counterpart to `create_footer`, removes an entire footer section, unlike `delete_content_range` which targets text within a specified index range. |
21
- | `create_header` | Creates a header in a specified Google Document via the batchUpdate API endpoint. This function allows defining the header type and can optionally associate it with a specific section break location. It complements the `delete_header` and `create_footer` functions for managing document structure. |
22
- | `delete_header` | Removes a specific header from a Google Document using its unique ID. This function sends a `deleteHeader` request to the batch update API, acting as the direct counterpart to `create_header` and distinguishing it from `delete_footer` which removes footers. |
23
- | `apply_list_style` | Applies a predefined bulleted or numbered list format to paragraphs within a specified range using a `bullet_preset`. This function adds list formatting, distinguishing it from its counterpart, `delete_paragraph_bullets`, and other styling functions like `update_paragraph_style`, which handles alignment and headings. |
24
- | `delete_paragraph_bullets` | Removes bullet points or numbering from paragraphs within a specified range in a Google Document. This function reverts list formatting via the batch update API, acting as the direct counterpart to `apply_list_style` and preserving the underlying text content, unlike `delete_content_range`. |
25
- | `update_paragraph_style` | Applies paragraph-level formatting, such as named styles (e.g., 'HEADING_1'), alignment, or text direction, to a specified text range. This function handles block-level styles, distinguishing it from `apply_text_style`, which formats individual characters with attributes like bold or italic. |
12
+ | `create_document` | Creates a blank Google Document with a specified title by sending a POST request to the Google Docs API. The function returns a dictionary containing the new document's metadata, including the unique document ID required by other functions for subsequent modifications or retrieval. Note that you need to call other google_docs functions (e.g. `google_docs__insert_text`) to actually add content after creating the document. |
13
+ | `get_document` | Retrieves the complete, raw JSON object for a Google Document by its ID. This function returns the full, unprocessed API response with all metadata and structural elements, distinguishing it from `get_document_content`, which parses this data to extract only the title and plain text. |
14
+ | `get_document_content` | Retrieves and converts a Google Docs document into Markdown-formatted content. |
15
+ | `insert_text` | Inserts a text string at a specified index within a Google Document using the batchUpdate API. Unlike functions that format existing text or delete content ranges, this method specifically adds new textual content to the document body. |
16
+ | `apply_text_style` | Applies character-level formatting (e.g., bold, italic, color, links) to a specified text range. This function modifies text attributes directly, distinguishing it from `update_paragraph_style` which handles block-level properties like alignment. |
17
+ | `update_paragraph_style` | Applies paragraph-level formatting like alignment, named styles (e.g., 'HEADING_1'), and text direction to a text range in a Google Doc. Distinct from `apply_text_style`, which handles character formatting, this method modifies properties for entire paragraphs using the batchUpdate API. |
18
+ | `delete_content_range` | Removes content from a specified index range in a Google Document via the batchUpdate API. Unlike functions that delete entire elements (e.g., `delete_header`), this provides granular control by targeting content based on its precise start and end location, optionally within a specific segment or tab. |
19
+ | `insert_table` | Inserts a table with specified rows and columns at a given index in a Google Document using the batchUpdate API. It can optionally place the table within specific document segments, such as headers or footers, handling structural additions rather than text or style modifications. |
20
+ | `create_footer` | Creates a footer of a specified type in a Google Document using the batch update API. This function, distinct from `create_header`, can optionally associate the new footer with a specific section break, enabling section-specific footers within the document. |
21
+ | `create_footnote` | Inserts a numbered footnote reference into a Google Document using the batchUpdate API. The footnote can be placed at a precise index or at the end of a document segment, distinct from the `create_footer` function which adds standard page footers. |
22
+ | `delete_footer` | Deletes a specific footer from a Google Document using its unique ID via a batchUpdate API request. This operation removes the entire footer object, optionally within a specific tab, distinguishing it from functions that delete headers (`delete_header`) or general content (`delete_content_range`). |
23
+ | `create_header` | Creates a header of a specified type in a Google Document using the batchUpdate API. This function can optionally associate the new header with a specific section break, distinguishing it from the `create_footer` method, which performs the equivalent action for footers. |
24
+ | `delete_header` | Deletes a specific header from a Google Document using its unique ID via a batchUpdate API request. This function, the counterpart to `create_header`, removes headers and can optionally target a header within a specific tab. It requires both the document and header IDs for the operation. |
25
+ | `apply_list_style` | Applies a predefined list style (bulleted or numbered) to paragraphs within a specified range using a chosen preset. Unlike `delete_paragraph_bullets`, which removes list formatting, this function creates it, distinguishing it from other text and paragraph styling methods in the class. |
26
+ | `delete_paragraph_bullets` | Removes bullet points or numbering from paragraphs within a specified index range in a Google Document. This reverts list formatting to normal text while preserving content, acting as the inverse operation to the `apply_list_style` function. |
@@ -33,7 +33,7 @@ class GoogleDocsApp(APIApplication):
33
33
  payload["Note"] = "You must load and call other google docs content functions (like google_docs__insert_text)"
34
34
  return payload
35
35
 
36
- def get_document(self, document_id: str) -> dict[str, Any]:
36
+ async def get_document(self, document_id: str) -> dict[str, Any]:
37
37
  """
38
38
  Retrieves the complete, raw JSON object for a Google Document by its ID. This function returns the full, unprocessed API response with all metadata and structural elements, distinguishing it from `get_document_content`, which parses this data to extract only the title and plain text.
39
39
 
@@ -51,7 +51,7 @@ class GoogleDocsApp(APIApplication):
51
51
  retrieve, read, api, document, google-docs, important
52
52
  """
53
53
  url = f"{self.base_api_url}/{document_id}"
54
- response = self._get(url)
54
+ response = await self._aget(url)
55
55
  return response.json()
56
56
 
57
57
  async def get_document_content(self, document_id: str) -> dict[str, Any]:
@@ -85,7 +85,7 @@ class GoogleDocsApp(APIApplication):
85
85
  """
86
86
  import re
87
87
 
88
- response = self.get_document(document_id)
88
+ response = await self.get_document(document_id)
89
89
  title = response.get("title", "")
90
90
  body_content = response.get("body", {}).get("content", [])
91
91
  inline_objects = response.get("inlineObjects", {})
@@ -714,9 +714,11 @@ class GoogleDocsApp(APIApplication):
714
714
  def list_tools(self):
715
715
  return [
716
716
  self.create_document,
717
+ self.get_document,
717
718
  self.get_document_content,
718
719
  self.insert_text,
719
720
  self.apply_text_style,
721
+ self.update_paragraph_style,
720
722
  self.delete_content_range,
721
723
  self.insert_table,
722
724
  self.create_footer,
@@ -726,5 +728,4 @@ class GoogleDocsApp(APIApplication):
726
728
  self.delete_header,
727
729
  self.apply_list_style,
728
730
  self.delete_paragraph_bullets,
729
- self.update_paragraph_style,
730
731
  ]
@@ -16,11 +16,10 @@ class GoogleGeminiApp(APIApplication):
16
16
  super().__init__(name="google_gemini", integration=integration, **kwargs)
17
17
  self._genai_client = None
18
18
 
19
- @property
20
- def genai_client(self) -> genai.Client:
19
+ async def get_genai_client(self) -> genai.Client:
21
20
  if self._genai_client is not None:
22
21
  return self._genai_client
23
- credentials = self.integration.get_credentials()
22
+ credentials = await self.integration.get_credentials_async()
24
23
  api_key = credentials.get("api_key") or credentials.get("API_KEY") or credentials.get("apiKey")
25
24
  if not api_key:
26
25
  raise ValueError("API key not found in integration credentials")
@@ -54,17 +53,15 @@ class GoogleGeminiApp(APIApplication):
54
53
  Tags:
55
54
  text, generate, llm, important
56
55
  """
57
- response = self.genai_client.models.generate_content(contents=prompt, model=model)
56
+ client = await self.get_genai_client()
57
+ response = client.models.generate_content(contents=prompt, model=model)
58
58
  return response.text
59
59
 
60
60
  async def generate_image(
61
61
  self,
62
62
  prompt: Annotated[str, "The prompt to generate image from"],
63
63
  images: Annotated[list[str], "The reference image URLs"] | None = None,
64
- model: Literal[
65
- "gemini-3-pro-image-preview",
66
- "gemini-2.5-flash-image"
67
- ] = "gemini-2.5-flash-image",
64
+ model: Literal["gemini-3-pro-image-preview", "gemini-2.5-flash-image"] = "gemini-2.5-flash-image",
68
65
  ) -> dict:
69
66
  """
70
67
  Generates an image based on a text prompt and an optional reference image using the Google Gemini model.
@@ -74,7 +71,7 @@ class GoogleGeminiApp(APIApplication):
74
71
  Args:
75
72
  prompt (str): The descriptive text prompt to guide the image generation. For example: "A futuristic city at sunset with flying cars."
76
73
  images (list[str], optional): An optional list of URLs to reference images. These images will be used as a basis for the generation.
77
- model (str, optional): The Gemini model to use for image generation. Defaults to "gemini-2.5-flash-image".
74
+ model (str, optional): The Gemini model to use for image generation. Defaults to "gemini-2.5-flash-image".
78
75
 
79
76
  Returns:
80
77
  dict: A dictionary containing:
@@ -92,6 +89,7 @@ class GoogleGeminiApp(APIApplication):
92
89
  Tags:
93
90
  image, generate, vision, important
94
91
  """
92
+ client = await self.get_genai_client()
95
93
  contents = [prompt]
96
94
  if images:
97
95
  for image in images:
@@ -104,7 +102,7 @@ class GoogleGeminiApp(APIApplication):
104
102
  else:
105
103
  image = Image.open(image)
106
104
  contents.append(image)
107
- response = self.genai_client.models.generate_content(model=model, contents=contents)
105
+ response = client.models.generate_content(model=model, contents=contents)
108
106
  candidate = response.candidates[0]
109
107
  text = ""
110
108
  for part in candidate.content.parts:
@@ -119,11 +117,8 @@ class GoogleGeminiApp(APIApplication):
119
117
  async def generate_audio(
120
118
  self,
121
119
  prompt: Annotated[str, "The prompt to generate audio from"],
122
- model: Literal[
123
- "gemini-2.5-flash-preview-tts",
124
- "gemini-2.5-pro-preview-tts"
125
- ] = "gemini-2.5-flash-preview-tts",
126
- ) -> str:
120
+ model: Literal["gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts"] = "gemini-2.5-flash-preview-tts",
121
+ ) -> str:
127
122
  """Generates audio from a given text prompt using the Google Gemini model's Text-to-Speech (TTS) capabilities.
128
123
  This tool is useful for converting text into spoken audio, which can be used for voiceovers, accessibility features, or interactive applications.
129
124
  It returns a dictionary containing the generated audio data (base64 encoded), its MIME type, and a suggested file name.
@@ -145,6 +140,7 @@ class GoogleGeminiApp(APIApplication):
145
140
  Tags:
146
141
  audio, generate, tts, speech, important
147
142
  """
143
+ client = await self.get_genai_client()
148
144
 
149
145
  def wave_file(filename, pcm, channels=1, rate=24000, sample_width=2):
150
146
  with wave.open(filename, "wb") as wf:
@@ -153,7 +149,7 @@ class GoogleGeminiApp(APIApplication):
153
149
  wf.setframerate(rate)
154
150
  wf.writeframes(pcm)
155
151
 
156
- response = self.genai_client.models.generate_content(
152
+ response = client.models.generate_content(
157
153
  model=model,
158
154
  contents=prompt,
159
155
  config=types.GenerateContentConfig(
@@ -174,8 +170,56 @@ class GoogleGeminiApp(APIApplication):
174
170
  audio_base64 = base64.b64encode(data).decode("utf-8")
175
171
  return {"type": "audio", "data": audio_base64, "mime_type": "audio/wav", "file_name": file_name}
176
172
 
173
+ async def analyze_image(
174
+ self,
175
+ images: Annotated[list[str], "The reference image URLs"],
176
+ prompt: Annotated[str, "The prompt to describe or ask about the image"] = "Describe this image",
177
+ model: Literal["gemini-2.5-flash", "gemini-2.5-pro"] = "gemini-2.5-flash",
178
+ ) -> str:
179
+ """
180
+ Analyzes one or more images based on a text prompt using the Google Gemini model.
181
+ This tool is capable of describing images, answering questions about them, or performing visual reasoning.
182
+ It accepts image URLs and a text prompt, returning a natural language response.
183
+
184
+ Args:
185
+ images (list[str]): A list of URLs for the images to be analyzed.
186
+ prompt (str, optional): The text prompt or question about the images. Defaults to "Describe this image".
187
+ model (str, optional): The Gemini model to use for analysis. Defaults to "gemini-2.5-flash".
188
+
189
+ Returns:
190
+ str: The generated text response containing the analysis or description of the images.
191
+
192
+ Raises:
193
+ requests.exceptions.RequestException: If there's an issue fetching a remote image.
194
+ FileNotFoundError: If a local image path is invalid.
195
+ Exception: If the underlying Gemini API call fails.
196
+
197
+ Tags:
198
+ image, analyze, vision, describe, question, important
199
+ """
200
+ client = await self.get_genai_client()
201
+ contents = [prompt]
202
+ if images:
203
+ for image in images:
204
+ if image.startswith(("http://", "https://")):
205
+ import requests
206
+
207
+ response = requests.get(image)
208
+ response.raise_for_status()
209
+ image = Image.open(io.BytesIO(response.content))
210
+ else:
211
+ image = Image.open(image)
212
+ contents.append(image)
213
+ response = client.models.generate_content(model=model, contents=contents)
214
+ return response.text
215
+
177
216
  def list_tools(self):
178
- return [self.generate_text, self.generate_image, self.generate_audio]
217
+ return [
218
+ self.generate_text,
219
+ self.generate_image,
220
+ self.analyze_image,
221
+ self.generate_audio,
222
+ ]
179
223
 
180
224
 
181
225
  async def test_google_gemini():
@@ -9,7 +9,7 @@ This is automatically generated from OpenAPI schema for the GoogleSheetApp API.
9
9
 
10
10
  | Tool | Description |
11
11
  |------|-------------|
12
- | `create_spreadsheet` | Creates a new, blank Google Spreadsheet file with a specified title. This function generates a completely new document, unlike `add_sheet` which adds a worksheet (tab) to an existing spreadsheet. It returns the API response containing the new spreadsheet's metadata. |
12
+ | `create_spreadsheet` | Creates a new, blank Google Spreadsheet file with a specified title. This function generates a completely new document, unlike `add_sheet` which adds a worksheet (tab) to an existing spreadsheet. It returns the API response containing the new spreadsheet's metadata. Note that you need to call other google_sheet functions (e.g. `google_sheet__write_values_to_sheet`) to actually add content after creating the spreadsheet. |
13
13
  | `get_spreadsheet_metadata` | Retrieves a spreadsheet's metadata and structural properties, such as sheet names, IDs, and named ranges, using its unique ID. This function intentionally excludes cell data, distinguishing it from `get_values` which fetches the actual content within cells. |
14
14
  | `get_values` | Retrieves cell values from a single, specified A1 notation range. Unlike `batch_get_values_by_range` which fetches multiple ranges, this function is for a singular query and provides options to control the data's output format (e.g., rows vs. columns, formatted vs. raw values). |
15
15
  | `batch_get_values_by_range` | Efficiently retrieves values from multiple predefined A1 notation ranges in a single API request. Unlike `get_values`, which fetches a single range, or `batch_get_values_by_data_filter`, which uses dynamic filtering criteria, this function operates on a simple list of range strings for bulk data retrieval. |
@@ -34,3 +34,4 @@ This is automatically generated from OpenAPI schema for the GoogleSheetApp API.
34
34
  | `analyze_table_schema` | Infers a specified table's schema by analyzing a data sample. After locating the table by name (a value discovered via `discover_tables`), this function determines the most likely data type and properties for each column, providing a detailed structural breakdown of its content. |
35
35
  | `set_basic_filter` | Sets or updates a basic filter on a specified range within a sheet, enabling data sorting and filtering. The filter's target range and optional sort specifications are defined in a dictionary argument. It is the counterpart to `clear_basic_filter`, which removes an existing filter. |
36
36
  | `format_cells` | Applies comprehensive formatting to a specified cell range in a worksheet. It modifies visual properties like text style, color, alignment, borders, and can merge cells, without altering the underlying cell values, distinguishing it from data-modification functions like `update_values`. |
37
+ | `batch_update_values` | Updates multiple ranges of values in a spreadsheet in a single batch request. This method allows you to update several disjoint ranges or multiple sheets simultaneously, which is more efficient than making separate calls for each range. |
@@ -1455,6 +1455,60 @@ class GoogleSheetApp(APIApplication):
1455
1455
  response = await self._apost(url, data=request_body)
1456
1456
  return self._handle_response(response)
1457
1457
 
1458
+ async def batch_update_values(
1459
+ self,
1460
+ spreadsheetId: str,
1461
+ data: list[dict],
1462
+ value_input_option: str = "USER_ENTERED",
1463
+ include_values_in_response: bool = False,
1464
+ response_value_render_option: str | None = None,
1465
+ response_date_time_render_option: str | None = None,
1466
+ ) -> dict[str, Any]:
1467
+ """
1468
+ Updates multiple ranges of values in a spreadsheet in a single batch request. This method allows you to update several disjoint ranges or multiple sheets simultaneously, which is more efficient than making separate calls for each range.
1469
+
1470
+ Args:
1471
+ spreadsheetId: The unique identifier of the Google Spreadsheet to update. Example: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
1472
+ data: A list of dictionaries, where each dictionary represents a range to update. Each dictionary must contain:
1473
+ - range: The A1 notation of the range to update (e.g., "Sheet1!A1:B2").
1474
+ - values: A 2D list of values to write into that range.
1475
+ Example: [{"range": "Sheet1!A1", "values": [["Header"]]}, {"range": "Sheet2!B2", "values": [[100]]}]
1476
+ value_input_option: How the input data should be interpreted. Options: "RAW" (as-is) or "USER_ENTERED" (parsed as if typed by user). Defaults to "USER_ENTERED".
1477
+ include_values_in_response: If True, the response will include the values of the cells that were updated. Defaults to False.
1478
+ response_value_render_option: Determines how values in the response should be rendered. Options: "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA".
1479
+ response_date_time_render_option: Determines how dates/times in the response should be rendered. Options: "SERIAL_NUMBER", "FORMATTED_STRING".
1480
+
1481
+ Returns:
1482
+ A dictionary containing the Google Sheets API response with details of the batch update.
1483
+
1484
+ Raises:
1485
+ HTTPError: When the API request fails due to invalid parameters or insufficient permissions
1486
+ ValueError: When spreadsheetId is empty or data is empty
1487
+
1488
+ Tags:
1489
+ batch, update, values, spreadsheet, write, important
1490
+ """
1491
+ if not spreadsheetId:
1492
+ raise ValueError("spreadsheetId cannot be empty")
1493
+ if not data:
1494
+ raise ValueError("data cannot be empty")
1495
+
1496
+ url = f"{self.base_url}/{spreadsheetId}/values:batchUpdate"
1497
+
1498
+ request_body = {
1499
+ "valueInputOption": value_input_option,
1500
+ "data": data,
1501
+ "includeValuesInResponse": include_values_in_response,
1502
+ }
1503
+
1504
+ if response_value_render_option:
1505
+ request_body["responseValueRenderOption"] = response_value_render_option
1506
+ if response_date_time_render_option:
1507
+ request_body["responseDateTimeRenderOption"] = response_date_time_render_option
1508
+
1509
+ response = await self._apost(url, data=request_body)
1510
+ return self._handle_response(response)
1511
+
1458
1512
  def list_tools(self):
1459
1513
  """Returns a list of methods exposed as tools."""
1460
1514
  return [
@@ -1483,4 +1537,5 @@ class GoogleSheetApp(APIApplication):
1483
1537
  self.analyze_table_schema,
1484
1538
  self.set_basic_filter,
1485
1539
  self.format_cells,
1540
+ self.batch_update_values,
1486
1541
  ]
@@ -9,36 +9,14 @@ This is automatically generated from OpenAPI schema for the HeygenApp API.
9
9
 
10
10
  | Tool | Description |
11
11
  |------|-------------|
12
- | `get_v1_voice_list` | Retrieves the list of available voices from the v1 voice API endpoint. |
13
- | `get_v1_avatar_list` | Retrieves a list of available avatars from the v1 API endpoint. |
14
- | `get_v2_voices` | Retrieves the list of available v2 voices from the API endpoint. |
15
12
  | `get_v2_avatars` | Retrieves a list of avatar objects from the /v2/avatars API endpoint. |
16
- | `get_v1_video_list` | Retrieves a list of videos from the v1 API endpoint. |
17
- | `post_v2_video_generate` | Submits a request to generate a video using specified input parameters via the v2 video generate API endpoint. |
18
- | `delete_v1_video` | Deletes a video using the v1 API endpoint with the specified video ID. |
19
- | `get_v2_templates` | Retrieves the list of v2 templates from the API endpoint. |
20
- | `get_v2_template_by_id` | Retrieves a v2 template resource by its unique identifier. |
21
- | `post_v2_template_generate_by_id` | Generates content from a template specified by ID using the provided title and variables, and returns the generation result. |
22
- | `get_v2_video_translate_target_languages` | Retrieves the list of supported target languages for video translation via the v2 API. |
23
- | `post_v2_video_translate` | Submits a video translation request and returns the API response as JSON. |
24
- | `get_v2_video_translate_status_by_id` | Retrieves the status of a video translation job by its unique identifier. |
25
- | `post_streaming_new` | Initiates a new streaming session with optional quality parameter and returns the server's JSON response. |
26
- | `get_streaming_list` | Retrieves the list of available streaming resources from the remote API. |
27
- | `post_streaming_ice` | Sends an ICE candidate for a streaming session to the server and returns the JSON response. |
28
- | `post_streaming_task` | Submits a streaming task for the specified session and text input, returning the response from the remote API. |
29
- | `post_streaming_stop` | Stops an ongoing streaming session by sending a stop request for the specified session ID. |
30
- | `post_streaming_interrupt` | Sends a request to interrupt an active streaming session identified by the given session ID. |
31
- | `post_streaming_create_token` | Creates a new streaming token with an optional expiry time by sending a POST request to the streaming token API endpoint. |
32
- | `get_streaming_avatar_list` | Retrieves a list of available streaming avatars from the API endpoint. |
33
- | `get_v1_webhook_list` | Retrieves a list of all registered webhooks via the v1 API endpoint. |
34
- | `post_v1_webhook_endpoint_add` | Registers a new webhook endpoint with the specified URL and events. |
35
- | `delete_v1_webhook_endpoint_by_id` | Deletes a webhook endpoint identified by its ID via a DELETE request to the v1 API. |
36
- | `get_v1_webhook_endpoint_list` | Retrieves a list of webhook endpoints from the v1 API. |
37
- | `get_v1_talking_photo_list` | Retrieves the list of talking photos from the v1 API endpoint. |
38
- | `delete_v2_talking_photo_by_id` | Deletes a v2 talking photo resource identified by its unique ID. |
39
- | `post_personalized_video_add_contact` | Adds a new contact to a personalized video project by sending the contact variables to the server. |
40
- | `get_personalized_video_audience_detail` | Retrieves detailed information about a personalized video audience by ID. |
41
- | `get_personalized_video_project_detail` | Retrieves the details of a personalized video project by its unique identifier. |
42
- | `get_v2_user_remaining_quota` | Retrieves the current remaining quota information for the user from the v2 API endpoint. |
43
- | `post_v1_asset_upload` | Uploads an asset to the server using a POST request to the '/v1/asset' endpoint. |
44
- | `get_v1_video_status` | Retrieves the status of a video by making a GET request to the v1 video_status endpoint. |
13
+ | `list_avatar_groups` | Retrieves a list of avatar groups from the /v2/avatar_group.list API endpoint. |
14
+ | `list_avatars_in_group` | Retrieves a list of avatars from a specific avatar group. |
15
+ | `get_avatar_details` | Retrieves detailed information about a specific avatar by its ID. |
16
+ | `create_avatar_video` | Creates a new avatar video using the /v2/video/generate API endpoint. |
17
+ | `get_video_status` | Retrieves the status and details of a specific video by ID using the /v1/video_status.get endpoint. |
18
+ | `create_folder` | Creates a new folder under your account using the /v1/folders/create endpoint. |
19
+ | `list_folders` | Retrieves a paginated list of folders created under your account using the /v1/folders endpoint. |
20
+ | `update_folder` | Updates the name of an existing folder using the /v1/folders/{folder_id} endpoint. |
21
+ | `trash_folder` | Deletes (trashes) a specific folder by its unique folder ID using the /v1/folders/{folder_id}/trash endpoint. |
22
+ | `restore_folder` | Restores a previously deleted folder using the /v1/folders/{folder_id}/restore endpoint. |