msgraph-mcp-server 0.4.0__tar.gz → 0.5.1__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.1}/.github/dependabot.yml +6 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/PKG-INFO +3 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/pyproject.toml +9 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/server.json +2 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/config.py +0 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/auth_provider.py +0 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/batch.py +3 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/pagination.py +0 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/serialize.py +4 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/trimming.py +2 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/server.py +5 -6
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/__init__.py +2 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/_binary.py +3 -3
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/calendar.py +65 -10
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/mail_folders.py +6 -6
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/mail_read.py +5 -5
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/mail_rules.py +5 -5
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/mail_write.py +7 -8
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/teams_channels.py +8 -7
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/teams_chats.py +2 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/teams_content.py +1 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/util.py +1 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/fixtures/event_minimal.json +2 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/integration/test_live_smoke.py +2 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_batch.py +1 -1
- msgraph_mcp_server-0.5.1/tests/test_graph_me_url.py +36 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_pagination.py +2 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_serialize.py +13 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_trimming.py +11 -5
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_server.py +4 -5
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_calendar.py +101 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_mail_batch.py +1 -1
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_mail_folders.py +2 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_mail_read.py +2 -2
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_mail_rules.py +9 -9
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_teams_content.py +2 -2
- msgraph_mcp_server-0.5.1/uv.lock +2004 -0
- msgraph_mcp_server-0.4.0/uv.lock +0 -1776
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/.env.example +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/.github/workflows/ci.yml +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/.github/workflows/release.yml +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/.gitignore +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/CONTRIBUTING.md +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/LICENSE +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/README.md +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/auth/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/auth/cli.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/auth/msal_app.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/auth/token.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/client.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/errors.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/mail_actions.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/tools/mail_batch.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/conftest.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/fixtures/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/fixtures/message_minimal.json +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/integration/__init__.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/integration/test_rules_and_folders_live.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_auth_cli.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_auth_msal_app.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_auth_token.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_config.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_auth_provider.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_client.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_graph_errors.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_mail_actions.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_mail_write.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_teams_channels.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_teams_chats.py +0 -0
- {msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/tests/test_tools_util.py +0 -0
|
@@ -7,6 +7,12 @@ updates:
|
|
|
7
7
|
groups:
|
|
8
8
|
python-dependencies:
|
|
9
9
|
patterns: ["*"]
|
|
10
|
+
# Groups only cover version updates unless told otherwise, so security
|
|
11
|
+
# fixes used to arrive as one PR per package, each rewriting uv.lock and
|
|
12
|
+
# conflicting with the rest.
|
|
13
|
+
python-security:
|
|
14
|
+
applies-to: security-updates
|
|
15
|
+
patterns: ["*"]
|
|
10
16
|
|
|
11
17
|
- package-ecosystem: github-actions
|
|
12
18
|
directory: /
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: msgraph-mcp-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -19,7 +19,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
19
|
Classifier: Topic :: Communications :: Email
|
|
20
20
|
Classifier: Topic :: Office/Business :: Scheduling
|
|
21
21
|
Requires-Python: >=3.11
|
|
22
|
-
Requires-Dist: mcp
|
|
22
|
+
Requires-Dist: mcp<3,>=2.2
|
|
23
|
+
Requires-Dist: microsoft-kiota-http<1.13,>=1.11.6
|
|
23
24
|
Requires-Dist: msal>=1.31
|
|
24
25
|
Requires-Dist: msgraph-sdk>=1.0
|
|
25
26
|
Requires-Dist: python-dotenv>=1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "msgraph-mcp-server"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.1"
|
|
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"
|
|
@@ -20,8 +20,15 @@ classifiers = [
|
|
|
20
20
|
"Topic :: Office/Business :: Scheduling",
|
|
21
21
|
]
|
|
22
22
|
dependencies = [
|
|
23
|
-
"mcp>=
|
|
23
|
+
"mcp>=2.2,<3",
|
|
24
24
|
"msgraph-sdk>=1.0",
|
|
25
|
+
# Transitive, bounded on purpose. kiota-http 1.13 moved request options
|
|
26
|
+
# from a request attribute into request.extensions, but msgraph-core (up to
|
|
27
|
+
# 1.5.1) only runs its middleware when that attribute exists. With 1.13+ the
|
|
28
|
+
# whole pipeline is skipped, so /me is never rewritten and every call fails
|
|
29
|
+
# with ErrorInvalidUser 'me-token-to-replace'. Drop this once msgraph-core
|
|
30
|
+
# reads the options from extensions.
|
|
31
|
+
"microsoft-kiota-http>=1.11.6,<1.13",
|
|
25
32
|
"msal>=1.31",
|
|
26
33
|
"python-dotenv>=1.0",
|
|
27
34
|
]
|
|
@@ -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.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "msgraph-mcp-server",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.5.1",
|
|
16
16
|
"runtimeHint": "uvx",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
{msgraph_mcp_server-0.4.0 → msgraph_mcp_server-0.5.1}/src/msgraph_mcp/graph/auth_provider.py
RENAMED
|
@@ -15,7 +15,6 @@ from azure.core.credentials import AccessToken, TokenCredential
|
|
|
15
15
|
|
|
16
16
|
from msgraph_mcp.auth.token import get_access_token
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
# Short expiry: MSAL's cache handles real refresh. Each get_token call goes
|
|
20
19
|
# through get_access_token() which calls acquire_token_silent (cheap, cached).
|
|
21
20
|
_BUFFER_SECONDS = 300
|
|
@@ -200,7 +200,9 @@ class GraphBatchTransport:
|
|
|
200
200
|
import json
|
|
201
201
|
|
|
202
202
|
from kiota_abstractions.method import Method # type: ignore[import-untyped]
|
|
203
|
-
from kiota_abstractions.request_information import
|
|
203
|
+
from kiota_abstractions.request_information import (
|
|
204
|
+
RequestInformation, # type: ignore[import-untyped]
|
|
205
|
+
)
|
|
204
206
|
|
|
205
207
|
ri = RequestInformation()
|
|
206
208
|
ri.url = self.GRAPH_BATCH_URL
|
|
@@ -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
|
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""MCP server entry point.
|
|
2
2
|
|
|
3
3
|
Boot order:
|
|
4
4
|
1. Validate config (fail fast if env is missing).
|
|
5
5
|
2. Build the GraphClient (lazy token acquisition — no Graph calls yet).
|
|
6
|
-
3. Build
|
|
6
|
+
3. Build an MCPServer app, register all tools, and run on stdio.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
from mcp.server.
|
|
11
|
+
from mcp.server.mcpserver import MCPServer
|
|
12
12
|
|
|
13
13
|
from msgraph_mcp import config
|
|
14
14
|
from msgraph_mcp.graph.client import GraphClient
|
|
15
15
|
from msgraph_mcp.tools import register_all
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
_INSTRUCTIONS = """
|
|
19
18
|
Microsoft Outlook (mail + calendar) and Microsoft Teams (read-only) via Microsoft Graph.
|
|
20
19
|
|
|
@@ -45,12 +44,12 @@ that returns objects to also get the full Graph payload.
|
|
|
45
44
|
""".strip()
|
|
46
45
|
|
|
47
46
|
|
|
48
|
-
def build_server() ->
|
|
47
|
+
def build_server() -> MCPServer:
|
|
49
48
|
# Fail fast on missing env (raises ConfigError before we hit the event loop)
|
|
50
49
|
config.client_id()
|
|
51
50
|
config.authority()
|
|
52
51
|
|
|
53
|
-
mcp =
|
|
52
|
+
mcp = MCPServer(name="msgraph-mcp", instructions=_INSTRUCTIONS)
|
|
54
53
|
graph = GraphClient()
|
|
55
54
|
register_all(mcp, graph=graph)
|
|
56
55
|
return mcp
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tool registration entry point.
|
|
2
2
|
|
|
3
|
-
register_all(mcp) wires every tool into the
|
|
3
|
+
register_all(mcp) wires every tool into the MCPServer app. Each tool module
|
|
4
4
|
exposes a register(mcp, *, graph) function so we can keep modules small.
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -14,8 +14,8 @@ from msgraph_mcp.tools import (
|
|
|
14
14
|
mail_read,
|
|
15
15
|
mail_rules,
|
|
16
16
|
mail_write,
|
|
17
|
-
teams_chats,
|
|
18
17
|
teams_channels,
|
|
18
|
+
teams_chats,
|
|
19
19
|
teams_content,
|
|
20
20
|
util,
|
|
21
21
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Shared helpers for tools that return downloaded binary content.
|
|
2
2
|
|
|
3
3
|
Image bytes are returned to the client as a native MCP image block
|
|
4
|
-
(mcp.server.
|
|
4
|
+
(mcp.server.mcpserver.Image) so agents see the image directly
|
|
5
5
|
instead of a base64 text payload; save_path writes bytes to disk instead and
|
|
6
6
|
returns only metadata. Non-image content without save_path falls back to the
|
|
7
7
|
legacy base64 dict.
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import pathlib
|
|
13
13
|
from typing import TypeGuard
|
|
14
14
|
|
|
15
|
-
from mcp.server.
|
|
15
|
+
from mcp.server.mcpserver import Image
|
|
16
16
|
|
|
17
17
|
_EXT_BY_CONTENT_TYPE = {
|
|
18
18
|
"image/png": ".png",
|
|
@@ -31,7 +31,7 @@ def ext_for(content_type: str | None) -> str:
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def image_result(meta: dict, data: bytes, content_type: str) -> list:
|
|
34
|
-
"""Metadata dict + native MCP image block (
|
|
34
|
+
"""Metadata dict + native MCP image block (MCPServer renders both)."""
|
|
35
35
|
return [meta, Image(data=data, format=content_type.removeprefix("image/"))]
|
|
36
36
|
|
|
37
37
|
|
|
@@ -47,7 +47,6 @@ from msgraph_mcp.graph.pagination import (
|
|
|
47
47
|
from msgraph_mcp.graph.serialize import calendar_to_dict, event_to_dict
|
|
48
48
|
from msgraph_mcp.graph.trimming import trim_calendar, trim_event
|
|
49
49
|
|
|
50
|
-
|
|
51
50
|
_VALID_RESPONSES = {
|
|
52
51
|
"accept": "accepted",
|
|
53
52
|
"tentativelyAccept": "tentativelyAccepted",
|
|
@@ -73,7 +72,7 @@ async def list_calendars(
|
|
|
73
72
|
collection = await graph.mailbox(mailbox).calendars.get()
|
|
74
73
|
except NotAuthenticatedError:
|
|
75
74
|
raise
|
|
76
|
-
except Exception as exc:
|
|
75
|
+
except Exception as exc:
|
|
77
76
|
raise map_kiota_error(exc) from exc
|
|
78
77
|
items = [
|
|
79
78
|
trim_calendar(calendar_to_dict(c), include_raw=include_raw)
|
|
@@ -139,7 +138,7 @@ async def list_events(
|
|
|
139
138
|
)
|
|
140
139
|
except NotAuthenticatedError:
|
|
141
140
|
raise
|
|
142
|
-
except Exception as exc:
|
|
141
|
+
except Exception as exc:
|
|
143
142
|
raise map_kiota_error(exc) from exc
|
|
144
143
|
|
|
145
144
|
items = [
|
|
@@ -167,7 +166,7 @@ async def get_event(
|
|
|
167
166
|
evt = await graph.mailbox(mailbox).events.by_event_id(event_id).get()
|
|
168
167
|
except NotAuthenticatedError:
|
|
169
168
|
raise
|
|
170
|
-
except Exception as exc:
|
|
169
|
+
except Exception as exc:
|
|
171
170
|
raise map_kiota_error(exc) from exc
|
|
172
171
|
return trim_event(
|
|
173
172
|
event_to_dict(evt), include_body=include_body, include_raw=include_raw
|
|
@@ -187,6 +186,28 @@ def _free_busy(show_as: str) -> FreeBusyStatus:
|
|
|
187
186
|
return _SHOW_AS[show_as]
|
|
188
187
|
|
|
189
188
|
|
|
189
|
+
def _apply_reminder(
|
|
190
|
+
evt: Event,
|
|
191
|
+
*,
|
|
192
|
+
is_reminder_on: bool | None,
|
|
193
|
+
reminder_minutes_before_start: int | None,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Set reminder fields on evt. Giving minutes turns the reminder on."""
|
|
196
|
+
if reminder_minutes_before_start is not None:
|
|
197
|
+
if reminder_minutes_before_start < 0:
|
|
198
|
+
raise GraphValidationError(
|
|
199
|
+
"reminder_minutes_before_start must be zero or greater"
|
|
200
|
+
)
|
|
201
|
+
if is_reminder_on is False:
|
|
202
|
+
raise GraphValidationError(
|
|
203
|
+
"reminder_minutes_before_start cannot be set when is_reminder_on is False"
|
|
204
|
+
)
|
|
205
|
+
evt.reminder_minutes_before_start = reminder_minutes_before_start
|
|
206
|
+
evt.is_reminder_on = True
|
|
207
|
+
elif is_reminder_on is not None:
|
|
208
|
+
evt.is_reminder_on = is_reminder_on
|
|
209
|
+
|
|
210
|
+
|
|
190
211
|
def _build_event(
|
|
191
212
|
*,
|
|
192
213
|
subject: str,
|
|
@@ -200,6 +221,8 @@ def _build_event(
|
|
|
200
221
|
is_online_meeting: bool,
|
|
201
222
|
is_all_day: bool,
|
|
202
223
|
show_as: ShowAs | None,
|
|
224
|
+
is_reminder_on: bool | None,
|
|
225
|
+
reminder_minutes_before_start: int | None,
|
|
203
226
|
) -> Event:
|
|
204
227
|
e = Event()
|
|
205
228
|
e.subject = subject
|
|
@@ -228,6 +251,11 @@ def _build_event(
|
|
|
228
251
|
e.is_all_day = is_all_day
|
|
229
252
|
if show_as is not None:
|
|
230
253
|
e.show_as = _free_busy(show_as)
|
|
254
|
+
_apply_reminder(
|
|
255
|
+
e,
|
|
256
|
+
is_reminder_on=is_reminder_on,
|
|
257
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
258
|
+
)
|
|
231
259
|
return e
|
|
232
260
|
|
|
233
261
|
|
|
@@ -245,6 +273,8 @@ async def create_event(
|
|
|
245
273
|
is_online_meeting: bool = False,
|
|
246
274
|
is_all_day: bool = False,
|
|
247
275
|
show_as: ShowAs | None = None,
|
|
276
|
+
is_reminder_on: bool | None = None,
|
|
277
|
+
reminder_minutes_before_start: int | None = None,
|
|
248
278
|
calendar_id: str | None = None,
|
|
249
279
|
mailbox: str | None = None,
|
|
250
280
|
include_raw: bool = False,
|
|
@@ -263,6 +293,10 @@ async def create_event(
|
|
|
263
293
|
is_all_day: All-day event flag.
|
|
264
294
|
show_as: Free/busy status shown to others: "free", "tentative", "busy",
|
|
265
295
|
"oof", or "workingElsewhere". Omit for Outlook's default ("busy").
|
|
296
|
+
is_reminder_on: False creates the event with no reminder. Omit for
|
|
297
|
+
Outlook's default (a reminder 15 minutes before start).
|
|
298
|
+
reminder_minutes_before_start: Minutes before start to alert (0 or
|
|
299
|
+
more). Setting this turns the reminder on.
|
|
266
300
|
calendar_id: Optional calendar id; default is the user's primary calendar.
|
|
267
301
|
mailbox: Optional mailbox.
|
|
268
302
|
|
|
@@ -281,6 +315,8 @@ async def create_event(
|
|
|
281
315
|
is_online_meeting=is_online_meeting,
|
|
282
316
|
is_all_day=is_all_day,
|
|
283
317
|
show_as=show_as,
|
|
318
|
+
is_reminder_on=is_reminder_on,
|
|
319
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
284
320
|
)
|
|
285
321
|
try:
|
|
286
322
|
target = graph.mailbox(mailbox)
|
|
@@ -289,7 +325,7 @@ async def create_event(
|
|
|
289
325
|
created = await target.events.post(evt)
|
|
290
326
|
except NotAuthenticatedError:
|
|
291
327
|
raise
|
|
292
|
-
except Exception as exc:
|
|
328
|
+
except Exception as exc:
|
|
293
329
|
raise map_kiota_error(exc) from exc
|
|
294
330
|
return trim_event(
|
|
295
331
|
event_to_dict(created), include_body=False, include_raw=include_raw
|
|
@@ -310,6 +346,8 @@ async def update_event(
|
|
|
310
346
|
is_online_meeting: bool | None = None,
|
|
311
347
|
is_all_day: bool | None = None,
|
|
312
348
|
show_as: ShowAs | None = None,
|
|
349
|
+
is_reminder_on: bool | None = None,
|
|
350
|
+
reminder_minutes_before_start: int | None = None,
|
|
313
351
|
mailbox: str | None = None,
|
|
314
352
|
include_raw: bool = False,
|
|
315
353
|
) -> dict:
|
|
@@ -319,6 +357,10 @@ async def update_event(
|
|
|
319
357
|
|
|
320
358
|
show_as sets the free/busy status shown to others: "free", "tentative",
|
|
321
359
|
"busy", "oof", or "workingElsewhere".
|
|
360
|
+
|
|
361
|
+
is_reminder_on=False removes the reminder. reminder_minutes_before_start
|
|
362
|
+
sets how many minutes before start to alert (0 or more) and turns the
|
|
363
|
+
reminder on.
|
|
322
364
|
"""
|
|
323
365
|
if (start_datetime or end_datetime) and not time_zone:
|
|
324
366
|
raise GraphValidationError("time_zone is required when updating start or end")
|
|
@@ -344,11 +386,16 @@ async def update_event(
|
|
|
344
386
|
patch.is_all_day = is_all_day
|
|
345
387
|
if show_as is not None:
|
|
346
388
|
patch.show_as = _free_busy(show_as)
|
|
389
|
+
_apply_reminder(
|
|
390
|
+
patch,
|
|
391
|
+
is_reminder_on=is_reminder_on,
|
|
392
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
393
|
+
)
|
|
347
394
|
try:
|
|
348
395
|
updated = await graph.mailbox(mailbox).events.by_event_id(event_id).patch(patch)
|
|
349
396
|
except NotAuthenticatedError:
|
|
350
397
|
raise
|
|
351
|
-
except Exception as exc:
|
|
398
|
+
except Exception as exc:
|
|
352
399
|
raise map_kiota_error(exc) from exc
|
|
353
400
|
return trim_event(
|
|
354
401
|
event_to_dict(updated), include_body=False, include_raw=include_raw
|
|
@@ -361,7 +408,7 @@ async def delete_event(*, graph, event_id: str, mailbox: str | None = None) -> d
|
|
|
361
408
|
await graph.mailbox(mailbox).events.by_event_id(event_id).delete()
|
|
362
409
|
except NotAuthenticatedError:
|
|
363
410
|
raise
|
|
364
|
-
except Exception as exc:
|
|
411
|
+
except Exception as exc:
|
|
365
412
|
raise map_kiota_error(exc) from exc
|
|
366
413
|
return {"status": "deleted"}
|
|
367
414
|
|
|
@@ -377,7 +424,7 @@ async def cancel_event(
|
|
|
377
424
|
await graph.mailbox(mailbox).events.by_event_id(event_id).cancel.post(body)
|
|
378
425
|
except NotAuthenticatedError:
|
|
379
426
|
raise
|
|
380
|
-
except Exception as exc:
|
|
427
|
+
except Exception as exc:
|
|
381
428
|
raise map_kiota_error(exc) from exc
|
|
382
429
|
return {"status": "cancelled"}
|
|
383
430
|
|
|
@@ -422,7 +469,7 @@ async def respond_to_event(
|
|
|
422
469
|
await evt_builder.decline.post(decline_body)
|
|
423
470
|
except NotAuthenticatedError:
|
|
424
471
|
raise
|
|
425
|
-
except Exception as exc:
|
|
472
|
+
except Exception as exc:
|
|
426
473
|
raise map_kiota_error(exc) from exc
|
|
427
474
|
return {"status": _VALID_RESPONSES[response]}
|
|
428
475
|
|
|
@@ -488,7 +535,7 @@ async def find_meeting_times(
|
|
|
488
535
|
result = await graph.mailbox(mailbox).find_meeting_times.post(body)
|
|
489
536
|
except NotAuthenticatedError:
|
|
490
537
|
raise
|
|
491
|
-
except Exception as exc:
|
|
538
|
+
except Exception as exc:
|
|
492
539
|
raise map_kiota_error(exc) from exc
|
|
493
540
|
|
|
494
541
|
suggestions_raw = getattr(result, "meeting_time_suggestions", None) or []
|
|
@@ -578,6 +625,8 @@ def register(mcp, *, graph) -> None:
|
|
|
578
625
|
is_online_meeting: bool = False,
|
|
579
626
|
is_all_day: bool = False,
|
|
580
627
|
show_as: ShowAs | None = None,
|
|
628
|
+
is_reminder_on: bool | None = None,
|
|
629
|
+
reminder_minutes_before_start: int | None = None,
|
|
581
630
|
calendar_id: str | None = None,
|
|
582
631
|
mailbox: str | None = None,
|
|
583
632
|
include_raw: bool = False,
|
|
@@ -595,6 +644,8 @@ def register(mcp, *, graph) -> None:
|
|
|
595
644
|
is_online_meeting=is_online_meeting,
|
|
596
645
|
is_all_day=is_all_day,
|
|
597
646
|
show_as=show_as,
|
|
647
|
+
is_reminder_on=is_reminder_on,
|
|
648
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
598
649
|
calendar_id=calendar_id,
|
|
599
650
|
mailbox=mailbox,
|
|
600
651
|
include_raw=include_raw,
|
|
@@ -613,6 +664,8 @@ def register(mcp, *, graph) -> None:
|
|
|
613
664
|
is_online_meeting: bool | None = None,
|
|
614
665
|
is_all_day: bool | None = None,
|
|
615
666
|
show_as: ShowAs | None = None,
|
|
667
|
+
is_reminder_on: bool | None = None,
|
|
668
|
+
reminder_minutes_before_start: int | None = None,
|
|
616
669
|
mailbox: str | None = None,
|
|
617
670
|
include_raw: bool = False,
|
|
618
671
|
):
|
|
@@ -629,6 +682,8 @@ def register(mcp, *, graph) -> None:
|
|
|
629
682
|
is_online_meeting=is_online_meeting,
|
|
630
683
|
is_all_day=is_all_day,
|
|
631
684
|
show_as=show_as,
|
|
685
|
+
is_reminder_on=is_reminder_on,
|
|
686
|
+
reminder_minutes_before_start=reminder_minutes_before_start,
|
|
632
687
|
mailbox=mailbox,
|
|
633
688
|
include_raw=include_raw,
|
|
634
689
|
)
|
|
@@ -31,7 +31,7 @@ async def list_folders(
|
|
|
31
31
|
collection = await graph.mailbox(mailbox).mail_folders.get()
|
|
32
32
|
except NotAuthenticatedError:
|
|
33
33
|
raise
|
|
34
|
-
except Exception as exc:
|
|
34
|
+
except Exception as exc:
|
|
35
35
|
raise map_kiota_error(exc) from exc
|
|
36
36
|
items = [
|
|
37
37
|
trim_folder(folder_to_dict(f), include_raw=include_raw)
|
|
@@ -70,7 +70,7 @@ async def move_message(
|
|
|
70
70
|
)
|
|
71
71
|
except NotAuthenticatedError:
|
|
72
72
|
raise
|
|
73
|
-
except Exception as exc:
|
|
73
|
+
except Exception as exc:
|
|
74
74
|
raise map_kiota_error(exc) from exc
|
|
75
75
|
return trim_message(
|
|
76
76
|
message_to_dict(moved), include_body=False, include_raw=include_raw
|
|
@@ -113,7 +113,7 @@ async def create_folder(
|
|
|
113
113
|
created = await graph.mailbox(mailbox).mail_folders.post(body)
|
|
114
114
|
except NotAuthenticatedError:
|
|
115
115
|
raise
|
|
116
|
-
except Exception as exc:
|
|
116
|
+
except Exception as exc:
|
|
117
117
|
raise map_kiota_error(exc) from exc
|
|
118
118
|
return trim_folder(folder_to_dict(created), include_raw=include_raw)
|
|
119
119
|
|
|
@@ -166,7 +166,7 @@ async def update_folder(
|
|
|
166
166
|
result = await folder_endpoint.move.post(move_body)
|
|
167
167
|
except NotAuthenticatedError:
|
|
168
168
|
raise
|
|
169
|
-
except Exception as exc:
|
|
169
|
+
except Exception as exc:
|
|
170
170
|
raise map_kiota_error(exc) from exc
|
|
171
171
|
return trim_folder(folder_to_dict(result), include_raw=include_raw)
|
|
172
172
|
|
|
@@ -198,7 +198,7 @@ async def delete_folder(
|
|
|
198
198
|
folder = await folder_endpoint.get()
|
|
199
199
|
except NotAuthenticatedError:
|
|
200
200
|
raise
|
|
201
|
-
except Exception as exc:
|
|
201
|
+
except Exception as exc:
|
|
202
202
|
raise map_kiota_error(exc) from exc
|
|
203
203
|
total = getattr(folder, "total_item_count", None) or 0
|
|
204
204
|
children = getattr(folder, "child_folder_count", None) or 0
|
|
@@ -212,7 +212,7 @@ async def delete_folder(
|
|
|
212
212
|
await folder_endpoint.delete()
|
|
213
213
|
except NotAuthenticatedError:
|
|
214
214
|
raise
|
|
215
|
-
except Exception as exc:
|
|
215
|
+
except Exception as exc:
|
|
216
216
|
raise map_kiota_error(exc) from exc
|
|
217
217
|
return {"deleted": True, "id": folder_id}
|
|
218
218
|
|
|
@@ -109,7 +109,7 @@ async def list_messages(
|
|
|
109
109
|
)
|
|
110
110
|
except NotAuthenticatedError:
|
|
111
111
|
raise
|
|
112
|
-
except Exception as exc:
|
|
112
|
+
except Exception as exc:
|
|
113
113
|
raise map_kiota_error(exc) from exc
|
|
114
114
|
|
|
115
115
|
items = [
|
|
@@ -166,7 +166,7 @@ async def search_messages(
|
|
|
166
166
|
)
|
|
167
167
|
except NotAuthenticatedError:
|
|
168
168
|
raise
|
|
169
|
-
except Exception as exc:
|
|
169
|
+
except Exception as exc:
|
|
170
170
|
raise map_kiota_error(exc) from exc
|
|
171
171
|
|
|
172
172
|
items = [
|
|
@@ -205,7 +205,7 @@ async def get_message(
|
|
|
205
205
|
msg = await graph.mailbox(mailbox).messages.by_message_id(message_id).get()
|
|
206
206
|
except NotAuthenticatedError:
|
|
207
207
|
raise
|
|
208
|
-
except Exception as exc:
|
|
208
|
+
except Exception as exc:
|
|
209
209
|
raise map_kiota_error(exc) from exc
|
|
210
210
|
return trim_message(
|
|
211
211
|
message_to_dict(msg), include_body=include_body, include_raw=include_raw
|
|
@@ -228,7 +228,7 @@ async def list_attachments(
|
|
|
228
228
|
)
|
|
229
229
|
except NotAuthenticatedError:
|
|
230
230
|
raise
|
|
231
|
-
except Exception as exc:
|
|
231
|
+
except Exception as exc:
|
|
232
232
|
raise map_kiota_error(exc) from exc
|
|
233
233
|
items = [
|
|
234
234
|
trim_attachment_list(
|
|
@@ -270,7 +270,7 @@ async def download_attachment(
|
|
|
270
270
|
)
|
|
271
271
|
except NotAuthenticatedError:
|
|
272
272
|
raise
|
|
273
|
-
except Exception as exc:
|
|
273
|
+
except Exception as exc:
|
|
274
274
|
raise map_kiota_error(exc) from exc
|
|
275
275
|
|
|
276
276
|
content = getattr(att, "content_bytes", None)
|
|
@@ -35,7 +35,7 @@ async def list_rules(
|
|
|
35
35
|
collection = await _rules_endpoint(graph, mailbox).get()
|
|
36
36
|
except NotAuthenticatedError:
|
|
37
37
|
raise
|
|
38
|
-
except Exception as exc:
|
|
38
|
+
except Exception as exc:
|
|
39
39
|
raise map_kiota_error(exc) from exc
|
|
40
40
|
items = [
|
|
41
41
|
trim_message_rule(message_rule_to_dict(r), include_raw=include_raw)
|
|
@@ -63,7 +63,7 @@ async def get_rule(
|
|
|
63
63
|
rule = await _rules_endpoint(graph, mailbox).by_message_rule_id(rule_id).get()
|
|
64
64
|
except NotAuthenticatedError:
|
|
65
65
|
raise
|
|
66
|
-
except Exception as exc:
|
|
66
|
+
except Exception as exc:
|
|
67
67
|
raise map_kiota_error(exc) from exc
|
|
68
68
|
return trim_message_rule(message_rule_to_dict(rule), include_raw=include_raw)
|
|
69
69
|
|
|
@@ -241,7 +241,7 @@ async def create_rule(
|
|
|
241
241
|
created = await _rules_endpoint(graph, mailbox).post(body)
|
|
242
242
|
except NotAuthenticatedError:
|
|
243
243
|
raise
|
|
244
|
-
except Exception as exc:
|
|
244
|
+
except Exception as exc:
|
|
245
245
|
raise map_kiota_error(exc) from exc
|
|
246
246
|
return trim_message_rule(message_rule_to_dict(created), include_raw=include_raw)
|
|
247
247
|
|
|
@@ -346,7 +346,7 @@ async def update_rule(
|
|
|
346
346
|
)
|
|
347
347
|
except NotAuthenticatedError:
|
|
348
348
|
raise
|
|
349
|
-
except Exception as exc:
|
|
349
|
+
except Exception as exc:
|
|
350
350
|
raise map_kiota_error(exc) from exc
|
|
351
351
|
return trim_message_rule(message_rule_to_dict(updated), include_raw=include_raw)
|
|
352
352
|
|
|
@@ -367,7 +367,7 @@ async def delete_rule(*, graph, rule_id: str, mailbox: str | None = None) -> dic
|
|
|
367
367
|
await _rules_endpoint(graph, mailbox).by_message_rule_id(rule_id).delete()
|
|
368
368
|
except NotAuthenticatedError:
|
|
369
369
|
raise
|
|
370
|
-
except Exception as exc:
|
|
370
|
+
except Exception as exc:
|
|
371
371
|
raise map_kiota_error(exc) from exc
|
|
372
372
|
return {"deleted": True, "id": rule_id}
|
|
373
373
|
|
|
@@ -33,7 +33,6 @@ from msgraph_mcp.graph.errors import GraphValidationError, map_kiota_error
|
|
|
33
33
|
from msgraph_mcp.graph.serialize import message_to_dict
|
|
34
34
|
from msgraph_mcp.graph.trimming import trim_message
|
|
35
35
|
|
|
36
|
-
|
|
37
36
|
_MAX_INLINE_ATTACHMENT_BYTES = 3 * 1024 * 1024
|
|
38
37
|
|
|
39
38
|
|
|
@@ -153,7 +152,7 @@ async def send_message(
|
|
|
153
152
|
await graph.mailbox(mailbox).send_mail.post(req)
|
|
154
153
|
except NotAuthenticatedError:
|
|
155
154
|
raise
|
|
156
|
-
except Exception as exc:
|
|
155
|
+
except Exception as exc:
|
|
157
156
|
raise map_kiota_error(exc) from exc
|
|
158
157
|
return {"status": "sent"}
|
|
159
158
|
|
|
@@ -185,7 +184,7 @@ async def create_draft(
|
|
|
185
184
|
created = await graph.mailbox(mailbox).messages.post(msg)
|
|
186
185
|
except NotAuthenticatedError:
|
|
187
186
|
raise
|
|
188
|
-
except Exception as exc:
|
|
187
|
+
except Exception as exc:
|
|
189
188
|
raise map_kiota_error(exc) from exc
|
|
190
189
|
return trim_message(
|
|
191
190
|
message_to_dict(created), include_body=False, include_raw=include_raw
|
|
@@ -235,7 +234,7 @@ async def reply_message(
|
|
|
235
234
|
)
|
|
236
235
|
except NotAuthenticatedError:
|
|
237
236
|
raise
|
|
238
|
-
except Exception as exc:
|
|
237
|
+
except Exception as exc:
|
|
239
238
|
raise map_kiota_error(exc) from exc
|
|
240
239
|
return {"status": "sent"}
|
|
241
240
|
|
|
@@ -256,7 +255,7 @@ async def reply_all_message(
|
|
|
256
255
|
)
|
|
257
256
|
except NotAuthenticatedError:
|
|
258
257
|
raise
|
|
259
|
-
except Exception as exc:
|
|
258
|
+
except Exception as exc:
|
|
260
259
|
raise map_kiota_error(exc) from exc
|
|
261
260
|
return {"status": "sent"}
|
|
262
261
|
|
|
@@ -280,7 +279,7 @@ async def forward_message(
|
|
|
280
279
|
)
|
|
281
280
|
except NotAuthenticatedError:
|
|
282
281
|
raise
|
|
283
|
-
except Exception as exc:
|
|
282
|
+
except Exception as exc:
|
|
284
283
|
raise map_kiota_error(exc) from exc
|
|
285
284
|
return {"status": "sent"}
|
|
286
285
|
|
|
@@ -343,7 +342,7 @@ async def update_message(
|
|
|
343
342
|
)
|
|
344
343
|
except NotAuthenticatedError:
|
|
345
344
|
raise
|
|
346
|
-
except Exception as exc:
|
|
345
|
+
except Exception as exc:
|
|
347
346
|
raise map_kiota_error(exc) from exc
|
|
348
347
|
return trim_message(
|
|
349
348
|
message_to_dict(updated), include_body=False, include_raw=include_raw
|
|
@@ -356,7 +355,7 @@ async def delete_message(*, graph, message_id: str, mailbox: str | None = None)
|
|
|
356
355
|
await graph.mailbox(mailbox).messages.by_message_id(message_id).delete()
|
|
357
356
|
except NotAuthenticatedError:
|
|
358
357
|
raise
|
|
359
|
-
except Exception as exc:
|
|
358
|
+
except Exception as exc:
|
|
360
359
|
raise map_kiota_error(exc) from exc
|
|
361
360
|
return {"status": "deleted"}
|
|
362
361
|
|