msgraph-mcp-server 0.3.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 (67) hide show
  1. msgraph_mcp_server-0.3.0/.env.example +5 -0
  2. msgraph_mcp_server-0.3.0/.github/workflows/release.yml +46 -0
  3. msgraph_mcp_server-0.3.0/.gitignore +31 -0
  4. msgraph_mcp_server-0.3.0/LICENSE +21 -0
  5. msgraph_mcp_server-0.3.0/PKG-INFO +243 -0
  6. msgraph_mcp_server-0.3.0/README.md +216 -0
  7. msgraph_mcp_server-0.3.0/pyproject.toml +67 -0
  8. msgraph_mcp_server-0.3.0/src/msgraph_mcp/__init__.py +0 -0
  9. msgraph_mcp_server-0.3.0/src/msgraph_mcp/auth/__init__.py +0 -0
  10. msgraph_mcp_server-0.3.0/src/msgraph_mcp/auth/cli.py +45 -0
  11. msgraph_mcp_server-0.3.0/src/msgraph_mcp/auth/msal_app.py +47 -0
  12. msgraph_mcp_server-0.3.0/src/msgraph_mcp/auth/token.py +32 -0
  13. msgraph_mcp_server-0.3.0/src/msgraph_mcp/config.py +79 -0
  14. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/__init__.py +0 -0
  15. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/auth_provider.py +29 -0
  16. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/batch.py +206 -0
  17. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/client.py +37 -0
  18. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/errors.py +43 -0
  19. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/pagination.py +47 -0
  20. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/serialize.py +351 -0
  21. msgraph_mcp_server-0.3.0/src/msgraph_mcp/graph/trimming.py +359 -0
  22. msgraph_mcp_server-0.3.0/src/msgraph_mcp/server.py +61 -0
  23. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/__init__.py +35 -0
  24. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/_binary.py +48 -0
  25. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/calendar.py +585 -0
  26. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/mail_actions.py +83 -0
  27. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/mail_batch.py +220 -0
  28. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/mail_folders.py +263 -0
  29. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/mail_read.py +333 -0
  30. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/mail_rules.py +468 -0
  31. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/mail_write.py +403 -0
  32. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/teams_channels.py +223 -0
  33. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/teams_chats.py +140 -0
  34. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/teams_content.py +150 -0
  35. msgraph_mcp_server-0.3.0/src/msgraph_mcp/tools/util.py +35 -0
  36. msgraph_mcp_server-0.3.0/tests/__init__.py +0 -0
  37. msgraph_mcp_server-0.3.0/tests/conftest.py +18 -0
  38. msgraph_mcp_server-0.3.0/tests/fixtures/__init__.py +8 -0
  39. msgraph_mcp_server-0.3.0/tests/fixtures/event_minimal.json +25 -0
  40. msgraph_mcp_server-0.3.0/tests/fixtures/message_minimal.json +19 -0
  41. msgraph_mcp_server-0.3.0/tests/integration/__init__.py +0 -0
  42. msgraph_mcp_server-0.3.0/tests/integration/test_live_smoke.py +111 -0
  43. msgraph_mcp_server-0.3.0/tests/integration/test_rules_and_folders_live.py +75 -0
  44. msgraph_mcp_server-0.3.0/tests/test_auth_cli.py +58 -0
  45. msgraph_mcp_server-0.3.0/tests/test_auth_msal_app.py +62 -0
  46. msgraph_mcp_server-0.3.0/tests/test_auth_token.py +65 -0
  47. msgraph_mcp_server-0.3.0/tests/test_config.py +90 -0
  48. msgraph_mcp_server-0.3.0/tests/test_graph_auth_provider.py +21 -0
  49. msgraph_mcp_server-0.3.0/tests/test_graph_batch.py +224 -0
  50. msgraph_mcp_server-0.3.0/tests/test_graph_client.py +29 -0
  51. msgraph_mcp_server-0.3.0/tests/test_graph_errors.py +47 -0
  52. msgraph_mcp_server-0.3.0/tests/test_graph_pagination.py +63 -0
  53. msgraph_mcp_server-0.3.0/tests/test_graph_serialize.py +272 -0
  54. msgraph_mcp_server-0.3.0/tests/test_graph_trimming.py +370 -0
  55. msgraph_mcp_server-0.3.0/tests/test_server.py +33 -0
  56. msgraph_mcp_server-0.3.0/tests/test_tools_calendar.py +204 -0
  57. msgraph_mcp_server-0.3.0/tests/test_tools_mail_actions.py +72 -0
  58. msgraph_mcp_server-0.3.0/tests/test_tools_mail_batch.py +235 -0
  59. msgraph_mcp_server-0.3.0/tests/test_tools_mail_folders.py +281 -0
  60. msgraph_mcp_server-0.3.0/tests/test_tools_mail_read.py +310 -0
  61. msgraph_mcp_server-0.3.0/tests/test_tools_mail_rules.py +353 -0
  62. msgraph_mcp_server-0.3.0/tests/test_tools_mail_write.py +136 -0
  63. msgraph_mcp_server-0.3.0/tests/test_tools_teams_channels.py +173 -0
  64. msgraph_mcp_server-0.3.0/tests/test_tools_teams_chats.py +101 -0
  65. msgraph_mcp_server-0.3.0/tests/test_tools_teams_content.py +164 -0
  66. msgraph_mcp_server-0.3.0/tests/test_tools_util.py +72 -0
  67. msgraph_mcp_server-0.3.0/uv.lock +1776 -0
@@ -0,0 +1,5 @@
1
+ MSGRAPH_MCP_CLIENT_ID=
2
+ MSGRAPH_MCP_TENANT_ID=
3
+ # Optional: override token cache location (default ~/.msgraph-mcp/token_cache.bin)
4
+ # MSGRAPH_MCP_TOKEN_CACHE_PATH=
5
+ # Legacy OUTLOOK_MCP_* names are still honored as a fallback.
@@ -0,0 +1,46 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v5
16
+
17
+ - name: Build sdist and wheel
18
+ run: uv build
19
+
20
+ - name: Check tag matches package version
21
+ run: |
22
+ version=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
23
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
24
+ echo "Tag $GITHUB_REF_NAME does not match pyproject.toml version $version" >&2
25
+ exit 1
26
+ fi
27
+
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment: pypi
37
+ permissions:
38
+ id-token: write # required for PyPI Trusted Publishing (OIDC)
39
+ steps:
40
+ - uses: actions/download-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+
45
+ - name: Publish to PyPI
46
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ .pyright/
12
+ dist/
13
+ build/
14
+
15
+ # Env / secrets
16
+ .env
17
+ .env.local
18
+
19
+ # Local working docs (not committed)
20
+ docs/specs/
21
+ docs/plans/
22
+
23
+ # Editor / OS
24
+ .vscode/
25
+ .idea/
26
+ .DS_Store
27
+
28
+ .claude/
29
+
30
+ # Brainstorming visual companion artifacts
31
+ .superpowers/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tim Furlong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.5
2
+ Name: msgraph-mcp-server
3
+ Version: 0.3.0
4
+ Summary: MCP server for Microsoft Graph: Outlook mail and calendar, plus read-only Microsoft Teams
5
+ Project-URL: Homepage, https://github.com/timfurlong/msgraph-mcp
6
+ Project-URL: Repository, https://github.com/timfurlong/msgraph-mcp
7
+ Project-URL: Issues, https://github.com/timfurlong/msgraph-mcp/issues
8
+ Author: Tim Furlong
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: calendar,email,mcp,microsoft-graph,model-context-protocol,outlook,teams
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Communications :: Email
20
+ Classifier: Topic :: Office/Business :: Scheduling
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: mcp>=1.0
23
+ Requires-Dist: msal>=1.31
24
+ Requires-Dist: msgraph-sdk>=1.0
25
+ Requires-Dist: python-dotenv>=1.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # MSGraph MCP
29
+
30
+ <!-- mcp-name: io.github.timfurlong/msgraph-mcp -->
31
+
32
+ A Model Context Protocol (MCP) server for **Microsoft Graph**. It exposes Microsoft Outlook **mail** and **calendar**, plus read-only Microsoft **Teams** message history, to AI agents via the Microsoft Graph SDK. Acts as the signed-in user (delegated permissions, MSAL device code flow).
33
+
34
+ > Formerly published as `outlook-mcp`. Renamed because the scope grew beyond Outlook (Teams today, potentially other Graph surfaces later). Outlook mail and calendar remain first-class capabilities. See [Migrating from outlook-mcp](#migrating-from-outlook-mcp).
35
+
36
+ ## What it does
37
+
38
+ Workflow-oriented tools covering common mail, calendar, and read-only Teams operations:
39
+
40
+ | Group | Tools |
41
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
42
+ | Util | `whoami` |
43
+ | Mail — read | `list_messages`, `search_messages`, `get_message`, `list_attachments`, `download_attachment` |
44
+ | Mail — write | `send_message`, `create_draft`, `reply_message`, `reply_all_message`, `forward_message`, `update_message`, `delete_message` |
45
+ | Mail — folders | `list_folders`, `create_folder`, `update_folder`, `delete_folder`, `move_message` |
46
+ | Mail — actions | `archive_message`, `mark_read`, `mark_unread`, `flag_message`, `unflag_message` |
47
+ | Mail — rules | `list_rules`, `get_rule`, `create_rule`, `update_rule`, `delete_rule` |
48
+ | Calendar | `list_calendars`, `list_events`, `get_event`, `create_event`, `update_event`, `delete_event`, `cancel_event`, `respond_to_event`, `find_meeting_times` |
49
+ | Teams (read) | `list_chats`, `list_chat_messages`, `list_joined_teams`, `list_channels`, `list_channel_messages`, `list_message_replies`, `download_hosted_content` |
50
+
51
+ Every tool that touches a mailbox or calendar accepts an optional `mailbox` argument (email or user ID) to target shared mailboxes/calendars. Omit it to use the signed-in user's own mailbox.
52
+
53
+ Every tool that returns objects accepts `include_raw=true` to also include the full Graph payload.
54
+
55
+ List/search tools support pagination via `limit` (1-100, default 25) and `page_token`.
56
+
57
+ ## Prerequisites
58
+
59
+ - Python ≥ 3.11
60
+ - `uv`: https://docs.astral.sh/uv/
61
+ - An Entra (Azure AD) app registration with the right permissions (see "Entra setup" below)
62
+
63
+ ## Install
64
+
65
+ From PyPI (package `msgraph-mcp-server`; the commands it installs are `msgraph-mcp` and `msgraph-mcp-login`):
66
+
67
+ ```bash
68
+ uv tool install msgraph-mcp-server # or: pip install msgraph-mcp-server
69
+ ```
70
+
71
+ Then use `msgraph-mcp-login` / `msgraph-mcp` directly wherever the quickstart below says `uv run ...`, and wire the host with `claude mcp add msgraph -- msgraph-mcp`.
72
+
73
+ ## Quickstart (from source)
74
+
75
+ ```bash
76
+ # 1. Install dependencies
77
+ uv sync
78
+
79
+ # 2. Configure environment
80
+ cp .env.example .env
81
+ # Fill in MSGRAPH_MCP_CLIENT_ID and MSGRAPH_MCP_TENANT_ID
82
+
83
+ # 3. One-time sign-in (device code flow)
84
+ uv run msgraph-mcp-login
85
+ # Follow the prompt: visit the URL, enter the code, complete sign-in.
86
+ # A token cache is written to ~/.msgraph-mcp/token_cache.bin (mode 0600).
87
+
88
+ # 4. Wire the MCP into your host
89
+ # - Claude Code:
90
+ claude mcp add msgraph -- uv --directory "$(pwd)" run msgraph-mcp
91
+
92
+ # - Anything else: configure the host to launch `uv run msgraph-mcp` (stdio).
93
+ ```
94
+
95
+ ## Entra setup
96
+
97
+ The app registration (e.g. "MSGraph MCP") requires:
98
+
99
+ - **Account type:** single tenant
100
+ - **Redirect URI (public client):** `https://login.microsoftonline.com/common/oauth2/nativeclient`
101
+ - **Delegated permissions** (Microsoft Graph):
102
+ - `Mail.ReadWrite`
103
+ - `Mail.ReadWrite.Shared`
104
+ - `Mail.Send`
105
+ - `MailboxSettings.ReadWrite`
106
+ - `Calendars.ReadWrite`
107
+ - `Calendars.ReadWrite.Shared`
108
+ - `User.Read`
109
+ - `Chat.Read` (Teams)
110
+ - `Team.ReadBasic.All` (Teams)
111
+ - `Channel.ReadBasic.All` (Teams)
112
+ - `ChannelMessage.Read.All` (Teams)
113
+ - Admin consent: required for `ChannelMessage.Read.All` (always), plus the `*.Shared` permissions if your tenant requires it.
114
+
115
+ > If you signed in before any of these scopes were added to the app (for example `MailboxSettings.ReadWrite`, or the Teams scopes), re-run `uv run msgraph-mcp-login` so the cached token picks up the new scopes. Without them, calls needing the missing scope fail with a consent error.
116
+
117
+ The CLI uses public-client device code flow — **no client secret** is needed or stored.
118
+
119
+ ## Environment variables
120
+
121
+ | Var | Required | Default | Purpose |
122
+ | ------------------------------ | -------- | -------------------------------- | ----------------------------------- |
123
+ | `MSGRAPH_MCP_CLIENT_ID` | yes | — | Entra (Azure AD) app client ID |
124
+ | `MSGRAPH_MCP_TENANT_ID` | yes | — | Tenant ID (single-tenant authority) |
125
+ | `MSGRAPH_MCP_TOKEN_CACHE_PATH` | no | `~/.msgraph-mcp/token_cache.bin` | Override token cache file location |
126
+
127
+ Process env wins; `.env` at the repo root is loaded as a dev fallback.
128
+
129
+ Legacy `OUTLOOK_MCP_*` names are honored as a fallback for each variable (the `MSGRAPH_MCP_*` name wins when both are set).
130
+
131
+ ## Migrating from outlook-mcp
132
+
133
+ This project was named `outlook-mcp` through v0.2.0. What changed in the rename:
134
+
135
+ | Old | New |
136
+ | ---------------------------------- | ---------------------------------- |
137
+ | package `outlook-mcp` | package `msgraph-mcp` |
138
+ | module `outlook_mcp` | module `msgraph_mcp` |
139
+ | `uv run outlook-mcp` | `uv run msgraph-mcp` |
140
+ | `uv run outlook-mcp-login` | `uv run msgraph-mcp-login` |
141
+ | `OUTLOOK_MCP_*` env vars | `MSGRAPH_MCP_*` env vars |
142
+ | `~/.outlook-mcp/token_cache.bin` | `~/.msgraph-mcp/token_cache.bin` |
143
+
144
+ Backward compatibility, so an existing setup keeps working without re-authenticating:
145
+
146
+ - `OUTLOOK_MCP_*` env vars are still read as a fallback.
147
+ - If `~/.msgraph-mcp/token_cache.bin` does not exist but `~/.outlook-mcp/token_cache.bin` does, the legacy cache is used. To move to the new location: `mv ~/.outlook-mcp ~/.msgraph-mcp`.
148
+
149
+ You do need to update anything that launches the server by script name (MCP host configs): `outlook-mcp` → `msgraph-mcp`.
150
+
151
+ ## Security
152
+
153
+ - The token cache contains your **refresh token**, which can mint access tokens for your mail, calendar, and Teams data. Treat it like a credential.
154
+ - Default location: `~/.msgraph-mcp/token_cache.bin`, mode `0600`, parent dir mode `0700`.
155
+ - To **revoke** access: sign in to https://account.microsoft.com or your org's identity portal, revoke the app, then `rm ~/.msgraph-mcp/token_cache.bin`.
156
+ - To **switch accounts**: `rm ~/.msgraph-mcp/token_cache.bin` and re-run `msgraph-mcp-login`.
157
+
158
+ ## Recipes
159
+
160
+ ### Route a sender into a new folder
161
+
162
+ ```
163
+ # 1. Make a folder for the notifications.
164
+ create_folder(display_name="Notifications")
165
+ # -> {"id": "AAMkFolderId", "display_name": "Notifications", ...}
166
+
167
+ # 2. Create an inbox rule that moves matching senders into it.
168
+ create_rule(
169
+ display_name="Notifications",
170
+ sender_contains=["example.com"],
171
+ move_to_folder="AAMkFolderId",
172
+ stop_processing_rules=True,
173
+ )
174
+ ```
175
+
176
+ Conditions inside one rule are AND-ed by Outlook. Pass a list to a single condition (e.g. `sender_contains=["example.com", "monitor.io"]`) for OR within that condition. Rules only run against the inbox — Graph's `messageRules` endpoint is hardcoded there and does not support per-folder rules.
177
+
178
+ `create_rule` requires at least one condition and one action. `update_rule` patches a rule in place but **replaces** the `conditions` or `actions` block whenever you pass any condition/action arg — call `get_rule` first if you need to preserve existing values.
179
+
180
+ ## Microsoft Teams (read-only)
181
+
182
+ Read Teams message history as the signed-in user:
183
+
184
+ - `list_chats`, `list_chat_messages`: your 1:1 and group chats.
185
+ - `list_joined_teams`, `list_channels`, `list_channel_messages`, `list_message_replies`: team channels and their threads.
186
+ - `download_hosted_content`: download an inline image referenced by a message (`hosted_content_refs`). Images come back as a native MCP image block the agent can view directly; pass `save_path` (file or existing directory) to write the bytes to disk and get back a path instead.
187
+
188
+ ### Permissions and consent
189
+
190
+ These delegated scopes are required (already listed in `SCOPES`):
191
+
192
+ - `Chat.Read`, `Team.ReadBasic.All`, `Channel.ReadBasic.All`: user-consentable.
193
+ - `ChannelMessage.Read.All`: requires tenant administrator consent.
194
+
195
+ Setup:
196
+
197
+ 1. Add the four delegated permissions to the app registration.
198
+ 2. Grant tenant admin consent for `ChannelMessage.Read.All`.
199
+ 3. Because the scope set changed, re-run the device-code login so the cached token carries the new scopes.
200
+
201
+ ### Notes and limits
202
+
203
+ - Reading is delegated-only: you can read your own chats, not other users' chats.
204
+ - Channel message and reply pages are capped at 50 by Graph.
205
+ - SharePoint/OneDrive-backed file attachments are not downloadable here. In Teams, shared files are attachments whose `contentUrl` points into SharePoint, which is a different Graph surface (needs `Files.Read.All` / `Sites.Read.All` and the driveItem APIs). `download_hosted_content` covers inline hosted content (images), not shared files. This is deferred.
206
+
207
+ ## Future work
208
+
209
+ Not implemented; reasonable additions:
210
+
211
+ - **Graph `$batch` requests.** Performance optimization that bundles multiple Graph calls into one HTTP round-trip; would speed up multi-step workflows but adds complexity. Defer until profiling proves the win.
212
+
213
+ Other known gaps (intentionally out of scope): chunked attachment upload (>3 MB), category master-list management, mail signatures, contacts/To-Do/OneNote, multi-account switching, change-notification subscriptions, force-delete of non-empty folders.
214
+
215
+ ## Development
216
+
217
+ ```bash
218
+ # Run unit tests
219
+ uv run pytest
220
+
221
+ # Run unit + live integration smoke (requires a valid token cache)
222
+ MSGRAPH_MCP_INTEGRATION=1 uv run pytest
223
+
224
+ # Type check
225
+ uv run pyright src tests
226
+
227
+ # Lint
228
+ uv run ruff check .
229
+ ```
230
+
231
+ ## Troubleshooting
232
+
233
+ | Symptom | Fix |
234
+ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
235
+ | `NotAuthenticatedError: Not authenticated. Run \`msgraph-mcp-login\`...` | Run `uv run msgraph-mcp-login`. |
236
+ | `ConfigError: Missing required env var: MSGRAPH_MCP_CLIENT_ID` | Set the var in `.env` or in your MCP host's env config. |
237
+ | `Graph API 403: ErrorAccessDenied — ...` | Permission mismatch on the Entra app. Verify the delegated permissions list above and re-consent. |
238
+ | `Graph API 400: BadRequest — Syntax error: character ... is not valid at position N` from `search_messages` | The query is passed to Graph's `$search` as-is. Wrap literal/multi-character tokens in double quotes (e.g. `"weekly report"`), or use KQL fielded forms (e.g. `from:alice subject:"report"`). Bare alphanumeric strings with embedded digits are invalid KQL. |
239
+ | Server boots but tools 404 in the host | Confirm the host is launching `uv run msgraph-mcp` with the right working directory. |
240
+
241
+ ---
242
+
243
+ Spec: `docs/specs/2026-05-19-outlook-mcp-design.md` (gitignored — local working doc).
@@ -0,0 +1,216 @@
1
+ # MSGraph MCP
2
+
3
+ <!-- mcp-name: io.github.timfurlong/msgraph-mcp -->
4
+
5
+ A Model Context Protocol (MCP) server for **Microsoft Graph**. It exposes Microsoft Outlook **mail** and **calendar**, plus read-only Microsoft **Teams** message history, to AI agents via the Microsoft Graph SDK. Acts as the signed-in user (delegated permissions, MSAL device code flow).
6
+
7
+ > Formerly published as `outlook-mcp`. Renamed because the scope grew beyond Outlook (Teams today, potentially other Graph surfaces later). Outlook mail and calendar remain first-class capabilities. See [Migrating from outlook-mcp](#migrating-from-outlook-mcp).
8
+
9
+ ## What it does
10
+
11
+ Workflow-oriented tools covering common mail, calendar, and read-only Teams operations:
12
+
13
+ | Group | Tools |
14
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
15
+ | Util | `whoami` |
16
+ | Mail — read | `list_messages`, `search_messages`, `get_message`, `list_attachments`, `download_attachment` |
17
+ | Mail — write | `send_message`, `create_draft`, `reply_message`, `reply_all_message`, `forward_message`, `update_message`, `delete_message` |
18
+ | Mail — folders | `list_folders`, `create_folder`, `update_folder`, `delete_folder`, `move_message` |
19
+ | Mail — actions | `archive_message`, `mark_read`, `mark_unread`, `flag_message`, `unflag_message` |
20
+ | Mail — rules | `list_rules`, `get_rule`, `create_rule`, `update_rule`, `delete_rule` |
21
+ | Calendar | `list_calendars`, `list_events`, `get_event`, `create_event`, `update_event`, `delete_event`, `cancel_event`, `respond_to_event`, `find_meeting_times` |
22
+ | Teams (read) | `list_chats`, `list_chat_messages`, `list_joined_teams`, `list_channels`, `list_channel_messages`, `list_message_replies`, `download_hosted_content` |
23
+
24
+ Every tool that touches a mailbox or calendar accepts an optional `mailbox` argument (email or user ID) to target shared mailboxes/calendars. Omit it to use the signed-in user's own mailbox.
25
+
26
+ Every tool that returns objects accepts `include_raw=true` to also include the full Graph payload.
27
+
28
+ List/search tools support pagination via `limit` (1-100, default 25) and `page_token`.
29
+
30
+ ## Prerequisites
31
+
32
+ - Python ≥ 3.11
33
+ - `uv`: https://docs.astral.sh/uv/
34
+ - An Entra (Azure AD) app registration with the right permissions (see "Entra setup" below)
35
+
36
+ ## Install
37
+
38
+ From PyPI (package `msgraph-mcp-server`; the commands it installs are `msgraph-mcp` and `msgraph-mcp-login`):
39
+
40
+ ```bash
41
+ uv tool install msgraph-mcp-server # or: pip install msgraph-mcp-server
42
+ ```
43
+
44
+ Then use `msgraph-mcp-login` / `msgraph-mcp` directly wherever the quickstart below says `uv run ...`, and wire the host with `claude mcp add msgraph -- msgraph-mcp`.
45
+
46
+ ## Quickstart (from source)
47
+
48
+ ```bash
49
+ # 1. Install dependencies
50
+ uv sync
51
+
52
+ # 2. Configure environment
53
+ cp .env.example .env
54
+ # Fill in MSGRAPH_MCP_CLIENT_ID and MSGRAPH_MCP_TENANT_ID
55
+
56
+ # 3. One-time sign-in (device code flow)
57
+ uv run msgraph-mcp-login
58
+ # Follow the prompt: visit the URL, enter the code, complete sign-in.
59
+ # A token cache is written to ~/.msgraph-mcp/token_cache.bin (mode 0600).
60
+
61
+ # 4. Wire the MCP into your host
62
+ # - Claude Code:
63
+ claude mcp add msgraph -- uv --directory "$(pwd)" run msgraph-mcp
64
+
65
+ # - Anything else: configure the host to launch `uv run msgraph-mcp` (stdio).
66
+ ```
67
+
68
+ ## Entra setup
69
+
70
+ The app registration (e.g. "MSGraph MCP") requires:
71
+
72
+ - **Account type:** single tenant
73
+ - **Redirect URI (public client):** `https://login.microsoftonline.com/common/oauth2/nativeclient`
74
+ - **Delegated permissions** (Microsoft Graph):
75
+ - `Mail.ReadWrite`
76
+ - `Mail.ReadWrite.Shared`
77
+ - `Mail.Send`
78
+ - `MailboxSettings.ReadWrite`
79
+ - `Calendars.ReadWrite`
80
+ - `Calendars.ReadWrite.Shared`
81
+ - `User.Read`
82
+ - `Chat.Read` (Teams)
83
+ - `Team.ReadBasic.All` (Teams)
84
+ - `Channel.ReadBasic.All` (Teams)
85
+ - `ChannelMessage.Read.All` (Teams)
86
+ - Admin consent: required for `ChannelMessage.Read.All` (always), plus the `*.Shared` permissions if your tenant requires it.
87
+
88
+ > If you signed in before any of these scopes were added to the app (for example `MailboxSettings.ReadWrite`, or the Teams scopes), re-run `uv run msgraph-mcp-login` so the cached token picks up the new scopes. Without them, calls needing the missing scope fail with a consent error.
89
+
90
+ The CLI uses public-client device code flow — **no client secret** is needed or stored.
91
+
92
+ ## Environment variables
93
+
94
+ | Var | Required | Default | Purpose |
95
+ | ------------------------------ | -------- | -------------------------------- | ----------------------------------- |
96
+ | `MSGRAPH_MCP_CLIENT_ID` | yes | — | Entra (Azure AD) app client ID |
97
+ | `MSGRAPH_MCP_TENANT_ID` | yes | — | Tenant ID (single-tenant authority) |
98
+ | `MSGRAPH_MCP_TOKEN_CACHE_PATH` | no | `~/.msgraph-mcp/token_cache.bin` | Override token cache file location |
99
+
100
+ Process env wins; `.env` at the repo root is loaded as a dev fallback.
101
+
102
+ Legacy `OUTLOOK_MCP_*` names are honored as a fallback for each variable (the `MSGRAPH_MCP_*` name wins when both are set).
103
+
104
+ ## Migrating from outlook-mcp
105
+
106
+ This project was named `outlook-mcp` through v0.2.0. What changed in the rename:
107
+
108
+ | Old | New |
109
+ | ---------------------------------- | ---------------------------------- |
110
+ | package `outlook-mcp` | package `msgraph-mcp` |
111
+ | module `outlook_mcp` | module `msgraph_mcp` |
112
+ | `uv run outlook-mcp` | `uv run msgraph-mcp` |
113
+ | `uv run outlook-mcp-login` | `uv run msgraph-mcp-login` |
114
+ | `OUTLOOK_MCP_*` env vars | `MSGRAPH_MCP_*` env vars |
115
+ | `~/.outlook-mcp/token_cache.bin` | `~/.msgraph-mcp/token_cache.bin` |
116
+
117
+ Backward compatibility, so an existing setup keeps working without re-authenticating:
118
+
119
+ - `OUTLOOK_MCP_*` env vars are still read as a fallback.
120
+ - If `~/.msgraph-mcp/token_cache.bin` does not exist but `~/.outlook-mcp/token_cache.bin` does, the legacy cache is used. To move to the new location: `mv ~/.outlook-mcp ~/.msgraph-mcp`.
121
+
122
+ You do need to update anything that launches the server by script name (MCP host configs): `outlook-mcp` → `msgraph-mcp`.
123
+
124
+ ## Security
125
+
126
+ - The token cache contains your **refresh token**, which can mint access tokens for your mail, calendar, and Teams data. Treat it like a credential.
127
+ - Default location: `~/.msgraph-mcp/token_cache.bin`, mode `0600`, parent dir mode `0700`.
128
+ - To **revoke** access: sign in to https://account.microsoft.com or your org's identity portal, revoke the app, then `rm ~/.msgraph-mcp/token_cache.bin`.
129
+ - To **switch accounts**: `rm ~/.msgraph-mcp/token_cache.bin` and re-run `msgraph-mcp-login`.
130
+
131
+ ## Recipes
132
+
133
+ ### Route a sender into a new folder
134
+
135
+ ```
136
+ # 1. Make a folder for the notifications.
137
+ create_folder(display_name="Notifications")
138
+ # -> {"id": "AAMkFolderId", "display_name": "Notifications", ...}
139
+
140
+ # 2. Create an inbox rule that moves matching senders into it.
141
+ create_rule(
142
+ display_name="Notifications",
143
+ sender_contains=["example.com"],
144
+ move_to_folder="AAMkFolderId",
145
+ stop_processing_rules=True,
146
+ )
147
+ ```
148
+
149
+ Conditions inside one rule are AND-ed by Outlook. Pass a list to a single condition (e.g. `sender_contains=["example.com", "monitor.io"]`) for OR within that condition. Rules only run against the inbox — Graph's `messageRules` endpoint is hardcoded there and does not support per-folder rules.
150
+
151
+ `create_rule` requires at least one condition and one action. `update_rule` patches a rule in place but **replaces** the `conditions` or `actions` block whenever you pass any condition/action arg — call `get_rule` first if you need to preserve existing values.
152
+
153
+ ## Microsoft Teams (read-only)
154
+
155
+ Read Teams message history as the signed-in user:
156
+
157
+ - `list_chats`, `list_chat_messages`: your 1:1 and group chats.
158
+ - `list_joined_teams`, `list_channels`, `list_channel_messages`, `list_message_replies`: team channels and their threads.
159
+ - `download_hosted_content`: download an inline image referenced by a message (`hosted_content_refs`). Images come back as a native MCP image block the agent can view directly; pass `save_path` (file or existing directory) to write the bytes to disk and get back a path instead.
160
+
161
+ ### Permissions and consent
162
+
163
+ These delegated scopes are required (already listed in `SCOPES`):
164
+
165
+ - `Chat.Read`, `Team.ReadBasic.All`, `Channel.ReadBasic.All`: user-consentable.
166
+ - `ChannelMessage.Read.All`: requires tenant administrator consent.
167
+
168
+ Setup:
169
+
170
+ 1. Add the four delegated permissions to the app registration.
171
+ 2. Grant tenant admin consent for `ChannelMessage.Read.All`.
172
+ 3. Because the scope set changed, re-run the device-code login so the cached token carries the new scopes.
173
+
174
+ ### Notes and limits
175
+
176
+ - Reading is delegated-only: you can read your own chats, not other users' chats.
177
+ - Channel message and reply pages are capped at 50 by Graph.
178
+ - SharePoint/OneDrive-backed file attachments are not downloadable here. In Teams, shared files are attachments whose `contentUrl` points into SharePoint, which is a different Graph surface (needs `Files.Read.All` / `Sites.Read.All` and the driveItem APIs). `download_hosted_content` covers inline hosted content (images), not shared files. This is deferred.
179
+
180
+ ## Future work
181
+
182
+ Not implemented; reasonable additions:
183
+
184
+ - **Graph `$batch` requests.** Performance optimization that bundles multiple Graph calls into one HTTP round-trip; would speed up multi-step workflows but adds complexity. Defer until profiling proves the win.
185
+
186
+ Other known gaps (intentionally out of scope): chunked attachment upload (>3 MB), category master-list management, mail signatures, contacts/To-Do/OneNote, multi-account switching, change-notification subscriptions, force-delete of non-empty folders.
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ # Run unit tests
192
+ uv run pytest
193
+
194
+ # Run unit + live integration smoke (requires a valid token cache)
195
+ MSGRAPH_MCP_INTEGRATION=1 uv run pytest
196
+
197
+ # Type check
198
+ uv run pyright src tests
199
+
200
+ # Lint
201
+ uv run ruff check .
202
+ ```
203
+
204
+ ## Troubleshooting
205
+
206
+ | Symptom | Fix |
207
+ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
208
+ | `NotAuthenticatedError: Not authenticated. Run \`msgraph-mcp-login\`...` | Run `uv run msgraph-mcp-login`. |
209
+ | `ConfigError: Missing required env var: MSGRAPH_MCP_CLIENT_ID` | Set the var in `.env` or in your MCP host's env config. |
210
+ | `Graph API 403: ErrorAccessDenied — ...` | Permission mismatch on the Entra app. Verify the delegated permissions list above and re-consent. |
211
+ | `Graph API 400: BadRequest — Syntax error: character ... is not valid at position N` from `search_messages` | The query is passed to Graph's `$search` as-is. Wrap literal/multi-character tokens in double quotes (e.g. `"weekly report"`), or use KQL fielded forms (e.g. `from:alice subject:"report"`). Bare alphanumeric strings with embedded digits are invalid KQL. |
212
+ | Server boots but tools 404 in the host | Confirm the host is launching `uv run msgraph-mcp` with the right working directory. |
213
+
214
+ ---
215
+
216
+ Spec: `docs/specs/2026-05-19-outlook-mcp-design.md` (gitignored — local working doc).
@@ -0,0 +1,67 @@
1
+ [project]
2
+ name = "msgraph-mcp-server"
3
+ version = "0.3.0"
4
+ description = "MCP server for Microsoft Graph: Outlook mail and calendar, plus read-only Microsoft Teams"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ authors = [{ name = "Tim Furlong" }]
10
+ keywords = ["mcp", "model-context-protocol", "microsoft-graph", "outlook", "teams", "email", "calendar"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Operating System :: OS Independent",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Communications :: Email",
20
+ "Topic :: Office/Business :: Scheduling",
21
+ ]
22
+ dependencies = [
23
+ "mcp>=1.0",
24
+ "msgraph-sdk>=1.0",
25
+ "msal>=1.31",
26
+ "python-dotenv>=1.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/timfurlong/msgraph-mcp"
31
+ Repository = "https://github.com/timfurlong/msgraph-mcp"
32
+ Issues = "https://github.com/timfurlong/msgraph-mcp/issues"
33
+
34
+ [project.scripts]
35
+ msgraph-mcp = "msgraph_mcp.server:main"
36
+ msgraph-mcp-login = "msgraph_mcp.auth.cli:main"
37
+
38
+ [build-system]
39
+ requires = ["hatchling"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/msgraph_mcp"]
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "pytest>=8",
48
+ "pytest-asyncio>=0.24",
49
+ "pyright>=1.1.380",
50
+ "ruff>=0.6",
51
+ ]
52
+
53
+ [tool.pytest.ini_options]
54
+ asyncio_mode = "auto"
55
+ testpaths = ["tests"]
56
+ # Integration tests are gated by tests/conftest.py — they skip unless
57
+ # MSGRAPH_MCP_INTEGRATION=1 is set in the environment.
58
+ #
59
+ # msgraph-sdk and kiota_abstractions emit DeprecationWarnings on import for
60
+ # nested *RequestConfiguration / GetQueryParameters classes whose generated
61
+ # bodies are scheduled for removal. Our code already uses the recommended
62
+ # generic RequestConfiguration[...]; these warnings come from the SDK module
63
+ # bodies themselves and fire regardless of how we consume them.
64
+ filterwarnings = [
65
+ "ignore:.*GetQueryParameters is deprecated.*:DeprecationWarning:kiota_abstractions",
66
+ "ignore:.*This class is deprecated.*:DeprecationWarning:msgraph.generated",
67
+ ]
File without changes