msgraph-mcp-server 0.3.0__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.
- msgraph_mcp/__init__.py +0 -0
- msgraph_mcp/auth/__init__.py +0 -0
- msgraph_mcp/auth/cli.py +45 -0
- msgraph_mcp/auth/msal_app.py +47 -0
- msgraph_mcp/auth/token.py +32 -0
- msgraph_mcp/config.py +79 -0
- msgraph_mcp/graph/__init__.py +0 -0
- msgraph_mcp/graph/auth_provider.py +29 -0
- msgraph_mcp/graph/batch.py +206 -0
- msgraph_mcp/graph/client.py +37 -0
- msgraph_mcp/graph/errors.py +43 -0
- msgraph_mcp/graph/pagination.py +47 -0
- msgraph_mcp/graph/serialize.py +351 -0
- msgraph_mcp/graph/trimming.py +359 -0
- msgraph_mcp/server.py +61 -0
- msgraph_mcp/tools/__init__.py +35 -0
- msgraph_mcp/tools/_binary.py +48 -0
- msgraph_mcp/tools/calendar.py +585 -0
- msgraph_mcp/tools/mail_actions.py +83 -0
- msgraph_mcp/tools/mail_batch.py +220 -0
- msgraph_mcp/tools/mail_folders.py +263 -0
- msgraph_mcp/tools/mail_read.py +333 -0
- msgraph_mcp/tools/mail_rules.py +468 -0
- msgraph_mcp/tools/mail_write.py +403 -0
- msgraph_mcp/tools/teams_channels.py +223 -0
- msgraph_mcp/tools/teams_chats.py +140 -0
- msgraph_mcp/tools/teams_content.py +150 -0
- msgraph_mcp/tools/util.py +35 -0
- msgraph_mcp_server-0.3.0.dist-info/METADATA +243 -0
- msgraph_mcp_server-0.3.0.dist-info/RECORD +33 -0
- msgraph_mcp_server-0.3.0.dist-info/WHEEL +4 -0
- msgraph_mcp_server-0.3.0.dist-info/entry_points.txt +3 -0
- msgraph_mcp_server-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""Outlook inbox mail-rule tools.
|
|
2
|
+
|
|
3
|
+
Wraps the Graph endpoint /me/mailFolders/inbox/messageRules. All rule
|
|
4
|
+
operations target the inbox folder — Outlook doesn't support per-folder
|
|
5
|
+
rules (the official endpoint is hardcoded to the inbox).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from msgraph.generated.models.email_address import EmailAddress
|
|
11
|
+
from msgraph.generated.models.message_rule import MessageRule
|
|
12
|
+
from msgraph.generated.models.message_rule_actions import MessageRuleActions
|
|
13
|
+
from msgraph.generated.models.message_rule_predicates import MessageRulePredicates
|
|
14
|
+
from msgraph.generated.models.recipient import Recipient
|
|
15
|
+
|
|
16
|
+
from msgraph_mcp.auth.token import NotAuthenticatedError
|
|
17
|
+
from msgraph_mcp.graph.errors import GraphValidationError, map_kiota_error
|
|
18
|
+
from msgraph_mcp.graph.serialize import message_rule_to_dict
|
|
19
|
+
from msgraph_mcp.graph.trimming import trim_message_rule
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _rules_endpoint(graph, mailbox: str | None):
|
|
23
|
+
return (
|
|
24
|
+
graph.mailbox(mailbox)
|
|
25
|
+
.mail_folders.by_mail_folder_id("inbox")
|
|
26
|
+
.message_rules
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def list_rules(*, graph, mailbox: str | None = None, include_raw: bool = False) -> dict:
|
|
31
|
+
"""List inbox mail rules.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
{"items": [trimmed_rule, ...], "next_page_token": None}
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
collection = await _rules_endpoint(graph, mailbox).get()
|
|
38
|
+
except NotAuthenticatedError:
|
|
39
|
+
raise
|
|
40
|
+
except Exception as exc: # noqa: BLE001
|
|
41
|
+
raise map_kiota_error(exc) from exc
|
|
42
|
+
items = [
|
|
43
|
+
trim_message_rule(message_rule_to_dict(r), include_raw=include_raw)
|
|
44
|
+
for r in (collection.value or [])
|
|
45
|
+
]
|
|
46
|
+
return {"items": items, "next_page_token": None}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def get_rule(
|
|
50
|
+
*, graph, rule_id: str, mailbox: str | None = None, include_raw: bool = False
|
|
51
|
+
) -> dict:
|
|
52
|
+
"""Get a single inbox mail rule by id.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
rule_id: Graph rule id. Required.
|
|
56
|
+
mailbox: Optional mailbox (email or user id) for shared mailboxes.
|
|
57
|
+
include_raw: Include the raw Graph payload.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Trimmed rule dict.
|
|
61
|
+
"""
|
|
62
|
+
if not rule_id:
|
|
63
|
+
raise GraphValidationError("`rule_id` is required")
|
|
64
|
+
try:
|
|
65
|
+
rule = await _rules_endpoint(graph, mailbox).by_message_rule_id(rule_id).get()
|
|
66
|
+
except NotAuthenticatedError:
|
|
67
|
+
raise
|
|
68
|
+
except Exception as exc: # noqa: BLE001
|
|
69
|
+
raise map_kiota_error(exc) from exc
|
|
70
|
+
return trim_message_rule(message_rule_to_dict(rule), include_raw=include_raw)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _recipients_from_addresses(addresses: list[str] | None) -> list[Recipient] | None:
|
|
74
|
+
if not addresses:
|
|
75
|
+
return None
|
|
76
|
+
out: list[Recipient] = []
|
|
77
|
+
for addr in addresses:
|
|
78
|
+
if not addr or "@" not in addr:
|
|
79
|
+
raise GraphValidationError(f"Invalid email address: {addr!r}")
|
|
80
|
+
email = EmailAddress()
|
|
81
|
+
email.address = addr
|
|
82
|
+
recipient = Recipient()
|
|
83
|
+
recipient.email_address = email
|
|
84
|
+
out.append(recipient)
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _build_predicates(
|
|
89
|
+
*,
|
|
90
|
+
sender_contains: list[str] | None,
|
|
91
|
+
subject_contains: list[str] | None,
|
|
92
|
+
body_contains: list[str] | None,
|
|
93
|
+
body_or_subject_contains: list[str] | None,
|
|
94
|
+
from_addresses: list[str] | None,
|
|
95
|
+
has_attachments: bool | None,
|
|
96
|
+
) -> MessageRulePredicates | None:
|
|
97
|
+
# Only assign attrs that have real values. Setting `pred.x = None` registers
|
|
98
|
+
# as an explicit null in kiota's backing store and the SDK then emits it as
|
|
99
|
+
# `write_null_value(...)` on the *parent* writer during serialization, which
|
|
100
|
+
# mixes scalar nulls with object keys and trips "Invalid Json output".
|
|
101
|
+
from_recipients = _recipients_from_addresses(from_addresses)
|
|
102
|
+
fields: dict = {}
|
|
103
|
+
if sender_contains:
|
|
104
|
+
fields["sender_contains"] = sender_contains
|
|
105
|
+
if subject_contains:
|
|
106
|
+
fields["subject_contains"] = subject_contains
|
|
107
|
+
if body_contains:
|
|
108
|
+
fields["body_contains"] = body_contains
|
|
109
|
+
if body_or_subject_contains:
|
|
110
|
+
fields["body_or_subject_contains"] = body_or_subject_contains
|
|
111
|
+
if from_recipients:
|
|
112
|
+
fields["from_addresses"] = from_recipients
|
|
113
|
+
if has_attachments is not None:
|
|
114
|
+
fields["has_attachments"] = has_attachments
|
|
115
|
+
if not fields:
|
|
116
|
+
return None
|
|
117
|
+
pred = MessageRulePredicates()
|
|
118
|
+
for name, value in fields.items():
|
|
119
|
+
setattr(pred, name, value)
|
|
120
|
+
return pred
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _build_actions(
|
|
124
|
+
*,
|
|
125
|
+
move_to_folder: str | None,
|
|
126
|
+
mark_as_read: bool | None,
|
|
127
|
+
delete: bool | None,
|
|
128
|
+
stop_processing_rules: bool | None,
|
|
129
|
+
) -> MessageRuleActions | None:
|
|
130
|
+
# See note in _build_predicates: only set non-None attrs to avoid the
|
|
131
|
+
# backing-store "explicit null" serialization bug.
|
|
132
|
+
fields: dict = {}
|
|
133
|
+
if move_to_folder is not None:
|
|
134
|
+
fields["move_to_folder"] = move_to_folder
|
|
135
|
+
if mark_as_read is not None:
|
|
136
|
+
fields["mark_as_read"] = mark_as_read
|
|
137
|
+
if delete is not None:
|
|
138
|
+
fields["delete"] = delete
|
|
139
|
+
if stop_processing_rules is not None:
|
|
140
|
+
fields["stop_processing_rules"] = stop_processing_rules
|
|
141
|
+
if not fields:
|
|
142
|
+
return None
|
|
143
|
+
actions = MessageRuleActions()
|
|
144
|
+
for name, value in fields.items():
|
|
145
|
+
setattr(actions, name, value)
|
|
146
|
+
return actions
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def create_rule(
|
|
150
|
+
*,
|
|
151
|
+
graph,
|
|
152
|
+
display_name: str,
|
|
153
|
+
sender_contains: list[str] | None = None,
|
|
154
|
+
subject_contains: list[str] | None = None,
|
|
155
|
+
body_contains: list[str] | None = None,
|
|
156
|
+
body_or_subject_contains: list[str] | None = None,
|
|
157
|
+
from_addresses: list[str] | None = None,
|
|
158
|
+
has_attachments: bool | None = None,
|
|
159
|
+
move_to_folder: str | None = None,
|
|
160
|
+
mark_as_read: bool | None = None,
|
|
161
|
+
delete: bool | None = None,
|
|
162
|
+
stop_processing_rules: bool | None = None,
|
|
163
|
+
sequence: int | None = None,
|
|
164
|
+
is_enabled: bool = True,
|
|
165
|
+
mailbox: str | None = None,
|
|
166
|
+
include_raw: bool = False,
|
|
167
|
+
) -> dict:
|
|
168
|
+
"""Create an inbox mail rule.
|
|
169
|
+
|
|
170
|
+
At least one condition AND one action are required. Conditions
|
|
171
|
+
within a rule are AND-ed by Outlook. Pass multiple values in a list
|
|
172
|
+
(e.g. ``sender_contains=["example.com", "monitor.io"]``) for OR
|
|
173
|
+
within that condition.
|
|
174
|
+
|
|
175
|
+
Common condition args:
|
|
176
|
+
sender_contains: Substrings to match against the sender display
|
|
177
|
+
name or address (e.g. ["example.com"]).
|
|
178
|
+
subject_contains: Substrings to match against the subject.
|
|
179
|
+
body_contains: Substrings to match against the message body.
|
|
180
|
+
body_or_subject_contains: Match either subject or body.
|
|
181
|
+
from_addresses: Exact email addresses to match (e.g.
|
|
182
|
+
["noreply@example.com"]).
|
|
183
|
+
has_attachments: True/False to require/exclude attachments.
|
|
184
|
+
|
|
185
|
+
Common action args:
|
|
186
|
+
move_to_folder: Destination folder id (use list_folders or
|
|
187
|
+
create_folder to get one).
|
|
188
|
+
mark_as_read: Mark matching messages as read.
|
|
189
|
+
delete: Move matching messages to Deleted Items.
|
|
190
|
+
stop_processing_rules: If True, no further rules run after this
|
|
191
|
+
one matches. Recommended for routing-to-folder rules.
|
|
192
|
+
|
|
193
|
+
Other args:
|
|
194
|
+
sequence: Optional rule order (lower runs first).
|
|
195
|
+
is_enabled: Whether the rule is active. Default True.
|
|
196
|
+
mailbox: Optional mailbox for shared mailboxes.
|
|
197
|
+
include_raw: Include the raw Graph payload.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The trimmed new rule.
|
|
201
|
+
"""
|
|
202
|
+
if not display_name or not display_name.strip():
|
|
203
|
+
raise GraphValidationError("`display_name` is required")
|
|
204
|
+
conditions = _build_predicates(
|
|
205
|
+
sender_contains=sender_contains,
|
|
206
|
+
subject_contains=subject_contains,
|
|
207
|
+
body_contains=body_contains,
|
|
208
|
+
body_or_subject_contains=body_or_subject_contains,
|
|
209
|
+
from_addresses=from_addresses,
|
|
210
|
+
has_attachments=has_attachments,
|
|
211
|
+
)
|
|
212
|
+
if conditions is None:
|
|
213
|
+
raise GraphValidationError(
|
|
214
|
+
"At least one condition is required "
|
|
215
|
+
"(sender_contains, subject_contains, body_contains, "
|
|
216
|
+
"body_or_subject_contains, from_addresses, or has_attachments)"
|
|
217
|
+
)
|
|
218
|
+
actions = _build_actions(
|
|
219
|
+
move_to_folder=move_to_folder,
|
|
220
|
+
mark_as_read=mark_as_read,
|
|
221
|
+
delete=delete,
|
|
222
|
+
stop_processing_rules=stop_processing_rules,
|
|
223
|
+
)
|
|
224
|
+
if actions is None:
|
|
225
|
+
raise GraphValidationError(
|
|
226
|
+
"At least one action is required "
|
|
227
|
+
"(move_to_folder, mark_as_read, delete, or stop_processing_rules)"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
body = MessageRule()
|
|
231
|
+
body.display_name = display_name
|
|
232
|
+
body.is_enabled = is_enabled
|
|
233
|
+
body.conditions = conditions
|
|
234
|
+
body.actions = actions
|
|
235
|
+
# Graph rejects sequence=0 ("InvalidValue"). Default to 1 so the rule lands
|
|
236
|
+
# at the head of the list — callers can override. Never set None here, or
|
|
237
|
+
# the backing store will emit `write_null_value("sequence")` on the parent
|
|
238
|
+
# writer (which conflicts with the serialized object dict; see kiota's
|
|
239
|
+
# BackingStoreSerializationWriterProxyFactory).
|
|
240
|
+
body.sequence = sequence if sequence is not None else 1
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
created = await _rules_endpoint(graph, mailbox).post(body)
|
|
244
|
+
except NotAuthenticatedError:
|
|
245
|
+
raise
|
|
246
|
+
except Exception as exc: # noqa: BLE001
|
|
247
|
+
raise map_kiota_error(exc) from exc
|
|
248
|
+
return trim_message_rule(message_rule_to_dict(created), include_raw=include_raw)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def update_rule(
|
|
252
|
+
*,
|
|
253
|
+
graph,
|
|
254
|
+
rule_id: str,
|
|
255
|
+
display_name: str | None = None,
|
|
256
|
+
is_enabled: bool | None = None,
|
|
257
|
+
sequence: int | None = None,
|
|
258
|
+
sender_contains: list[str] | None = None,
|
|
259
|
+
subject_contains: list[str] | None = None,
|
|
260
|
+
body_contains: list[str] | None = None,
|
|
261
|
+
body_or_subject_contains: list[str] | None = None,
|
|
262
|
+
from_addresses: list[str] | None = None,
|
|
263
|
+
has_attachments: bool | None = None,
|
|
264
|
+
move_to_folder: str | None = None,
|
|
265
|
+
mark_as_read: bool | None = None,
|
|
266
|
+
delete: bool | None = None,
|
|
267
|
+
stop_processing_rules: bool | None = None,
|
|
268
|
+
mailbox: str | None = None,
|
|
269
|
+
include_raw: bool = False,
|
|
270
|
+
) -> dict:
|
|
271
|
+
"""Patch fields of an existing inbox mail rule. Pass None to leave a
|
|
272
|
+
field unchanged.
|
|
273
|
+
|
|
274
|
+
Replacement semantics for nested blocks:
|
|
275
|
+
Graph PATCH on messageRules replaces the entire ``conditions`` or
|
|
276
|
+
``actions`` block when you send it. If you pass ANY condition arg
|
|
277
|
+
(sender_contains, subject_contains, body_contains,
|
|
278
|
+
body_or_subject_contains, from_addresses, has_attachments) the
|
|
279
|
+
rule's conditions are rebuilt from ONLY the args you pass — any
|
|
280
|
+
existing conditions you don't re-specify are dropped. Same for
|
|
281
|
+
actions (move_to_folder, mark_as_read, delete,
|
|
282
|
+
stop_processing_rules). If you don't pass any condition or
|
|
283
|
+
action args, the existing blocks are preserved.
|
|
284
|
+
|
|
285
|
+
If you want to add to a block without losing the rest, call
|
|
286
|
+
``get_rule`` first and re-supply the existing values.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
rule_id: Graph rule id. Required.
|
|
290
|
+
display_name: New display name. None = unchanged.
|
|
291
|
+
is_enabled: Enable/disable the rule. None = unchanged.
|
|
292
|
+
sequence: New order (lower runs first). None = unchanged.
|
|
293
|
+
sender_contains / subject_contains / body_contains /
|
|
294
|
+
body_or_subject_contains / from_addresses / has_attachments:
|
|
295
|
+
Passing any of these REPLACES the conditions block.
|
|
296
|
+
move_to_folder / mark_as_read / delete / stop_processing_rules:
|
|
297
|
+
Passing any of these REPLACES the actions block.
|
|
298
|
+
mailbox: Optional mailbox for shared mailboxes.
|
|
299
|
+
include_raw: Include the raw Graph payload.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
The trimmed updated rule.
|
|
303
|
+
"""
|
|
304
|
+
if not rule_id:
|
|
305
|
+
raise GraphValidationError("`rule_id` is required")
|
|
306
|
+
|
|
307
|
+
new_conditions = _build_predicates(
|
|
308
|
+
sender_contains=sender_contains,
|
|
309
|
+
subject_contains=subject_contains,
|
|
310
|
+
body_contains=body_contains,
|
|
311
|
+
body_or_subject_contains=body_or_subject_contains,
|
|
312
|
+
from_addresses=from_addresses,
|
|
313
|
+
has_attachments=has_attachments,
|
|
314
|
+
)
|
|
315
|
+
new_actions = _build_actions(
|
|
316
|
+
move_to_folder=move_to_folder,
|
|
317
|
+
mark_as_read=mark_as_read,
|
|
318
|
+
delete=delete,
|
|
319
|
+
stop_processing_rules=stop_processing_rules,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
has_top_level = (
|
|
323
|
+
display_name is not None or is_enabled is not None or sequence is not None
|
|
324
|
+
)
|
|
325
|
+
if not has_top_level and new_conditions is None and new_actions is None:
|
|
326
|
+
raise GraphValidationError(
|
|
327
|
+
"At least one field must be updated "
|
|
328
|
+
"(display_name, is_enabled, sequence, a condition, or an action)"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
patch = MessageRule()
|
|
332
|
+
if display_name is not None:
|
|
333
|
+
if not display_name.strip():
|
|
334
|
+
raise GraphValidationError("`display_name` cannot be empty")
|
|
335
|
+
patch.display_name = display_name
|
|
336
|
+
if is_enabled is not None:
|
|
337
|
+
patch.is_enabled = is_enabled
|
|
338
|
+
if sequence is not None:
|
|
339
|
+
patch.sequence = sequence
|
|
340
|
+
if new_conditions is not None:
|
|
341
|
+
patch.conditions = new_conditions
|
|
342
|
+
if new_actions is not None:
|
|
343
|
+
patch.actions = new_actions
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
updated = await (
|
|
347
|
+
_rules_endpoint(graph, mailbox).by_message_rule_id(rule_id).patch(patch)
|
|
348
|
+
)
|
|
349
|
+
except NotAuthenticatedError:
|
|
350
|
+
raise
|
|
351
|
+
except Exception as exc: # noqa: BLE001
|
|
352
|
+
raise map_kiota_error(exc) from exc
|
|
353
|
+
return trim_message_rule(message_rule_to_dict(updated), include_raw=include_raw)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
async def delete_rule(*, graph, rule_id: str, mailbox: str | None = None) -> dict:
|
|
357
|
+
"""Delete an inbox mail rule by id.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
rule_id: Graph rule id. Required.
|
|
361
|
+
mailbox: Optional mailbox (email or user id) for shared mailboxes.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
{"deleted": True, "id": <rule_id>}
|
|
365
|
+
"""
|
|
366
|
+
if not rule_id:
|
|
367
|
+
raise GraphValidationError("`rule_id` is required")
|
|
368
|
+
try:
|
|
369
|
+
await _rules_endpoint(graph, mailbox).by_message_rule_id(rule_id).delete()
|
|
370
|
+
except NotAuthenticatedError:
|
|
371
|
+
raise
|
|
372
|
+
except Exception as exc: # noqa: BLE001
|
|
373
|
+
raise map_kiota_error(exc) from exc
|
|
374
|
+
return {"deleted": True, "id": rule_id}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def register(mcp, *, graph) -> None:
|
|
378
|
+
@mcp.tool(name="list_rules", description=list_rules.__doc__ or "")
|
|
379
|
+
async def _list_rules(mailbox: str | None = None, include_raw: bool = False):
|
|
380
|
+
return await list_rules(graph=graph, mailbox=mailbox, include_raw=include_raw)
|
|
381
|
+
|
|
382
|
+
@mcp.tool(name="get_rule", description=get_rule.__doc__ or "")
|
|
383
|
+
async def _get_rule(
|
|
384
|
+
rule_id: str, mailbox: str | None = None, include_raw: bool = False
|
|
385
|
+
):
|
|
386
|
+
return await get_rule(
|
|
387
|
+
graph=graph, rule_id=rule_id, mailbox=mailbox, include_raw=include_raw
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
@mcp.tool(name="create_rule", description=create_rule.__doc__ or "")
|
|
391
|
+
async def _create_rule(
|
|
392
|
+
display_name: str,
|
|
393
|
+
sender_contains: list[str] | None = None,
|
|
394
|
+
subject_contains: list[str] | None = None,
|
|
395
|
+
body_contains: list[str] | None = None,
|
|
396
|
+
body_or_subject_contains: list[str] | None = None,
|
|
397
|
+
from_addresses: list[str] | None = None,
|
|
398
|
+
has_attachments: bool | None = None,
|
|
399
|
+
move_to_folder: str | None = None,
|
|
400
|
+
mark_as_read: bool | None = None,
|
|
401
|
+
delete: bool | None = None,
|
|
402
|
+
stop_processing_rules: bool | None = None,
|
|
403
|
+
sequence: int | None = None,
|
|
404
|
+
is_enabled: bool = True,
|
|
405
|
+
mailbox: str | None = None,
|
|
406
|
+
include_raw: bool = False,
|
|
407
|
+
):
|
|
408
|
+
return await create_rule(
|
|
409
|
+
graph=graph,
|
|
410
|
+
display_name=display_name,
|
|
411
|
+
sender_contains=sender_contains,
|
|
412
|
+
subject_contains=subject_contains,
|
|
413
|
+
body_contains=body_contains,
|
|
414
|
+
body_or_subject_contains=body_or_subject_contains,
|
|
415
|
+
from_addresses=from_addresses,
|
|
416
|
+
has_attachments=has_attachments,
|
|
417
|
+
move_to_folder=move_to_folder,
|
|
418
|
+
mark_as_read=mark_as_read,
|
|
419
|
+
delete=delete,
|
|
420
|
+
stop_processing_rules=stop_processing_rules,
|
|
421
|
+
sequence=sequence,
|
|
422
|
+
is_enabled=is_enabled,
|
|
423
|
+
mailbox=mailbox,
|
|
424
|
+
include_raw=include_raw,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
@mcp.tool(name="update_rule", description=update_rule.__doc__ or "")
|
|
428
|
+
async def _update_rule(
|
|
429
|
+
rule_id: str,
|
|
430
|
+
display_name: str | None = None,
|
|
431
|
+
is_enabled: bool | None = None,
|
|
432
|
+
sequence: int | None = None,
|
|
433
|
+
sender_contains: list[str] | None = None,
|
|
434
|
+
subject_contains: list[str] | None = None,
|
|
435
|
+
body_contains: list[str] | None = None,
|
|
436
|
+
body_or_subject_contains: list[str] | None = None,
|
|
437
|
+
from_addresses: list[str] | None = None,
|
|
438
|
+
has_attachments: bool | None = None,
|
|
439
|
+
move_to_folder: str | None = None,
|
|
440
|
+
mark_as_read: bool | None = None,
|
|
441
|
+
delete: bool | None = None,
|
|
442
|
+
stop_processing_rules: bool | None = None,
|
|
443
|
+
mailbox: str | None = None,
|
|
444
|
+
include_raw: bool = False,
|
|
445
|
+
):
|
|
446
|
+
return await update_rule(
|
|
447
|
+
graph=graph,
|
|
448
|
+
rule_id=rule_id,
|
|
449
|
+
display_name=display_name,
|
|
450
|
+
is_enabled=is_enabled,
|
|
451
|
+
sequence=sequence,
|
|
452
|
+
sender_contains=sender_contains,
|
|
453
|
+
subject_contains=subject_contains,
|
|
454
|
+
body_contains=body_contains,
|
|
455
|
+
body_or_subject_contains=body_or_subject_contains,
|
|
456
|
+
from_addresses=from_addresses,
|
|
457
|
+
has_attachments=has_attachments,
|
|
458
|
+
move_to_folder=move_to_folder,
|
|
459
|
+
mark_as_read=mark_as_read,
|
|
460
|
+
delete=delete,
|
|
461
|
+
stop_processing_rules=stop_processing_rules,
|
|
462
|
+
mailbox=mailbox,
|
|
463
|
+
include_raw=include_raw,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
@mcp.tool(name="delete_rule", description=delete_rule.__doc__ or "")
|
|
467
|
+
async def _delete_rule(rule_id: str, mailbox: str | None = None):
|
|
468
|
+
return await delete_rule(graph=graph, rule_id=rule_id, mailbox=mailbox)
|