universal-mcp-applications 0.1.22__py3-none-any.whl → 0.1.33__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 (24) hide show
  1. universal_mcp/applications/browser_use/app.py +14 -19
  2. universal_mcp/applications/google_docs/app.py +363 -289
  3. universal_mcp/applications/google_gemini/app.py +3 -3
  4. universal_mcp/applications/google_sheet/app.py +6 -2
  5. universal_mcp/applications/linkedin/README.md +18 -4
  6. universal_mcp/applications/linkedin/app.py +754 -153
  7. universal_mcp/applications/onedrive/README.md +24 -0
  8. universal_mcp/applications/onedrive/__init__.py +1 -0
  9. universal_mcp/applications/onedrive/app.py +338 -0
  10. universal_mcp/applications/outlook/app.py +281 -199
  11. universal_mcp/applications/reddit/app.py +30 -47
  12. universal_mcp/applications/scraper/README.md +7 -4
  13. universal_mcp/applications/scraper/app.py +310 -290
  14. universal_mcp/applications/sharepoint/README.md +16 -14
  15. universal_mcp/applications/sharepoint/app.py +267 -154
  16. universal_mcp/applications/slack/app.py +31 -0
  17. universal_mcp/applications/zenquotes/app.py +2 -2
  18. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.33.dist-info}/METADATA +2 -2
  19. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.33.dist-info}/RECORD +21 -21
  20. universal_mcp/applications/unipile/README.md +0 -28
  21. universal_mcp/applications/unipile/__init__.py +0 -1
  22. universal_mcp/applications/unipile/app.py +0 -1077
  23. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.33.dist-info}/WHEEL +0 -0
  24. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.33.dist-info}/licenses/LICENSE +0 -0
@@ -10,163 +10,189 @@ 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
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
25
- message_id (string): message-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(
44
- "Could not retrieve user ID from get_current_user_profile response."
45
- )
46
- if message_id is None:
47
- raise ValueError("Missing required parameter 'message-id'.")
48
- request_body_data = None
49
- request_body_data = {
50
- "comment": comment,
51
- "message": message,
52
- }
53
- request_body_data = {
54
- k: v for k, v in request_body_data.items() if v is not None
55
- }
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
+
56
61
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/reply"
57
- query_params = {}
62
+
58
63
  response = self._post(
59
64
  url,
60
65
  data=request_body_data,
61
- params=query_params,
66
+ params={},
62
67
  content_type="application/json",
63
68
  )
64
69
  return self._handle_response(response)
65
70
 
66
- def send_mail(
71
+ def send_email(
67
72
  self,
68
- message: dict[str, Any],
73
+ subject: str,
74
+ body: str,
75
+ to_recipients: list[str],
69
76
  user_id: str | None = None,
70
- saveToSentItems: bool | None = None,
71
- ) -> 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]:
72
83
  """
73
- 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.
74
85
 
75
86
  Args:
76
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
77
- 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'}]}.
78
- 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.
79
96
 
80
97
  Returns:
81
- Any: Success
98
+ dict[str, Any]: A dictionary confirming the send action.
82
99
 
83
100
  Raises:
84
- 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.
85
103
 
86
104
  Tags:
87
- users.user.Actions, important
105
+ important
88
106
  """
89
- # If user_id is not provided, get it automatically
90
107
  if user_id is None:
91
- user_info = self.get_current_user_profile()
108
+ user_info = self.get_my_profile()
92
109
  user_id = user_info.get("userPrincipalName")
93
110
  if not user_id:
94
- raise ValueError(
95
- "Could not retrieve user ID from get_current_user_profile response."
96
- )
97
- request_body_data = None
98
- request_body_data = {
99
- "message": message,
100
- "saveToSentItems": saveToSentItems,
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],
101
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
+
102
125
  request_body_data = {
103
- k: v for k, v in request_body_data.items() if v is not None
126
+ "message": message,
127
+ "saveToSentItems": save_to_sent_items,
104
128
  }
129
+
105
130
  url = f"{self.base_url}/users/{user_id}/sendMail"
106
- query_params = {}
131
+
107
132
  response = self._post(
108
133
  url,
109
134
  data=request_body_data,
110
- params=query_params,
135
+ params={},
111
136
  content_type="application/json",
112
137
  )
113
138
  return self._handle_response(response)
114
139
 
115
- def get_mail_folder(
140
+ def get_email_folder(
116
141
  self,
117
- mailFolder_id: str,
142
+ folder_id: str,
118
143
  user_id: str | None = None,
119
- includeHiddenFolders: str | None = None,
144
+ include_hidden: bool | None = None,
120
145
  select: list[str] | None = None,
121
146
  expand: list[str] | None = None,
122
- ) -> Any:
147
+ ) -> dict[str, Any]:
123
148
  """
124
- 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.
125
150
 
126
151
  Args:
127
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
128
- mailFolder_id (string): mailFolder-id
129
- includeHiddenFolders (string): Include Hidden Folders
130
- select (array): Select properties to be returned
131
- 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.
132
157
 
133
158
  Returns:
134
- Any: Retrieved navigation property
159
+ dict[str, Any]: A dictionary containing the mail folder's metadata.
135
160
 
136
161
  Raises:
137
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
138
-
162
+ HTTPStatusError: If the API request fails.
163
+ ValueError: If user_id cannot be retrieved or folder_id is missing.
139
164
  Tags:
140
- users.mailFolder, important
165
+ important
141
166
  """
142
- # If user_id is not provided, get it automatically
143
167
  if user_id is None:
144
- user_info = self.get_current_user_profile()
168
+ user_info = self.get_my_profile()
145
169
  user_id = user_info.get("userPrincipalName")
146
170
  if not user_id:
147
- raise ValueError(
148
- "Could not retrieve user ID from get_current_user_profile response."
149
- )
150
- if mailFolder_id is None:
151
- raise ValueError("Missing required parameter 'mailFolder-id'.")
152
- 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}"
176
+ select_str = ",".join(select) if select else None
177
+ expand_str = ",".join(expand) if expand else None
178
+
153
179
  query_params = {
154
180
  k: v
155
181
  for k, v in [
156
- ("includeHiddenFolders", includeHiddenFolders),
157
- ("$select", select),
158
- ("$expand", expand),
182
+ ("includeHiddenFolders", include_hidden),
183
+ ("$select", select_str),
184
+ ("$expand", expand_str),
159
185
  ]
160
186
  if v is not None
161
187
  }
162
188
  response = self._get(url, params=query_params)
163
189
  return self._handle_response(response)
164
190
 
165
- def list_user_messages(
191
+ def list_emails(
166
192
  self,
167
193
  user_id: str | None = None,
168
194
  select: list[str] = ["bodyPreview"],
169
- includeHiddenMessages: bool | None = None,
195
+ include_hidden: bool | None = None,
170
196
  top: int | None = None,
171
197
  skip: int | None = None,
172
198
  search: str | None = None,
@@ -176,48 +202,44 @@ class OutlookApp(APIApplication):
176
202
  expand: list[str] | None = None,
177
203
  ) -> dict[str, Any]:
178
204
  """
179
- 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.
205
+ Retrieves a list of emails from a user's mailbox.
180
206
 
181
207
  Args:
182
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
183
- select (list): Select properties to be returned. Defaults to ['bodyPreview'].
184
- Example: [
185
- 'id', 'categories', 'receivedDateTime', 'sentDateTime', 'hasAttachments', 'internetMessageId',
186
- 'subject', 'body', 'bodyPreview', 'importance', 'parentFolderId', 'conversationId',
187
- 'conversationIndex', 'isDeliveryReceiptRequested', 'isReadReceiptRequested', 'isRead', 'isDraft',
188
- 'webLink', 'inferenceClassification', 'sender', 'from', 'toRecipients', 'ccRecipients',
189
- 'bccRecipients', 'replyTo', 'flag', 'attachments', 'extensions', 'mentions', 'uniqueBody'
190
- ]
191
- includeHiddenMessages (boolean): Include Hidden Messages
192
- top (integer): Specify the number of items to be included in the result Example: '50'.
193
- skip (integer): Specify the number of items to skip in the result Example: '10'.
194
- search (string): Search items by search phrases
195
- filter (string): Filter items by property values
196
- count (boolean): Include count of items
197
- orderby (array): Order items by property values
198
- 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.
199
218
 
200
219
  Returns:
201
- dict[str, Any]: Retrieved collection
220
+ dict[str, Any]: A dictionary containing a list of emails and pagination information.
202
221
 
203
222
  Raises:
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
- # If user_id is not provided, get it automatically
228
+ if search:
229
+ if filter:
230
+ raise ValueError("The 'search' parameter cannot be used with 'filter'.")
231
+ if orderby:
232
+ raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
233
+ if skip:
234
+ raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
235
+
210
236
  if user_id is None:
211
- user_info = self.get_current_user_profile()
237
+ user_info = self.get_my_profile()
212
238
  user_id = user_info.get("userPrincipalName")
213
239
  if not user_id:
214
- raise ValueError(
215
- "Could not retrieve user ID from get_current_user_profile response."
216
- )
240
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
217
241
 
218
242
  url = f"{self.base_url}/users/{user_id}/messages"
219
-
220
- # Handle list parameters by joining with commas
221
243
  select_str = ",".join(select) if select else None
222
244
  orderby_str = ",".join(orderby) if orderby else None
223
245
  expand_str = ",".join(expand) if expand else None
@@ -225,7 +247,7 @@ class OutlookApp(APIApplication):
225
247
  query_params = {
226
248
  k: v
227
249
  for k, v in [
228
- ("includeHiddenMessages", includeHiddenMessages),
250
+ ("includeHiddenMessages", include_hidden),
229
251
  ("$top", top),
230
252
  ("$skip", skip),
231
253
  ("$search", search),
@@ -241,89 +263,87 @@ class OutlookApp(APIApplication):
241
263
  response = self._get(url, params=query_params)
242
264
  return self._handle_response(response)
243
265
 
244
- def get_user_message(
266
+ def get_email(
245
267
  self,
246
268
  message_id: str,
247
269
  user_id: str | None = None,
248
- includeHiddenMessages: str | None = None,
270
+ include_hidden: bool | None = None,
249
271
  select: list[str] | None = None,
250
272
  expand: list[str] | None = None,
251
- ) -> Any:
273
+ ) -> dict[str, Any]:
252
274
  """
253
- 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.
254
276
 
255
277
  Args:
256
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
257
- message_id (string): message-id
258
- includeHiddenMessages (string): Include Hidden Messages
259
- select (array): Select properties to be returned
260
- 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.
261
283
 
262
284
  Returns:
263
- Any: Retrieved navigation property
285
+ dict[str, Any]: A dictionary containing the email's details.
264
286
 
265
287
  Raises:
266
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
267
-
288
+ HTTPStatusError: If the API request fails.
289
+ ValueError: If user_id cannot be retrieved or message_id is missing.
268
290
  Tags:
269
- users.message, important
291
+ important
270
292
  """
271
- # If user_id is not provided, get it automatically
272
293
  if user_id is None:
273
- user_info = self.get_current_user_profile()
294
+ user_info = self.get_my_profile()
274
295
  user_id = user_info.get("userPrincipalName")
275
296
  if not user_id:
276
- raise ValueError(
277
- "Could not retrieve user ID from get_current_user_profile response."
278
- )
279
- if message_id is None:
280
- 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
+
281
301
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
302
+ select_str = ",".join(select) if select else None
303
+ expand_str = ",".join(expand) if expand else None
304
+
282
305
  query_params = {
283
306
  k: v
284
307
  for k, v in [
285
- ("includeHiddenMessages", includeHiddenMessages),
286
- ("$select", select),
287
- ("$expand", expand),
308
+ ("includeHiddenMessages", include_hidden),
309
+ ("$select", select_str),
310
+ ("$expand", expand_str),
288
311
  ]
289
312
  if v is not None
290
313
  }
291
314
  response = self._get(url, params=query_params)
292
315
  return self._handle_response(response)
293
316
 
294
- 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]:
295
318
  """
296
- 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.
297
320
 
298
321
  Args:
299
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
300
- message_id (string): message-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.
301
324
 
302
325
  Returns:
303
- Any: Success
326
+ dict[str, Any]: A dictionary confirming the deletion.
304
327
 
305
328
  Raises:
306
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
307
-
329
+ HTTPStatusError: If the API request fails.
330
+ ValueError: If user_id cannot be retrieved or message_id is missing.
308
331
  Tags:
309
- users.message, important
332
+ important
310
333
  """
311
- # If user_id is not provided, get it automatically
312
334
  if user_id is None:
313
- user_info = self.get_current_user_profile()
335
+ user_info = self.get_my_profile()
314
336
  user_id = user_info.get("userPrincipalName")
315
337
  if not user_id:
316
- raise ValueError(
317
- "Could not retrieve user ID from get_current_user_profile response."
318
- )
319
- if message_id is None:
320
- 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
+
321
342
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
322
- query_params = {}
323
- response = self._delete(url, params=query_params)
343
+ response = self._delete(url, params={})
324
344
  return self._handle_response(response)
325
345
 
326
- def list_message_attachments(
346
+ def list_email_attachments(
327
347
  self,
328
348
  message_id: str,
329
349
  user_id: str | None = None,
@@ -337,40 +357,50 @@ class OutlookApp(APIApplication):
337
357
  expand: list[str] | None = None,
338
358
  ) -> dict[str, Any]:
339
359
  """
340
- 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.
360
+ Retrieves attachments for a specific email.
341
361
 
342
362
  Args:
343
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
344
- message_id (string): message-id
345
- top (integer): Show only the first n items Example: '50'.
346
- skip (integer): Skip the first n items
347
- search (string): Search items by search phrases
348
- filter (string): Filter items by property values
349
- count (boolean): Include count of items
350
- orderby (array): Order items by property values
351
- select (array): Select properties to be returned
352
- 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.
353
373
 
354
374
  Returns:
355
- dict[str, Any]: Retrieved collection
375
+ dict[str, Any]: A dictionary containing a list of attachments.
356
376
 
357
377
  Raises:
358
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
359
-
378
+ ValueError: If incompatible parameters are used together.
379
+ HTTPStatusError: If the API request fails.
360
380
  Tags:
361
- users.message, important
381
+ important
362
382
  """
363
- # If user_id is not provided, get it automatically
383
+ if search:
384
+ if filter:
385
+ raise ValueError("The 'search' parameter cannot be used with 'filter'.")
386
+ if orderby:
387
+ raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
388
+ if skip:
389
+ raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
390
+
364
391
  if user_id is None:
365
- user_info = self.get_current_user_profile()
392
+ user_info = self.get_my_profile()
366
393
  user_id = user_info.get("userPrincipalName")
367
394
  if not user_id:
368
- raise ValueError(
369
- "Could not retrieve user ID from get_current_user_profile response."
370
- )
371
- if message_id is None:
372
- 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'.")
398
+
373
399
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments"
400
+ orderby_str = ",".join(orderby) if orderby else None
401
+ select_str = ",".join(select) if select else None
402
+ expand_str = ",".join(expand) if expand else None
403
+
374
404
  query_params = {
375
405
  k: v
376
406
  for k, v in [
@@ -379,64 +409,116 @@ class OutlookApp(APIApplication):
379
409
  ("$search", search),
380
410
  ("$filter", filter),
381
411
  ("$count", count),
382
- ("$orderby", orderby),
383
- ("$select", select),
384
- ("$expand", expand),
412
+ ("$orderby", orderby_str),
413
+ ("$select", select_str),
414
+ ("$expand", expand_str),
385
415
  ]
386
416
  if v is not None
387
417
  }
388
418
  response = self._get(url, params=query_params)
389
419
  return self._handle_response(response)
390
420
 
391
- def get_current_user_profile(
421
+ def get_attachment(
392
422
  self,
423
+ message_id: str,
424
+ attachment_id: str,
425
+ user_id: str | None = None,
393
426
  ) -> dict[str, Any]:
394
427
  """
395
- 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.
396
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.
397
434
 
398
435
  Returns:
399
- 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'.")
400
451
 
401
- Raises:
402
- 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}"
403
453
 
404
- Tags:
405
- 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.
406
478
  """
407
479
  url = f"{self.base_url}/me"
408
- query_params = {
409
- "$select": "userPrincipalName",
410
- }
480
+ query_params = {"$select": "userPrincipalName"}
411
481
  response = self._get(url, params=query_params)
412
482
  return self._handle_response(response)
413
483
 
414
- def get_next_page(self, url: str) -> dict[str, Any]:
484
+ def get_next_page_results(self, url: str) -> dict[str, Any]:
415
485
  """
416
- 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
417
498
  """
418
499
  if not url:
419
500
  raise ValueError("Missing required parameter 'url'.")
420
501
  if not url.startswith(self.base_url):
421
- raise ValueError(
422
- f"The provided URL '{url}' does not start with the expected base URL '{self.base_url}'."
423
- )
502
+ raise ValueError(f"The provided URL must start with '{self.base_url}'.")
503
+
424
504
  relative_part = url[len(self.base_url) :]
425
505
  parsed_relative = urlparse(relative_part)
426
506
  path_only = parsed_relative.path
427
507
  params = {k: v[0] for k, v in parse_qs(parsed_relative.query).items()}
508
+
428
509
  response = self._get(path_only, params=params)
429
510
  return self._handle_response(response)
430
511
 
431
512
  def list_tools(self):
432
513
  return [
433
- self.reply_to_message,
434
- self.send_mail,
435
- self.get_mail_folder,
436
- self.list_user_messages,
437
- self.get_user_message,
438
- self.user_delete_message,
439
- self.list_message_attachments,
440
- self.get_current_user_profile,
441
- 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,
442
524
  ]