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.
- outlook_mcp_com-0.1.0/LICENSE +21 -0
- outlook_mcp_com-0.1.0/PKG-INFO +195 -0
- outlook_mcp_com-0.1.0/README.md +148 -0
- outlook_mcp_com-0.1.0/outlook_mcp/__init__.py +3 -0
- outlook_mcp_com-0.1.0/outlook_mcp/__main__.py +4 -0
- outlook_mcp_com-0.1.0/outlook_mcp/constants.py +75 -0
- outlook_mcp_com-0.1.0/outlook_mcp/errors.py +26 -0
- outlook_mcp_com-0.1.0/outlook_mcp/outlook/__init__.py +0 -0
- outlook_mcp_com-0.1.0/outlook_mcp/outlook/base.py +118 -0
- outlook_mcp_com-0.1.0/outlook_mcp/outlook/client.py +542 -0
- outlook_mcp_com-0.1.0/outlook_mcp/server.py +237 -0
- outlook_mcp_com-0.1.0/outlook_mcp_com.egg-info/PKG-INFO +195 -0
- outlook_mcp_com-0.1.0/outlook_mcp_com.egg-info/SOURCES.txt +19 -0
- outlook_mcp_com-0.1.0/outlook_mcp_com.egg-info/dependency_links.txt +1 -0
- outlook_mcp_com-0.1.0/outlook_mcp_com.egg-info/entry_points.txt +2 -0
- outlook_mcp_com-0.1.0/outlook_mcp_com.egg-info/requires.txt +7 -0
- outlook_mcp_com-0.1.0/outlook_mcp_com.egg-info/top_level.txt +1 -0
- outlook_mcp_com-0.1.0/pyproject.toml +37 -0
- outlook_mcp_com-0.1.0/setup.cfg +4 -0
- outlook_mcp_com-0.1.0/tests/test_registry.py +41 -0
- outlook_mcp_com-0.1.0/tests/test_tools.py +199 -0
|
@@ -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,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
|