universal-mcp-applications 0.1.2__py3-none-any.whl → 0.1.3__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 (80) hide show
  1. universal_mcp/applications/airtable/app.py +1 -0
  2. universal_mcp/applications/apollo/app.py +1 -0
  3. universal_mcp/applications/aws_s3/app.py +3 -4
  4. universal_mcp/applications/bill/app.py +3 -3
  5. universal_mcp/applications/box/app.py +2 -6
  6. universal_mcp/applications/braze/app.py +2 -6
  7. universal_mcp/applications/cal_com_v2/app.py +22 -64
  8. universal_mcp/applications/confluence/app.py +1 -0
  9. universal_mcp/applications/contentful/app.py +8 -19
  10. universal_mcp/applications/digitalocean/app.py +9 -27
  11. universal_mcp/applications/{domain-checker → domain_checker}/app.py +2 -1
  12. universal_mcp/applications/elevenlabs/app.py +98 -3188
  13. universal_mcp/applications/falai/app.py +1 -0
  14. universal_mcp/applications/file_system/__init__.py +1 -0
  15. universal_mcp/applications/file_system/app.py +96 -0
  16. universal_mcp/applications/fireflies/app.py +4 -3
  17. universal_mcp/applications/fpl/app.py +1 -0
  18. universal_mcp/applications/fpl/utils/fixtures.py +1 -1
  19. universal_mcp/applications/fpl/utils/helper.py +1 -1
  20. universal_mcp/applications/fpl/utils/position_utils.py +0 -1
  21. universal_mcp/applications/{ghost-content → ghost_content}/app.py +2 -1
  22. universal_mcp/applications/github/app.py +3 -1
  23. universal_mcp/applications/google_calendar/app.py +2 -1
  24. universal_mcp/applications/google_docs/app.py +1 -1
  25. universal_mcp/applications/google_drive/app.py +2 -1
  26. universal_mcp/applications/google_gemini/app.py +138 -618
  27. universal_mcp/applications/google_mail/app.py +2 -1
  28. universal_mcp/applications/{google-searchconsole → google_searchconsole}/app.py +1 -1
  29. universal_mcp/applications/google_sheet/app.py +2 -1
  30. universal_mcp/applications/google_sheet/helper.py +156 -116
  31. universal_mcp/applications/hashnode/app.py +1 -0
  32. universal_mcp/applications/{http-tools → http_tools}/app.py +2 -1
  33. universal_mcp/applications/hubspot/app.py +4 -1
  34. universal_mcp/applications/jira/app.py +7 -18
  35. universal_mcp/applications/markitdown/app.py +2 -3
  36. universal_mcp/applications/ms_teams/app.py +1 -1
  37. universal_mcp/applications/openai/app.py +2 -3
  38. universal_mcp/applications/outlook/app.py +1 -3
  39. universal_mcp/applications/pipedrive/app.py +2 -6
  40. universal_mcp/applications/reddit/app.py +1 -0
  41. universal_mcp/applications/replicate/app.py +3 -3
  42. universal_mcp/applications/resend/app.py +1 -2
  43. universal_mcp/applications/rocketlane/app.py +1 -0
  44. universal_mcp/applications/semrush/app.py +1 -1
  45. universal_mcp/applications/sentry/README.md +20 -20
  46. universal_mcp/applications/sentry/app.py +40 -40
  47. universal_mcp/applications/serpapi/app.py +2 -2
  48. universal_mcp/applications/sharepoint/app.py +1 -0
  49. universal_mcp/applications/shopify/app.py +1 -0
  50. universal_mcp/applications/slack/app.py +3 -3
  51. universal_mcp/applications/trello/app.py +9 -27
  52. universal_mcp/applications/twilio/__init__.py +1 -0
  53. universal_mcp/applications/{twillo → twilio}/app.py +2 -2
  54. universal_mcp/applications/twitter/README.md +1 -1
  55. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +2 -2
  56. universal_mcp/applications/twitter/api_segments/lists_api.py +1 -1
  57. universal_mcp/applications/unipile/app.py +5 -1
  58. universal_mcp/applications/whatsapp/app.py +18 -17
  59. universal_mcp/applications/whatsapp/audio.py +110 -0
  60. universal_mcp/applications/whatsapp/whatsapp.py +398 -0
  61. universal_mcp/applications/whatsapp_business/app.py +1 -1
  62. universal_mcp/applications/youtube/app.py +195 -191
  63. universal_mcp/applications/zenquotes/app.py +1 -1
  64. {universal_mcp_applications-0.1.2.dist-info → universal_mcp_applications-0.1.3.dist-info}/METADATA +4 -2
  65. {universal_mcp_applications-0.1.2.dist-info → universal_mcp_applications-0.1.3.dist-info}/RECORD +76 -75
  66. universal_mcp/applications/google-ads/__init__.py +0 -1
  67. universal_mcp/applications/google-ads/app.py +0 -23
  68. universal_mcp/applications/twillo/README.md +0 -0
  69. universal_mcp/applications/twillo/__init__.py +0 -1
  70. /universal_mcp/applications/{domain-checker → domain_checker}/README.md +0 -0
  71. /universal_mcp/applications/{domain-checker → domain_checker}/__init__.py +0 -0
  72. /universal_mcp/applications/{ghost-content → ghost_content}/README.md +0 -0
  73. /universal_mcp/applications/{ghost-content → ghost_content}/__init__.py +0 -0
  74. /universal_mcp/applications/{google-searchconsole → google_searchconsole}/README.md +0 -0
  75. /universal_mcp/applications/{google-searchconsole → google_searchconsole}/__init__.py +0 -0
  76. /universal_mcp/applications/{http-tools → http_tools}/README.md +0 -0
  77. /universal_mcp/applications/{http-tools → http_tools}/__init__.py +0 -0
  78. /universal_mcp/applications/{google-ads → twilio}/README.md +0 -0
  79. {universal_mcp_applications-0.1.2.dist-info → universal_mcp_applications-0.1.3.dist-info}/WHEEL +0 -0
  80. {universal_mcp_applications-0.1.2.dist-info → universal_mcp_applications-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,398 @@
1
+ import requests
2
+ import json
3
+ from datetime import datetime
4
+ from dataclasses import dataclass
5
+ from typing import Optional, List, Tuple
6
+ import os.path
7
+ from . import audio
8
+ from dotenv import load_dotenv
9
+
10
+ load_dotenv()
11
+
12
+ WHATSAPP_API_BASE_URL = os.getenv('WHATSAPP_API_BASE_URL', "http://134.209.144.43:8080")
13
+
14
+ @dataclass
15
+ class Message:
16
+ timestamp: datetime
17
+ sender: str
18
+ content: str
19
+ is_from_me: bool
20
+ chat_jid: str
21
+ id: str
22
+ chat_name: Optional[str] = None
23
+ media_type: Optional[str] = None
24
+
25
+ @dataclass
26
+ class Chat:
27
+ jid: str
28
+ name: Optional[str]
29
+ last_message_time: Optional[datetime]
30
+ last_message: Optional[str] = None
31
+ last_sender: Optional[str] = None
32
+ last_is_from_me: Optional[bool] = None
33
+
34
+ @property
35
+ def is_group(self) -> bool:
36
+ """Determine if chat is a group based on JID pattern."""
37
+ return self.jid.endswith("@g.us")
38
+
39
+ @dataclass
40
+ class Contact:
41
+ phone_number: str
42
+ name: Optional[str]
43
+ jid: str
44
+
45
+ @dataclass
46
+ class MessageContext:
47
+ message: Message
48
+ before: List[Message]
49
+ after: List[Message]
50
+
51
+ def _make_api_request(endpoint: str, method: str = "GET", data: dict = None, user_id: str = "default_user") -> dict:
52
+ """Make HTTP request to WhatsApp Bridge API."""
53
+ url = f"{WHATSAPP_API_BASE_URL}/api/{endpoint}"
54
+
55
+ # Add user_id to query parameters for GET requests
56
+ if method.upper() == "GET" and data:
57
+ data["user_id"] = user_id
58
+ elif method.upper() == "GET":
59
+ data = {"user_id": user_id}
60
+
61
+ try:
62
+ if method.upper() == "GET":
63
+ response = requests.get(url, params=data, timeout=30)
64
+ elif method.upper() == "POST":
65
+ response = requests.post(url, json=data, timeout=30)
66
+ else:
67
+ raise ValueError(f"Unsupported HTTP method: {method}")
68
+
69
+ if response.status_code == 200:
70
+ return response.json()
71
+ else:
72
+ return {"error": f"HTTP {response.status_code}: {response.text}"}
73
+
74
+ except requests.RequestException as e:
75
+ return {"error": f"Request failed: {str(e)}"}
76
+ except json.JSONDecodeError:
77
+ return {"error": f"Invalid JSON response: {response.text}"}
78
+
79
+ def get_sender_name(sender_jid: str, user_id: str = "default_user") -> str:
80
+ """Get sender name via API call."""
81
+ result = _make_api_request("sender_name", data={"sender_jid": sender_jid}, user_id=user_id)
82
+
83
+ if "error" in result:
84
+ return sender_jid
85
+
86
+ return result.get("name", sender_jid)
87
+
88
+ def format_message(message: Message, show_chat_info: bool = True, user_id: str = "default_user") -> str:
89
+ """Print a single message with consistent formatting."""
90
+ output = ""
91
+
92
+ if show_chat_info and message.chat_name:
93
+ output += f"[{message.timestamp:%Y-%m-%d %H:%M:%S}] Chat: {message.chat_name} "
94
+ else:
95
+ output += f"[{message.timestamp:%Y-%m-%d %H:%M:%S}] "
96
+
97
+ content_prefix = ""
98
+ if hasattr(message, 'media_type') and message.media_type:
99
+ content_prefix = f"[{message.media_type} - Message ID: {message.id} - Chat JID: {message.chat_jid}] "
100
+
101
+ try:
102
+ sender_name = get_sender_name(message.sender, user_id) if not message.is_from_me else "Me"
103
+ output += f"From: {sender_name}: {content_prefix}{message.content}\n"
104
+ except Exception as e:
105
+ print(f"Error formatting message: {e}")
106
+ return output
107
+
108
+ def format_messages_list(messages: List[Message], show_chat_info: bool = True, user_id: str = "default_user") -> str:
109
+ output = ""
110
+ if not messages:
111
+ output += "No messages to display."
112
+ return output
113
+
114
+ for message in messages:
115
+ output += format_message(message, show_chat_info, user_id)
116
+ return output
117
+
118
+ def list_messages(
119
+ after: Optional[str] = None,
120
+ before: Optional[str] = None,
121
+ sender_phone_number: Optional[str] = None,
122
+ chat_jid: Optional[str] = None,
123
+ query: Optional[str] = None,
124
+ limit: int = 20,
125
+ page: int = 0,
126
+ include_context: bool = True,
127
+ context_before: int = 1,
128
+ context_after: int = 1,
129
+ user_id: str = "default_user"
130
+ ) -> str:
131
+ """Get messages matching the specified criteria with optional context via API."""
132
+ params = {
133
+ "after": after,
134
+ "before": before,
135
+ "sender_phone_number": sender_phone_number,
136
+ "chat_jid": chat_jid,
137
+ "query": query,
138
+ "limit": limit,
139
+ "page": page,
140
+ "include_context": include_context,
141
+ "context_before": context_before,
142
+ "context_after": context_after
143
+ }
144
+
145
+ # Remove None values
146
+ params = {k: v for k, v in params.items() if v is not None}
147
+
148
+ result = _make_api_request("messages", data=params, user_id=user_id)
149
+
150
+ if "error" in result:
151
+ return f"Error retrieving messages: {result['error']}"
152
+
153
+ return result.get("messages", "No messages found")
154
+
155
+ def get_message_context(
156
+ message_id: str,
157
+ before: int = 5,
158
+ after: int = 5,
159
+ user_id: str = "default_user"
160
+ ) -> MessageContext:
161
+ """Get context around a specific message via API."""
162
+ params = {
163
+ "message_id": message_id,
164
+ "before": before,
165
+ "after": after
166
+ }
167
+
168
+ result = _make_api_request("message_context", data=params, user_id=user_id)
169
+
170
+ if "error" in result:
171
+ raise ValueError(f"Error getting message context: {result['error']}")
172
+
173
+ # Parse the response into MessageContext object
174
+ # This would need to be implemented based on the API response structure
175
+ # For now, returning a simple error message
176
+ raise NotImplementedError("Message context API not yet implemented in bridge")
177
+
178
+ def list_chats(
179
+ query: Optional[str] = None,
180
+ limit: int = 20,
181
+ page: int = 0,
182
+ include_last_message: bool = True,
183
+ sort_by: str = "last_active",
184
+ user_id: str = "default_user"
185
+ ) -> List[Chat]:
186
+ """Get chats matching the specified criteria via API."""
187
+ params = {
188
+ "query": query,
189
+ "limit": limit,
190
+ "page": page,
191
+ "include_last_message": include_last_message,
192
+ "sort_by": sort_by
193
+ }
194
+
195
+ # Remove None values
196
+ params = {k: v for k, v in params.items() if v is not None}
197
+
198
+ result = _make_api_request("chats", data=params, user_id=user_id)
199
+
200
+ if "error" in result:
201
+ print(f"Error retrieving chats: {result['error']}")
202
+ return []
203
+
204
+ chats_data = result.get("chats", [])
205
+ chats = []
206
+
207
+ for chat_data in chats_data:
208
+ chat = Chat(
209
+ jid=chat_data["jid"],
210
+ name=chat_data.get("name"),
211
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
212
+ last_message=chat_data.get("last_message"),
213
+ last_sender=chat_data.get("last_sender"),
214
+ last_is_from_me=chat_data.get("last_is_from_me")
215
+ )
216
+ chats.append(chat)
217
+
218
+ return chats
219
+
220
+ def search_contacts(query: str, user_id: str = "default_user") -> List[Contact]:
221
+ """Search contacts by name or phone number via API."""
222
+ result = _make_api_request("contacts", data={"query": query}, user_id=user_id)
223
+
224
+ if "error" in result:
225
+ print(f"Error searching contacts: {result['error']}")
226
+ return []
227
+
228
+ contacts_data = result.get("contacts", [])
229
+ contacts = []
230
+
231
+ for contact_data in contacts_data:
232
+ contact = Contact(
233
+ phone_number=contact_data["phone_number"],
234
+ name=contact_data.get("name"),
235
+ jid=contact_data["jid"]
236
+ )
237
+ contacts.append(contact)
238
+
239
+ return contacts
240
+
241
+ def get_contact_chats(jid: str, limit: int = 20, page: int = 0, user_id: str = "default_user") -> List[Chat]:
242
+ """Get all chats involving the contact via API."""
243
+ params = {
244
+ "jid": jid,
245
+ "limit": limit,
246
+ "page": page
247
+ }
248
+
249
+ result = _make_api_request("contact_chats", data=params, user_id=user_id)
250
+
251
+ if "error" in result:
252
+ print(f"Error getting contact chats: {result['error']}")
253
+ return []
254
+
255
+ chats_data = result.get("chats", [])
256
+ chats = []
257
+
258
+ for chat_data in chats_data:
259
+ chat = Chat(
260
+ jid=chat_data["jid"],
261
+ name=chat_data.get("name"),
262
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
263
+ last_message=chat_data.get("last_message"),
264
+ last_sender=chat_data.get("last_sender"),
265
+ last_is_from_me=chat_data.get("last_is_from_me")
266
+ )
267
+ chats.append(chat)
268
+
269
+ return chats
270
+
271
+ def get_last_interaction(jid: str, user_id: str = "default_user") -> str:
272
+ """Get most recent message involving the contact via API."""
273
+ result = _make_api_request("last_interaction", data={"jid": jid}, user_id=user_id)
274
+
275
+ if "error" in result:
276
+ return f"Error getting last interaction: {result['error']}"
277
+
278
+ return result.get("message", "No interaction found")
279
+
280
+ def get_chat(chat_jid: str, include_last_message: bool = True, user_id: str = "default_user") -> Optional[Chat]:
281
+ """Get chat metadata by JID via API."""
282
+ params = {
283
+ "chat_jid": chat_jid,
284
+ "include_last_message": include_last_message
285
+ }
286
+
287
+ result = _make_api_request("chat", data=params, user_id=user_id)
288
+
289
+ if "error" in result:
290
+ print(f"Error getting chat: {result['error']}")
291
+ return None
292
+
293
+ chat_data = result.get("chat")
294
+ if not chat_data:
295
+ return None
296
+
297
+ return Chat(
298
+ jid=chat_data["jid"],
299
+ name=chat_data.get("name"),
300
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
301
+ last_message=chat_data.get("last_message"),
302
+ last_sender=chat_data.get("last_sender"),
303
+ last_is_from_me=chat_data.get("last_is_from_me")
304
+ )
305
+
306
+ def get_direct_chat_by_contact(sender_phone_number: str, user_id: str = "default_user") -> Optional[Chat]:
307
+ """Get chat metadata by sender phone number via API."""
308
+ result = _make_api_request("direct_chat", data={"sender_phone_number": sender_phone_number}, user_id=user_id)
309
+
310
+ if "error" in result:
311
+ print(f"Error getting direct chat: {result['error']}")
312
+ return None
313
+
314
+ chat_data = result.get("chat")
315
+ if not chat_data:
316
+ return None
317
+
318
+ return Chat(
319
+ jid=chat_data["jid"],
320
+ name=chat_data.get("name"),
321
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
322
+ last_message=chat_data.get("last_message"),
323
+ last_sender=chat_data.get("last_sender"),
324
+ last_is_from_me=chat_data.get("last_is_from_me")
325
+ )
326
+
327
+ def send_message(recipient: str, message: str, user_id: str = "default_user") -> Tuple[bool, str]:
328
+ """Send message via API."""
329
+ payload = {
330
+ "user_id": user_id,
331
+ "recipient": recipient,
332
+ "message": message
333
+ }
334
+
335
+ result = _make_api_request("send", method="POST", data=payload, user_id=user_id)
336
+
337
+ if "error" in result:
338
+ return False, result["error"]
339
+
340
+ return result.get("success", False), result.get("message", "Unknown response")
341
+
342
+ def send_file(recipient: str, media_path: str, user_id: str = "default_user") -> Tuple[bool, str]:
343
+ """Send file via API."""
344
+ payload = {
345
+ "user_id": user_id,
346
+ "recipient": recipient,
347
+ "media_path": media_path
348
+ }
349
+
350
+ result = _make_api_request("send", method="POST", data=payload, user_id=user_id)
351
+
352
+ if "error" in result:
353
+ return False, result["error"]
354
+
355
+ return result.get("success", False), result.get("message", "Unknown response")
356
+
357
+ def send_audio_message(recipient: str, media_path: str, user_id: str = "default_user") -> Tuple[bool, str]:
358
+ """Send audio message via API."""
359
+ if not media_path.endswith(".ogg"):
360
+ try:
361
+ media_path = audio.convert_to_opus_ogg_temp(media_path)
362
+ except Exception as e:
363
+ return False, f"Error converting file to opus ogg. You likely need to install ffmpeg: {str(e)}"
364
+
365
+ payload = {
366
+ "user_id": user_id,
367
+ "recipient": recipient,
368
+ "media_path": media_path
369
+ }
370
+
371
+ result = _make_api_request("send", method="POST", data=payload, user_id=user_id)
372
+
373
+ if "error" in result:
374
+ return False, result["error"]
375
+
376
+ return result.get("success", False), result.get("message", "Unknown response")
377
+
378
+ def download_media(message_id: str, chat_jid: str, user_id: str = "default_user") -> Optional[str]:
379
+ """Download media from a message via API."""
380
+ payload = {
381
+ "user_id": user_id,
382
+ "message_id": message_id,
383
+ "chat_jid": chat_jid
384
+ }
385
+
386
+ result = _make_api_request("download", method="POST", data=payload, user_id=user_id)
387
+
388
+ if "error" in result:
389
+ print(f"Download failed: {result['error']}")
390
+ return None
391
+
392
+ if result.get("success", False):
393
+ path = result.get("path")
394
+ print(f"Media downloaded successfully: {path}")
395
+ return path
396
+ else:
397
+ print(f"Download failed: {result.get('message', 'Unknown error')}")
398
+ return None
@@ -6,7 +6,7 @@ from universal_mcp.integrations import Integration
6
6
 
7
7
  class WhatsappBusinessApp(APIApplication):
8
8
  def __init__(self, integration: Integration = None, **kwargs) -> None:
9
- super().__init__(name="whatsapp-business", integration=integration, **kwargs)
9
+ super().__init__(name="whatsapp_business", integration=integration, **kwargs)
10
10
  self.base_url = "https://graph.facebook.com"
11
11
 
12
12
  def get_analytics(