@rixtay/mail-mcp 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/auth.js +83 -0
- package/dist/auth.js.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/format.js +62 -0
- package/dist/format.js.map +1 -0
- package/dist/graph.js +158 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/login.js +13 -0
- package/dist/login.js.map +1 -0
- package/dist/mail.js +286 -0
- package/dist/mail.js.map +1 -0
- package/dist/server.js +16 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/actions.js +102 -0
- package/dist/tools/actions.js.map +1 -0
- package/dist/tools/folders.js +44 -0
- package/dist/tools/folders.js.map +1 -0
- package/dist/tools/messages.js +130 -0
- package/dist/tools/messages.js.map +1 -0
- package/dist/tools/shared.js +20 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/unsubscribe.js +92 -0
- package/dist/unsubscribe.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rixtayz
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Mail-MCP
|
|
2
|
+
|
|
3
|
+
A small, focused [MCP](https://modelcontextprotocol.io) server that lets Claude (Claude Desktop, Cowork, Claude Code or any MCP client) work on a **personal Outlook.com / Hotmail / Live mailbox** through Microsoft Graph: read and search mail, file it into folders, delete it, and **unsubscribe from newsletters**.
|
|
4
|
+
|
|
5
|
+
Built for one job: clean up an overflowing personal inbox with an AI assistant, safely.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Tools
|
|
10
|
+
|
|
11
|
+
Nine tools, all prefixed `mail_`:
|
|
12
|
+
|
|
13
|
+
| Tool | What it does |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `mail_list_folders` | Folder tree with total / unread counts |
|
|
16
|
+
| `mail_create_folder` | Create a folder (idempotent) |
|
|
17
|
+
| `mail_search` | List / search messages (sender, date range, unread, full text), paginated |
|
|
18
|
+
| `mail_get_message` | Full content of one message + detected unsubscribe options |
|
|
19
|
+
| `mail_senders_summary` | **Aggregate a whole folder by sender**: volume, latest message, available unsubscribe method |
|
|
20
|
+
| `mail_move` | Move up to 500 messages to a folder |
|
|
21
|
+
| `mail_delete` | Move to Deleted Items by default, `permanent: true` to purge |
|
|
22
|
+
| `mail_bulk_by_sender` | Move or delete every message from one sender (`dryRun` supported) |
|
|
23
|
+
| `mail_unsubscribe` | RFC 8058 one-click POST → `mailto:` email → otherwise a URL for the assistant to open in a browser |
|
|
24
|
+
|
|
25
|
+
Design choices:
|
|
26
|
+
|
|
27
|
+
- **Safe by default.** Deletion goes to Deleted Items (visible and restorable in Outlook). Permanent deletion requires an explicit flag. Bulk actions support `dryRun`.
|
|
28
|
+
- **Efficient on big mailboxes.** `mail_senders_summary` scans thousands of messages in a few seconds (1,000-item pages, minimal `$select`) and only fetches headers for the top senders through `$batch`.
|
|
29
|
+
- **No generic send tool.** The `Mail.Send` permission is used solely to send `mailto:` unsubscribe requests.
|
|
30
|
+
- Every tool ships a strict input schema, an output schema and MCP annotations (`readOnlyHint`, `destructiveHint`, …) so hosts can auto-approve read-only calls.
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Node.js 20 or newer.
|
|
35
|
+
- A personal Microsoft account (outlook.com, hotmail.com, live.com, msn.com).
|
|
36
|
+
- A free Microsoft Entra app registration (5 minutes, below). Password-based IMAP was switched off for personal accounts in September 2024, so an OAuth app is the only supported way in.
|
|
37
|
+
|
|
38
|
+
## 1. Register an app in Microsoft Entra (once, free)
|
|
39
|
+
|
|
40
|
+
No Azure subscription is needed for a public client app.
|
|
41
|
+
|
|
42
|
+
1. Go to <https://entra.microsoft.com> and sign in with your personal Microsoft account.
|
|
43
|
+
2. **Identity → Applications → App registrations → New registration**.
|
|
44
|
+
3. Fill in:
|
|
45
|
+
- **Name**: `Mail-MCP`
|
|
46
|
+
- **Supported account types**: **Personal Microsoft accounts only**
|
|
47
|
+
- **Redirect URI**: platform **Mobile and desktop applications**, value `http://localhost`
|
|
48
|
+
4. Click **Register** and copy the **Application (client) ID** (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`).
|
|
49
|
+
5. Under **Authentication**, make sure `http://localhost` is listed under "Mobile and desktop applications" and **Allow public client flows** is **Yes**.
|
|
50
|
+
6. Under **API permissions**, add Microsoft Graph *delegated* permissions: `Mail.ReadWrite`, `Mail.Send`, `User.Read`, `offline_access`. No admin consent is needed; you consent at first sign-in.
|
|
51
|
+
|
|
52
|
+
No client secret is created: the server is a public client using the authorization code flow with PKCE.
|
|
53
|
+
|
|
54
|
+
## 2. Sign in
|
|
55
|
+
|
|
56
|
+
No install needed, `npx` fetches the package from npm (`@rixtay/mail-mcp`, the installed command is `mail-mcp`):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
MAIL_MCP_CLIENT_ID=<your-client-id> npx -y @rixtay/mail-mcp login
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or from a clone:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/Rixtayz/Mail-MCP.git
|
|
66
|
+
cd Mail-MCP
|
|
67
|
+
npm install && npm run build
|
|
68
|
+
MAIL_MCP_CLIENT_ID=<your-client-id> npm run login
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `login` command opens your system browser, signs you in with Microsoft, then stores the token cache in `~/.mail-mcp/token-cache.json` (file mode 600). Tokens refresh silently for 90 rolling days. If a tool ever answers "Token expired", run the same command again.
|
|
72
|
+
|
|
73
|
+
| Environment variable | Purpose |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `MAIL_MCP_CLIENT_ID` | **Required.** Application (client) ID from step 1 |
|
|
76
|
+
| `MAIL_MCP_CACHE_PATH` | Optional. Token cache location |
|
|
77
|
+
|
|
78
|
+
## 3. Connect to Claude Desktop / Cowork
|
|
79
|
+
|
|
80
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS (or `%APPDATA%\Claude\claude_desktop_config.json` on Windows), reachable through **Settings → Developer → Edit Config**.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"mail": {
|
|
86
|
+
"command": "npx",
|
|
87
|
+
"args": ["-y", "@rixtay/mail-mcp"],
|
|
88
|
+
"env": {
|
|
89
|
+
"MAIL_MCP_CLIENT_ID": "<your-client-id>"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If Claude Desktop cannot find `npx` (it does not inherit your shell `PATH`), use absolute paths instead: `"command": "/absolute/path/to/node"`, `"args": ["/absolute/path/to/Mail-MCP/dist/index.js"]`.
|
|
97
|
+
|
|
98
|
+
Quit Claude Desktop completely and start it again. Logs: `~/Library/Logs/Claude/mcp-server-mail.log`.
|
|
99
|
+
|
|
100
|
+
Cowork runs local MCP servers only in **local** sessions, not in cloud sessions.
|
|
101
|
+
|
|
102
|
+
## 4. Connect to Claude Code
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
claude mcp add --scope user --env MAIL_MCP_CLIENT_ID=<your-client-id> --transport stdio mail -- npx -y @rixtay/mail-mcp
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 5. Example prompts
|
|
109
|
+
|
|
110
|
+
- "Summarise the senders in my inbox and flag the newsletters."
|
|
111
|
+
- "Unsubscribe me from every newsletter I haven't opened in six months, then move their messages to Deleted Items."
|
|
112
|
+
- "Create an Invoices folder and move everything from billing@vendor.com into it."
|
|
113
|
+
- "Show me the latest email from my bank."
|
|
114
|
+
|
|
115
|
+
Typical flow: `mail_senders_summary` → the assistant proposes a list, you confirm → `mail_unsubscribe(lastMessageId)` per newsletter (when the answer is `method: "browser"`, the assistant opens the URL in its browser and finishes there) → `mail_bulk_by_sender(action: "delete")`.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm test # vitest: header parsing, Graph retry/batch/pagination, service with a mocked Graph
|
|
121
|
+
npm run typecheck
|
|
122
|
+
npm run inspect # MCP Inspector against dist/index.js
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Layout:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
src/index.ts CLI entry point: `mail-mcp` serves stdio, `mail-mcp login` signs in
|
|
129
|
+
src/login.ts interactive sign-in flow
|
|
130
|
+
src/server.ts builds the McpServer and registers the tools
|
|
131
|
+
src/auth.ts MSAL public client, file-based token cache
|
|
132
|
+
src/graph.ts Graph client: bearer auth, 429/503 retry, pagination, $batch in chunks of 20
|
|
133
|
+
src/mail.ts business logic (folders, search, sender summary, move/delete, unsubscribe cascade)
|
|
134
|
+
src/unsubscribe.ts List-Unsubscribe / List-Unsubscribe-Post parsing, RFC 8058 one-click POST
|
|
135
|
+
src/tools/*.ts tool definitions (zod v4 schemas, annotations)
|
|
136
|
+
scripts/login.ts one-time interactive sign-in
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Stack: `@modelcontextprotocol/server` v2, zod v4, `@azure/msal-node` v6, `html-to-text`.
|
|
140
|
+
|
|
141
|
+
## Things worth knowing
|
|
142
|
+
|
|
143
|
+
- **Message ids change** whenever a message changes folder. `mail_move` and `mail_delete` return the old → new id mapping.
|
|
144
|
+
- A normal delete is a **move to Deleted Items**. Graph's own `DELETE` would drop the item into Recoverable Items, which is invisible in Outlook, so it is deliberately not used.
|
|
145
|
+
- Microsoft throttles Outlook to 10,000 requests per 10 minutes per mailbox and 4 concurrent requests. Batches are serialised and retried on 429.
|
|
146
|
+
- `mail_search` with `query` (full text) cannot be combined with the other filters (Graph limitation) and tops out at a few hundred results.
|
|
147
|
+
- Whether an unsubscribe actually takes effect is up to the sender. One-click and `mailto:` send the request; the assistant's browser handles the rest.
|
|
148
|
+
- The authority is `login.microsoftonline.com/consumers`. With `common`, refresh tokens for personal accounts are rejected after the first refresh.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PublicClientApplication } from "@azure/msal-node";
|
|
2
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { AUTHORITY, SCOPES } from "./constants.js";
|
|
6
|
+
export class AuthError extends Error {
|
|
7
|
+
}
|
|
8
|
+
export function cachePath() {
|
|
9
|
+
return process.env.MAIL_MCP_CACHE_PATH ?? join(homedir(), ".mail-mcp", "token-cache.json");
|
|
10
|
+
}
|
|
11
|
+
export function clientId() {
|
|
12
|
+
const id = process.env.MAIL_MCP_CLIENT_ID;
|
|
13
|
+
if (!id) {
|
|
14
|
+
throw new AuthError("MAIL_MCP_CLIENT_ID is not set. Put the Application (client) ID of your Entra app registration in the MAIL_MCP_CLIENT_ID environment variable (see README).");
|
|
15
|
+
}
|
|
16
|
+
return id;
|
|
17
|
+
}
|
|
18
|
+
/** Persists the MSAL token cache to a 0600 JSON file. */
|
|
19
|
+
export class FileCachePlugin {
|
|
20
|
+
path;
|
|
21
|
+
constructor(path) {
|
|
22
|
+
this.path = path;
|
|
23
|
+
}
|
|
24
|
+
async beforeCacheAccess(ctx) {
|
|
25
|
+
try {
|
|
26
|
+
const data = await readFile(this.path, "utf8");
|
|
27
|
+
ctx.tokenCache.deserialize(data);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err.code !== "ENOENT")
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async afterCacheAccess(ctx) {
|
|
35
|
+
if (!ctx.cacheHasChanged)
|
|
36
|
+
return;
|
|
37
|
+
await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
38
|
+
await writeFile(this.path, ctx.tokenCache.serialize(), { mode: 0o600 });
|
|
39
|
+
await chmod(this.path, 0o600);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let pca;
|
|
43
|
+
export function getClient() {
|
|
44
|
+
if (!pca) {
|
|
45
|
+
pca = new PublicClientApplication({
|
|
46
|
+
auth: { clientId: clientId(), authority: AUTHORITY },
|
|
47
|
+
cache: { cachePlugin: new FileCachePlugin(cachePath()) },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return pca;
|
|
51
|
+
}
|
|
52
|
+
export async function getAccount() {
|
|
53
|
+
const accounts = await getClient().getTokenCache().getAllAccounts();
|
|
54
|
+
return accounts[0];
|
|
55
|
+
}
|
|
56
|
+
/** Silent token acquisition. Throws AuthError with an actionable message when login is required. */
|
|
57
|
+
export async function getAccessToken() {
|
|
58
|
+
const account = await getAccount();
|
|
59
|
+
if (!account) {
|
|
60
|
+
throw new AuthError("No account is signed in. Run `npm run login` in the Mail-MCP folder to sign in to your Outlook.com account once.");
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const result = await getClient().acquireTokenSilent({ account, scopes: SCOPES });
|
|
64
|
+
return result.accessToken;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
+
throw new AuthError(`Token expired or invalid (${msg.split("\n")[0]}). Run \`npm run login\` again in the Mail-MCP folder.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Interactive login through the system browser (auth code + PKCE, localhost loopback). */
|
|
72
|
+
export async function loginInteractive(openBrowser) {
|
|
73
|
+
const result = await getClient().acquireTokenInteractive({
|
|
74
|
+
scopes: SCOPES,
|
|
75
|
+
openBrowser,
|
|
76
|
+
successTemplate: "<h2>Mail-MCP is connected.</h2><p>You can close this tab.</p>",
|
|
77
|
+
errorTemplate: "<h2>Mail-MCP sign-in failed.</h2><p>Go back to the terminal.</p>",
|
|
78
|
+
});
|
|
79
|
+
if (!result.account)
|
|
80
|
+
throw new AuthError("Sign-in did not return an account.");
|
|
81
|
+
return result.account;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const GRAPH_BASE = "https://graph.microsoft.com/v1.0";
|
|
2
|
+
export const CHARACTER_LIMIT = 25_000;
|
|
3
|
+
export const BATCH_SIZE = 20;
|
|
4
|
+
export const MAX_RETRIES = 5;
|
|
5
|
+
export const PAGE_SIZE_SCAN = 1000;
|
|
6
|
+
export const SCOPES = ["Mail.ReadWrite", "Mail.Send", "User.Read"];
|
|
7
|
+
export const AUTHORITY = "https://login.microsoftonline.com/consumers";
|
|
8
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,kCAAkC,CAAC;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AACtC,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AACnC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,gBAAgB,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,SAAS,GAAG,6CAA6C,CAAC"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { htmlToText } from "html-to-text";
|
|
2
|
+
import { CHARACTER_LIMIT } from "./constants.js";
|
|
3
|
+
/** Strip invisible filler characters newsletters use for preview padding, collapse whitespace. */
|
|
4
|
+
export function cleanPreview(text, max = 200) {
|
|
5
|
+
return text
|
|
6
|
+
.replace(/[\u00AD\u034F\u061C\u180E\u200B-\u200F\u2028-\u202F\u205F-\u206F\u2800\u3164\uFEFF\uFFA0]/g, "")
|
|
7
|
+
.replace(/\s+/g, " ")
|
|
8
|
+
.trim()
|
|
9
|
+
.slice(0, max);
|
|
10
|
+
}
|
|
11
|
+
export function summarizeMessage(m) {
|
|
12
|
+
return {
|
|
13
|
+
id: m.id,
|
|
14
|
+
from: m.from?.emailAddress?.address?.toLowerCase() ?? "",
|
|
15
|
+
fromName: m.from?.emailAddress?.name ?? "",
|
|
16
|
+
subject: m.subject ?? "(no subject)",
|
|
17
|
+
receivedDateTime: m.receivedDateTime ?? "",
|
|
18
|
+
isRead: m.isRead ?? false,
|
|
19
|
+
preview: cleanPreview(m.bodyPreview ?? ""),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Convert a Graph body (html or text) to readable plain text. */
|
|
23
|
+
export function bodyToText(body) {
|
|
24
|
+
if (!body?.content)
|
|
25
|
+
return "";
|
|
26
|
+
if (body.contentType !== "html")
|
|
27
|
+
return body.content;
|
|
28
|
+
return htmlToText(body.content, {
|
|
29
|
+
wordwrap: false,
|
|
30
|
+
selectors: [
|
|
31
|
+
{ selector: "img", format: "skip" },
|
|
32
|
+
{ selector: "style", format: "skip" },
|
|
33
|
+
{ selector: "script", format: "skip" },
|
|
34
|
+
{ selector: "h1", options: { uppercase: false } },
|
|
35
|
+
{ selector: "h2", options: { uppercase: false } },
|
|
36
|
+
{ selector: "h3", options: { uppercase: false } },
|
|
37
|
+
{ selector: "a", options: { ignoreHref: false, hideLinkHrefIfSameAsText: true } },
|
|
38
|
+
],
|
|
39
|
+
})
|
|
40
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
41
|
+
.trim();
|
|
42
|
+
}
|
|
43
|
+
/** Truncate text to the character limit, appending a marker when cut. */
|
|
44
|
+
export function truncate(text, limit = CHARACTER_LIMIT) {
|
|
45
|
+
if (text.length <= limit)
|
|
46
|
+
return { text, truncated: false };
|
|
47
|
+
return { text: text.slice(0, limit) + "\n\n[... truncated]", truncated: true };
|
|
48
|
+
}
|
|
49
|
+
/** Render a list of message summaries as compact markdown. */
|
|
50
|
+
export function messagesToMarkdown(items) {
|
|
51
|
+
if (items.length === 0)
|
|
52
|
+
return "_No messages._";
|
|
53
|
+
return items
|
|
54
|
+
.map((m) => {
|
|
55
|
+
const flag = m.isRead ? " " : "•";
|
|
56
|
+
const date = m.receivedDateTime.slice(0, 16).replace("T", " ");
|
|
57
|
+
const who = m.fromName ? `${m.fromName} <${m.from}>` : m.from;
|
|
58
|
+
return `${flag} ${date} | ${who} | **${m.subject}** | id: \`${m.id}\`\n ${m.preview}`;
|
|
59
|
+
})
|
|
60
|
+
.join("\n");
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/graph.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { BATCH_SIZE, GRAPH_BASE, MAX_RETRIES } from "./constants.js";
|
|
2
|
+
export class GraphError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, status, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
12
|
+
/** Map Graph HTTP errors to actionable messages for the model. */
|
|
13
|
+
export function describeGraphError(status, code, raw) {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case 401:
|
|
16
|
+
return "Unauthorized (401): the token is expired or invalid. Run `npm run login` again in the Mail-MCP folder.";
|
|
17
|
+
case 403:
|
|
18
|
+
return `Forbidden (403${code ? ", " + code : ""}): the app registration probably lacks the Mail.ReadWrite / Mail.Send permissions. Check the README, then run \`npm run login\` again.`;
|
|
19
|
+
case 404:
|
|
20
|
+
return "Not found (404): the message or folder no longer exists (moved or deleted; ids change when a message changes folder). Call mail_search or mail_list_folders again to get fresh ids.";
|
|
21
|
+
case 429:
|
|
22
|
+
return "Microsoft rate limit hit (429) despite retries. Wait a minute, then retry with fewer items.";
|
|
23
|
+
default:
|
|
24
|
+
return `Graph error ${status}${code ? " (" + code + ")" : ""}: ${raw.slice(0, 300)}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class GraphClient {
|
|
28
|
+
getToken;
|
|
29
|
+
fetchImpl;
|
|
30
|
+
sleep;
|
|
31
|
+
baseUrl;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.getToken = opts.getToken;
|
|
34
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
35
|
+
this.sleep = opts.sleep ?? defaultSleep;
|
|
36
|
+
this.baseUrl = opts.baseUrl ?? GRAPH_BASE;
|
|
37
|
+
}
|
|
38
|
+
/** Absolute URL for a Graph path (or pass-through for a full nextLink URL). */
|
|
39
|
+
url(pathOrUrl) {
|
|
40
|
+
return /^https?:\/\//i.test(pathOrUrl) ? pathOrUrl : `${this.baseUrl}${pathOrUrl.startsWith("/") ? "" : "/"}${pathOrUrl}`;
|
|
41
|
+
}
|
|
42
|
+
/** Perform a request with bearer auth and retry on 429/503/504. Returns parsed JSON (or undefined for 204). */
|
|
43
|
+
async request(method, pathOrUrl, body, extraHeaders = {}) {
|
|
44
|
+
let attempt = 0;
|
|
45
|
+
for (;;) {
|
|
46
|
+
const token = await this.getToken();
|
|
47
|
+
const res = await this.fetchImpl(this.url(pathOrUrl), {
|
|
48
|
+
method,
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${token}`,
|
|
51
|
+
Accept: "application/json",
|
|
52
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
53
|
+
...extraHeaders,
|
|
54
|
+
},
|
|
55
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
56
|
+
});
|
|
57
|
+
if ((res.status === 429 || res.status === 503 || res.status === 504) && attempt < MAX_RETRIES) {
|
|
58
|
+
attempt++;
|
|
59
|
+
const retryAfter = Number(res.headers.get("Retry-After"));
|
|
60
|
+
const delay = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : Math.min(2 ** attempt * 500, 10_000);
|
|
61
|
+
await this.sleep(delay);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (res.status === 204 || res.status === 202)
|
|
65
|
+
return undefined;
|
|
66
|
+
const text = await res.text();
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
let code;
|
|
69
|
+
let message = text;
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(text);
|
|
72
|
+
code = parsed.error?.code;
|
|
73
|
+
message = parsed.error?.message ?? text;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* non-JSON error body */
|
|
77
|
+
}
|
|
78
|
+
throw new GraphError(describeGraphError(res.status, code, message), res.status, code);
|
|
79
|
+
}
|
|
80
|
+
return (text ? JSON.parse(text) : undefined);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
get(path) {
|
|
84
|
+
return this.request("GET", path);
|
|
85
|
+
}
|
|
86
|
+
post(path, body) {
|
|
87
|
+
return this.request("POST", path, body);
|
|
88
|
+
}
|
|
89
|
+
delete(path) {
|
|
90
|
+
return this.request("DELETE", path);
|
|
91
|
+
}
|
|
92
|
+
/** Iterate every item of a paged collection, following @odata.nextLink. */
|
|
93
|
+
async *paginate(firstPath, maxItems = Number.POSITIVE_INFINITY) {
|
|
94
|
+
let next = firstPath;
|
|
95
|
+
let count = 0;
|
|
96
|
+
while (next && count < maxItems) {
|
|
97
|
+
const page = await this.get(next);
|
|
98
|
+
for (const item of page.value) {
|
|
99
|
+
yield item;
|
|
100
|
+
if (++count >= maxItems)
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
next = page["@odata.nextLink"];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Fetch one page and return items plus the opaque cursor for the next page. */
|
|
107
|
+
async page(pathOrCursor) {
|
|
108
|
+
const page = await this.get(pathOrCursor);
|
|
109
|
+
const nextCursor = page["@odata.nextLink"];
|
|
110
|
+
return { items: page.value, ...(nextCursor ? { nextCursor } : {}) };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Execute requests through /$batch in serialized chunks of 20.
|
|
114
|
+
* Returns one response per request id, in input order. Retries throttled sub-requests.
|
|
115
|
+
*/
|
|
116
|
+
async batch(requests) {
|
|
117
|
+
const results = new Map();
|
|
118
|
+
let pending = requests.map((r) => ({ ...r, url: r.url.startsWith("/") ? r.url : `/${r.url}` }));
|
|
119
|
+
let attempt = 0;
|
|
120
|
+
while (pending.length > 0) {
|
|
121
|
+
const retry = [];
|
|
122
|
+
for (const chunk of chunkArray(pending, BATCH_SIZE)) {
|
|
123
|
+
const res = await this.post("/$batch", { requests: chunk });
|
|
124
|
+
for (const r of res.responses) {
|
|
125
|
+
if ((r.status === 429 || r.status === 503) && attempt < MAX_RETRIES) {
|
|
126
|
+
const original = chunk.find((c) => c.id === r.id);
|
|
127
|
+
if (original)
|
|
128
|
+
retry.push(original);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
results.set(r.id, r);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (retry.length === 0)
|
|
136
|
+
break;
|
|
137
|
+
attempt++;
|
|
138
|
+
await this.sleep(Math.min(2 ** attempt * 1000, 15_000));
|
|
139
|
+
pending = retry;
|
|
140
|
+
}
|
|
141
|
+
// Anything still pending after retries is reported as 429.
|
|
142
|
+
for (const p of pending)
|
|
143
|
+
if (!results.has(p.id))
|
|
144
|
+
results.set(p.id, { id: p.id, status: 429 });
|
|
145
|
+
return requests.map((r) => results.get(r.id) ?? { id: r.id, status: 0 });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function chunkArray(arr, size) {
|
|
149
|
+
const out = [];
|
|
150
|
+
for (let i = 0; i < arr.length; i += size)
|
|
151
|
+
out.push(arr.slice(i, i + size));
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
/** Escape a string literal for use inside an OData $filter. */
|
|
155
|
+
export function odataString(value) {
|
|
156
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
3
|
+
import { runLogin } from "./login.js";
|
|
4
|
+
import { buildServer } from "./server.js";
|
|
5
|
+
// stdout is reserved for JSON-RPC; everything else goes to stderr.
|
|
6
|
+
console.log = (...args) => console.error(...args);
|
|
7
|
+
const command = process.argv[2];
|
|
8
|
+
if (command === "login") {
|
|
9
|
+
runLogin().catch((err) => {
|
|
10
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
else if (command === "--help" || command === "-h" || command === "help") {
|
|
15
|
+
console.error("Usage:\n mail-mcp start the MCP server on stdio\n mail-mcp login sign in to your Microsoft account once (needs MAIL_MCP_CLIENT_ID)");
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
serveStdio(() => buildServer());
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,mEAAmE;AACnE,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AAE7D,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEhC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;IACxB,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IAC1E,OAAO,CAAC,KAAK,CAAC,kJAAkJ,CAAC,CAAC;AACpK,CAAC;KAAM,CAAC;IACN,UAAU,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/login.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import open from "open";
|
|
2
|
+
import { cachePath, clientId, loginInteractive } from "./auth.js";
|
|
3
|
+
/** One-time interactive sign-in. Writes only to stderr so it is safe to call from any context. */
|
|
4
|
+
export async function runLogin() {
|
|
5
|
+
clientId();
|
|
6
|
+
console.error("Opening your browser for Microsoft sign-in…");
|
|
7
|
+
const account = await loginInteractive(async (url) => {
|
|
8
|
+
console.error(`If the browser does not open, visit:\n${url}\n`);
|
|
9
|
+
await open(url);
|
|
10
|
+
});
|
|
11
|
+
console.error(`Signed in as ${account.username}\nToken cache saved to ${cachePath()}`);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElE,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,QAAQ,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACnD,OAAO,CAAC,KAAK,CAAC,yCAAyC,GAAG,IAAI,CAAC,CAAC;QAChE,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,CAAC,gBAAgB,OAAO,CAAC,QAAQ,0BAA0B,SAAS,EAAE,EAAE,CAAC,CAAC;AACzF,CAAC"}
|