msgraph-mcp-server 0.4.0__tar.gz → 0.5.0__tar.gz
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_server-0.4.0 → msgraph_mcp_server-0.5.0}/PKG-INFO +1 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/pyproject.toml +1 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/server.json +2 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/serialize.py +4 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/trimming.py +2 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/calendar.py +56 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/fixtures/event_minimal.json +2 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_serialize.py +12 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_trimming.py +7 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_calendar.py +100 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/uv.lock +1 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/.env.example +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/.github/dependabot.yml +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/.github/workflows/ci.yml +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/.github/workflows/release.yml +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/.gitignore +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/CONTRIBUTING.md +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/LICENSE +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/README.md +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/auth/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/auth/cli.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/auth/msal_app.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/auth/token.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/config.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/auth_provider.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/batch.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/client.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/errors.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/pagination.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/server.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/_binary.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/mail_actions.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/mail_batch.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/mail_folders.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/mail_read.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/mail_rules.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/mail_write.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/teams_channels.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/teams_chats.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/teams_content.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/util.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/conftest.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/fixtures/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/fixtures/message_minimal.json +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/integration/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/integration/test_live_smoke.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/integration/test_rules_and_folders_live.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_auth_cli.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_auth_msal_app.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_auth_token.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_config.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_auth_provider.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_batch.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_client.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_errors.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_graph_pagination.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_server.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_mail_actions.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_mail_batch.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_mail_folders.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_mail_read.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_mail_rules.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_mail_write.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_teams_channels.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_teams_chats.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_teams_content.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/tests/test_tools_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: msgraph-mcp-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: MCP server for Microsoft Graph: Outlook mail and calendar, plus read-only Microsoft Teams
|
|
5
5
|
Project-URL: Homepage, https://github.com/timfurlong/msgraph-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/timfurlong/msgraph-mcp
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/timfurlong/msgraph-mcp",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.5.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "msgraph-mcp-server",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.5.0",
|
|
16
16
|
"runtimeHint": "uvx",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
@@ -188,6 +188,10 @@ def event_to_dict(evt: Any) -> dict:
|
|
|
188
188
|
"body": _body_to_dict(getattr(evt, "body", None)),
|
|
189
189
|
"showAs": _enum_value(getattr(evt, "show_as", None)),
|
|
190
190
|
"sensitivity": _enum_value(getattr(evt, "sensitivity", None)),
|
|
191
|
+
"isReminderOn": getattr(evt, "is_reminder_on", None),
|
|
192
|
+
"reminderMinutesBeforeStart": getattr(
|
|
193
|
+
evt, "reminder_minutes_before_start", None
|
|
194
|
+
),
|
|
191
195
|
"recurrence": getattr(evt, "recurrence", None),
|
|
192
196
|
"webLink": getattr(evt, "web_link", None),
|
|
193
197
|
}
|
|
@@ -101,6 +101,8 @@ def trim_event(raw: dict, *, include_body: bool, include_raw: bool) -> dict:
|
|
|
101
101
|
"body_preview": raw.get("bodyPreview"),
|
|
102
102
|
"show_as": raw.get("showAs"),
|
|
103
103
|
"sensitivity": raw.get("sensitivity"),
|
|
104
|
+
"is_reminder_on": raw.get("isReminderOn"),
|
|
105
|
+
"reminder_minutes_before_start": raw.get("reminderMinutesBeforeStart"),
|
|
104
106
|
"recurrence": raw.get("recurrence"),
|
|
105
107
|
"web_link": raw.get("webLink"),
|
|
106
108
|
}
|
|
@@ -187,6 +187,28 @@ def _free_busy(show_as: str) -> FreeBusyStatus:
|
|
|
187
187
|
return _SHOW_AS[show_as]
|
|
188
188
|
|
|
189
189
|
|
|
190
|
+
def _apply_reminder(
|
|
191
|
+
evt: Event,
|
|
192
|
+
*,
|
|
193
|
+
is_reminder_on: bool | None,
|
|
194
|
+
reminder_minutes_before_start: int | None,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Set reminder fields on evt. Giving minutes turns the reminder on."""
|
|
197
|
+
if reminder_minutes_before_start is not None:
|
|
198
|
+
if reminder_minutes_before_start < 0:
|
|
199
|
+
raise GraphValidationError(
|
|
200
|
+
"reminder_minutes_before_start must be zero or greater"
|
|
201
|
+
)
|
|
202
|
+
if is_reminder_on is False:
|
|
203
|
+
raise GraphValidationError(
|
|
204
|
+
"reminder_minutes_before_start cannot be set when is_reminder_on is False"
|
|
205
|
+
)
|
|
206
|
+
evt.reminder_minutes_before_start = reminder_minutes_before_start
|
|
207
|
+
evt.is_reminder_on = True
|
|
208
|
+
elif is_reminder_on is not None:
|
|
209
|
+
evt.is_reminder_on = is_reminder_on
|
|
210
|
+
|
|
211
|
+
|
|
190
212
|
def _build_event(
|
|
191
213
|
*,
|
|
192
214
|
subject: str,
|
|
@@ -200,6 +222,8 @@ def _build_event(
|
|
|
200
222
|
is_online_meeting: bool,
|
|
201
223
|
is_all_day: bool,
|
|
202
224
|
show_as: ShowAs | None,
|
|
225
|
+
is_reminder_on: bool | None,
|
|
226
|
+
reminder_minutes_before_start: int | None,
|
|
203
227
|
) -> Event:
|
|
204
228
|
e = Event()
|
|
205
229
|
e.subject = subject
|
|
@@ -228,6 +252,11 @@ def _build_event(
|
|
|
228
252
|
e.is_all_day = is_all_day
|
|
229
253
|
if show_as is not None:
|
|
230
254
|
e.show_as = _free_busy(show_as)
|
|
255
|
+
_apply_reminder(
|
|
256
|
+
e,
|
|
257
|
+
is_reminder_on=is_reminder_on,
|
|
258
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
259
|
+
)
|
|
231
260
|
return e
|
|
232
261
|
|
|
233
262
|
|
|
@@ -245,6 +274,8 @@ async def create_event(
|
|
|
245
274
|
is_online_meeting: bool = False,
|
|
246
275
|
is_all_day: bool = False,
|
|
247
276
|
show_as: ShowAs | None = None,
|
|
277
|
+
is_reminder_on: bool | None = None,
|
|
278
|
+
reminder_minutes_before_start: int | None = None,
|
|
248
279
|
calendar_id: str | None = None,
|
|
249
280
|
mailbox: str | None = None,
|
|
250
281
|
include_raw: bool = False,
|
|
@@ -263,6 +294,10 @@ async def create_event(
|
|
|
263
294
|
is_all_day: All-day event flag.
|
|
264
295
|
show_as: Free/busy status shown to others: "free", "tentative", "busy",
|
|
265
296
|
"oof", or "workingElsewhere". Omit for Outlook's default ("busy").
|
|
297
|
+
is_reminder_on: False creates the event with no reminder. Omit for
|
|
298
|
+
Outlook's default (a reminder 15 minutes before start).
|
|
299
|
+
reminder_minutes_before_start: Minutes before start to alert (0 or
|
|
300
|
+
more). Setting this turns the reminder on.
|
|
266
301
|
calendar_id: Optional calendar id; default is the user's primary calendar.
|
|
267
302
|
mailbox: Optional mailbox.
|
|
268
303
|
|
|
@@ -281,6 +316,8 @@ async def create_event(
|
|
|
281
316
|
is_online_meeting=is_online_meeting,
|
|
282
317
|
is_all_day=is_all_day,
|
|
283
318
|
show_as=show_as,
|
|
319
|
+
is_reminder_on=is_reminder_on,
|
|
320
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
284
321
|
)
|
|
285
322
|
try:
|
|
286
323
|
target = graph.mailbox(mailbox)
|
|
@@ -310,6 +347,8 @@ async def update_event(
|
|
|
310
347
|
is_online_meeting: bool | None = None,
|
|
311
348
|
is_all_day: bool | None = None,
|
|
312
349
|
show_as: ShowAs | None = None,
|
|
350
|
+
is_reminder_on: bool | None = None,
|
|
351
|
+
reminder_minutes_before_start: int | None = None,
|
|
313
352
|
mailbox: str | None = None,
|
|
314
353
|
include_raw: bool = False,
|
|
315
354
|
) -> dict:
|
|
@@ -319,6 +358,10 @@ async def update_event(
|
|
|
319
358
|
|
|
320
359
|
show_as sets the free/busy status shown to others: "free", "tentative",
|
|
321
360
|
"busy", "oof", or "workingElsewhere".
|
|
361
|
+
|
|
362
|
+
is_reminder_on=False removes the reminder. reminder_minutes_before_start
|
|
363
|
+
sets how many minutes before start to alert (0 or more) and turns the
|
|
364
|
+
reminder on.
|
|
322
365
|
"""
|
|
323
366
|
if (start_datetime or end_datetime) and not time_zone:
|
|
324
367
|
raise GraphValidationError("time_zone is required when updating start or end")
|
|
@@ -344,6 +387,11 @@ async def update_event(
|
|
|
344
387
|
patch.is_all_day = is_all_day
|
|
345
388
|
if show_as is not None:
|
|
346
389
|
patch.show_as = _free_busy(show_as)
|
|
390
|
+
_apply_reminder(
|
|
391
|
+
patch,
|
|
392
|
+
is_reminder_on=is_reminder_on,
|
|
393
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
394
|
+
)
|
|
347
395
|
try:
|
|
348
396
|
updated = await graph.mailbox(mailbox).events.by_event_id(event_id).patch(patch)
|
|
349
397
|
except NotAuthenticatedError:
|
|
@@ -578,6 +626,8 @@ def register(mcp, *, graph) -> None:
|
|
|
578
626
|
is_online_meeting: bool = False,
|
|
579
627
|
is_all_day: bool = False,
|
|
580
628
|
show_as: ShowAs | None = None,
|
|
629
|
+
is_reminder_on: bool | None = None,
|
|
630
|
+
reminder_minutes_before_start: int | None = None,
|
|
581
631
|
calendar_id: str | None = None,
|
|
582
632
|
mailbox: str | None = None,
|
|
583
633
|
include_raw: bool = False,
|
|
@@ -595,6 +645,8 @@ def register(mcp, *, graph) -> None:
|
|
|
595
645
|
is_online_meeting=is_online_meeting,
|
|
596
646
|
is_all_day=is_all_day,
|
|
597
647
|
show_as=show_as,
|
|
648
|
+
is_reminder_on=is_reminder_on,
|
|
649
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
598
650
|
calendar_id=calendar_id,
|
|
599
651
|
mailbox=mailbox,
|
|
600
652
|
include_raw=include_raw,
|
|
@@ -613,6 +665,8 @@ def register(mcp, *, graph) -> None:
|
|
|
613
665
|
is_online_meeting: bool | None = None,
|
|
614
666
|
is_all_day: bool | None = None,
|
|
615
667
|
show_as: ShowAs | None = None,
|
|
668
|
+
is_reminder_on: bool | None = None,
|
|
669
|
+
reminder_minutes_before_start: int | None = None,
|
|
616
670
|
mailbox: str | None = None,
|
|
617
671
|
include_raw: bool = False,
|
|
618
672
|
):
|
|
@@ -629,6 +683,8 @@ def register(mcp, *, graph) -> None:
|
|
|
629
683
|
is_online_meeting=is_online_meeting,
|
|
630
684
|
is_all_day=is_all_day,
|
|
631
685
|
show_as=show_as,
|
|
686
|
+
is_reminder_on=is_reminder_on,
|
|
687
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
632
688
|
mailbox=mailbox,
|
|
633
689
|
include_raw=include_raw,
|
|
634
690
|
)
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"body": {"contentType": "html", "content": "<p>Daily standup</p>"},
|
|
21
21
|
"showAs": "busy",
|
|
22
22
|
"sensitivity": "normal",
|
|
23
|
+
"isReminderOn": true,
|
|
24
|
+
"reminderMinutesBeforeStart": 15,
|
|
23
25
|
"recurrence": null,
|
|
24
26
|
"webLink": "https://outlook.office.com/owa/?ItemID=evt"
|
|
25
27
|
}
|
|
@@ -151,6 +151,18 @@ def test_message_rule_predicates_to_dict_minimal():
|
|
|
151
151
|
assert d["fromAddresses"] == []
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def test_event_to_dict_includes_reminder():
|
|
155
|
+
evt = SimpleNamespace(
|
|
156
|
+
id="e1",
|
|
157
|
+
is_reminder_on=True,
|
|
158
|
+
reminder_minutes_before_start=30,
|
|
159
|
+
additional_data={},
|
|
160
|
+
)
|
|
161
|
+
d = serialize.event_to_dict(evt)
|
|
162
|
+
assert d["isReminderOn"] is True
|
|
163
|
+
assert d["reminderMinutesBeforeStart"] == 30
|
|
164
|
+
|
|
165
|
+
|
|
154
166
|
def test_message_rule_actions_to_dict_minimal():
|
|
155
167
|
actions = SimpleNamespace(
|
|
156
168
|
assign_categories=None,
|
|
@@ -84,6 +84,13 @@ def test_trim_event_minimal():
|
|
|
84
84
|
assert "body" not in t
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def test_trim_event_includes_reminder():
|
|
88
|
+
raw = load("event_minimal.json")
|
|
89
|
+
t = trimming.trim_event(raw, include_body=False, include_raw=False)
|
|
90
|
+
assert t["is_reminder_on"] is True
|
|
91
|
+
assert t["reminder_minutes_before_start"] == 15
|
|
92
|
+
|
|
93
|
+
|
|
87
94
|
def test_trim_event_with_body_and_raw():
|
|
88
95
|
raw = load("event_minimal.json")
|
|
89
96
|
t = trimming.trim_event(raw, include_body=True, include_raw=True)
|
|
@@ -228,6 +228,106 @@ def test_register_exposes_show_as_on_create_and_update():
|
|
|
228
228
|
assert "show_as" in inspect.signature(registered[name]).parameters
|
|
229
229
|
|
|
230
230
|
|
|
231
|
+
async def _create_with(**kwargs):
|
|
232
|
+
graph = MagicMock()
|
|
233
|
+
mb = MagicMock()
|
|
234
|
+
mb.events.post = AsyncMock(return_value=_fake_event(id_="new1"))
|
|
235
|
+
graph.mailbox = MagicMock(return_value=mb)
|
|
236
|
+
await cal_tools.create_event(
|
|
237
|
+
graph=graph,
|
|
238
|
+
subject="Standup",
|
|
239
|
+
start_datetime="2026-05-19T15:00:00",
|
|
240
|
+
end_datetime="2026-05-19T15:30:00",
|
|
241
|
+
**kwargs,
|
|
242
|
+
)
|
|
243
|
+
assert mb.events.post.await_args is not None
|
|
244
|
+
return mb.events.post.await_args.args[0]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
async def _update_with(**kwargs):
|
|
248
|
+
graph = MagicMock()
|
|
249
|
+
mb = MagicMock()
|
|
250
|
+
evt_builder = MagicMock(patch=AsyncMock(return_value=_fake_event(id_="e1")))
|
|
251
|
+
mb.events.by_event_id = MagicMock(return_value=evt_builder)
|
|
252
|
+
graph.mailbox = MagicMock(return_value=mb)
|
|
253
|
+
await cal_tools.update_event(graph=graph, event_id="e1", **kwargs)
|
|
254
|
+
return evt_builder.patch.await_args.args[0]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@pytest.mark.asyncio
|
|
258
|
+
async def test_create_event_can_turn_reminder_off():
|
|
259
|
+
posted = await _create_with(is_reminder_on=False)
|
|
260
|
+
assert posted.is_reminder_on is False
|
|
261
|
+
assert posted.reminder_minutes_before_start is None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@pytest.mark.asyncio
|
|
265
|
+
async def test_create_event_reminder_minutes_turns_reminder_on():
|
|
266
|
+
posted = await _create_with(reminder_minutes_before_start=60)
|
|
267
|
+
assert posted.reminder_minutes_before_start == 60
|
|
268
|
+
assert posted.is_reminder_on is True
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@pytest.mark.asyncio
|
|
272
|
+
async def test_create_event_leaves_reminder_unset_by_default():
|
|
273
|
+
posted = await _create_with()
|
|
274
|
+
assert posted.is_reminder_on is None
|
|
275
|
+
assert posted.reminder_minutes_before_start is None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@pytest.mark.asyncio
|
|
279
|
+
async def test_create_event_rejects_negative_reminder_minutes():
|
|
280
|
+
with pytest.raises(GraphValidationError):
|
|
281
|
+
await _create_with(reminder_minutes_before_start=-5)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@pytest.mark.asyncio
|
|
285
|
+
async def test_create_event_rejects_minutes_with_reminder_off():
|
|
286
|
+
with pytest.raises(GraphValidationError):
|
|
287
|
+
await _create_with(is_reminder_on=False, reminder_minutes_before_start=10)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@pytest.mark.asyncio
|
|
291
|
+
async def test_update_event_can_turn_reminder_off():
|
|
292
|
+
patched = await _update_with(is_reminder_on=False)
|
|
293
|
+
assert patched.is_reminder_on is False
|
|
294
|
+
assert patched.reminder_minutes_before_start is None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@pytest.mark.asyncio
|
|
298
|
+
async def test_update_event_reminder_minutes_turns_reminder_on():
|
|
299
|
+
patched = await _update_with(reminder_minutes_before_start=0)
|
|
300
|
+
assert patched.reminder_minutes_before_start == 0
|
|
301
|
+
assert patched.is_reminder_on is True
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@pytest.mark.asyncio
|
|
305
|
+
async def test_update_event_rejects_negative_reminder_minutes():
|
|
306
|
+
with pytest.raises(GraphValidationError):
|
|
307
|
+
await _update_with(reminder_minutes_before_start=-1)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_register_exposes_reminder_params_on_create_and_update():
|
|
311
|
+
import inspect
|
|
312
|
+
|
|
313
|
+
registered: dict = {}
|
|
314
|
+
|
|
315
|
+
class FakeMCP:
|
|
316
|
+
def tool(self, *, name, description=""): # noqa: ARG002
|
|
317
|
+
def deco(fn):
|
|
318
|
+
registered[name] = fn
|
|
319
|
+
return fn
|
|
320
|
+
|
|
321
|
+
return deco
|
|
322
|
+
|
|
323
|
+
cal_tools.register(FakeMCP(), graph=MagicMock())
|
|
324
|
+
|
|
325
|
+
for name in ("create_event", "update_event"):
|
|
326
|
+
params = inspect.signature(registered[name]).parameters
|
|
327
|
+
assert "is_reminder_on" in params
|
|
328
|
+
assert "reminder_minutes_before_start" in params
|
|
329
|
+
|
|
330
|
+
|
|
231
331
|
@pytest.mark.asyncio
|
|
232
332
|
async def test_delete_event_calls_delete():
|
|
233
333
|
graph = MagicMock()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/graph/auth_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/teams_channels.py
RENAMED
|
File without changes
|
|
File without changes
|
{msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.0}/src/msgraph_mcp/tools/teams_content.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|