universal-mcp-applications 0.1.30rc2__py3-none-any.whl → 0.1.36rc2__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.
Files changed (105) hide show
  1. universal_mcp/applications/ahrefs/app.py +52 -198
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +111 -464
  4. universal_mcp/applications/asana/app.py +417 -1567
  5. universal_mcp/applications/aws_s3/app.py +36 -103
  6. universal_mcp/applications/bill/app.py +546 -1957
  7. universal_mcp/applications/box/app.py +1068 -3981
  8. universal_mcp/applications/braze/app.py +364 -1430
  9. universal_mcp/applications/browser_use/app.py +2 -8
  10. universal_mcp/applications/cal_com_v2/app.py +207 -625
  11. universal_mcp/applications/calendly/app.py +61 -200
  12. universal_mcp/applications/canva/app.py +45 -110
  13. universal_mcp/applications/clickup/app.py +207 -674
  14. universal_mcp/applications/coda/app.py +146 -426
  15. universal_mcp/applications/confluence/app.py +310 -1098
  16. universal_mcp/applications/contentful/app.py +36 -151
  17. universal_mcp/applications/crustdata/app.py +28 -107
  18. universal_mcp/applications/dialpad/app.py +283 -756
  19. universal_mcp/applications/digitalocean/app.py +1766 -5777
  20. universal_mcp/applications/domain_checker/app.py +3 -54
  21. universal_mcp/applications/e2b/app.py +14 -64
  22. universal_mcp/applications/elevenlabs/app.py +9 -47
  23. universal_mcp/applications/exa/app.py +6 -17
  24. universal_mcp/applications/falai/app.py +24 -101
  25. universal_mcp/applications/figma/app.py +53 -137
  26. universal_mcp/applications/file_system/app.py +2 -13
  27. universal_mcp/applications/firecrawl/app.py +51 -152
  28. universal_mcp/applications/fireflies/app.py +59 -281
  29. universal_mcp/applications/fpl/app.py +91 -528
  30. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  31. universal_mcp/applications/fpl/utils/helper.py +25 -89
  32. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  33. universal_mcp/applications/ghost_content/app.py +52 -161
  34. universal_mcp/applications/github/app.py +19 -56
  35. universal_mcp/applications/gong/app.py +88 -248
  36. universal_mcp/applications/google_calendar/app.py +16 -68
  37. universal_mcp/applications/google_docs/app.py +88 -188
  38. universal_mcp/applications/google_drive/app.py +141 -463
  39. universal_mcp/applications/google_gemini/app.py +12 -64
  40. universal_mcp/applications/google_mail/app.py +28 -157
  41. universal_mcp/applications/google_searchconsole/app.py +15 -48
  42. universal_mcp/applications/google_sheet/app.py +103 -580
  43. universal_mcp/applications/google_sheet/helper.py +10 -37
  44. universal_mcp/applications/hashnode/app.py +57 -269
  45. universal_mcp/applications/heygen/app.py +44 -122
  46. universal_mcp/applications/http_tools/app.py +10 -32
  47. universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
  48. universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
  49. universal_mcp/applications/hubspot/app.py +23 -87
  50. universal_mcp/applications/jira/app.py +2071 -7986
  51. universal_mcp/applications/klaviyo/app.py +494 -1376
  52. universal_mcp/applications/linkedin/README.md +9 -2
  53. universal_mcp/applications/linkedin/app.py +392 -212
  54. universal_mcp/applications/mailchimp/app.py +450 -1605
  55. universal_mcp/applications/markitdown/app.py +8 -20
  56. universal_mcp/applications/miro/app.py +217 -699
  57. universal_mcp/applications/ms_teams/app.py +64 -186
  58. universal_mcp/applications/neon/app.py +86 -192
  59. universal_mcp/applications/notion/app.py +21 -36
  60. universal_mcp/applications/onedrive/app.py +16 -38
  61. universal_mcp/applications/openai/app.py +42 -165
  62. universal_mcp/applications/outlook/app.py +24 -84
  63. universal_mcp/applications/perplexity/app.py +4 -19
  64. universal_mcp/applications/pipedrive/app.py +832 -3142
  65. universal_mcp/applications/posthog/app.py +163 -432
  66. universal_mcp/applications/reddit/app.py +40 -139
  67. universal_mcp/applications/resend/app.py +41 -107
  68. universal_mcp/applications/retell/app.py +14 -41
  69. universal_mcp/applications/rocketlane/app.py +221 -934
  70. universal_mcp/applications/scraper/README.md +7 -4
  71. universal_mcp/applications/scraper/app.py +216 -102
  72. universal_mcp/applications/semanticscholar/app.py +22 -64
  73. universal_mcp/applications/semrush/app.py +43 -77
  74. universal_mcp/applications/sendgrid/app.py +512 -1262
  75. universal_mcp/applications/sentry/app.py +271 -906
  76. universal_mcp/applications/serpapi/app.py +40 -143
  77. universal_mcp/applications/sharepoint/app.py +17 -39
  78. universal_mcp/applications/shopify/app.py +1551 -4287
  79. universal_mcp/applications/shortcut/app.py +155 -417
  80. universal_mcp/applications/slack/app.py +50 -101
  81. universal_mcp/applications/spotify/app.py +126 -325
  82. universal_mcp/applications/supabase/app.py +104 -213
  83. universal_mcp/applications/tavily/app.py +1 -1
  84. universal_mcp/applications/trello/app.py +693 -2656
  85. universal_mcp/applications/twilio/app.py +14 -50
  86. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  87. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  88. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  89. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  90. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  91. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  92. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  93. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  94. universal_mcp/applications/whatsapp/app.py +35 -186
  95. universal_mcp/applications/whatsapp/audio.py +2 -6
  96. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  97. universal_mcp/applications/whatsapp_business/app.py +70 -283
  98. universal_mcp/applications/wrike/app.py +45 -118
  99. universal_mcp/applications/yahoo_finance/app.py +19 -65
  100. universal_mcp/applications/youtube/app.py +75 -261
  101. universal_mcp/applications/zenquotes/app.py +2 -2
  102. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.30rc2.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,42 +1,18 @@
1
1
  import logging
2
2
  import sys
3
3
  from typing import Any
4
-
5
4
  import dns.resolver
6
5
  import requests
7
6
  from universal_mcp.applications.application import APIApplication
8
7
  from universal_mcp.integrations import Integration
9
8
 
10
- # Configure logging
11
9
  logging.basicConfig(
12
- level=logging.INFO,
13
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
14
- handlers=[logging.StreamHandler(sys.stderr)],
10
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler(sys.stderr)]
15
11
  )
16
-
17
12
  logger = logging.getLogger("domain_checker")
18
-
19
- # Constants
20
13
  RDAP_BOOTSTRAP_URL = "https://data.iana.org/rdap/dns.json"
21
14
  USER_AGENT = "DomainCheckerBot/1.0"
22
-
23
- # Top TLDs to check
24
- TOP_TLDS = [
25
- "com",
26
- "net",
27
- "org",
28
- "io",
29
- "co",
30
- "app",
31
- "dev",
32
- "ai",
33
- "me",
34
- "info",
35
- "xyz",
36
- "online",
37
- "site",
38
- "tech",
39
- ]
15
+ TOP_TLDS = ["com", "net", "org", "io", "co", "app", "dev", "ai", "me", "info", "xyz", "online", "site", "tech"]
40
16
 
41
17
 
42
18
  class DomainCheckerApp(APIApplication):
@@ -52,22 +28,16 @@ class DomainCheckerApp(APIApplication):
52
28
  Fetches a domain's registration details from Registration Data Access Protocol (RDAP) servers. It dynamically selects the appropriate server URL based on the domain's TLD, with special handling for common ones. Returns the JSON data as a dictionary or None if the request fails or data is unavailable.
53
29
  """
54
30
  try:
55
- # Special case for .ch and .li domains
56
31
  tld = domain.split(".")[-1].lower()
57
32
  if tld in ["ch", "li"]:
58
33
  rdap_url = f"https://rdap.nic.{tld}/domain/{domain}"
59
- # Use common RDAP servers for known TLDs
60
34
  elif tld in ["com", "net"]:
61
35
  rdap_url = f"https://rdap.verisign.com/{tld}/v1/domain/{domain}"
62
36
  elif tld == "org":
63
- rdap_url = (
64
- f"https://rdap.publicinterestregistry.org/rdap/domain/{domain}"
65
- )
37
+ rdap_url = f"https://rdap.publicinterestregistry.org/rdap/domain/{domain}"
66
38
  else:
67
39
  rdap_url = f"https://rdap.org/domain/{domain}"
68
-
69
40
  headers = {"Accept": "application/rdap+json", "User-Agent": USER_AGENT}
70
-
71
41
  response = requests.get(rdap_url, headers=headers, timeout=5)
72
42
  if response.status_code == 200:
73
43
  return response.json()
@@ -122,21 +92,13 @@ class DomainCheckerApp(APIApplication):
122
92
  domain, availability, registration, dns, rdap, important
123
93
  """
124
94
  logger.info(f"Checking domain: {domain}")
125
-
126
- # First check DNS
127
95
  has_dns = await self._check_dns(domain)
128
-
129
96
  if has_dns:
130
- # Domain exists, get RDAP data if possible
131
97
  rdap_data = await self._get_rdap_data(domain)
132
-
133
98
  if rdap_data:
134
- # Extract data from RDAP
135
99
  registrar = "Unknown"
136
100
  reg_date = "Unknown"
137
101
  exp_date = "Unknown"
138
-
139
- # Extract registrar
140
102
  entities = rdap_data.get("entities", [])
141
103
  for entity in entities:
142
104
  if "registrar" in entity.get("roles", []):
@@ -146,15 +108,12 @@ class DomainCheckerApp(APIApplication):
146
108
  if entry[0] in ["fn", "org"] and len(entry) > 3:
147
109
  registrar = entry[3]
148
110
  break
149
-
150
- # Extract dates
151
111
  events = rdap_data.get("events", [])
152
112
  for event in events:
153
113
  if event.get("eventAction") == "registration":
154
114
  reg_date = event.get("eventDate", "Unknown")
155
115
  elif event.get("eventAction") == "expiration":
156
116
  exp_date = event.get("eventDate", "Unknown")
157
-
158
117
  return {
159
118
  "domain": domain,
160
119
  "status": "Registered",
@@ -175,8 +134,6 @@ class DomainCheckerApp(APIApplication):
175
134
  "rdap_data_available": False,
176
135
  "note": "Domain has DNS records but RDAP data couldn't be retrieved",
177
136
  }
178
-
179
- # Try RDAP one more time even if DNS not found
180
137
  rdap_data = await self._get_rdap_data(domain)
181
138
  if rdap_data:
182
139
  return {
@@ -189,8 +146,6 @@ class DomainCheckerApp(APIApplication):
189
146
  "rdap_data_available": True,
190
147
  "note": "Domain found in RDAP registry",
191
148
  }
192
-
193
- # If we get here, the domain is likely available
194
149
  return {
195
150
  "domain": domain,
196
151
  "status": "Available",
@@ -233,17 +188,12 @@ class DomainCheckerApp(APIApplication):
233
188
  tld, keyword, domain-search, availability, bulk-check, important
234
189
  """
235
190
  logger.info(f"Checking keyword: {keyword} across TLDs")
236
-
237
191
  available = []
238
192
  taken = []
239
-
240
- # Check each TLD in sequence
241
193
  for tld in TOP_TLDS:
242
194
  domain = f"{keyword}.{tld}"
243
195
  has_dns = await self._check_dns(domain)
244
-
245
196
  if not has_dns:
246
- # Double-check with RDAP if no DNS
247
197
  rdap_data = await self._get_rdap_data(domain)
248
198
  if not rdap_data:
249
199
  available.append(domain)
@@ -251,7 +201,6 @@ class DomainCheckerApp(APIApplication):
251
201
  taken.append(domain)
252
202
  else:
253
203
  taken.append(domain)
254
-
255
204
  return {
256
205
  "keyword": keyword,
257
206
  "tlds_checked": len(TOP_TLDS),
@@ -1,17 +1,12 @@
1
1
  import os
2
2
  from typing import Annotated, Any
3
-
4
3
  from loguru import logger
5
4
 
6
5
  try:
7
6
  from e2b_code_interpreter import Sandbox
8
-
9
7
  except ImportError:
10
8
  Sandbox = None
11
- logger.error(
12
- "Failed to import E2B Sandbox. Please ensure 'e2b_code_interpreter' is installed."
13
- )
14
-
9
+ logger.error("Failed to import E2B Sandbox. Please ensure 'e2b_code_interpreter' is installed.")
15
10
  from universal_mcp.applications.application import APIApplication
16
11
  from universal_mcp.exceptions import NotAuthorizedError, ToolError
17
12
  from universal_mcp.integrations import Integration
@@ -28,9 +23,7 @@ class E2bApp(APIApplication):
28
23
  super().__init__(name="e2b", integration=integration, **kwargs)
29
24
  self._e2b_api_key: str | None = None
30
25
  if Sandbox is None:
31
- logger.warning(
32
- "E2B Sandbox SDK is not available. E2B tools will not function."
33
- )
26
+ logger.warning("E2B Sandbox SDK is not available. E2B tools will not function.")
34
27
 
35
28
  @property
36
29
  def e2b_api_key(self) -> str:
@@ -40,54 +33,31 @@ class E2bApp(APIApplication):
40
33
  if self._e2b_api_key is None:
41
34
  if not self.integration:
42
35
  logger.error("E2B App: Integration not configured.")
43
- raise NotAuthorizedError(
44
- "Integration not configured for E2B App. Cannot retrieve API key."
45
- )
46
-
36
+ raise NotAuthorizedError("Integration not configured for E2B App. Cannot retrieve API key.")
47
37
  try:
48
38
  credentials = self.integration.get_credentials()
49
39
  except NotAuthorizedError as e:
50
- logger.error(
51
- f"E2B App: Authorization error when fetching credentials: {e.message}"
52
- )
53
- raise # Re-raise the original NotAuthorizedError
40
+ logger.error(f"E2B App: Authorization error when fetching credentials: {e.message}")
41
+ raise
54
42
  except Exception as e:
55
- logger.error(
56
- f"E2B App: Unexpected error when fetching credentials: {e}",
57
- exc_info=True,
58
- )
43
+ logger.error(f"E2B App: Unexpected error when fetching credentials: {e}", exc_info=True)
59
44
  raise NotAuthorizedError(f"Failed to get E2B credentials: {e}")
60
-
61
- api_key = (
62
- credentials.get("api_key")
63
- or credentials.get("API_KEY") # Check common variations
64
- or credentials.get("apiKey")
65
- )
66
-
45
+ api_key = credentials.get("api_key") or credentials.get("API_KEY") or credentials.get("apiKey")
67
46
  if not api_key:
68
47
  logger.error("E2B App: API key not found in credentials.")
69
48
  action_message = "API key for E2B is missing. Please ensure it's set in the store via MCP frontend or configuration."
70
- if hasattr(self.integration, "authorize") and callable(
71
- self.integration.authorize
72
- ):
49
+ if hasattr(self.integration, "authorize") and callable(self.integration.authorize):
73
50
  try:
74
51
  auth_details = self.integration.authorize()
75
52
  if isinstance(auth_details, str):
76
53
  action_message = auth_details
77
54
  elif isinstance(auth_details, dict) and "url" in auth_details:
78
- action_message = (
79
- f"Please authorize via: {auth_details['url']}"
80
- )
81
- elif (
82
- isinstance(auth_details, dict) and "message" in auth_details
83
- ):
55
+ action_message = f"Please authorize via: {auth_details['url']}"
56
+ elif isinstance(auth_details, dict) and "message" in auth_details:
84
57
  action_message = auth_details["message"]
85
58
  except Exception as auth_e:
86
- logger.warning(
87
- f"Could not retrieve specific authorization action for E2B: {auth_e}"
88
- )
59
+ logger.warning(f"Could not retrieve specific authorization action for E2B: {auth_e}")
89
60
  raise NotAuthorizedError(action_message)
90
-
91
61
  self._e2b_api_key = api_key
92
62
  logger.info("E2B API Key successfully retrieved and cached.")
93
63
  return self._e2b_api_key
@@ -95,40 +65,28 @@ class E2bApp(APIApplication):
95
65
  def _format_execution_output(self, execution: Any) -> str:
96
66
  """Helper function to format the E2B execution logs nicely."""
97
67
  output_parts = []
98
-
99
68
  try:
100
69
  logs = getattr(execution, "logs", None)
101
-
102
70
  if logs is not None:
103
- # Collect stdout
104
71
  if getattr(logs, "stdout", None):
105
72
  stdout_content = "".join(logs.stdout).strip()
106
73
  if stdout_content:
107
74
  output_parts.append(stdout_content)
108
-
109
- # Collect stderr
110
75
  if getattr(logs, "stderr", None):
111
76
  stderr_content = "".join(logs.stderr).strip()
112
77
  if stderr_content:
113
78
  output_parts.append(f"--- ERROR ---\n{stderr_content}")
114
-
115
- # Fallback: check execution.text (covers expressions returning values)
116
79
  if not output_parts and hasattr(execution, "text"):
117
80
  text_content = str(execution.text).strip()
118
81
  if text_content:
119
82
  output_parts.append(text_content)
120
-
121
83
  except Exception as e:
122
84
  output_parts.append(f"Failed to format execution output: {e}")
123
-
124
85
  if not output_parts:
125
86
  return "Execution finished with no output (stdout/stderr)."
126
-
127
87
  return "\n\n".join(output_parts)
128
88
 
129
- def execute_python_code(
130
- self, code: Annotated[str, "The Python code to execute."]
131
- ) -> str:
89
+ async def execute_python_code(self, code: Annotated[str, "The Python code to execute."]) -> str:
132
90
  """
133
91
  Executes a Python code string in a secure E2B sandbox. It authenticates using the configured API key, runs the code, and returns a formatted string containing the execution's output (stdout/stderr). It raises specific exceptions for authorization failures or general execution issues.
134
92
 
@@ -151,7 +109,6 @@ class E2bApp(APIApplication):
151
109
  raise ToolError("E2B Sandbox SDK (e2b_code_interpreter) is not installed.")
152
110
  if not code or not isinstance(code, str):
153
111
  raise ValueError("Provided code must be a non-empty string.")
154
-
155
112
  try:
156
113
  logger.info("Attempting to execute Python code in E2B Sandbox.")
157
114
  os.environ["E2B_API_KEY"] = self.e2b_api_key
@@ -164,17 +121,10 @@ class E2bApp(APIApplication):
164
121
  except Exception as e:
165
122
  logger.exception("E2B code execution failed.")
166
123
  lower = str(e).lower()
167
- if (
168
- "authentication" in lower
169
- or "api key" in lower
170
- or "401" in lower
171
- or "403" in lower
172
- ):
124
+ if "authentication" in lower or "api key" in lower or "401" in lower or ("403" in lower):
173
125
  raise NotAuthorizedError(f"E2B authentication/permission failed: {e}")
174
126
  raise ToolError(f"E2B code execution failed: {e}")
175
127
 
176
128
  def list_tools(self) -> list[callable]:
177
129
  """Lists the tools available from the E2bApp."""
178
- return [
179
- self.execute_python_code,
180
- ]
130
+ return [self.execute_python_code]
@@ -1,11 +1,9 @@
1
1
  import uuid
2
2
  from io import BytesIO
3
-
4
3
  import requests
5
4
  from universal_mcp.applications.application import APIApplication
6
5
  from universal_mcp.exceptions import NotAuthorizedError
7
6
  from universal_mcp.integrations import Integration
8
-
9
7
  from elevenlabs import ElevenLabs
10
8
  from universal_mcp.applications.file_system.app import FileSystemApp
11
9
 
@@ -24,24 +22,14 @@ class ElevenlabsApp(APIApplication):
24
22
  credentials = self.integration.get_credentials()
25
23
  if not credentials:
26
24
  raise NotAuthorizedError("No credentials found")
27
- api_key = (
28
- credentials.get("api_key")
29
- or credentials.get("API_KEY")
30
- or credentials.get("apiKey")
31
- )
25
+ api_key = credentials.get("api_key") or credentials.get("API_KEY") or credentials.get("apiKey")
32
26
  if not api_key:
33
27
  raise NotAuthorizedError("No api key found")
34
28
  self._client = ElevenLabs(api_key=api_key)
35
29
  return self._client
36
30
 
37
- # def get_voices(self):
38
- # return self.client.voices.list_voices()
39
-
40
31
  async def generate_speech_audio_url(
41
- self,
42
- text: str,
43
- voice_id: str = "21m00Tcm4TlvDq8ikWAM",
44
- model_id: str = "eleven_multilingual_v2",
32
+ self, text: str, voice_id: str = "21m00Tcm4TlvDq8ikWAM", model_id: str = "eleven_multilingual_v2"
45
33
  ) -> bytes:
46
34
  """
47
35
  Converts a text string into speech using the ElevenLabs API. The function then saves the generated audio to a temporary MP3 file and returns a public URL to access it, rather than the raw audio bytes.
@@ -59,26 +47,14 @@ class ElevenlabsApp(APIApplication):
59
47
  Tags:
60
48
  important
61
49
  """
62
- audio_generator = self.client.text_to_speech.convert(
63
- text=text,
64
- voice_id=voice_id,
65
- model_id=model_id,
66
- output_format="mp3_44100_128",
67
- )
68
-
69
- # Collect all audio chunks from the generator
50
+ audio_generator = self.client.text_to_speech.convert(text=text, voice_id=voice_id, model_id=model_id, output_format="mp3_44100_128")
70
51
  audio_data = b""
71
52
  for chunk in audio_generator:
72
53
  audio_data += chunk
73
-
74
- upload_result = await FileSystemApp.write_file(
75
- audio_data, f"/tmp/{uuid.uuid4()}.mp3"
76
- )
54
+ upload_result = await FileSystemApp.write_file(audio_data, f"/tmp/{uuid.uuid4()}.mp3")
77
55
  return upload_result["data"]["url"]
78
56
 
79
- async def speech_to_text(
80
- self, audio_file_path: str, language_code: str = "eng", diarize: bool = True
81
- ) -> str:
57
+ async def speech_to_text(self, audio_file_path: str, language_code: str = "eng", diarize: bool = True) -> str:
82
58
  """
83
59
  Transcribes an audio file into text using the ElevenLabs API. It supports language specification and speaker diarization, providing the inverse operation to the audio-generating `text_to_speech` method. Note: The docstring indicates this is a placeholder for an undocumented endpoint.
84
60
 
@@ -92,19 +68,12 @@ class ElevenlabsApp(APIApplication):
92
68
  important
93
69
  """
94
70
  transcription = self.client.speech_to_text.convert(
95
- file=audio_file_path,
96
- model_id="scribe_v1", # Model to use, for now only "scribe_v1" is supported
97
- tag_audio_events=True, # Tag audio events like laughter, applause, etc.
98
- language_code=language_code, # Language of the audio file. If set to None, the model will detect the language automatically.
99
- diarize=diarize, # Whether to annotate who is speaking
71
+ file=audio_file_path, model_id="scribe_v1", tag_audio_events=True, language_code=language_code, diarize=diarize
100
72
  )
101
73
  return transcription
102
74
 
103
75
  async def speech_to_speech(
104
- self,
105
- audio_url: str,
106
- voice_id: str = "21m00Tcm4TlvDq8ikWAM",
107
- model_id: str = "eleven_multilingual_sts_v2",
76
+ self, audio_url: str, voice_id: str = "21m00Tcm4TlvDq8ikWAM", model_id: str = "eleven_multilingual_sts_v2"
108
77
  ) -> bytes:
109
78
  """
110
79
  Downloads an audio file from a URL and converts the speech into a specified target voice using the ElevenLabs API. This function transforms the speaker's voice in an existing recording and returns the new audio data as bytes, distinct from creating audio from text.
@@ -123,19 +92,12 @@ class ElevenlabsApp(APIApplication):
123
92
  response = requests.get(audio_url)
124
93
  audio_data = BytesIO(response.content)
125
94
  response = self.client.speech_to_speech.convert(
126
- voice_id=voice_id,
127
- audio=audio_data,
128
- model_id=model_id,
129
- output_format="mp3_44100_128",
95
+ voice_id=voice_id, audio=audio_data, model_id=model_id, output_format="mp3_44100_128"
130
96
  )
131
97
  return response.content
132
98
 
133
99
  def list_tools(self):
134
- return [
135
- self.generate_speech_audio_url,
136
- self.speech_to_text,
137
- self.speech_to_speech,
138
- ]
100
+ return [self.generate_speech_audio_url, self.speech_to_text, self.speech_to_speech]
139
101
 
140
102
 
141
103
  async def demo_text_to_speech():
@@ -1,5 +1,4 @@
1
1
  from typing import Any
2
-
3
2
  from universal_mcp.applications.application import APIApplication
4
3
  from universal_mcp.integrations import Integration
5
4
 
@@ -9,7 +8,7 @@ class ExaApp(APIApplication):
9
8
  super().__init__(name="exa", integration=integration, **kwargs)
10
9
  self.base_url = "https://api.exa.ai"
11
10
 
12
- def search_with_filters(
11
+ async def search_with_filters(
13
12
  self,
14
13
  query,
15
14
  useAutoprompt=None,
@@ -74,7 +73,7 @@ class ExaApp(APIApplication):
74
73
  response.raise_for_status()
75
74
  return response.json()
76
75
 
77
- def find_similar_by_url(
76
+ async def find_similar_by_url(
78
77
  self,
79
78
  url,
80
79
  numResults=None,
@@ -130,7 +129,7 @@ class ExaApp(APIApplication):
130
129
  response.raise_for_status()
131
130
  return response.json()
132
131
 
133
- def fetch_page_content(
132
+ async def fetch_page_content(
134
133
  self,
135
134
  urls,
136
135
  ids=None,
@@ -188,7 +187,7 @@ class ExaApp(APIApplication):
188
187
  response.raise_for_status()
189
188
  return response.json()
190
189
 
191
- def answer(self, query, stream=None, text=None, model=None) -> dict[str, Any]:
190
+ async def answer(self, query, stream=None, text=None, model=None) -> dict[str, Any]:
192
191
  """
193
192
  Retrieves a direct, synthesized answer for a given query by calling the Exa `/answer` API endpoint. Unlike `search`, which returns web results, this function provides a conclusive response. It supports streaming, including source text, and selecting a search model.
194
193
 
@@ -204,12 +203,7 @@ class ExaApp(APIApplication):
204
203
  Tags:
205
204
  important
206
205
  """
207
- request_body = {
208
- "query": query,
209
- "stream": stream,
210
- "text": text,
211
- "model": model,
212
- }
206
+ request_body = {"query": query, "stream": stream, "text": text, "model": model}
213
207
  request_body = {k: v for k, v in request_body.items() if v is not None}
214
208
  url = f"{self.base_url}/answer"
215
209
  query_params = {}
@@ -218,9 +212,4 @@ class ExaApp(APIApplication):
218
212
  return response.json()
219
213
 
220
214
  def list_tools(self):
221
- return [
222
- self.search_with_filters,
223
- self.find_similar_by_url,
224
- self.fetch_page_content,
225
- self.answer,
226
- ]
215
+ return [self.search_with_filters, self.find_similar_by_url, self.fetch_page_content, self.answer]