outlook-mcp-com 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Kopelman
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,195 @@
1
+ Metadata-Version: 2.4
2
+ Name: outlook-mcp-com
3
+ Version: 0.1.0
4
+ Summary: MCP server controlling Microsoft Outlook desktop via the Win32 COM API
5
+ Author: Adam Kopelman
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Adam Kopelman
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/adamkopelman/outlook-mcp
29
+ Project-URL: Repository, https://github.com/adamkopelman/outlook-mcp
30
+ Keywords: mcp,outlook,windows,com,pywin32,model-context-protocol
31
+ Classifier: Development Status :: 4 - Beta
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: Microsoft :: Windows
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Requires-Python: >=3.10
40
+ Description-Content-Type: text/markdown
41
+ License-File: LICENSE
42
+ Requires-Dist: mcp>=1.2
43
+ Requires-Dist: pywin32>=306; sys_platform == "win32"
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest>=7; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # outlook-mcp
49
+
50
+ An [MCP](https://modelcontextprotocol.io) server that lets Claude (or any MCP
51
+ client) control **Microsoft Outlook desktop on Windows** through the Win32 COM
52
+ API — read and send email, manage your calendar, save attachments, and work
53
+ with tasks and notes, all against the Outlook profile you are already signed
54
+ in to. No Azure app registration, no Graph API tokens.
55
+
56
+ ## Requirements
57
+
58
+ - **Windows** with **classic Outlook desktop** installed and a configured
59
+ mail profile.
60
+ > ⚠️ The "new Outlook" (`olk.exe`) does **not** expose a COM API and will
61
+ > not work. You need classic Outlook (Microsoft 365 / Office 2016+).
62
+ - **Python 3.10+**
63
+ - The server only *runs* on Windows; the test suite runs anywhere (COM access
64
+ is mocked).
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ git clone https://github.com/adamkopelman/outlook-mcp.git
70
+ cd outlook-mcp
71
+ pip install .
72
+ ```
73
+
74
+ This installs the `outlook-mcp` console command (and `pywin32` on Windows).
75
+
76
+ ## Hooking it up to Claude
77
+
78
+ **As a Claude Code plugin** (recommended for Claude Code) — this repo is itself a
79
+ plugin (`.claude-plugin/plugin.json`) that registers the `outlook` MCP server
80
+ for you. After `pip install .` (above), either:
81
+
82
+ ```bash
83
+ # Try it locally without installing anything into Claude Code's config:
84
+ claude --plugin-dir /path/to/outlook-mcp
85
+
86
+ # Or install it properly, from a local checkout or directly from GitHub:
87
+ claude plugin install outlook-mcp@/path/to/outlook-mcp
88
+ claude plugin install outlook-mcp@github.com/adamkopelman/outlook-mcp
89
+ ```
90
+
91
+ The plugin launches the server as `python -m outlook_mcp` rather than via the
92
+ `outlook-mcp` console script, so it works even if pip's script directory isn't
93
+ on `PATH` — it just needs `outlook_mcp` importable by whichever `python` is
94
+ first on `PATH`.
95
+
96
+ **Claude Desktop** — add to `%APPDATA%\Claude\claude_desktop_config.json`:
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "outlook": {
102
+ "command": "outlook-mcp"
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ If `outlook-mcp` isn't on PATH, use the full interpreter instead:
109
+
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "outlook": {
114
+ "command": "C:\\Path\\To\\python.exe",
115
+ "args": ["-m", "outlook_mcp"]
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ **Claude Code (manual, without the plugin):**
122
+
123
+ ```bash
124
+ claude mcp add outlook -- outlook-mcp
125
+ ```
126
+
127
+ ## Tools
128
+
129
+ | Tool | Description |
130
+ | --- | --- |
131
+ | `list_folders` | List mail folders with item/unread counts |
132
+ | `list_emails` | Recent emails in a folder (newest first, `unread_only` option) |
133
+ | `search_emails` | Search subject/sender/body, optional `since_days` window |
134
+ | `get_email` | Full email by id (body, recipients, attachment names) |
135
+ | `send_email` | **Send immediately** (to/cc/bcc, plain or HTML body) |
136
+ | `create_draft` | Compose and save to Drafts without sending |
137
+ | `reply_email` | Reply / reply-all (send, or save as draft with `send=false`) |
138
+ | `move_email` | Move an email to another folder (returns its new id) |
139
+ | `delete_email` | Move an email to Deleted Items |
140
+ | `list_events` | Calendar events in a date range (recurrences expanded) |
141
+ | `get_event` | Full event details including attendees |
142
+ | `create_event` | Create an appointment — adding `attendees` sends invites |
143
+ | `respond_to_meeting` | Accept / decline / tentative a meeting invite |
144
+ | `list_attachments` | List an email's attachments |
145
+ | `save_attachments` | Save attachments to a local directory |
146
+ | `list_tasks` | List tasks (open only by default) |
147
+ | `create_task` | Create a task with due date and importance |
148
+ | `complete_task` | Mark a task complete |
149
+ | `list_notes` | List sticky notes |
150
+ | `get_note` | Read a note's full body |
151
+ | `create_note` | Create a sticky note |
152
+
153
+ Items are addressed by an opaque `id` returned from list/search tools.
154
+ **Ids change when an item moves folders** — `move_email` returns the new id,
155
+ and a stale id produces a clear "item not found" error.
156
+
157
+ ## Security notes
158
+
159
+ - `send_email`, `reply_email`, `create_event` (with attendees) and
160
+ `respond_to_meeting` act **immediately as the signed-in Outlook user**,
161
+ with no confirmation step inside the server. If you want a human in the
162
+ loop, prefer `create_draft` / `send=false`, or deny the sending tools in
163
+ your MCP client's permission settings.
164
+ - Outlook's object model guard may show a *"A program is trying to send an
165
+ e-mail message on your behalf"* prompt, typically when no up-to-date
166
+ antivirus is registered with Windows or group policy demands it. The
167
+ prompt blocks the tool call until answered. Do **not** disable Outlook
168
+ security to avoid it.
169
+ - `save_attachments` writes to any local path the MCP client asks for.
170
+
171
+ ## Troubleshooting
172
+
173
+ - *"Outlook is not available..."* — you're not on Windows, or classic
174
+ Outlook isn't installed.
175
+ - The first tool call may take a few seconds while Outlook launches.
176
+ - *"Item not found"* — the id went stale (item moved or was deleted);
177
+ list/search again to get a fresh id.
178
+ - Date filters use Outlook's JET format internally; if `search_emails` with
179
+ `since_days` misbehaves on a heavily localized system, try without it and
180
+ filter by eye.
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ pip install -e .[dev]
186
+ pytest
187
+ ```
188
+
189
+ The COM layer lives in `outlook_mcp/outlook/client.py` behind the
190
+ `OutlookClientBase` interface (`outlook_mcp/outlook/base.py`); tests run on
191
+ any OS against an in-memory fake (`tests/conftest.py`).
192
+
193
+ ## License
194
+
195
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,148 @@
1
+ # outlook-mcp
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server that lets Claude (or any MCP
4
+ client) control **Microsoft Outlook desktop on Windows** through the Win32 COM
5
+ API — read and send email, manage your calendar, save attachments, and work
6
+ with tasks and notes, all against the Outlook profile you are already signed
7
+ in to. No Azure app registration, no Graph API tokens.
8
+
9
+ ## Requirements
10
+
11
+ - **Windows** with **classic Outlook desktop** installed and a configured
12
+ mail profile.
13
+ > ⚠️ The "new Outlook" (`olk.exe`) does **not** expose a COM API and will
14
+ > not work. You need classic Outlook (Microsoft 365 / Office 2016+).
15
+ - **Python 3.10+**
16
+ - The server only *runs* on Windows; the test suite runs anywhere (COM access
17
+ is mocked).
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ git clone https://github.com/adamkopelman/outlook-mcp.git
23
+ cd outlook-mcp
24
+ pip install .
25
+ ```
26
+
27
+ This installs the `outlook-mcp` console command (and `pywin32` on Windows).
28
+
29
+ ## Hooking it up to Claude
30
+
31
+ **As a Claude Code plugin** (recommended for Claude Code) — this repo is itself a
32
+ plugin (`.claude-plugin/plugin.json`) that registers the `outlook` MCP server
33
+ for you. After `pip install .` (above), either:
34
+
35
+ ```bash
36
+ # Try it locally without installing anything into Claude Code's config:
37
+ claude --plugin-dir /path/to/outlook-mcp
38
+
39
+ # Or install it properly, from a local checkout or directly from GitHub:
40
+ claude plugin install outlook-mcp@/path/to/outlook-mcp
41
+ claude plugin install outlook-mcp@github.com/adamkopelman/outlook-mcp
42
+ ```
43
+
44
+ The plugin launches the server as `python -m outlook_mcp` rather than via the
45
+ `outlook-mcp` console script, so it works even if pip's script directory isn't
46
+ on `PATH` — it just needs `outlook_mcp` importable by whichever `python` is
47
+ first on `PATH`.
48
+
49
+ **Claude Desktop** — add to `%APPDATA%\Claude\claude_desktop_config.json`:
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "outlook": {
55
+ "command": "outlook-mcp"
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ If `outlook-mcp` isn't on PATH, use the full interpreter instead:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "outlook": {
67
+ "command": "C:\\Path\\To\\python.exe",
68
+ "args": ["-m", "outlook_mcp"]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ **Claude Code (manual, without the plugin):**
75
+
76
+ ```bash
77
+ claude mcp add outlook -- outlook-mcp
78
+ ```
79
+
80
+ ## Tools
81
+
82
+ | Tool | Description |
83
+ | --- | --- |
84
+ | `list_folders` | List mail folders with item/unread counts |
85
+ | `list_emails` | Recent emails in a folder (newest first, `unread_only` option) |
86
+ | `search_emails` | Search subject/sender/body, optional `since_days` window |
87
+ | `get_email` | Full email by id (body, recipients, attachment names) |
88
+ | `send_email` | **Send immediately** (to/cc/bcc, plain or HTML body) |
89
+ | `create_draft` | Compose and save to Drafts without sending |
90
+ | `reply_email` | Reply / reply-all (send, or save as draft with `send=false`) |
91
+ | `move_email` | Move an email to another folder (returns its new id) |
92
+ | `delete_email` | Move an email to Deleted Items |
93
+ | `list_events` | Calendar events in a date range (recurrences expanded) |
94
+ | `get_event` | Full event details including attendees |
95
+ | `create_event` | Create an appointment — adding `attendees` sends invites |
96
+ | `respond_to_meeting` | Accept / decline / tentative a meeting invite |
97
+ | `list_attachments` | List an email's attachments |
98
+ | `save_attachments` | Save attachments to a local directory |
99
+ | `list_tasks` | List tasks (open only by default) |
100
+ | `create_task` | Create a task with due date and importance |
101
+ | `complete_task` | Mark a task complete |
102
+ | `list_notes` | List sticky notes |
103
+ | `get_note` | Read a note's full body |
104
+ | `create_note` | Create a sticky note |
105
+
106
+ Items are addressed by an opaque `id` returned from list/search tools.
107
+ **Ids change when an item moves folders** — `move_email` returns the new id,
108
+ and a stale id produces a clear "item not found" error.
109
+
110
+ ## Security notes
111
+
112
+ - `send_email`, `reply_email`, `create_event` (with attendees) and
113
+ `respond_to_meeting` act **immediately as the signed-in Outlook user**,
114
+ with no confirmation step inside the server. If you want a human in the
115
+ loop, prefer `create_draft` / `send=false`, or deny the sending tools in
116
+ your MCP client's permission settings.
117
+ - Outlook's object model guard may show a *"A program is trying to send an
118
+ e-mail message on your behalf"* prompt, typically when no up-to-date
119
+ antivirus is registered with Windows or group policy demands it. The
120
+ prompt blocks the tool call until answered. Do **not** disable Outlook
121
+ security to avoid it.
122
+ - `save_attachments` writes to any local path the MCP client asks for.
123
+
124
+ ## Troubleshooting
125
+
126
+ - *"Outlook is not available..."* — you're not on Windows, or classic
127
+ Outlook isn't installed.
128
+ - The first tool call may take a few seconds while Outlook launches.
129
+ - *"Item not found"* — the id went stale (item moved or was deleted);
130
+ list/search again to get a fresh id.
131
+ - Date filters use Outlook's JET format internally; if `search_emails` with
132
+ `since_days` misbehaves on a heavily localized system, try without it and
133
+ filter by eye.
134
+
135
+ ## Development
136
+
137
+ ```bash
138
+ pip install -e .[dev]
139
+ pytest
140
+ ```
141
+
142
+ The COM layer lives in `outlook_mcp/outlook/client.py` behind the
143
+ `OutlookClientBase` interface (`outlook_mcp/outlook/base.py`); tests run on
144
+ any OS against an in-memory fake (`tests/conftest.py`).
145
+
146
+ ## License
147
+
148
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """MCP server controlling Microsoft Outlook desktop via the Win32 COM API."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from outlook_mcp.server import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,75 @@
1
+ """Outlook object model constants, hardcoded so we never depend on
2
+ win32com.client.constants (which requires makepy/gencache type libraries).
3
+
4
+ Values come from the documented Outlook enumerations:
5
+ https://learn.microsoft.com/en-us/office/vba/api/outlook.oldefaultfolders
6
+ """
7
+
8
+ # OlDefaultFolders
9
+ OL_FOLDER_DELETED_ITEMS = 3
10
+ OL_FOLDER_OUTBOX = 4
11
+ OL_FOLDER_SENT_MAIL = 5
12
+ OL_FOLDER_INBOX = 6
13
+ OL_FOLDER_CALENDAR = 9
14
+ OL_FOLDER_CONTACTS = 10
15
+ OL_FOLDER_JOURNAL = 11
16
+ OL_FOLDER_NOTES = 12
17
+ OL_FOLDER_TASKS = 13
18
+ OL_FOLDER_DRAFTS = 16
19
+
20
+ # OlItemType (Application.CreateItem)
21
+ OL_MAIL_ITEM = 0
22
+ OL_APPOINTMENT_ITEM = 1
23
+ OL_CONTACT_ITEM = 2
24
+ OL_TASK_ITEM = 3
25
+ OL_JOURNAL_ITEM = 4
26
+ OL_NOTE_ITEM = 5
27
+ OL_POST_ITEM = 6
28
+
29
+ # OlBodyFormat
30
+ OL_FORMAT_PLAIN = 1
31
+ OL_FORMAT_HTML = 2
32
+ OL_FORMAT_RICHTEXT = 3
33
+
34
+ # OlMeetingResponse (AppointmentItem.Respond)
35
+ OL_MEETING_TENTATIVE = 2
36
+ OL_MEETING_ACCEPTED = 3
37
+ OL_MEETING_DECLINED = 4
38
+
39
+ # OlMeetingStatus
40
+ OL_NONMEETING = 0
41
+ OL_MEETING = 1
42
+
43
+ # OlTaskStatus
44
+ OL_TASK_NOT_STARTED = 0
45
+ OL_TASK_IN_PROGRESS = 1
46
+ OL_TASK_COMPLETE = 2
47
+
48
+ # OlImportance
49
+ OL_IMPORTANCE_LOW = 0
50
+ OL_IMPORTANCE_NORMAL = 1
51
+ OL_IMPORTANCE_HIGH = 2
52
+
53
+ # Friendly folder names accepted by tools, mapped to default folder ids.
54
+ FOLDER_NAME_TO_ID = {
55
+ "inbox": OL_FOLDER_INBOX,
56
+ "sent": OL_FOLDER_SENT_MAIL,
57
+ "sent items": OL_FOLDER_SENT_MAIL,
58
+ "drafts": OL_FOLDER_DRAFTS,
59
+ "deleted": OL_FOLDER_DELETED_ITEMS,
60
+ "deleted items": OL_FOLDER_DELETED_ITEMS,
61
+ "trash": OL_FOLDER_DELETED_ITEMS,
62
+ "outbox": OL_FOLDER_OUTBOX,
63
+ }
64
+
65
+ IMPORTANCE_NAME_TO_ID = {
66
+ "low": OL_IMPORTANCE_LOW,
67
+ "normal": OL_IMPORTANCE_NORMAL,
68
+ "high": OL_IMPORTANCE_HIGH,
69
+ }
70
+
71
+ MEETING_RESPONSE_TO_ID = {
72
+ "accept": OL_MEETING_ACCEPTED,
73
+ "decline": OL_MEETING_DECLINED,
74
+ "tentative": OL_MEETING_TENTATIVE,
75
+ }
@@ -0,0 +1,26 @@
1
+ """Error types and COM error formatting."""
2
+
3
+
4
+ class ToolError(Exception):
5
+ """User-facing tool failure (bad input, missing item, COM error)."""
6
+
7
+
8
+ def format_com_error(exc: BaseException) -> str:
9
+ """Turn a pywintypes.com_error into a readable one-line message.
10
+
11
+ com_error args are (hresult, strerror, excepinfo, argerror) where
12
+ excepinfo is None or a 6-tuple whose third element is the source
13
+ application's own description (usually the most useful text).
14
+ """
15
+ try:
16
+ hresult = exc.args[0]
17
+ strerror = exc.args[1]
18
+ excepinfo = exc.args[2]
19
+ except (IndexError, AttributeError):
20
+ return f"Outlook COM error: {exc}"
21
+ detail = None
22
+ if excepinfo and len(excepinfo) > 2 and excepinfo[2]:
23
+ detail = str(excepinfo[2]).strip()
24
+ if detail:
25
+ return f"Outlook error: {detail} (HRESULT {hresult})"
26
+ return f"Outlook error: {strerror} (HRESULT {hresult})"
File without changes
@@ -0,0 +1,118 @@
1
+ """Abstract Outlook client interface.
2
+
3
+ This module is importable on any platform. The real implementation
4
+ (WindowsOutlookClient in outlook_mcp.outlook.client) is only imported on
5
+ Windows; tests inject a fake implementing this interface.
6
+
7
+ All methods accept and return plain JSON-able Python values (str, int,
8
+ bool, dict, list). Items are addressed by an opaque ``id`` string of the
9
+ form ``"{EntryID}|{StoreID}"`` returned from list/search calls.
10
+ """
11
+
12
+ from typing import Optional
13
+
14
+ from outlook_mcp.errors import ToolError
15
+
16
+
17
+ class OutlookClientBase:
18
+ # ---- Email -----------------------------------------------------
19
+ def list_folders(self) -> list:
20
+ raise NotImplementedError
21
+
22
+ def list_emails(self, folder: str = "inbox", count: int = 10,
23
+ unread_only: bool = False) -> list:
24
+ raise NotImplementedError
25
+
26
+ def search_emails(self, query: str, folder: str = "inbox", count: int = 10,
27
+ since_days: Optional[int] = None) -> list:
28
+ raise NotImplementedError
29
+
30
+ def get_email(self, email_id: str, prefer_html: bool = False) -> dict:
31
+ raise NotImplementedError
32
+
33
+ def send_email(self, to: list, subject: str, body: str,
34
+ cc: Optional[list] = None, bcc: Optional[list] = None,
35
+ html: bool = False) -> dict:
36
+ raise NotImplementedError
37
+
38
+ def create_draft(self, to: list, subject: str, body: str,
39
+ cc: Optional[list] = None, bcc: Optional[list] = None,
40
+ html: bool = False) -> dict:
41
+ raise NotImplementedError
42
+
43
+ def reply_email(self, email_id: str, body: str, reply_all: bool = False,
44
+ html: bool = False, send: bool = True) -> dict:
45
+ raise NotImplementedError
46
+
47
+ def move_email(self, email_id: str, target_folder: str) -> dict:
48
+ raise NotImplementedError
49
+
50
+ def delete_email(self, email_id: str) -> dict:
51
+ raise NotImplementedError
52
+
53
+ # ---- Calendar --------------------------------------------------
54
+ def list_events(self, start_date: Optional[str] = None,
55
+ end_date: Optional[str] = None) -> list:
56
+ raise NotImplementedError
57
+
58
+ def get_event(self, event_id: str) -> dict:
59
+ raise NotImplementedError
60
+
61
+ def create_event(self, subject: str, start: str, end: str,
62
+ body: Optional[str] = None, location: Optional[str] = None,
63
+ attendees: Optional[list] = None, all_day: bool = False,
64
+ reminder_minutes: Optional[int] = None) -> dict:
65
+ raise NotImplementedError
66
+
67
+ def respond_to_meeting(self, event_id: str, response: str,
68
+ comment: Optional[str] = None,
69
+ send: bool = True) -> dict:
70
+ raise NotImplementedError
71
+
72
+ # ---- Attachments -----------------------------------------------
73
+ def list_attachments(self, email_id: str) -> list:
74
+ raise NotImplementedError
75
+
76
+ def save_attachments(self, email_id: str, save_dir: str,
77
+ attachment_names: Optional[list] = None) -> list:
78
+ raise NotImplementedError
79
+
80
+ # ---- Tasks -----------------------------------------------------
81
+ def list_tasks(self, include_completed: bool = False) -> list:
82
+ raise NotImplementedError
83
+
84
+ def create_task(self, subject: str, body: Optional[str] = None,
85
+ due_date: Optional[str] = None,
86
+ importance: str = "normal") -> dict:
87
+ raise NotImplementedError
88
+
89
+ def complete_task(self, task_id: str) -> dict:
90
+ raise NotImplementedError
91
+
92
+ # ---- Notes -----------------------------------------------------
93
+ def list_notes(self) -> list:
94
+ raise NotImplementedError
95
+
96
+ def get_note(self, note_id: str) -> dict:
97
+ raise NotImplementedError
98
+
99
+ def create_note(self, body: str) -> dict:
100
+ raise NotImplementedError
101
+
102
+
103
+ class UnavailableClient(OutlookClientBase):
104
+ """Stands in when not running on Windows: the server starts and lists
105
+ tools anywhere, but every call explains the platform requirement."""
106
+
107
+ _MESSAGE = ("Outlook is not available: this server requires Windows with "
108
+ "classic Outlook desktop installed (Outlook COM automation).")
109
+
110
+ def __getattribute__(self, name):
111
+ base = object.__getattribute__(self, "__class__").__mro__[1]
112
+ if name.startswith("_") or not callable(getattr(base, name, None)):
113
+ return object.__getattribute__(self, name)
114
+
115
+ def _unavailable(*args, **kwargs):
116
+ raise ToolError(UnavailableClient._MESSAGE)
117
+
118
+ return _unavailable