msgraph-mcp-server 0.3.2__tar.gz → 0.4.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.
Files changed (71) hide show
  1. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/PKG-INFO +1 -1
  2. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/pyproject.toml +1 -1
  3. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/server.json +2 -2
  4. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/calendar.py +34 -0
  5. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_calendar.py +94 -0
  6. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/uv.lock +1 -1
  7. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/.env.example +0 -0
  8. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/.github/dependabot.yml +0 -0
  9. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/.github/workflows/ci.yml +0 -0
  10. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/.github/workflows/release.yml +0 -0
  11. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/.gitignore +0 -0
  12. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/CONTRIBUTING.md +0 -0
  13. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/LICENSE +0 -0
  14. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/README.md +0 -0
  15. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/__init__.py +0 -0
  16. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/auth/__init__.py +0 -0
  17. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/auth/cli.py +0 -0
  18. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/auth/msal_app.py +0 -0
  19. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/auth/token.py +0 -0
  20. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/config.py +0 -0
  21. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/__init__.py +0 -0
  22. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/auth_provider.py +0 -0
  23. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/batch.py +0 -0
  24. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/client.py +0 -0
  25. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/errors.py +0 -0
  26. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/pagination.py +0 -0
  27. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/serialize.py +0 -0
  28. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/graph/trimming.py +0 -0
  29. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/server.py +0 -0
  30. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/__init__.py +0 -0
  31. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/_binary.py +0 -0
  32. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/mail_actions.py +0 -0
  33. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/mail_batch.py +0 -0
  34. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/mail_folders.py +0 -0
  35. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/mail_read.py +0 -0
  36. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/mail_rules.py +0 -0
  37. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/mail_write.py +0 -0
  38. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/teams_channels.py +0 -0
  39. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/teams_chats.py +0 -0
  40. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/teams_content.py +0 -0
  41. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/src/msgraph_mcp/tools/util.py +0 -0
  42. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/__init__.py +0 -0
  43. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/conftest.py +0 -0
  44. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/fixtures/__init__.py +0 -0
  45. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/fixtures/event_minimal.json +0 -0
  46. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/fixtures/message_minimal.json +0 -0
  47. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/integration/__init__.py +0 -0
  48. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/integration/test_live_smoke.py +0 -0
  49. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/integration/test_rules_and_folders_live.py +0 -0
  50. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_auth_cli.py +0 -0
  51. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_auth_msal_app.py +0 -0
  52. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_auth_token.py +0 -0
  53. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_config.py +0 -0
  54. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_auth_provider.py +0 -0
  55. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_batch.py +0 -0
  56. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_client.py +0 -0
  57. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_errors.py +0 -0
  58. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_pagination.py +0 -0
  59. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_serialize.py +0 -0
  60. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_graph_trimming.py +0 -0
  61. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_server.py +0 -0
  62. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_mail_actions.py +0 -0
  63. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_mail_batch.py +0 -0
  64. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_mail_folders.py +0 -0
  65. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_mail_read.py +0 -0
  66. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_mail_rules.py +0 -0
  67. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_mail_write.py +0 -0
  68. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_teams_channels.py +0 -0
  69. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_teams_chats.py +0 -0
  70. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.0}/tests/test_tools_teams_content.py +0 -0
  71. {msgraph_mcp_server-0.3.2 → msgraph_mcp_server-0.4.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.2
3
+ Version: 0.4.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "msgraph-mcp-server"
3
- version = "0.3.2"
3
+ version = "0.4.0"
4
4
  description = "MCP server for Microsoft Graph: Outlook mail and calendar, plus read-only Microsoft Teams"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/timfurlong/msgraph-mcp",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.3.2",
10
+ "version": "0.4.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "msgraph-mcp-server",
15
- "version": "0.3.2",
15
+ "version": "0.4.0",
16
16
  "runtimeHint": "uvx",
17
17
  "transport": {
18
18
  "type": "stdio"
@@ -13,6 +13,7 @@ from msgraph.generated.models.body_type import BodyType
13
13
  from msgraph.generated.models.date_time_time_zone import DateTimeTimeZone
14
14
  from msgraph.generated.models.email_address import EmailAddress
15
15
  from msgraph.generated.models.event import Event
16
+ from msgraph.generated.models.free_busy_status import FreeBusyStatus
16
17
  from msgraph.generated.models.item_body import ItemBody
17
18
  from msgraph.generated.models.location import Location
18
19
  from msgraph.generated.models.time_constraint import TimeConstraint
@@ -53,6 +54,16 @@ _VALID_RESPONSES = {
53
54
  "decline": "declined",
54
55
  }
55
56
 
57
+ ShowAs = Literal["free", "tentative", "busy", "oof", "workingElsewhere"]
58
+
59
+ _SHOW_AS = {
60
+ "free": FreeBusyStatus.Free,
61
+ "tentative": FreeBusyStatus.Tentative,
62
+ "busy": FreeBusyStatus.Busy,
63
+ "oof": FreeBusyStatus.Oof,
64
+ "workingElsewhere": FreeBusyStatus.WorkingElsewhere,
65
+ }
66
+
56
67
 
57
68
  async def list_calendars(
58
69
  *, graph, mailbox: str | None = None, include_raw: bool = False
@@ -170,6 +181,12 @@ def _dttz(dt: str, tz: str) -> DateTimeTimeZone:
170
181
  return obj
171
182
 
172
183
 
184
+ def _free_busy(show_as: str) -> FreeBusyStatus:
185
+ if show_as not in _SHOW_AS:
186
+ raise GraphValidationError(f"show_as must be one of {sorted(_SHOW_AS)}")
187
+ return _SHOW_AS[show_as]
188
+
189
+
173
190
  def _build_event(
174
191
  *,
175
192
  subject: str,
@@ -182,6 +199,7 @@ def _build_event(
182
199
  attendees: list[str] | None,
183
200
  is_online_meeting: bool,
184
201
  is_all_day: bool,
202
+ show_as: ShowAs | None,
185
203
  ) -> Event:
186
204
  e = Event()
187
205
  e.subject = subject
@@ -208,6 +226,8 @@ def _build_event(
208
226
  e.attendees = atts
209
227
  e.is_online_meeting = is_online_meeting
210
228
  e.is_all_day = is_all_day
229
+ if show_as is not None:
230
+ e.show_as = _free_busy(show_as)
211
231
  return e
212
232
 
213
233
 
@@ -224,6 +244,7 @@ async def create_event(
224
244
  attendees: list[str] | None = None,
225
245
  is_online_meeting: bool = False,
226
246
  is_all_day: bool = False,
247
+ show_as: ShowAs | None = None,
227
248
  calendar_id: str | None = None,
228
249
  mailbox: str | None = None,
229
250
  include_raw: bool = False,
@@ -240,6 +261,8 @@ async def create_event(
240
261
  attendees: Optional list of attendee email addresses (treated as required).
241
262
  is_online_meeting: When True, Outlook adds a Teams meeting link.
242
263
  is_all_day: All-day event flag.
264
+ show_as: Free/busy status shown to others: "free", "tentative", "busy",
265
+ "oof", or "workingElsewhere". Omit for Outlook's default ("busy").
243
266
  calendar_id: Optional calendar id; default is the user's primary calendar.
244
267
  mailbox: Optional mailbox.
245
268
 
@@ -257,6 +280,7 @@ async def create_event(
257
280
  attendees=attendees,
258
281
  is_online_meeting=is_online_meeting,
259
282
  is_all_day=is_all_day,
283
+ show_as=show_as,
260
284
  )
261
285
  try:
262
286
  target = graph.mailbox(mailbox)
@@ -285,12 +309,16 @@ async def update_event(
285
309
  location: str | None = None,
286
310
  is_online_meeting: bool | None = None,
287
311
  is_all_day: bool | None = None,
312
+ show_as: ShowAs | None = None,
288
313
  mailbox: str | None = None,
289
314
  include_raw: bool = False,
290
315
  ) -> dict:
291
316
  """Patch fields of an existing event. Pass None to leave a field unchanged.
292
317
 
293
318
  If you change start_datetime or end_datetime, you must also pass time_zone.
319
+
320
+ show_as sets the free/busy status shown to others: "free", "tentative",
321
+ "busy", "oof", or "workingElsewhere".
294
322
  """
295
323
  if (start_datetime or end_datetime) and not time_zone:
296
324
  raise GraphValidationError("time_zone is required when updating start or end")
@@ -314,6 +342,8 @@ async def update_event(
314
342
  patch.is_online_meeting = is_online_meeting
315
343
  if is_all_day is not None:
316
344
  patch.is_all_day = is_all_day
345
+ if show_as is not None:
346
+ patch.show_as = _free_busy(show_as)
317
347
  try:
318
348
  updated = await graph.mailbox(mailbox).events.by_event_id(event_id).patch(patch)
319
349
  except NotAuthenticatedError:
@@ -547,6 +577,7 @@ def register(mcp, *, graph) -> None:
547
577
  attendees: list[str] | None = None,
548
578
  is_online_meeting: bool = False,
549
579
  is_all_day: bool = False,
580
+ show_as: ShowAs | None = None,
550
581
  calendar_id: str | None = None,
551
582
  mailbox: str | None = None,
552
583
  include_raw: bool = False,
@@ -563,6 +594,7 @@ def register(mcp, *, graph) -> None:
563
594
  attendees=attendees,
564
595
  is_online_meeting=is_online_meeting,
565
596
  is_all_day=is_all_day,
597
+ show_as=show_as,
566
598
  calendar_id=calendar_id,
567
599
  mailbox=mailbox,
568
600
  include_raw=include_raw,
@@ -580,6 +612,7 @@ def register(mcp, *, graph) -> None:
580
612
  location: str | None = None,
581
613
  is_online_meeting: bool | None = None,
582
614
  is_all_day: bool | None = None,
615
+ show_as: ShowAs | None = None,
583
616
  mailbox: str | None = None,
584
617
  include_raw: bool = False,
585
618
  ):
@@ -595,6 +628,7 @@ def register(mcp, *, graph) -> None:
595
628
  location=location,
596
629
  is_online_meeting=is_online_meeting,
597
630
  is_all_day=is_all_day,
631
+ show_as=show_as,
598
632
  mailbox=mailbox,
599
633
  include_raw=include_raw,
600
634
  )
@@ -2,6 +2,7 @@ from types import SimpleNamespace
2
2
  from unittest.mock import AsyncMock, MagicMock
3
3
 
4
4
  import pytest
5
+ from msgraph.generated.models.free_busy_status import FreeBusyStatus
5
6
 
6
7
  from msgraph_mcp.graph.errors import GraphValidationError
7
8
  from msgraph_mcp.tools import calendar as cal_tools
@@ -134,6 +135,99 @@ async def test_update_event_patches():
134
135
  assert result["id"] == "e1"
135
136
 
136
137
 
138
+ @pytest.mark.asyncio
139
+ async def test_create_event_sets_show_as():
140
+ graph = MagicMock()
141
+ mb = MagicMock()
142
+ mb.events.post = AsyncMock(return_value=_fake_event(id_="new1"))
143
+ graph.mailbox = MagicMock(return_value=mb)
144
+
145
+ await cal_tools.create_event(
146
+ graph=graph,
147
+ subject="Focus time",
148
+ start_datetime="2026-05-19T15:00:00",
149
+ end_datetime="2026-05-19T15:30:00",
150
+ show_as="free",
151
+ )
152
+ assert mb.events.post.await_args is not None
153
+ posted = mb.events.post.await_args.args[0]
154
+ assert posted.show_as == FreeBusyStatus.Free
155
+
156
+
157
+ @pytest.mark.asyncio
158
+ async def test_create_event_leaves_show_as_unset_by_default():
159
+ graph = MagicMock()
160
+ mb = MagicMock()
161
+ mb.events.post = AsyncMock(return_value=_fake_event(id_="new1"))
162
+ graph.mailbox = MagicMock(return_value=mb)
163
+
164
+ await cal_tools.create_event(
165
+ graph=graph,
166
+ subject="Standup",
167
+ start_datetime="2026-05-19T15:00:00",
168
+ end_datetime="2026-05-19T15:30:00",
169
+ )
170
+ assert mb.events.post.await_args is not None
171
+ posted = mb.events.post.await_args.args[0]
172
+ assert posted.show_as is None
173
+
174
+
175
+ @pytest.mark.asyncio
176
+ async def test_create_event_rejects_bad_show_as():
177
+ graph = MagicMock()
178
+ with pytest.raises(GraphValidationError):
179
+ await cal_tools.create_event(
180
+ graph=graph,
181
+ subject="Standup",
182
+ start_datetime="2026-05-19T15:00:00",
183
+ end_datetime="2026-05-19T15:30:00",
184
+ show_as="available", # type: ignore[arg-type]
185
+ )
186
+
187
+
188
+ @pytest.mark.asyncio
189
+ async def test_update_event_patches_show_as():
190
+ graph = MagicMock()
191
+ mb = MagicMock()
192
+ evt_builder = MagicMock(patch=AsyncMock(return_value=_fake_event(id_="e1")))
193
+ mb.events.by_event_id = MagicMock(return_value=evt_builder)
194
+ graph.mailbox = MagicMock(return_value=mb)
195
+
196
+ await cal_tools.update_event(graph=graph, event_id="e1", show_as="workingElsewhere")
197
+ patched = evt_builder.patch.await_args.args[0]
198
+ assert patched.show_as == FreeBusyStatus.WorkingElsewhere
199
+
200
+
201
+ @pytest.mark.asyncio
202
+ async def test_update_event_rejects_bad_show_as():
203
+ graph = MagicMock()
204
+ with pytest.raises(GraphValidationError):
205
+ await cal_tools.update_event(
206
+ graph=graph,
207
+ event_id="e1",
208
+ show_as="available", # type: ignore[arg-type]
209
+ )
210
+
211
+
212
+ def test_register_exposes_show_as_on_create_and_update():
213
+ import inspect
214
+
215
+ registered: dict = {}
216
+
217
+ class FakeMCP:
218
+ def tool(self, *, name, description=""): # noqa: ARG002
219
+ def deco(fn):
220
+ registered[name] = fn
221
+ return fn
222
+
223
+ return deco
224
+
225
+ cal_tools.register(FakeMCP(), graph=MagicMock())
226
+
227
+ for name in ("create_event", "update_event"):
228
+ assert "show_as" in inspect.signature(registered[name]).parameters
229
+
230
+
137
231
  @pytest.mark.asyncio
138
232
  async def test_delete_event_calls_delete():
139
233
  graph = MagicMock()
@@ -827,7 +827,7 @@ wheels = [
827
827
 
828
828
  [[package]]
829
829
  name = "msgraph-mcp-server"
830
- version = "0.3.2"
830
+ version = "0.4.0"
831
831
  source = { editable = "." }
832
832
  dependencies = [
833
833
  { name = "mcp" },