universal-mcp-applications 0.1.33__py3-none-any.whl → 0.1.39rc8__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 (113) hide show
  1. universal_mcp/applications/ahrefs/app.py +92 -238
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +122 -475
  4. universal_mcp/applications/asana/app.py +605 -1755
  5. universal_mcp/applications/aws_s3/app.py +36 -103
  6. universal_mcp/applications/bill/app.py +644 -2055
  7. universal_mcp/applications/box/app.py +1246 -4159
  8. universal_mcp/applications/braze/app.py +410 -1476
  9. universal_mcp/applications/browser_use/README.md +15 -1
  10. universal_mcp/applications/browser_use/__init__.py +1 -0
  11. universal_mcp/applications/browser_use/app.py +86 -24
  12. universal_mcp/applications/cal_com_v2/app.py +207 -625
  13. universal_mcp/applications/calendly/app.py +103 -242
  14. universal_mcp/applications/canva/app.py +75 -140
  15. universal_mcp/applications/clickup/app.py +331 -798
  16. universal_mcp/applications/coda/app.py +240 -520
  17. universal_mcp/applications/confluence/app.py +497 -1285
  18. universal_mcp/applications/contentful/app.py +36 -151
  19. universal_mcp/applications/crustdata/app.py +42 -121
  20. universal_mcp/applications/dialpad/app.py +451 -924
  21. universal_mcp/applications/digitalocean/app.py +2071 -6082
  22. universal_mcp/applications/domain_checker/app.py +3 -54
  23. universal_mcp/applications/e2b/app.py +14 -64
  24. universal_mcp/applications/elevenlabs/app.py +9 -47
  25. universal_mcp/applications/exa/README.md +8 -4
  26. universal_mcp/applications/exa/app.py +408 -186
  27. universal_mcp/applications/falai/app.py +24 -101
  28. universal_mcp/applications/figma/app.py +91 -175
  29. universal_mcp/applications/file_system/app.py +2 -13
  30. universal_mcp/applications/firecrawl/app.py +186 -163
  31. universal_mcp/applications/fireflies/app.py +59 -281
  32. universal_mcp/applications/fpl/app.py +92 -529
  33. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  34. universal_mcp/applications/fpl/utils/helper.py +25 -89
  35. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  36. universal_mcp/applications/ghost_content/app.py +66 -175
  37. universal_mcp/applications/github/app.py +28 -65
  38. universal_mcp/applications/gong/app.py +140 -300
  39. universal_mcp/applications/google_calendar/app.py +26 -78
  40. universal_mcp/applications/google_docs/app.py +98 -202
  41. universal_mcp/applications/google_drive/app.py +194 -793
  42. universal_mcp/applications/google_gemini/app.py +27 -62
  43. universal_mcp/applications/google_mail/README.md +1 -0
  44. universal_mcp/applications/google_mail/app.py +93 -214
  45. universal_mcp/applications/google_searchconsole/app.py +25 -58
  46. universal_mcp/applications/google_sheet/app.py +171 -624
  47. universal_mcp/applications/google_sheet/helper.py +26 -53
  48. universal_mcp/applications/hashnode/app.py +57 -269
  49. universal_mcp/applications/heygen/app.py +77 -155
  50. universal_mcp/applications/http_tools/app.py +10 -32
  51. universal_mcp/applications/hubspot/README.md +1 -1
  52. universal_mcp/applications/hubspot/app.py +7508 -99
  53. universal_mcp/applications/jira/app.py +2419 -8334
  54. universal_mcp/applications/klaviyo/app.py +737 -1619
  55. universal_mcp/applications/linkedin/README.md +5 -0
  56. universal_mcp/applications/linkedin/app.py +332 -227
  57. universal_mcp/applications/mailchimp/app.py +696 -1851
  58. universal_mcp/applications/markitdown/app.py +8 -20
  59. universal_mcp/applications/miro/app.py +333 -815
  60. universal_mcp/applications/ms_teams/app.py +85 -207
  61. universal_mcp/applications/neon/app.py +144 -250
  62. universal_mcp/applications/notion/app.py +36 -51
  63. universal_mcp/applications/onedrive/app.py +26 -48
  64. universal_mcp/applications/openai/app.py +42 -165
  65. universal_mcp/applications/outlook/README.md +22 -9
  66. universal_mcp/applications/outlook/app.py +403 -141
  67. universal_mcp/applications/perplexity/README.md +2 -1
  68. universal_mcp/applications/perplexity/app.py +162 -20
  69. universal_mcp/applications/pipedrive/app.py +1021 -3331
  70. universal_mcp/applications/posthog/app.py +272 -541
  71. universal_mcp/applications/reddit/app.py +61 -160
  72. universal_mcp/applications/resend/app.py +41 -107
  73. universal_mcp/applications/retell/app.py +23 -50
  74. universal_mcp/applications/rocketlane/app.py +250 -963
  75. universal_mcp/applications/scraper/app.py +67 -125
  76. universal_mcp/applications/semanticscholar/app.py +36 -78
  77. universal_mcp/applications/semrush/app.py +43 -77
  78. universal_mcp/applications/sendgrid/app.py +826 -1576
  79. universal_mcp/applications/sentry/app.py +444 -1079
  80. universal_mcp/applications/serpapi/app.py +40 -143
  81. universal_mcp/applications/sharepoint/app.py +27 -49
  82. universal_mcp/applications/shopify/app.py +1743 -4479
  83. universal_mcp/applications/shortcut/app.py +272 -534
  84. universal_mcp/applications/slack/app.py +41 -123
  85. universal_mcp/applications/spotify/app.py +206 -405
  86. universal_mcp/applications/supabase/app.py +174 -283
  87. universal_mcp/applications/tavily/app.py +2 -2
  88. universal_mcp/applications/trello/app.py +853 -2816
  89. universal_mcp/applications/twilio/app.py +14 -50
  90. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  91. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  92. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  93. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  94. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  95. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  96. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  97. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  98. universal_mcp/applications/whatsapp/app.py +35 -186
  99. universal_mcp/applications/whatsapp/audio.py +2 -6
  100. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  101. universal_mcp/applications/whatsapp_business/app.py +86 -299
  102. universal_mcp/applications/wrike/app.py +80 -153
  103. universal_mcp/applications/yahoo_finance/app.py +19 -65
  104. universal_mcp/applications/youtube/app.py +120 -306
  105. universal_mcp/applications/zenquotes/app.py +3 -3
  106. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/METADATA +4 -2
  107. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/RECORD +109 -113
  108. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/WHEEL +1 -1
  109. universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
  110. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
  111. universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
  112. universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
  113. {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,5 @@
1
1
  from typing import Any
2
2
  from urllib.parse import parse_qs, urlparse
3
-
4
3
  from universal_mcp.applications.application import APIApplication
5
4
  from universal_mcp.integrations import Integration
6
5
 
@@ -10,12 +9,16 @@ class OutlookApp(APIApplication):
10
9
  super().__init__(name="outlook", integration=integration, **kwargs)
11
10
  self.base_url = "https://graph.microsoft.com/v1.0"
12
11
 
13
- def reply_to_email(
14
- self,
15
- message_id: str,
16
- comment: str,
17
- user_id: str | None = None,
18
- attachments: list[dict[str, Any]] | None = None,
12
+ async def _get_user_id(self) -> str:
13
+ """Helper to get the userPrincipalName from the profile."""
14
+ user_info = await self.get_my_profile()
15
+ user_id = user_info.get("userPrincipalName")
16
+ if not user_id:
17
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
18
+ return user_id
19
+
20
+ async def reply_to_email(
21
+ self, message_id: str, comment: str, attachments: list[dict[str, Any]] | None = None
19
22
  ) -> dict[str, Any]:
20
23
  """
21
24
  Replies to a specific email message.
@@ -23,7 +26,6 @@ class OutlookApp(APIApplication):
23
26
  Args:
24
27
  message_id (str): The ID of the email message to reply to.
25
28
  comment (str): The body of the reply.
26
- user_id (str, optional): The ID of the user to send the reply from. Defaults to the authenticated user.
27
29
  attachments (list[dict[str, Any]], optional): A list of attachment objects to include in the reply.
28
30
  Each attachment dictionary should conform to the Microsoft Graph API specification.
29
31
  Example:
@@ -41,39 +43,26 @@ class OutlookApp(APIApplication):
41
43
 
42
44
  Raises:
43
45
  HTTPStatusError: If the API request fails.
44
- ValueError: If the user_id cannot be retrieved or message_id is missing.
46
+ ValueError: If message_id is missing.
45
47
 
46
48
  Tags:
47
49
  important
48
50
  """
49
- if user_id is None:
50
- user_info = self.get_my_profile()
51
- user_id = user_info.get("userPrincipalName")
52
- if not user_id:
53
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
51
+ user_id = await self._get_user_id()
54
52
  if not message_id:
55
53
  raise ValueError("Missing required parameter 'message_id'.")
56
-
57
54
  request_body_data = {"comment": comment}
58
55
  if attachments:
59
56
  request_body_data["message"] = {"attachments": attachments}
60
-
61
57
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/reply"
62
-
63
- response = self._post(
64
- url,
65
- data=request_body_data,
66
- params={},
67
- content_type="application/json",
68
- )
58
+ response = await self._apost(url, data=request_body_data, params={}, content_type="application/json")
69
59
  return self._handle_response(response)
70
60
 
71
- def send_email(
61
+ async def send_email(
72
62
  self,
73
63
  subject: str,
74
64
  body: str,
75
65
  to_recipients: list[str],
76
- user_id: str | None = None,
77
66
  cc_recipients: list[str] | None = None,
78
67
  bcc_recipients: list[str] | None = None,
79
68
  attachments: list[dict[str, Any]] | None = None,
@@ -87,7 +76,6 @@ class OutlookApp(APIApplication):
87
76
  subject (str): The subject of the email.
88
77
  body (str): The body of the email.
89
78
  to_recipients (list[str]): A list of email addresses for the 'To' recipients.
90
- user_id (str, optional): The ID of the user to send the email from. Defaults to the authenticated user.
91
79
  cc_recipients (list[str], optional): A list of email addresses for the 'Cc' recipients.
92
80
  bcc_recipients (list[str], optional): A list of email addresses for the 'Bcc' recipients.
93
81
  attachments (list[dict[str, Any]], optional): A list of attachment objects. See `reply_to_email` for an example.
@@ -99,17 +87,11 @@ class OutlookApp(APIApplication):
99
87
 
100
88
  Raises:
101
89
  HTTPStatusError: If the API request fails.
102
- ValueError: If the user_id cannot be retrieved.
103
90
 
104
91
  Tags:
105
92
  important
106
93
  """
107
- if user_id is None:
108
- user_info = self.get_my_profile()
109
- user_id = user_info.get("userPrincipalName")
110
- if not user_id:
111
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
112
-
94
+ user_id = await self._get_user_id()
113
95
  message = {
114
96
  "subject": subject,
115
97
  "body": {"contentType": body_content_type, "content": body},
@@ -121,26 +103,14 @@ class OutlookApp(APIApplication):
121
103
  message["bccRecipients"] = [{"emailAddress": {"address": email}} for email in bcc_recipients]
122
104
  if attachments:
123
105
  message["attachments"] = attachments
124
-
125
- request_body_data = {
126
- "message": message,
127
- "saveToSentItems": save_to_sent_items,
128
- }
129
-
106
+ request_body_data = {"message": message, "saveToSentItems": save_to_sent_items}
130
107
  url = f"{self.base_url}/users/{user_id}/sendMail"
131
-
132
- response = self._post(
133
- url,
134
- data=request_body_data,
135
- params={},
136
- content_type="application/json",
137
- )
108
+ response = await self._apost(url, data=request_body_data, params={}, content_type="application/json")
138
109
  return self._handle_response(response)
139
110
 
140
- def get_email_folder(
111
+ async def get_email_folder(
141
112
  self,
142
113
  folder_id: str,
143
- user_id: str | None = None,
144
114
  include_hidden: bool | None = None,
145
115
  select: list[str] | None = None,
146
116
  expand: list[str] | None = None,
@@ -150,7 +120,6 @@ class OutlookApp(APIApplication):
150
120
 
151
121
  Args:
152
122
  folder_id (str): The unique identifier for the mail folder.
153
- user_id (str, optional): The ID of the user who owns the folder. Defaults to the authenticated user.
154
123
  include_hidden (bool, optional): If true, includes hidden folders in the results.
155
124
  select (list[str], optional): A list of properties to return.
156
125
  expand (list[str], optional): A list of related entities to expand.
@@ -160,37 +129,24 @@ class OutlookApp(APIApplication):
160
129
 
161
130
  Raises:
162
131
  HTTPStatusError: If the API request fails.
163
- ValueError: If user_id cannot be retrieved or folder_id is missing.
132
+ ValueError: If folder_id is missing.
164
133
  Tags:
165
134
  important
166
135
  """
167
- if user_id is None:
168
- user_info = self.get_my_profile()
169
- user_id = user_info.get("userPrincipalName")
170
- if not user_id:
171
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
136
+ user_id = await self._get_user_id()
172
137
  if not folder_id:
173
138
  raise ValueError("Missing required parameter 'folder_id'.")
174
-
175
139
  url = f"{self.base_url}/users/{user_id}/mailFolders/{folder_id}"
176
140
  select_str = ",".join(select) if select else None
177
141
  expand_str = ",".join(expand) if expand else None
178
-
179
142
  query_params = {
180
- k: v
181
- for k, v in [
182
- ("includeHiddenFolders", include_hidden),
183
- ("$select", select_str),
184
- ("$expand", expand_str),
185
- ]
186
- if v is not None
143
+ k: v for k, v in [("includeHiddenFolders", include_hidden), ("$select", select_str), ("$expand", expand_str)] if v is not None
187
144
  }
188
- response = self._get(url, params=query_params)
145
+ response = await self._aget(url, params=query_params)
189
146
  return self._handle_response(response)
190
147
 
191
- def list_emails(
148
+ async def list_emails(
192
149
  self,
193
- user_id: str | None = None,
194
150
  select: list[str] = ["bodyPreview"],
195
151
  include_hidden: bool | None = None,
196
152
  top: int | None = None,
@@ -205,7 +161,6 @@ class OutlookApp(APIApplication):
205
161
  Retrieves a list of emails from a user's mailbox.
206
162
 
207
163
  Args:
208
- user_id (str, optional): The ID of the user. Defaults to the authenticated user.
209
164
  select (list[str], optional): A list of properties to return for each email. Defaults to ['bodyPreview'].
210
165
  include_hidden (bool, optional): If true, includes hidden messages.
211
166
  top (int, optional): The maximum number of emails to return.
@@ -232,18 +187,11 @@ class OutlookApp(APIApplication):
232
187
  raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
233
188
  if skip:
234
189
  raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
235
-
236
- if user_id is None:
237
- user_info = self.get_my_profile()
238
- user_id = user_info.get("userPrincipalName")
239
- if not user_id:
240
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
241
-
190
+ user_id = await self._get_user_id()
242
191
  url = f"{self.base_url}/users/{user_id}/messages"
243
192
  select_str = ",".join(select) if select else None
244
193
  orderby_str = ",".join(orderby) if orderby else None
245
194
  expand_str = ",".join(expand) if expand else None
246
-
247
195
  query_params = {
248
196
  k: v
249
197
  for k, v in [
@@ -259,14 +207,12 @@ class OutlookApp(APIApplication):
259
207
  ]
260
208
  if v is not None
261
209
  }
262
-
263
- response = self._get(url, params=query_params)
210
+ response = await self._aget(url, params=query_params)
264
211
  return self._handle_response(response)
265
212
 
266
- def get_email(
213
+ async def get_email(
267
214
  self,
268
215
  message_id: str,
269
- user_id: str | None = None,
270
216
  include_hidden: bool | None = None,
271
217
  select: list[str] | None = None,
272
218
  expand: list[str] | None = None,
@@ -276,7 +222,6 @@ class OutlookApp(APIApplication):
276
222
 
277
223
  Args:
278
224
  message_id (str): The unique identifier for the email.
279
- user_id (str, optional): The ID of the user who owns the email. Defaults to the authenticated user.
280
225
  include_hidden (bool, optional): If true, includes hidden messages.
281
226
  select (list[str], optional): A list of properties to return.
282
227
  expand (list[str], optional): A list of related entities to expand.
@@ -290,63 +235,44 @@ class OutlookApp(APIApplication):
290
235
  Tags:
291
236
  important
292
237
  """
293
- if user_id is None:
294
- user_info = self.get_my_profile()
295
- user_id = user_info.get("userPrincipalName")
296
- if not user_id:
297
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
238
+ user_id = await self._get_user_id()
298
239
  if not message_id:
299
240
  raise ValueError("Missing required parameter 'message_id'.")
300
-
301
241
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
302
242
  select_str = ",".join(select) if select else None
303
243
  expand_str = ",".join(expand) if expand else None
304
-
305
244
  query_params = {
306
- k: v
307
- for k, v in [
308
- ("includeHiddenMessages", include_hidden),
309
- ("$select", select_str),
310
- ("$expand", expand_str),
311
- ]
312
- if v is not None
245
+ k: v for k, v in [("includeHiddenMessages", include_hidden), ("$select", select_str), ("$expand", expand_str)] if v is not None
313
246
  }
314
- response = self._get(url, params=query_params)
247
+ response = await self._aget(url, params=query_params)
315
248
  return self._handle_response(response)
316
249
 
317
- def delete_email(self, message_id: str, user_id: str | None = None) -> dict[str, Any]:
250
+ async def delete_email(self, message_id: str) -> dict[str, Any]:
318
251
  """
319
252
  Permanently deletes a specific email by its ID.
320
253
 
321
254
  Args:
322
255
  message_id (str): The unique identifier for the email to be deleted.
323
- user_id (str, optional): The ID of the user who owns the email. Defaults to the authenticated user.
324
256
 
325
257
  Returns:
326
258
  dict[str, Any]: A dictionary confirming the deletion.
327
259
 
328
260
  Raises:
329
261
  HTTPStatusError: If the API request fails.
330
- ValueError: If user_id cannot be retrieved or message_id is missing.
262
+ ValueError: If message_id is missing.
331
263
  Tags:
332
264
  important
333
265
  """
334
- if user_id is None:
335
- user_info = self.get_my_profile()
336
- user_id = user_info.get("userPrincipalName")
337
- if not user_id:
338
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
266
+ user_id = await self._get_user_id()
339
267
  if not message_id:
340
268
  raise ValueError("Missing required parameter 'message_id'.")
341
-
342
269
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
343
- response = self._delete(url, params={})
270
+ response = await self._adelete(url, params={})
344
271
  return self._handle_response(response)
345
272
 
346
- def list_email_attachments(
273
+ async def list_email_attachments(
347
274
  self,
348
275
  message_id: str,
349
- user_id: str | None = None,
350
276
  top: int | None = None,
351
277
  skip: int | None = None,
352
278
  search: str | None = None,
@@ -361,7 +287,6 @@ class OutlookApp(APIApplication):
361
287
 
362
288
  Args:
363
289
  message_id (str): The unique identifier for the email.
364
- user_id (str, optional): The ID of the user who owns the email. Defaults to the authenticated user.
365
290
  top (int, optional): The maximum number of attachments to return.
366
291
  skip (int, optional): The number of attachments to skip. Cannot be used with 'search'.
367
292
  search (str, optional): A search query. Cannot be used with 'filter', 'orderby', or 'skip'.
@@ -387,20 +312,13 @@ class OutlookApp(APIApplication):
387
312
  raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
388
313
  if skip:
389
314
  raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
390
-
391
- if user_id is None:
392
- user_info = self.get_my_profile()
393
- user_id = user_info.get("userPrincipalName")
394
- if not user_id:
395
- raise ValueError("Could not retrieve user ID from get_my_profile response.")
315
+ user_id = await self._get_user_id()
396
316
  if not message_id:
397
317
  raise ValueError("Missing required parameter 'message_id'.")
398
-
399
318
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments"
400
319
  orderby_str = ",".join(orderby) if orderby else None
401
320
  select_str = ",".join(select) if select else None
402
321
  expand_str = ",".join(expand) if expand else None
403
-
404
322
  query_params = {
405
323
  k: v
406
324
  for k, v in [
@@ -415,22 +333,16 @@ class OutlookApp(APIApplication):
415
333
  ]
416
334
  if v is not None
417
335
  }
418
- response = self._get(url, params=query_params)
336
+ response = await self._aget(url, params=query_params)
419
337
  return self._handle_response(response)
420
338
 
421
- def get_attachment(
422
- self,
423
- message_id: str,
424
- attachment_id: str,
425
- user_id: str | None = None,
426
- ) -> dict[str, Any]:
339
+ async def get_attachment(self, message_id: str, attachment_id: str) -> dict[str, Any]:
427
340
  """
428
341
  Retrieves a specific attachment from an email message and formats it as a dictionary.
429
342
 
430
343
  Args:
431
344
  message_id (str): The ID of the email message.
432
345
  attachment_id (str): The ID of the attachment.
433
- user_id (str, optional): The ID of the user. Defaults to the authenticated user.
434
346
 
435
347
  Returns:
436
348
  dict[str, Any]: A dictionary containing the attachment details:
@@ -441,24 +353,16 @@ class OutlookApp(APIApplication):
441
353
  Tags:
442
354
  important
443
355
  """
444
- if user_id is None:
445
- user_info = self.get_my_profile()
446
- user_id = user_info.get("userPrincipalName")
447
- if not user_id:
448
- raise ValueError("Could not retrieve user ID.")
356
+ user_id = await self._get_user_id()
449
357
  if not message_id or not attachment_id:
450
358
  raise ValueError("Missing required parameter 'message_id' or 'attachment_id'.")
451
-
452
359
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments/{attachment_id}"
453
-
454
- response = self._get(url, params={})
360
+ response = await self._aget(url, params={})
455
361
  attachment_data = self._handle_response(response)
456
-
457
362
  content_type = attachment_data.get("contentType", "application/octet-stream")
458
363
  attachment_type = content_type.split("/")[0] if "/" in content_type else "file"
459
364
  if attachment_type not in ["image", "audio", "video", "text"]:
460
365
  attachment_type = "file"
461
-
462
366
  return {
463
367
  "type": attachment_type,
464
368
  "data": attachment_data.get("contentBytes"),
@@ -466,7 +370,7 @@ class OutlookApp(APIApplication):
466
370
  "file_name": attachment_data.get("name"),
467
371
  }
468
372
 
469
- def get_my_profile(self) -> dict[str, Any]:
373
+ async def get_my_profile(self) -> dict[str, Any]:
470
374
  """
471
375
  Fetches the userPrincipalName for the currently authenticated user.
472
376
 
@@ -478,10 +382,358 @@ class OutlookApp(APIApplication):
478
382
  """
479
383
  url = f"{self.base_url}/me"
480
384
  query_params = {"$select": "userPrincipalName"}
481
- response = self._get(url, params=query_params)
385
+ response = await self._aget(url, params=query_params)
482
386
  return self._handle_response(response)
483
387
 
484
- def get_next_page_results(self, url: str) -> dict[str, Any]:
388
+ async def list_calendars(
389
+ self,
390
+ top: int | None = None,
391
+ skip: int | None = None,
392
+ select: list[str] | None = None,
393
+ ) -> dict[str, Any]:
394
+ """
395
+ Retrieves a list of calendars for the user.
396
+
397
+ Args:
398
+ top (int, optional): The maximum number of calendars to return.
399
+ skip (int, optional): The number of calendars to skip.
400
+ select (list[str], optional): A list of properties to return for each calendar.
401
+
402
+ Returns:
403
+ dict[str, Any]: A dictionary containing a list of calendars.
404
+
405
+ Tags:
406
+ important
407
+ """
408
+ user_id = await self._get_user_id()
409
+ url = f"{self.base_url}/users/{user_id}/calendars"
410
+ select_str = ",".join(select) if select else None
411
+ query_params = {k: v for k, v in [("$top", top), ("$skip", skip), ("$select", select_str)] if v is not None}
412
+ response = await self._aget(url, params=query_params)
413
+ return self._handle_response(response)
414
+
415
+ async def get_calendar(
416
+ self, calendar_id: str, select: list[str] | None = None
417
+ ) -> dict[str, Any]:
418
+ """
419
+ Retrieves a specific calendar by its ID.
420
+
421
+ Args:
422
+ calendar_id (str): The unique identifier for the calendar.
423
+ select (list[str], optional): A list of properties to return.
424
+
425
+ Returns:
426
+ dict[str, Any]: A dictionary containing the calendar's details.
427
+
428
+ Tags:
429
+ important
430
+ """
431
+ user_id = await self._get_user_id()
432
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}"
433
+ select_str = ",".join(select) if select else None
434
+ query_params = {"$select": select_str} if select_str else {}
435
+ response = await self._aget(url, params=query_params)
436
+ return self._handle_response(response)
437
+
438
+ async def create_calendar(self, name: str) -> dict[str, Any]:
439
+ """
440
+ Creates a new calendar for the user.
441
+
442
+ Args:
443
+ name (str): The name of the new calendar.
444
+
445
+ Returns:
446
+ dict[str, Any]: A dictionary containing the created calendar's details.
447
+
448
+ Tags:
449
+ important
450
+ """
451
+ user_id = await self._get_user_id()
452
+ url = f"{self.base_url}/users/{user_id}/calendars"
453
+ request_body = {"name": name}
454
+ response = await self._apost(url, data=request_body, params={}, content_type="application/json")
455
+ return self._handle_response(response)
456
+
457
+ async def update_calendar(self, calendar_id: str, name: str) -> dict[str, Any]:
458
+ """
459
+ Updates an existing calendar's name.
460
+
461
+ Args:
462
+ calendar_id (str): The unique identifier for the calendar.
463
+ name (str): The new name for the calendar.
464
+
465
+ Returns:
466
+ dict[str, Any]: A dictionary containing the updated calendar's details.
467
+
468
+ Tags:
469
+ important
470
+ """
471
+ user_id = await self._get_user_id()
472
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}"
473
+ request_body = {"name": name}
474
+ response = await self._apatch(url, data=request_body, params={}, content_type="application/json")
475
+ return self._handle_response(response)
476
+
477
+ async def delete_calendar(self, calendar_id: str) -> dict[str, Any]:
478
+ """
479
+ Deletes a specific calendar.
480
+
481
+ Args:
482
+ calendar_id (str): The unique identifier for the calendar to delete.
483
+
484
+ Returns:
485
+ dict[str, Any]: A dictionary confirming the deletion.
486
+
487
+ Tags:
488
+ important
489
+ """
490
+ user_id = await self._get_user_id()
491
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}"
492
+ response = await self._adelete(url, params={})
493
+ return self._handle_response(response)
494
+
495
+ async def list_events(
496
+ self,
497
+ calendar_id: str | None = None,
498
+ top: int | None = None,
499
+ skip: int | None = None,
500
+ filter: str | None = None,
501
+ select: list[str] | None = None,
502
+ orderby: list[str] | None = None,
503
+ ) -> dict[str, Any]:
504
+ """
505
+ Retrieves a list of events from a calendar or the user's default calendar.
506
+
507
+ Args:
508
+ calendar_id (str, optional): The ID of the calendar. If not provided, the default calendar is used.
509
+ top (int, optional): The maximum number of events to return.
510
+ skip (int, optional): The number of events to skip.
511
+ filter (str, optional): A filter query (e.g., "start/dateTime ge '2023-01-01T00:00:00Z'").
512
+ select (list[str], optional): A list of properties to return for each event.
513
+ orderby (list[str], optional): A list of properties to sort the results by.
514
+
515
+ Returns:
516
+ dict[str, Any]: A dictionary containing a list of events.
517
+
518
+ Tags:
519
+ important
520
+ """
521
+ user_id = await self._get_user_id()
522
+ if calendar_id:
523
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}/events"
524
+ else:
525
+ url = f"{self.base_url}/users/{user_id}/calendar/events"
526
+ select_str = ",".join(select) if select else None
527
+ orderby_str = ",".join(orderby) if orderby else None
528
+ query_params = {
529
+ k: v
530
+ for k, v in [("$top", top), ("$skip", skip), ("$filter", filter), ("$select", select_str), ("$orderby", orderby_str)]
531
+ if v is not None
532
+ }
533
+ response = await self._aget(url, params=query_params)
534
+ return self._handle_response(response)
535
+
536
+ async def get_event(
537
+ self, event_id: str, select: list[str] | None = None
538
+ ) -> dict[str, Any]:
539
+ """
540
+ Retrieves a specific event by its ID.
541
+
542
+ Args:
543
+ event_id (str): The unique identifier for the event.
544
+ select (list[str], optional): A list of properties to return.
545
+
546
+ Returns:
547
+ dict[str, Any]: A dictionary containing the event's details.
548
+
549
+ Tags:
550
+ important
551
+ """
552
+ user_id = await self._get_user_id()
553
+ url = f"{self.base_url}/users/{user_id}/events/{event_id}"
554
+ select_str = ",".join(select) if select else None
555
+ query_params = {"$select": select_str} if select_str else {}
556
+ response = await self._aget(url, params=query_params)
557
+ return self._handle_response(response)
558
+
559
+ async def create_event(
560
+ self,
561
+ subject: str,
562
+ start_datetime: str,
563
+ end_datetime: str,
564
+ start_timezone: str = "UTC",
565
+ end_timezone: str = "UTC",
566
+ body: str | None = None,
567
+ body_content_type: str = "HTML",
568
+ location_display_name: str | None = None,
569
+ attendees: list[dict[str, Any]] | None = None,
570
+ calendar_id: str | None = None,
571
+ **kwargs,
572
+ ) -> dict[str, Any]:
573
+ """
574
+ Creates a new event in a calendar.
575
+
576
+ Args:
577
+ subject (str): The subject of the event.
578
+ start_datetime (str): The start time of the event (ISO 8601 string, e.g., '2023-12-25T09:00:00').
579
+ end_datetime (str): The end time of the event (ISO 8601 string, e.g., '2023-12-25T10:00:00').
580
+ start_timezone (str, optional): The timezone for the start time. Defaults to 'UTC'.
581
+ end_timezone (str, optional): The timezone for the end time. Defaults to 'UTC'.
582
+ body (str, optional): The body content of the event.
583
+ body_content_type (str, optional): The content type of the body (Text or HTML). Defaults to 'HTML'.
584
+ location_display_name (str, optional): The display name for the event location.
585
+ attendees (list[dict[str, Any]], optional): A list of attendee objects.
586
+ Example attendee: {"type": "required", "emailAddress": {"address": "bob@example.com", "name": "Bob"}}
587
+ calendar_id (str, optional): The ID of the calendar. If not provided, the default calendar is used.
588
+ **kwargs: Additional properties for the event (e.g., isOnlineMeeting, reminderMinutesBeforeStart).
589
+
590
+ Returns:
591
+ dict[str, Any]: A dictionary containing the created event's details.
592
+
593
+ Tags:
594
+ important
595
+ """
596
+ user_id = await self._get_user_id()
597
+ if calendar_id:
598
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}/events"
599
+ else:
600
+ url = f"{self.base_url}/users/{user_id}/calendar/events"
601
+ request_body = {
602
+ "subject": subject,
603
+ "start": {"dateTime": start_datetime, "timeZone": start_timezone},
604
+ "end": {"dateTime": end_datetime, "timeZone": end_timezone},
605
+ }
606
+ if body:
607
+ request_body["body"] = {"contentType": body_content_type, "content": body}
608
+ if location_display_name:
609
+ request_body["location"] = {"displayName": location_display_name}
610
+ if attendees:
611
+ request_body["attendees"] = attendees
612
+ request_body.update(kwargs)
613
+ response = await self._apost(url, data=request_body, params={}, content_type="application/json")
614
+ return self._handle_response(response)
615
+
616
+ async def update_event(
617
+ self,
618
+ event_id: str,
619
+ **kwargs,
620
+ ) -> dict[str, Any]:
621
+ """
622
+ Updates an existing event.
623
+
624
+ Args:
625
+ event_id (str): The unique identifier for the event.
626
+ **kwargs: Event properties to update (e.g., subject, start, end, body, attendees).
627
+ For start/end, use nested dictionaries: start={"dateTime": "...", "timeZone": "..."}.
628
+
629
+ Returns:
630
+ dict[str, Any]: A dictionary containing the updated event's details.
631
+
632
+ Tags:
633
+ important
634
+ """
635
+ user_id = await self._get_user_id()
636
+ url = f"{self.base_url}/users/{user_id}/events/{event_id}"
637
+ response = await self._apatch(url, data=kwargs, params={}, content_type="application/json")
638
+ return self._handle_response(response)
639
+
640
+ async def delete_event(self, event_id: str) -> dict[str, Any]:
641
+ """
642
+ Deletes a specific event.
643
+
644
+ Args:
645
+ event_id (str): The unique identifier for the event to delete.
646
+
647
+ Returns:
648
+ dict[str, Any]: A dictionary confirming the deletion.
649
+
650
+ Tags:
651
+ important
652
+ """
653
+ user_id = await self._get_user_id()
654
+ url = f"{self.base_url}/users/{user_id}/events/{event_id}"
655
+ response = await self._adelete(url, params={})
656
+ return self._handle_response(response)
657
+
658
+ async def list_calendar_view(
659
+ self,
660
+ start_datetime: str,
661
+ end_datetime: str,
662
+ calendar_id: str | None = None,
663
+ top: int | None = None,
664
+ skip: int | None = None,
665
+ select: list[str] | None = None,
666
+ ) -> dict[str, Any]:
667
+ """
668
+ Retrieves events between a start and end time (includes expanded recurring events).
669
+
670
+ Args:
671
+ start_datetime (str): The start of the time range (ISO 8601, e.g., '2023-12-25T00:00:00Z').
672
+ end_datetime (str): The end of the time range (ISO 8601, e.g., '2023-12-26T00:00:00Z').
673
+ calendar_id (str, optional): The ID of the calendar. If not provided, the default calendar is used.
674
+ top (int, optional): The maximum number of events to return.
675
+ skip (int, optional): The number of events to skip.
676
+ select (list[str], optional): A list of properties to return for each event.
677
+
678
+ Returns:
679
+ dict[str, Any]: A dictionary containing a list of event instances.
680
+
681
+ Tags:
682
+ important
683
+ """
684
+ user_id = await self._get_user_id()
685
+ if calendar_id:
686
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}/calendarView"
687
+ else:
688
+ url = f"{self.base_url}/users/{user_id}/calendar/calendarView"
689
+ select_str = ",".join(select) if select else None
690
+ query_params = {
691
+ "startDateTime": start_datetime,
692
+ "endDateTime": end_datetime,
693
+ }
694
+ if top is not None:
695
+ query_params["$top"] = top
696
+ if skip is not None:
697
+ query_params["$skip"] = skip
698
+ if select_str:
699
+ query_params["$select"] = select_str
700
+ response = await self._aget(url, params=query_params)
701
+ return self._handle_response(response)
702
+
703
+ async def get_schedule(
704
+ self,
705
+ schedules: list[str],
706
+ start_datetime: str,
707
+ end_datetime: str,
708
+ availability_view_interval: int = 30,
709
+ ) -> dict[str, Any]:
710
+ """
711
+ Retrieves free/busy information for a set of users, groups, or resources.
712
+
713
+ Args:
714
+ schedules (list[str]): A list of SMTP addresses (emails) to get schedules for.
715
+ start_datetime (str): The start of the time range (ISO 8601 with 'Z', e.g., '2023-12-25T00:00:00Z').
716
+ end_datetime (str): The end of the time range (ISO 8601 with 'Z', e.g., '2023-12-26T00:00:00Z').
717
+ availability_view_interval (int, optional): The duration of each time slot in minutes. Defaults to 30.
718
+
719
+ Returns:
720
+ dict[str, Any]: A dictionary containing schedule information.
721
+
722
+ Tags:
723
+ important
724
+ """
725
+ user_id = await self._get_user_id()
726
+ url = f"{self.base_url}/users/{user_id}/calendar/getSchedule"
727
+ request_body = {
728
+ "schedules": schedules,
729
+ "startTime": {"dateTime": start_datetime, "timeZone": "UTC"},
730
+ "endTime": {"dateTime": end_datetime, "timeZone": "UTC"},
731
+ "availabilityViewInterval": availability_view_interval,
732
+ }
733
+ response = await self._apost(url, data=request_body, params={}, content_type="application/json")
734
+ return self._handle_response(response)
735
+
736
+ async def get_next_page_results(self, url: str) -> dict[str, Any]:
485
737
  """
486
738
  Retrieves the next page of results from a paginated API response.
487
739
 
@@ -500,13 +752,11 @@ class OutlookApp(APIApplication):
500
752
  raise ValueError("Missing required parameter 'url'.")
501
753
  if not url.startswith(self.base_url):
502
754
  raise ValueError(f"The provided URL must start with '{self.base_url}'.")
503
-
504
755
  relative_part = url[len(self.base_url) :]
505
756
  parsed_relative = urlparse(relative_part)
506
757
  path_only = parsed_relative.path
507
758
  params = {k: v[0] for k, v in parse_qs(parsed_relative.query).items()}
508
-
509
- response = self._get(path_only, params=params)
759
+ response = await self._aget(path_only, params=params)
510
760
  return self._handle_response(response)
511
761
 
512
762
  def list_tools(self):
@@ -521,4 +771,16 @@ class OutlookApp(APIApplication):
521
771
  self.get_attachment,
522
772
  self.get_my_profile,
523
773
  self.get_next_page_results,
774
+ self.list_calendars,
775
+ self.get_calendar,
776
+ self.create_calendar,
777
+ self.update_calendar,
778
+ self.delete_calendar,
779
+ self.list_events,
780
+ self.get_event,
781
+ self.create_event,
782
+ self.update_event,
783
+ self.delete_event,
784
+ self.list_calendar_view,
785
+ self.get_schedule,
524
786
  ]