universal-mcp-applications 0.1.23__py3-none-any.whl → 0.1.25__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.
- universal_mcp/applications/browser_use/app.py +2 -2
- universal_mcp/applications/google_docs/app.py +152 -18
- universal_mcp/applications/outlook/app.py +93 -55
- {universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/METADATA +1 -1
- {universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/RECORD +7 -7
- {universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/WHEEL +0 -0
- {universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/licenses/LICENSE +0 -0
|
@@ -49,7 +49,7 @@ class BrowserUseApp(APIApplication):
|
|
|
49
49
|
llm=llm, task=task, max_steps=max_steps
|
|
50
50
|
)
|
|
51
51
|
result = created_task.complete()
|
|
52
|
-
return result.
|
|
52
|
+
return result.model_dump()
|
|
53
53
|
|
|
54
54
|
async def get_browser_task_status(
|
|
55
55
|
self,
|
|
@@ -65,7 +65,7 @@ class BrowserUseApp(APIApplication):
|
|
|
65
65
|
dict: The current status and details of the task.
|
|
66
66
|
"""
|
|
67
67
|
task = self.browser_client.tasks.get_task(task_id)
|
|
68
|
-
return task.
|
|
68
|
+
return task.model_dump()
|
|
69
69
|
|
|
70
70
|
def list_tools(self):
|
|
71
71
|
return [self.browser_task, self.get_browser_task_status]
|
|
@@ -55,37 +55,171 @@ class GoogleDocsApp(APIApplication):
|
|
|
55
55
|
|
|
56
56
|
def get_document_content(self, document_id: str) -> dict[str, Any]:
|
|
57
57
|
"""
|
|
58
|
-
Retrieves
|
|
59
|
-
|
|
58
|
+
Retrieves and converts a Google Docs document into Markdown-formatted content.
|
|
59
|
+
|
|
60
|
+
This method calls the Google Docs API via `get_document`, then parses the document structure
|
|
61
|
+
to extract paragraphs, headings, lists, tables, images, equations, footnotes, and horizontal rules.
|
|
62
|
+
The final result is a clean Markdown string that closely mirrors the layout and content of
|
|
63
|
+
the original document, including support for nested lists and multi-row tables.
|
|
64
|
+
|
|
60
65
|
Args:
|
|
61
|
-
document_id: The unique
|
|
62
|
-
|
|
66
|
+
document_id (str): The unique ID of the Google Document to retrieve.
|
|
67
|
+
|
|
63
68
|
Returns:
|
|
64
|
-
A dictionary
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
dict[str, Any]: A dictionary with the following keys:
|
|
70
|
+
- 'title' (str): The document's title.
|
|
71
|
+
- 'content' (str): The document content converted to Markdown.
|
|
72
|
+
Markdown Output Supports:
|
|
73
|
+
- Paragraphs and line breaks
|
|
74
|
+
- Headings (Heading 1-6)
|
|
75
|
+
- Bulleted and numbered lists (with nesting)
|
|
76
|
+
- Tables (converted to Markdown tables)
|
|
77
|
+
- Inline images (`` format)
|
|
78
|
+
- Equations (LaTeX-style `$...$`)
|
|
79
|
+
- Footnotes (`[^1]` references and notes at the end)
|
|
80
|
+
- Horizontal rules (`---`)
|
|
81
|
+
|
|
70
82
|
Tags:
|
|
71
|
-
|
|
83
|
+
google-docs, markdown, document-parsing, text-extraction, conversion, structured-data
|
|
72
84
|
"""
|
|
85
|
+
import re
|
|
86
|
+
|
|
73
87
|
response = self.get_document(document_id)
|
|
74
88
|
title = response.get("title", "")
|
|
75
|
-
text_chunks: list[str] = []
|
|
76
89
|
body_content = response.get("body", {}).get("content", [])
|
|
90
|
+
inline_objects = response.get("inlineObjects", {})
|
|
91
|
+
lists = response.get("lists", {})
|
|
92
|
+
footnotes_data = response.get("footnotes", {})
|
|
93
|
+
|
|
94
|
+
text_chunks: list[str] = []
|
|
95
|
+
footnotes: dict[str, str] = {}
|
|
96
|
+
footnote_index: dict[str, int] = {}
|
|
97
|
+
current_list_counters: dict[str, int] = {}
|
|
98
|
+
|
|
99
|
+
def extract_text_from_paragraph(paragraph: dict) -> str:
|
|
100
|
+
"""Extracts paragraph text with inline formatting."""
|
|
101
|
+
text = ""
|
|
102
|
+
for elem in paragraph.get("elements", []):
|
|
103
|
+
if "textRun" in elem:
|
|
104
|
+
content = elem["textRun"].get("content", "")
|
|
105
|
+
text += content
|
|
106
|
+
elif "inlineObjectElement" in elem:
|
|
107
|
+
obj_id = elem["inlineObjectElement"]["inlineObjectId"]
|
|
108
|
+
obj = inline_objects.get(obj_id, {})
|
|
109
|
+
embed = obj.get("inlineObjectProperties", {}).get("embeddedObject", {})
|
|
110
|
+
image_source = embed.get("imageProperties", {}).get("contentUri", "")
|
|
111
|
+
if image_source:
|
|
112
|
+
text += f"\n\n\n\n"
|
|
113
|
+
return text
|
|
114
|
+
|
|
115
|
+
def extract_table(table: dict) -> str:
|
|
116
|
+
rows = []
|
|
117
|
+
for i, row in enumerate(table.get("tableRows", [])):
|
|
118
|
+
cells = []
|
|
119
|
+
for cell in row.get("tableCells", []):
|
|
120
|
+
cell_text = ""
|
|
121
|
+
for content in cell.get("content", []):
|
|
122
|
+
if "paragraph" in content:
|
|
123
|
+
cell_text += extract_text_from_paragraph(content["paragraph"]).strip()
|
|
124
|
+
cells.append(cell_text)
|
|
125
|
+
row_line = "| " + " | ".join(cells) + " |"
|
|
126
|
+
rows.append(row_line)
|
|
127
|
+
if i == 0:
|
|
128
|
+
rows.insert(1, "| " + " | ".join(["---"] * len(cells)) + " |")
|
|
129
|
+
return "\n".join(rows)
|
|
130
|
+
|
|
131
|
+
def extract_heading_style(paragraph: dict) -> str:
|
|
132
|
+
"""Returns appropriate Markdown heading level."""
|
|
133
|
+
style = paragraph.get("paragraphStyle", {})
|
|
134
|
+
heading = style.get("namedStyleType", "")
|
|
135
|
+
match = re.match(r"HEADING_(\d)", heading)
|
|
136
|
+
if match:
|
|
137
|
+
level = int(match.group(1))
|
|
138
|
+
return "#" * level
|
|
139
|
+
return ""
|
|
140
|
+
|
|
141
|
+
def extract_list_prefix(paragraph: dict) -> str:
|
|
142
|
+
"""Generates proper list prefix (numbered or bullet)."""
|
|
143
|
+
list_id = paragraph.get("bullet", {}).get("listId")
|
|
144
|
+
if not list_id:
|
|
145
|
+
return ""
|
|
146
|
+
glyph = paragraph["bullet"].get("glyph", None)
|
|
147
|
+
nesting = paragraph["bullet"].get("nestingLevel", 0)
|
|
148
|
+
list_info = lists.get(list_id, {})
|
|
149
|
+
list_type = list_info.get("listProperties", {}).get("nestingLevels", [{}])[nesting].get("glyphType")
|
|
150
|
+
|
|
151
|
+
indent = " " * nesting
|
|
152
|
+
if list_type and "DECIMAL" in list_type:
|
|
153
|
+
current_list_counters[list_id] = current_list_counters.get(list_id, 1)
|
|
154
|
+
prefix = f"{current_list_counters[list_id]}. "
|
|
155
|
+
current_list_counters[list_id] += 1
|
|
156
|
+
else:
|
|
157
|
+
prefix = "- "
|
|
158
|
+
return indent + prefix
|
|
159
|
+
|
|
160
|
+
def extract_equation(paragraph: dict) -> str:
|
|
161
|
+
return "\n\n$" + paragraph.get("equation", {}).get("equation", "") + "$\n\n"
|
|
162
|
+
|
|
163
|
+
def extract_footnote_ref(footnote_id: str) -> str:
|
|
164
|
+
if footnote_id not in footnote_index:
|
|
165
|
+
footnote_index[footnote_id] = len(footnotes) + 1
|
|
166
|
+
fn_content = footnotes_data.get(footnote_id, {}).get("content", [])
|
|
167
|
+
fn_text = ""
|
|
168
|
+
for item in fn_content:
|
|
169
|
+
if "paragraph" in item:
|
|
170
|
+
fn_text += extract_text_from_paragraph(item["paragraph"]).strip()
|
|
171
|
+
footnotes[footnote_id] = fn_text
|
|
172
|
+
return f"[^{footnote_index[footnote_id]}]"
|
|
173
|
+
|
|
77
174
|
for element in body_content:
|
|
78
175
|
if "paragraph" in element:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
176
|
+
para = element["paragraph"]
|
|
177
|
+
if "equation" in para:
|
|
178
|
+
text_chunks.append(extract_equation(para))
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
heading_md = extract_heading_style(para)
|
|
182
|
+
list_prefix = extract_list_prefix(para)
|
|
183
|
+
|
|
184
|
+
para_text = extract_text_from_paragraph(para).strip()
|
|
185
|
+
if para_text:
|
|
186
|
+
if heading_md:
|
|
187
|
+
text_chunks.append(f"{heading_md} {para_text}")
|
|
188
|
+
elif list_prefix:
|
|
189
|
+
text_chunks.append(f"{list_prefix}{para_text}")
|
|
190
|
+
else:
|
|
191
|
+
text_chunks.append(para_text)
|
|
192
|
+
|
|
193
|
+
elif "table" in element:
|
|
194
|
+
table_md = extract_table(element["table"])
|
|
195
|
+
text_chunks.append(table_md)
|
|
196
|
+
|
|
197
|
+
elif "horizontalRule" in element:
|
|
198
|
+
text_chunks.append("\n---\n")
|
|
199
|
+
|
|
200
|
+
elif "tableOfContents" in element:
|
|
201
|
+
text_chunks.append("<!-- Table of Contents -->")
|
|
202
|
+
|
|
203
|
+
# Handle footnote references (inline elements)
|
|
204
|
+
elif "footnoteReference" in element:
|
|
205
|
+
footnote_id = element["footnoteReference"]["footnoteId"]
|
|
206
|
+
ref = extract_footnote_ref(footnote_id)
|
|
207
|
+
text_chunks.append(ref)
|
|
208
|
+
|
|
209
|
+
# Append footnotes at the end
|
|
210
|
+
if footnotes:
|
|
211
|
+
text_chunks.append("\n## Footnotes\n")
|
|
212
|
+
for fid, index in sorted(footnote_index.items(), key=lambda x: x[1]):
|
|
213
|
+
text_chunks.append(f"[^{index}]: {footnotes[fid]}")
|
|
214
|
+
|
|
215
|
+
content = "\n\n".join(chunk.strip() for chunk in text_chunks if chunk.strip())
|
|
216
|
+
|
|
84
217
|
return {
|
|
85
218
|
"title": title,
|
|
86
|
-
"content": content
|
|
219
|
+
"content": content
|
|
87
220
|
}
|
|
88
221
|
|
|
222
|
+
|
|
89
223
|
def insert_text(
|
|
90
224
|
self, document_id: str, content: str, index: int = 1
|
|
91
225
|
) -> dict[str, Any]:
|
|
@@ -21,8 +21,8 @@ class OutlookApp(APIApplication):
|
|
|
21
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.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
|
-
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
25
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
26
|
comment (string): A comment to include in the reply. Example: 'Thank you for your email. Here is my reply.'.
|
|
27
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'}]}.
|
|
28
28
|
|
|
@@ -40,9 +40,7 @@ class OutlookApp(APIApplication):
|
|
|
40
40
|
user_info = self.get_current_user_profile()
|
|
41
41
|
user_id = user_info.get("userPrincipalName")
|
|
42
42
|
if not user_id:
|
|
43
|
-
raise ValueError(
|
|
44
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
45
|
-
)
|
|
43
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
46
44
|
if message_id is None:
|
|
47
45
|
raise ValueError("Missing required parameter 'message-id'.")
|
|
48
46
|
request_body_data = None
|
|
@@ -50,9 +48,7 @@ class OutlookApp(APIApplication):
|
|
|
50
48
|
"comment": comment,
|
|
51
49
|
"message": message,
|
|
52
50
|
}
|
|
53
|
-
request_body_data = {
|
|
54
|
-
k: v for k, v in request_body_data.items() if v is not None
|
|
55
|
-
}
|
|
51
|
+
request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
|
|
56
52
|
url = f"{self.base_url}/users/{user_id}/messages/{message_id}/reply"
|
|
57
53
|
query_params = {}
|
|
58
54
|
response = self._post(
|
|
@@ -73,8 +69,8 @@ class OutlookApp(APIApplication):
|
|
|
73
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.
|
|
74
70
|
|
|
75
71
|
Args:
|
|
76
|
-
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
77
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.
|
|
78
74
|
saveToSentItems (boolean): saveToSentItems Example: 'False'.
|
|
79
75
|
|
|
80
76
|
Returns:
|
|
@@ -91,17 +87,13 @@ class OutlookApp(APIApplication):
|
|
|
91
87
|
user_info = self.get_current_user_profile()
|
|
92
88
|
user_id = user_info.get("userPrincipalName")
|
|
93
89
|
if not user_id:
|
|
94
|
-
raise ValueError(
|
|
95
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
96
|
-
)
|
|
90
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
97
91
|
request_body_data = None
|
|
98
92
|
request_body_data = {
|
|
99
93
|
"message": message,
|
|
100
94
|
"saveToSentItems": saveToSentItems,
|
|
101
95
|
}
|
|
102
|
-
request_body_data = {
|
|
103
|
-
k: v for k, v in request_body_data.items() if v is not None
|
|
104
|
-
}
|
|
96
|
+
request_body_data = {k: v for k, v in request_body_data.items() if v is not None}
|
|
105
97
|
url = f"{self.base_url}/users/{user_id}/sendMail"
|
|
106
98
|
query_params = {}
|
|
107
99
|
response = self._post(
|
|
@@ -116,7 +108,7 @@ class OutlookApp(APIApplication):
|
|
|
116
108
|
self,
|
|
117
109
|
mailFolder_id: str,
|
|
118
110
|
user_id: str | None = None,
|
|
119
|
-
includeHiddenFolders:
|
|
111
|
+
includeHiddenFolders: bool | None = None,
|
|
120
112
|
select: list[str] | None = None,
|
|
121
113
|
expand: list[str] | None = None,
|
|
122
114
|
) -> Any:
|
|
@@ -124,9 +116,9 @@ class OutlookApp(APIApplication):
|
|
|
124
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.
|
|
125
117
|
|
|
126
118
|
Args:
|
|
127
|
-
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
128
119
|
mailFolder_id (string): mailFolder-id
|
|
129
|
-
|
|
120
|
+
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
121
|
+
includeHiddenFolders (boolean): Include Hidden Folders
|
|
130
122
|
select (array): Select properties to be returned
|
|
131
123
|
expand (array): Expand related entities
|
|
132
124
|
|
|
@@ -144,18 +136,19 @@ class OutlookApp(APIApplication):
|
|
|
144
136
|
user_info = self.get_current_user_profile()
|
|
145
137
|
user_id = user_info.get("userPrincipalName")
|
|
146
138
|
if not user_id:
|
|
147
|
-
raise ValueError(
|
|
148
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
149
|
-
)
|
|
139
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
150
140
|
if mailFolder_id is None:
|
|
151
141
|
raise ValueError("Missing required parameter 'mailFolder-id'.")
|
|
152
142
|
url = f"{self.base_url}/users/{user_id}/mailFolders/{mailFolder_id}"
|
|
143
|
+
select_str = ",".join(select) if select else None
|
|
144
|
+
expand_str = ",".join(expand) if expand else None
|
|
145
|
+
|
|
153
146
|
query_params = {
|
|
154
147
|
k: v
|
|
155
148
|
for k, v in [
|
|
156
149
|
("includeHiddenFolders", includeHiddenFolders),
|
|
157
|
-
("$select",
|
|
158
|
-
("$expand",
|
|
150
|
+
("$select", select_str),
|
|
151
|
+
("$expand", expand_str),
|
|
159
152
|
]
|
|
160
153
|
if v is not None
|
|
161
154
|
}
|
|
@@ -178,6 +171,12 @@ class OutlookApp(APIApplication):
|
|
|
178
171
|
"""
|
|
179
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.
|
|
180
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
|
|
179
|
+
|
|
181
180
|
Args:
|
|
182
181
|
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
183
182
|
select (list): Select properties to be returned. Defaults to ['bodyPreview'].
|
|
@@ -190,30 +189,46 @@ class OutlookApp(APIApplication):
|
|
|
190
189
|
]
|
|
191
190
|
includeHiddenMessages (boolean): Include Hidden Messages
|
|
192
191
|
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
|
|
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'.
|
|
196
195
|
count (boolean): Include count of items
|
|
197
|
-
orderby (array): Order items by property values
|
|
196
|
+
orderby (array): Order items by property values. Cannot be used with 'search'.
|
|
198
197
|
expand (array): Expand related entities
|
|
199
198
|
|
|
200
199
|
Returns:
|
|
201
200
|
dict[str, Any]: Retrieved collection
|
|
202
201
|
|
|
203
202
|
Raises:
|
|
203
|
+
ValueError: If incompatible parameters are used together (search with filter/orderby/skip).
|
|
204
204
|
HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
|
|
205
205
|
|
|
206
206
|
Tags:
|
|
207
207
|
users.message, important
|
|
208
208
|
"""
|
|
209
|
+
if search:
|
|
210
|
+
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
|
+
)
|
|
215
|
+
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
|
+
)
|
|
220
|
+
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
|
+
)
|
|
225
|
+
|
|
209
226
|
# If user_id is not provided, get it automatically
|
|
210
227
|
if user_id is None:
|
|
211
228
|
user_info = self.get_current_user_profile()
|
|
212
229
|
user_id = user_info.get("userPrincipalName")
|
|
213
230
|
if not user_id:
|
|
214
|
-
raise ValueError(
|
|
215
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
216
|
-
)
|
|
231
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
217
232
|
|
|
218
233
|
url = f"{self.base_url}/users/{user_id}/messages"
|
|
219
234
|
|
|
@@ -245,7 +260,7 @@ class OutlookApp(APIApplication):
|
|
|
245
260
|
self,
|
|
246
261
|
message_id: str,
|
|
247
262
|
user_id: str | None = None,
|
|
248
|
-
includeHiddenMessages:
|
|
263
|
+
includeHiddenMessages: bool | None = None,
|
|
249
264
|
select: list[str] | None = None,
|
|
250
265
|
expand: list[str] | None = None,
|
|
251
266
|
) -> Any:
|
|
@@ -253,9 +268,9 @@ class OutlookApp(APIApplication):
|
|
|
253
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.
|
|
254
269
|
|
|
255
270
|
Args:
|
|
256
|
-
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
257
271
|
message_id (string): message-id
|
|
258
|
-
|
|
272
|
+
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
273
|
+
includeHiddenMessages (boolean): Include Hidden Messages
|
|
259
274
|
select (array): Select properties to be returned
|
|
260
275
|
expand (array): Expand related entities
|
|
261
276
|
|
|
@@ -273,18 +288,19 @@ class OutlookApp(APIApplication):
|
|
|
273
288
|
user_info = self.get_current_user_profile()
|
|
274
289
|
user_id = user_info.get("userPrincipalName")
|
|
275
290
|
if not user_id:
|
|
276
|
-
raise ValueError(
|
|
277
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
278
|
-
)
|
|
291
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
279
292
|
if message_id is None:
|
|
280
293
|
raise ValueError("Missing required parameter 'message-id'.")
|
|
281
294
|
url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
|
|
295
|
+
select_str = ",".join(select) if select else None
|
|
296
|
+
expand_str = ",".join(expand) if expand else None
|
|
297
|
+
|
|
282
298
|
query_params = {
|
|
283
299
|
k: v
|
|
284
300
|
for k, v in [
|
|
285
301
|
("includeHiddenMessages", includeHiddenMessages),
|
|
286
|
-
("$select",
|
|
287
|
-
("$expand",
|
|
302
|
+
("$select", select_str),
|
|
303
|
+
("$expand", expand_str),
|
|
288
304
|
]
|
|
289
305
|
if v is not None
|
|
290
306
|
}
|
|
@@ -296,8 +312,8 @@ class OutlookApp(APIApplication):
|
|
|
296
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.
|
|
297
313
|
|
|
298
314
|
Args:
|
|
299
|
-
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
300
315
|
message_id (string): message-id
|
|
316
|
+
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
301
317
|
|
|
302
318
|
Returns:
|
|
303
319
|
Any: Success
|
|
@@ -313,9 +329,7 @@ class OutlookApp(APIApplication):
|
|
|
313
329
|
user_info = self.get_current_user_profile()
|
|
314
330
|
user_id = user_info.get("userPrincipalName")
|
|
315
331
|
if not user_id:
|
|
316
|
-
raise ValueError(
|
|
317
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
318
|
-
)
|
|
332
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
319
333
|
if message_id is None:
|
|
320
334
|
raise ValueError("Missing required parameter 'message-id'.")
|
|
321
335
|
url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
|
|
@@ -339,15 +353,20 @@ class OutlookApp(APIApplication):
|
|
|
339
353
|
"""
|
|
340
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.
|
|
341
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
|
+
|
|
342
361
|
Args:
|
|
343
|
-
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
344
362
|
message_id (string): message-id
|
|
363
|
+
user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
|
|
345
364
|
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
|
|
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'.
|
|
349
368
|
count (boolean): Include count of items
|
|
350
|
-
orderby (array): Order items by property values
|
|
369
|
+
orderby (array): Order items by property values. Cannot be used with 'search'.
|
|
351
370
|
select (array): Select properties to be returned
|
|
352
371
|
expand (array): Expand related entities
|
|
353
372
|
|
|
@@ -355,22 +374,43 @@ class OutlookApp(APIApplication):
|
|
|
355
374
|
dict[str, Any]: Retrieved collection
|
|
356
375
|
|
|
357
376
|
Raises:
|
|
377
|
+
ValueError: If incompatible parameters are used together (search with filter/orderby/skip).
|
|
358
378
|
HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
|
|
359
379
|
|
|
360
380
|
Tags:
|
|
361
381
|
users.message, important
|
|
362
382
|
"""
|
|
383
|
+
if search:
|
|
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
|
+
)
|
|
389
|
+
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
|
+
)
|
|
394
|
+
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
|
+
)
|
|
399
|
+
|
|
363
400
|
# If user_id is not provided, get it automatically
|
|
364
401
|
if user_id is None:
|
|
365
402
|
user_info = self.get_current_user_profile()
|
|
366
403
|
user_id = user_info.get("userPrincipalName")
|
|
367
404
|
if not user_id:
|
|
368
|
-
raise ValueError(
|
|
369
|
-
"Could not retrieve user ID from get_current_user_profile response."
|
|
370
|
-
)
|
|
405
|
+
raise ValueError("Could not retrieve user ID from get_current_user_profile response.")
|
|
371
406
|
if message_id is None:
|
|
372
407
|
raise ValueError("Missing required parameter 'message-id'.")
|
|
408
|
+
|
|
373
409
|
url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments"
|
|
410
|
+
orderby_str = ",".join(orderby) if orderby else None
|
|
411
|
+
select_str = ",".join(select) if select else None
|
|
412
|
+
expand_str = ",".join(expand) if expand else None
|
|
413
|
+
|
|
374
414
|
query_params = {
|
|
375
415
|
k: v
|
|
376
416
|
for k, v in [
|
|
@@ -379,9 +419,9 @@ class OutlookApp(APIApplication):
|
|
|
379
419
|
("$search", search),
|
|
380
420
|
("$filter", filter),
|
|
381
421
|
("$count", count),
|
|
382
|
-
("$orderby",
|
|
383
|
-
("$select",
|
|
384
|
-
("$expand",
|
|
422
|
+
("$orderby", orderby_str),
|
|
423
|
+
("$select", select_str),
|
|
424
|
+
("$expand", expand_str),
|
|
385
425
|
]
|
|
386
426
|
if v is not None
|
|
387
427
|
}
|
|
@@ -418,9 +458,7 @@ class OutlookApp(APIApplication):
|
|
|
418
458
|
if not url:
|
|
419
459
|
raise ValueError("Missing required parameter 'url'.")
|
|
420
460
|
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
|
-
)
|
|
461
|
+
raise ValueError(f"The provided URL '{url}' does not start with the expected base URL '{self.base_url}'.")
|
|
424
462
|
relative_part = url[len(self.base_url) :]
|
|
425
463
|
parsed_relative = urlparse(relative_part)
|
|
426
464
|
path_only = parsed_relative.path
|
{universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-mcp-applications
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.25
|
|
4
4
|
Summary: A Universal MCP Application: universal_mcp_applications
|
|
5
5
|
Project-URL: Homepage, https://github.com/universal-mcp/applications
|
|
6
6
|
Project-URL: Repository, https://github.com/universal-mcp/applications
|
{universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/RECORD
RENAMED
|
@@ -25,7 +25,7 @@ universal_mcp/applications/braze/__init__.py,sha256=9HSB9jg_Ws55dLcTh2qEtLNHMFnR
|
|
|
25
25
|
universal_mcp/applications/braze/app.py,sha256=dJSivDLvc002IPdusKNvLWqIz035j3bRQw6FFXve2Ns,175855
|
|
26
26
|
universal_mcp/applications/browser_use/README.md,sha256=xLlNi7R94GfykFnUrxR9WN4G4cyG-VxHKEeTnGFdk10,13
|
|
27
27
|
universal_mcp/applications/browser_use/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
universal_mcp/applications/browser_use/app.py,sha256=
|
|
28
|
+
universal_mcp/applications/browser_use/app.py,sha256=n4K598QcQTF2e8ukme8kXh63lS8mIwNKqTZS1AVb554,2587
|
|
29
29
|
universal_mcp/applications/cal_com_v2/README.md,sha256=V7M5cx_Rhbj54mCj4LudO1g61UYlA5Ur65wWG1d6n2U,21047
|
|
30
30
|
universal_mcp/applications/cal_com_v2/__init__.py,sha256=OOTUXThnSL6bsOfTs4B0h-zKDo6b3LxL98JQlpZifCE,29
|
|
31
31
|
universal_mcp/applications/cal_com_v2/app.py,sha256=d38htmRGXJ_mNJdMbGQIwvkyAV0t8uiacvaKt7ymD_k,220974
|
|
@@ -105,7 +105,7 @@ universal_mcp/applications/google_calendar/__init__.py,sha256=qxVxf_Q5lOdxXRHzmE
|
|
|
105
105
|
universal_mcp/applications/google_calendar/app.py,sha256=FZptXBLsRo4Rp2kRrVJO_dM3Wr8G0XyMXLHWfPya80Q,25884
|
|
106
106
|
universal_mcp/applications/google_docs/README.md,sha256=KDy_X4SRELegE5sEdixAP0YeXZOXdADTX2D-tAUlCJM,4512
|
|
107
107
|
universal_mcp/applications/google_docs/__init__.py,sha256=U0pWagxnj0VD-AcKNd8eS0orzaMmlUOgvW9vkYBNH40,31
|
|
108
|
-
universal_mcp/applications/google_docs/app.py,sha256=
|
|
108
|
+
universal_mcp/applications/google_docs/app.py,sha256=V3Ys3D6Al-fYb_8CRj5I5BqcZ0V5ovtHN3eiOHPf07Y,39737
|
|
109
109
|
universal_mcp/applications/google_drive/README.md,sha256=Kmg7LLaDW-7bnsgdVimwxc5SdUf2uA9Fv8zIMXVa-Uc,15393
|
|
110
110
|
universal_mcp/applications/google_drive/__init__.py,sha256=DTyed4ADcCmALSyPT8whjXoosPXl3m-i8JrilPJ3ijU,32
|
|
111
111
|
universal_mcp/applications/google_drive/app.py,sha256=J81m8OBjE0552GGWsIfgM4idFjjZfPEOsjk0ZVeJzgM,257259
|
|
@@ -170,7 +170,7 @@ universal_mcp/applications/openai/__init__.py,sha256=7h2xDZdb1pRh7ZDLtFK9BNDEbRH
|
|
|
170
170
|
universal_mcp/applications/openai/app.py,sha256=5pW85lC5SsojpQNPTZO4QkGkDAcLk8M3nvl1m1Bi0Vk,34123
|
|
171
171
|
universal_mcp/applications/outlook/README.md,sha256=gEdQckOduZHCjLYACZtN3ApFC41y1YdcT0uFCiNvuvk,2830
|
|
172
172
|
universal_mcp/applications/outlook/__init__.py,sha256=yExFpo0apWqcpP7l4qCjAYwMyzomUaEeNDhY7Gi-We0,28
|
|
173
|
-
universal_mcp/applications/outlook/app.py,sha256=
|
|
173
|
+
universal_mcp/applications/outlook/app.py,sha256=RBlncTgS9eLrb1SAY7Rw7g0AYnWSqHsfthxEyWgNk4E,22470
|
|
174
174
|
universal_mcp/applications/perplexity/README.md,sha256=3DTRkkaGKmAyZz3Rpdo5OUyEXogWqzZ228gxrixO2pI,554
|
|
175
175
|
universal_mcp/applications/perplexity/__init__.py,sha256=f4uz8qlw-uek6AlyWxoWxukubaHb6YV4riTcvQhfvO0,31
|
|
176
176
|
universal_mcp/applications/perplexity/app.py,sha256=M47xUTRJbnd7zDxVG1R3nkX46aI7GZ-aVPphNQjMkvA,2815
|
|
@@ -276,7 +276,7 @@ universal_mcp/applications/youtube/app.py,sha256=eqgqe0b53W9Mj0FZGW3ZqY3xkGF4NbO
|
|
|
276
276
|
universal_mcp/applications/zenquotes/README.md,sha256=FJyoTGRCaZjF_bsCBqg1CrYcvIfuUG_Qk616G1wjhF8,512
|
|
277
277
|
universal_mcp/applications/zenquotes/__init__.py,sha256=C5nEHZ3Xy6nYUarq0BqQbbJnHs0UtSlqhk0DqmvWiHk,58
|
|
278
278
|
universal_mcp/applications/zenquotes/app.py,sha256=7xIEnSZWAGYu5583Be2ZjSCtLUAfMWRzucSpp7hw_h4,1299
|
|
279
|
-
universal_mcp_applications-0.1.
|
|
280
|
-
universal_mcp_applications-0.1.
|
|
281
|
-
universal_mcp_applications-0.1.
|
|
282
|
-
universal_mcp_applications-0.1.
|
|
279
|
+
universal_mcp_applications-0.1.25.dist-info/METADATA,sha256=yI7SU0Crz1RSAHM8JMGShgf-SJqMYzMwE4AQFm9rk4c,2956
|
|
280
|
+
universal_mcp_applications-0.1.25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
281
|
+
universal_mcp_applications-0.1.25.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
|
|
282
|
+
universal_mcp_applications-0.1.25.dist-info/RECORD,,
|
{universal_mcp_applications-0.1.23.dist-info → universal_mcp_applications-0.1.25.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|