universal-mcp-applications 0.1.25__py3-none-any.whl → 0.1.32__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.
@@ -10,143 +10,176 @@ class OutlookApp(APIApplication):
10
10
  super().__init__(name="outlook", integration=integration, **kwargs)
11
11
  self.base_url = "https://graph.microsoft.com/v1.0"
12
12
 
13
- def reply_to_message(
13
+ def reply_to_email(
14
14
  self,
15
15
  message_id: str,
16
+ comment: str,
16
17
  user_id: str | None = None,
17
- comment: str | None = None,
18
- message: dict[str, Any] | None = None,
19
- ) -> Any:
18
+ attachments: list[dict[str, Any]] | None = None,
19
+ ) -> dict[str, Any]:
20
20
  """
21
- Replies to an email using its message ID, with either a simple comment or a full message object including attachments. Unlike `send_mail`, which creates a new email, this function targets an existing message. It defaults to the current user if no user ID is specified.
21
+ Replies to a specific email message.
22
22
 
23
23
  Args:
24
- message_id (string): message-id
25
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
26
- comment (string): A comment to include in the reply. Example: 'Thank you for your email. Here is my reply.'.
27
- message (object): A message object to specify additional properties for the reply, such as attachments. Example: {'subject': 'RE: Project Update', 'body': {'contentType': 'Text', 'content': 'Thank you for the update. Looking forward to the next steps.'}, 'toRecipients': [{'emailAddress': {'address': 'alice@contoso.com'}}], 'attachments': [{'@odata.type': '#microsoft.graph.fileAttachment', 'name': 'agenda.pdf', 'contentType': 'application/pdf', 'contentBytes': 'SGVsbG8gV29ybGQh'}]}.
24
+ message_id (str): The ID of the email message to reply to.
25
+ 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
+ attachments (list[dict[str, Any]], optional): A list of attachment objects to include in the reply.
28
+ Each attachment dictionary should conform to the Microsoft Graph API specification.
29
+ Example:
30
+ [
31
+ {
32
+ "@odata.type": "#microsoft.graph.fileAttachment",
33
+ "name": "attachment.txt",
34
+ "contentType": "text/plain",
35
+ "contentBytes": "SGVsbG8gV29ybGQh"
36
+ }
37
+ ]
28
38
 
29
39
  Returns:
30
- Any: Success
40
+ dict[str, Any]: A dictionary confirming the reply action.
31
41
 
32
42
  Raises:
33
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
43
+ HTTPStatusError: If the API request fails.
44
+ ValueError: If the user_id cannot be retrieved or message_id is missing.
34
45
 
35
46
  Tags:
36
- users.message, important
47
+ important
37
48
  """
38
- # If user_id is not provided, get it automatically
39
49
  if user_id is None:
40
- user_info = self.get_current_user_profile()
50
+ user_info = self.get_my_profile()
41
51
  user_id = user_info.get("userPrincipalName")
42
52
  if not user_id:
43
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
44
- if message_id is None:
45
- raise ValueError("Missing required parameter 'message-id'.")
46
- request_body_data = None
47
- request_body_data = {
48
- "comment": comment,
49
- "message": message,
50
- }
51
- request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
53
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
54
+ if not message_id:
55
+ raise ValueError("Missing required parameter 'message_id'.")
56
+
57
+ request_body_data = {"comment": comment}
58
+ if attachments:
59
+ request_body_data["message"] = {"attachments": attachments}
60
+
52
61
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/reply"
53
- query_params = {}
62
+
54
63
  response = self._post(
55
64
  url,
56
65
  data=request_body_data,
57
- params=query_params,
66
+ params={},
58
67
  content_type="application/json",
59
68
  )
60
69
  return self._handle_response(response)
61
70
 
62
- def send_mail(
71
+ def send_email(
63
72
  self,
64
- message: dict[str, Any],
73
+ subject: str,
74
+ body: str,
75
+ to_recipients: list[str],
65
76
  user_id: str | None = None,
66
- saveToSentItems: bool | None = None,
67
- ) -> Any:
77
+ cc_recipients: list[str] | None = None,
78
+ bcc_recipients: list[str] | None = None,
79
+ attachments: list[dict[str, Any]] | None = None,
80
+ body_content_type: str = "Text",
81
+ save_to_sent_items: bool = True,
82
+ ) -> dict[str, Any]:
68
83
  """
69
- Sends a new email on behalf of a specified or current user, using a dictionary for content like recipients and subject. Unlike `reply_to_message`, which replies to an existing message, this function composes and sends an entirely new email from scratch.
84
+ Sends a new email.
70
85
 
71
86
  Args:
72
- message (object): message Example: {'subject': 'Meet for lunch?', 'body': {'contentType': 'Text', 'content': 'The new cafeteria is open.'}, 'toRecipients': [{'emailAddress': {'address': 'frannis@contoso.com'}}], 'ccRecipients': [{'emailAddress': {'address': 'danas@contoso.com'}}], 'bccRecipients': [{'emailAddress': {'address': 'bccuser@contoso.com'}}], 'attachments': [{'@odata.type': '#microsoft.graph.fileAttachment', 'name': 'attachment.txt', 'contentType': 'text/plain', 'contentBytes': 'SGVsbG8gV29ybGQh'}]}.
73
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
74
- saveToSentItems (boolean): saveToSentItems Example: 'False'.
87
+ subject (str): The subject of the email.
88
+ body (str): The body of the email.
89
+ 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
+ cc_recipients (list[str], optional): A list of email addresses for the 'Cc' recipients.
92
+ bcc_recipients (list[str], optional): A list of email addresses for the 'Bcc' recipients.
93
+ attachments (list[dict[str, Any]], optional): A list of attachment objects. See `reply_to_email` for an example.
94
+ body_content_type (str, optional): The content type of the email body, e.g., "Text" or "HTML". Defaults to "Text".
95
+ save_to_sent_items (bool, optional): Whether to save the email to the 'Sent Items' folder. Defaults to True.
75
96
 
76
97
  Returns:
77
- Any: Success
98
+ dict[str, Any]: A dictionary confirming the send action.
78
99
 
79
100
  Raises:
80
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
101
+ HTTPStatusError: If the API request fails.
102
+ ValueError: If the user_id cannot be retrieved.
81
103
 
82
104
  Tags:
83
- users.user.Actions, important
105
+ important
84
106
  """
85
- # If user_id is not provided, get it automatically
86
107
  if user_id is None:
87
- user_info = self.get_current_user_profile()
108
+ user_info = self.get_my_profile()
88
109
  user_id = user_info.get("userPrincipalName")
89
110
  if not user_id:
90
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
91
- request_body_data = None
111
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
112
+
113
+ message = {
114
+ "subject": subject,
115
+ "body": {"contentType": body_content_type, "content": body},
116
+ "toRecipients": [{"emailAddress": {"address": email}} for email in to_recipients],
117
+ }
118
+ if cc_recipients:
119
+ message["ccRecipients"] = [{"emailAddress": {"address": email}} for email in cc_recipients]
120
+ if bcc_recipients:
121
+ message["bccRecipients"] = [{"emailAddress": {"address": email}} for email in bcc_recipients]
122
+ if attachments:
123
+ message["attachments"] = attachments
124
+
92
125
  request_body_data = {
93
126
  "message": message,
94
- "saveToSentItems": saveToSentItems,
127
+ "saveToSentItems": save_to_sent_items,
95
128
  }
96
- request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
129
+
97
130
  url = f"{self.base_url}/users/{user_id}/sendMail"
98
- query_params = {}
131
+
99
132
  response = self._post(
100
133
  url,
101
134
  data=request_body_data,
102
- params=query_params,
135
+ params={},
103
136
  content_type="application/json",
104
137
  )
105
138
  return self._handle_response(response)
106
139
 
107
- def get_mail_folder(
140
+ def get_email_folder(
108
141
  self,
109
- mailFolder_id: str,
142
+ folder_id: str,
110
143
  user_id: str | None = None,
111
- includeHiddenFolders: bool | None = None,
144
+ include_hidden: bool | None = None,
112
145
  select: list[str] | None = None,
113
146
  expand: list[str] | None = None,
114
- ) -> Any:
147
+ ) -> dict[str, Any]:
115
148
  """
116
- Retrieves a specific mail folder's metadata by its ID for a given user. The response can be customized to include hidden folders or select specific properties. Unlike `list_user_messages`, this function fetches folder details, not the emails contained within it.
149
+ Retrieves a specific email folder's metadata by its ID.
117
150
 
118
151
  Args:
119
- mailFolder_id (string): mailFolder-id
120
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
121
- includeHiddenFolders (boolean): Include Hidden Folders
122
- select (array): Select properties to be returned
123
- expand (array): Expand related entities
152
+ 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
+ include_hidden (bool, optional): If true, includes hidden folders in the results.
155
+ select (list[str], optional): A list of properties to return.
156
+ expand (list[str], optional): A list of related entities to expand.
124
157
 
125
158
  Returns:
126
- Any: Retrieved navigation property
159
+ dict[str, Any]: A dictionary containing the mail folder's metadata.
127
160
 
128
161
  Raises:
129
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
130
-
162
+ HTTPStatusError: If the API request fails.
163
+ ValueError: If user_id cannot be retrieved or folder_id is missing.
131
164
  Tags:
132
- users.mailFolder, important
165
+ important
133
166
  """
134
- # If user_id is not provided, get it automatically
135
167
  if user_id is None:
136
- user_info = self.get_current_user_profile()
168
+ user_info = self.get_my_profile()
137
169
  user_id = user_info.get("userPrincipalName")
138
170
  if not user_id:
139
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
140
- if mailFolder_id is None:
141
- raise ValueError("Missing required parameter 'mailFolder-id'.")
142
- url = f"{self.base_url}/users/{user_id}/mailFolders/{mailFolder_id}"
171
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
172
+ if not folder_id:
173
+ raise ValueError("Missing required parameter 'folder_id'.")
174
+
175
+ url = f"{self.base_url}/users/{user_id}/mailFolders/{folder_id}"
143
176
  select_str = ",".join(select) if select else None
144
177
  expand_str = ",".join(expand) if expand else None
145
178
 
146
179
  query_params = {
147
180
  k: v
148
181
  for k, v in [
149
- ("includeHiddenFolders", includeHiddenFolders),
182
+ ("includeHiddenFolders", include_hidden),
150
183
  ("$select", select_str),
151
184
  ("$expand", expand_str),
152
185
  ]
@@ -155,11 +188,11 @@ class OutlookApp(APIApplication):
155
188
  response = self._get(url, params=query_params)
156
189
  return self._handle_response(response)
157
190
 
158
- def list_user_messages(
191
+ def list_emails(
159
192
  self,
160
193
  user_id: str | None = None,
161
194
  select: list[str] = ["bodyPreview"],
162
- includeHiddenMessages: bool | None = None,
195
+ include_hidden: bool | None = None,
163
196
  top: int | None = None,
164
197
  skip: int | None = None,
165
198
  search: str | None = None,
@@ -169,70 +202,44 @@ class OutlookApp(APIApplication):
169
202
  expand: list[str] | None = None,
170
203
  ) -> dict[str, Any]:
171
204
  """
172
- Retrieves a list of messages from a user's mailbox. This function supports powerful querying using optional parameters for filtering, searching, sorting, and pagination, unlike `get_user_message`, which fetches a single email by its ID.
173
-
174
- IMPORTANT LIMITATIONS (Microsoft Graph API restrictions):
175
- - `search` cannot be used with `filter`
176
- - `search` cannot be used with `orderby`
177
- - `search` cannot be used with `skip` (use pagination via @odata.nextLink and get_next_page instead)
178
- - When using `search`, pagination uses $skiptoken instead of $skip
205
+ Retrieves a list of emails from a user's mailbox.
179
206
 
180
207
  Args:
181
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
182
- select (list): Select properties to be returned. Defaults to ['bodyPreview'].
183
- Example: [
184
- 'id', 'categories', 'receivedDateTime', 'sentDateTime', 'hasAttachments', 'internetMessageId',
185
- 'subject', 'body', 'bodyPreview', 'importance', 'parentFolderId', 'conversationId',
186
- 'conversationIndex', 'isDeliveryReceiptRequested', 'isReadReceiptRequested', 'isRead', 'isDraft',
187
- 'webLink', 'inferenceClassification', 'sender', 'from', 'toRecipients', 'ccRecipients',
188
- 'bccRecipients', 'replyTo', 'flag', 'attachments', 'extensions', 'mentions', 'uniqueBody'
189
- ]
190
- includeHiddenMessages (boolean): Include Hidden Messages
191
- top (integer): Specify the number of items to be included in the result Example: '50'.
192
- skip (integer): Specify the number of items to skip in the result. Cannot be used with 'search'. Example: '10'.
193
- search (string): Search items by search phrases. Cannot be used with 'filter', 'orderby', or 'skip'.
194
- filter (string): Filter items by property values. Cannot be used with 'search'.
195
- count (boolean): Include count of items
196
- orderby (array): Order items by property values. Cannot be used with 'search'.
197
- expand (array): Expand related entities
208
+ user_id (str, optional): The ID of the user. Defaults to the authenticated user.
209
+ select (list[str], optional): A list of properties to return for each email. Defaults to ['bodyPreview'].
210
+ include_hidden (bool, optional): If true, includes hidden messages.
211
+ top (int, optional): The maximum number of emails to return.
212
+ skip (int, optional): The number of emails to skip. Cannot be used with 'search'.
213
+ search (str, optional): A search query. Cannot be used with 'filter', 'orderby', or 'skip'.
214
+ filter (str, optional): A filter query. Cannot be used with 'search'.
215
+ count (bool, optional): If true, includes the total count of emails in the response.
216
+ orderby (list[str], optional): A list of properties to sort the results by. Cannot be used with 'search'.
217
+ expand (list[str], optional): A list of related entities to expand.
198
218
 
199
219
  Returns:
200
- dict[str, Any]: Retrieved collection
220
+ dict[str, Any]: A dictionary containing a list of emails and pagination information.
201
221
 
202
222
  Raises:
203
- ValueError: If incompatible parameters are used together (search with filter/orderby/skip).
204
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
205
-
223
+ ValueError: If incompatible parameters are used together (e.g., 'search' with 'filter').
224
+ HTTPStatusError: If the API request fails.
206
225
  Tags:
207
- users.message, important
226
+ important
208
227
  """
209
228
  if search:
210
229
  if filter:
211
- raise ValueError(
212
- "The 'search' parameter cannot be used together with 'filter'. "
213
- "This is a Microsoft Graph API restriction. Please use either search or filter, not both."
214
- )
230
+ raise ValueError("The 'search' parameter cannot be used with 'filter'.")
215
231
  if orderby:
216
- raise ValueError(
217
- "The 'search' parameter cannot be used together with 'orderby'. "
218
- "This is a Microsoft Graph API restriction. When using search, results are sorted by relevance."
219
- )
232
+ raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
220
233
  if skip:
221
- raise ValueError(
222
- "The 'search' parameter cannot be used together with 'skip'. "
223
- "When using search, use pagination via @odata.nextLink and the get_next_page function instead."
224
- )
234
+ raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
225
235
 
226
- # If user_id is not provided, get it automatically
227
236
  if user_id is None:
228
- user_info = self.get_current_user_profile()
237
+ user_info = self.get_my_profile()
229
238
  user_id = user_info.get("userPrincipalName")
230
239
  if not user_id:
231
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
240
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
232
241
 
233
242
  url = f"{self.base_url}/users/{user_id}/messages"
234
-
235
- # Handle list parameters by joining with commas
236
243
  select_str = ",".join(select) if select else None
237
244
  orderby_str = ",".join(orderby) if orderby else None
238
245
  expand_str = ",".join(expand) if expand else None
@@ -240,7 +247,7 @@ class OutlookApp(APIApplication):
240
247
  query_params = {
241
248
  k: v
242
249
  for k, v in [
243
- ("includeHiddenMessages", includeHiddenMessages),
250
+ ("includeHiddenMessages", include_hidden),
244
251
  ("$top", top),
245
252
  ("$skip", skip),
246
253
  ("$search", search),
@@ -256,41 +263,41 @@ class OutlookApp(APIApplication):
256
263
  response = self._get(url, params=query_params)
257
264
  return self._handle_response(response)
258
265
 
259
- def get_user_message(
266
+ def get_email(
260
267
  self,
261
268
  message_id: str,
262
269
  user_id: str | None = None,
263
- includeHiddenMessages: bool | None = None,
270
+ include_hidden: bool | None = None,
264
271
  select: list[str] | None = None,
265
272
  expand: list[str] | None = None,
266
- ) -> Any:
273
+ ) -> dict[str, Any]:
267
274
  """
268
- Retrieves a specific email message by its ID for a given user, with options to select specific fields or expand related data. Unlike `list_user_messages`, which fetches a collection of emails with advanced filtering, this function is designed to retrieve a single, known message.
275
+ Retrieves a specific email by its ID.
269
276
 
270
277
  Args:
271
- message_id (string): message-id
272
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
273
- includeHiddenMessages (boolean): Include Hidden Messages
274
- select (array): Select properties to be returned
275
- expand (array): Expand related entities
278
+ 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
+ include_hidden (bool, optional): If true, includes hidden messages.
281
+ select (list[str], optional): A list of properties to return.
282
+ expand (list[str], optional): A list of related entities to expand.
276
283
 
277
284
  Returns:
278
- Any: Retrieved navigation property
285
+ dict[str, Any]: A dictionary containing the email's details.
279
286
 
280
287
  Raises:
281
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
282
-
288
+ HTTPStatusError: If the API request fails.
289
+ ValueError: If user_id cannot be retrieved or message_id is missing.
283
290
  Tags:
284
- users.message, important
291
+ important
285
292
  """
286
- # If user_id is not provided, get it automatically
287
293
  if user_id is None:
288
- user_info = self.get_current_user_profile()
294
+ user_info = self.get_my_profile()
289
295
  user_id = user_info.get("userPrincipalName")
290
296
  if not user_id:
291
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
292
- if message_id is None:
293
- raise ValueError("Missing required parameter 'message-id'.")
297
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
298
+ if not message_id:
299
+ raise ValueError("Missing required parameter 'message_id'.")
300
+
294
301
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
295
302
  select_str = ",".join(select) if select else None
296
303
  expand_str = ",".join(expand) if expand else None
@@ -298,7 +305,7 @@ class OutlookApp(APIApplication):
298
305
  query_params = {
299
306
  k: v
300
307
  for k, v in [
301
- ("includeHiddenMessages", includeHiddenMessages),
308
+ ("includeHiddenMessages", include_hidden),
302
309
  ("$select", select_str),
303
310
  ("$expand", expand_str),
304
311
  ]
@@ -307,37 +314,36 @@ class OutlookApp(APIApplication):
307
314
  response = self._get(url, params=query_params)
308
315
  return self._handle_response(response)
309
316
 
310
- def user_delete_message(self, message_id: str, user_id: str | None = None) -> Any:
317
+ def delete_email(self, message_id: str, user_id: str | None = None) -> dict[str, Any]:
311
318
  """
312
- Permanently deletes a specific email, identified by `message_id`, from a user's mailbox. If `user_id` is not provided, it defaults to the current authenticated user. Unlike retrieval functions such as `get_user_message`, this performs a destructive action to remove the specified email from Outlook.
319
+ Permanently deletes a specific email by its ID.
313
320
 
314
321
  Args:
315
- message_id (string): message-id
316
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
322
+ 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.
317
324
 
318
325
  Returns:
319
- Any: Success
326
+ dict[str, Any]: A dictionary confirming the deletion.
320
327
 
321
328
  Raises:
322
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
323
-
329
+ HTTPStatusError: If the API request fails.
330
+ ValueError: If user_id cannot be retrieved or message_id is missing.
324
331
  Tags:
325
- users.message, important
332
+ important
326
333
  """
327
- # If user_id is not provided, get it automatically
328
334
  if user_id is None:
329
- user_info = self.get_current_user_profile()
335
+ user_info = self.get_my_profile()
330
336
  user_id = user_info.get("userPrincipalName")
331
337
  if not user_id:
332
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
333
- if message_id is None:
334
- raise ValueError("Missing required parameter 'message-id'.")
338
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
339
+ if not message_id:
340
+ raise ValueError("Missing required parameter 'message_id'.")
341
+
335
342
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
336
- query_params = {}
337
- response = self._delete(url, params=query_params)
343
+ response = self._delete(url, params={})
338
344
  return self._handle_response(response)
339
345
 
340
- def list_message_attachments(
346
+ def list_email_attachments(
341
347
  self,
342
348
  message_id: str,
343
349
  user_id: str | None = None,
@@ -351,60 +357,44 @@ class OutlookApp(APIApplication):
351
357
  expand: list[str] | None = None,
352
358
  ) -> dict[str, Any]:
353
359
  """
354
- Retrieves attachments for a specific email message, identified by its ID. Supports advanced querying for filtering, sorting, and pagination, allowing users to select specific fields to return in the result set, focusing only on attachments rather than the full message content.
355
-
356
- IMPORTANT LIMITATIONS (Microsoft Graph API restrictions):
357
- - `search` cannot be used with `filter`
358
- - `search` cannot be used with `orderby`
359
- - `search` cannot be used with `skip` (use pagination via @odata.nextLink and get_next_page instead)
360
+ Retrieves attachments for a specific email.
360
361
 
361
362
  Args:
362
- message_id (string): message-id
363
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
364
- top (integer): Show only the first n items Example: '50'.
365
- skip (integer): Skip the first n items. Cannot be used with 'search'.
366
- search (string): Search items by search phrases. Cannot be used with 'filter', 'orderby', or 'skip'.
367
- filter (string): Filter items by property values. Cannot be used with 'search'.
368
- count (boolean): Include count of items
369
- orderby (array): Order items by property values. Cannot be used with 'search'.
370
- select (array): Select properties to be returned
371
- expand (array): Expand related entities
363
+ 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
+ top (int, optional): The maximum number of attachments to return.
366
+ skip (int, optional): The number of attachments to skip. Cannot be used with 'search'.
367
+ search (str, optional): A search query. Cannot be used with 'filter', 'orderby', or 'skip'.
368
+ filter (str, optional): A filter query. Cannot be used with 'search'.
369
+ count (bool, optional): If true, includes the total count of attachments.
370
+ orderby (list[str], optional): A list of properties to sort by. Cannot be used with 'search'.
371
+ select (list[str], optional): A list of properties to return.
372
+ expand (list[str], optional): A list of related entities to expand.
372
373
 
373
374
  Returns:
374
- dict[str, Any]: Retrieved collection
375
+ dict[str, Any]: A dictionary containing a list of attachments.
375
376
 
376
377
  Raises:
377
- ValueError: If incompatible parameters are used together (search with filter/orderby/skip).
378
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
379
-
378
+ ValueError: If incompatible parameters are used together.
379
+ HTTPStatusError: If the API request fails.
380
380
  Tags:
381
- users.message, important
381
+ important
382
382
  """
383
383
  if search:
384
384
  if filter:
385
- raise ValueError(
386
- "The 'search' parameter cannot be used together with 'filter'. "
387
- "This is a Microsoft Graph API restriction. Please use either search or filter, not both."
388
- )
385
+ raise ValueError("The 'search' parameter cannot be used with 'filter'.")
389
386
  if orderby:
390
- raise ValueError(
391
- "The 'search' parameter cannot be used together with 'orderby'. "
392
- "This is a Microsoft Graph API restriction. When using search, results are sorted by relevance."
393
- )
387
+ raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
394
388
  if skip:
395
- raise ValueError(
396
- "The 'search' parameter cannot be used together with 'skip'. "
397
- "When using search, use pagination via @odata.nextLink and the get_next_page function instead."
398
- )
389
+ raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
399
390
 
400
- # If user_id is not provided, get it automatically
401
391
  if user_id is None:
402
- user_info = self.get_current_user_profile()
392
+ user_info = self.get_my_profile()
403
393
  user_id = user_info.get("userPrincipalName")
404
394
  if not user_id:
405
- raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
406
- if message_id is None:
407
- raise ValueError("Missing required parameter 'message-id'.")
395
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
396
+ if not message_id:
397
+ raise ValueError("Missing required parameter 'message_id'.")
408
398
 
409
399
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments"
410
400
  orderby_str = ",".join(orderby) if orderby else None
@@ -428,53 +418,107 @@ class OutlookApp(APIApplication):
428
418
  response = self._get(url, params=query_params)
429
419
  return self._handle_response(response)
430
420
 
431
- def get_current_user_profile(
421
+ def get_attachment(
432
422
  self,
423
+ message_id: str,
424
+ attachment_id: str,
425
+ user_id: str | None = None,
433
426
  ) -> dict[str, Any]:
434
427
  """
435
- Fetches the `userPrincipalName` for the currently authenticated user from the `/me` endpoint. This internal helper is used by other methods to automatically obtain the user's ID for API calls when a `user_id` is not explicitly provided.
428
+ Retrieves a specific attachment from an email message and formats it as a dictionary.
436
429
 
430
+ Args:
431
+ message_id (str): The ID of the email message.
432
+ attachment_id (str): The ID of the attachment.
433
+ user_id (str, optional): The ID of the user. Defaults to the authenticated user.
437
434
 
438
435
  Returns:
439
- dict[str, Any]: Current user information
436
+ dict[str, Any]: A dictionary containing the attachment details:
437
+ - 'type' (str): The general type of the attachment (e.g., "image", "audio", "video", "file").
438
+ - 'data' (str): The base64 encoded content of the attachment.
439
+ - 'mime_type' (str): The MIME type of the attachment.
440
+ - 'file_name' (str): The name of the attachment file.
441
+ Tags:
442
+ important
443
+ """
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.")
449
+ if not message_id or not attachment_id:
450
+ raise ValueError("Missing required parameter 'message_id' or 'attachment_id'.")
440
451
 
441
- Raises:
442
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
452
+ url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments/{attachment_id}"
443
453
 
444
- Tags:
445
- me, important
454
+ response = self._get(url, params={})
455
+ attachment_data = self._handle_response(response)
456
+
457
+ content_type = attachment_data.get("contentType", "application/octet-stream")
458
+ attachment_type = content_type.split("/")[0] if "/" in content_type else "file"
459
+ if attachment_type not in ["image", "audio", "video", "text"]:
460
+ attachment_type = "file"
461
+
462
+ return {
463
+ "type": attachment_type,
464
+ "data": attachment_data.get("contentBytes"),
465
+ "mime_type": content_type,
466
+ "file_name": attachment_data.get("name"),
467
+ }
468
+
469
+ def get_my_profile(self) -> dict[str, Any]:
470
+ """
471
+ Fetches the userPrincipalName for the currently authenticated user.
472
+
473
+ Returns:
474
+ dict[str, Any]: A dictionary containing the user's principal name.
475
+
476
+ Raises:
477
+ HTTPStatusError: If the API request fails.
446
478
  """
447
479
  url = f"{self.base_url}/me"
448
- query_params = {
449
- "$select": "userPrincipalName",
450
- }
480
+ query_params = {"$select": "userPrincipalName"}
451
481
  response = self._get(url, params=query_params)
452
482
  return self._handle_response(response)
453
483
 
454
- def get_next_page(self, url: str) -> dict[str, Any]:
484
+ def get_next_page_results(self, url: str) -> dict[str, Any]:
455
485
  """
456
- Executes a GET request for a full URL, typically the `@odata.nextLink` from a previous paginated API response. It simplifies retrieving subsequent pages of data from list functions by handling URL validation and parsing before fetching the results for the next page.
486
+ Retrieves the next page of results from a paginated API response.
487
+
488
+ Args:
489
+ url (str): The full URL for the next page of results (@odata.nextLink).
490
+
491
+ Returns:
492
+ dict[str, Any]: A dictionary containing the next page of results.
493
+
494
+ Raises:
495
+ ValueError: If the URL is missing or invalid.
496
+ Tags:
497
+ important
457
498
  """
458
499
  if not url:
459
500
  raise ValueError("Missing required parameter 'url'.")
460
501
  if not url.startswith(self.base_url):
461
- raise ValueError(f"The provided URL '{url}' does not start with the expected base URL '{self.base_url}'.")
502
+ raise ValueError(f"The provided URL must start with '{self.base_url}'.")
503
+
462
504
  relative_part = url[len(self.base_url) :]
463
505
  parsed_relative = urlparse(relative_part)
464
506
  path_only = parsed_relative.path
465
507
  params = {k: v[0] for k, v in parse_qs(parsed_relative.query).items()}
508
+
466
509
  response = self._get(path_only, params=params)
467
510
  return self._handle_response(response)
468
511
 
469
512
  def list_tools(self):
470
513
  return [
471
- self.reply_to_message,
472
- self.send_mail,
473
- self.get_mail_folder,
474
- self.list_user_messages,
475
- self.get_user_message,
476
- self.user_delete_message,
477
- self.list_message_attachments,
478
- self.get_current_user_profile,
479
- self.get_next_page,
514
+ self.reply_to_email,
515
+ self.send_email,
516
+ self.get_email_folder,
517
+ self.list_emails,
518
+ self.get_email,
519
+ self.delete_email,
520
+ self.list_email_attachments,
521
+ self.get_attachment,
522
+ self.get_my_profile,
523
+ self.get_next_page_results,
480
524
  ]