@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/dist/mail.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { PAGE_SIZE_SCAN } from "./constants.js";
|
|
2
|
+
import { bodyToText, summarizeMessage, truncate } from "./format.js";
|
|
3
|
+
import { odataString } from "./graph.js";
|
|
4
|
+
import { oneClickPost, parseListUnsubscribe } from "./unsubscribe.js";
|
|
5
|
+
const WELL_KNOWN = new Set(["inbox", "drafts", "sentitems", "deleteditems", "junkemail", "archive", "outbox"]);
|
|
6
|
+
const LIST_SELECT = "id,subject,from,receivedDateTime,isRead,bodyPreview";
|
|
7
|
+
const SCAN_SELECT = "id,subject,from,receivedDateTime,isRead";
|
|
8
|
+
function isWellKnown(s) {
|
|
9
|
+
return WELL_KNOWN.has(s.toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
/** Heuristic: Graph ids are long base64-ish strings; folder names are not. */
|
|
12
|
+
function looksLikeId(s) {
|
|
13
|
+
return s.length > 60 && /^[A-Za-z0-9_=\-]+$/.test(s);
|
|
14
|
+
}
|
|
15
|
+
export class MailService {
|
|
16
|
+
graph;
|
|
17
|
+
fetchImpl;
|
|
18
|
+
folderCache;
|
|
19
|
+
summaryCache = new Map();
|
|
20
|
+
constructor(graph, fetchImpl = fetch) {
|
|
21
|
+
this.graph = graph;
|
|
22
|
+
this.fetchImpl = fetchImpl;
|
|
23
|
+
}
|
|
24
|
+
// ---------- Folders ----------
|
|
25
|
+
async listFolders(force = false) {
|
|
26
|
+
if (this.folderCache && !force)
|
|
27
|
+
return this.folderCache;
|
|
28
|
+
const out = [];
|
|
29
|
+
const walk = async (path, parentPath) => {
|
|
30
|
+
for await (const f of this.graph.paginate(`${path}?$top=250`)) {
|
|
31
|
+
const fullPath = parentPath ? `${parentPath}/${f.displayName}` : f.displayName;
|
|
32
|
+
out.push({
|
|
33
|
+
id: f.id,
|
|
34
|
+
name: f.displayName,
|
|
35
|
+
path: fullPath,
|
|
36
|
+
...(f.parentFolderId ? { parentId: f.parentFolderId } : {}),
|
|
37
|
+
total: f.totalItemCount ?? 0,
|
|
38
|
+
unread: f.unreadItemCount ?? 0,
|
|
39
|
+
...(f.wellKnownName ? { wellKnownName: f.wellKnownName } : {}),
|
|
40
|
+
});
|
|
41
|
+
if ((f.childFolderCount ?? 0) > 0)
|
|
42
|
+
await walk(`/me/mailFolders/${f.id}/childFolders`, fullPath);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
await walk("/me/mailFolders", "");
|
|
46
|
+
this.folderCache = out;
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/** Resolve a folder given a well-known name, display name, path, or id. Returns the Graph path segment. */
|
|
50
|
+
async resolveFolderId(nameOrId) {
|
|
51
|
+
if (!nameOrId)
|
|
52
|
+
return "inbox";
|
|
53
|
+
if (isWellKnown(nameOrId))
|
|
54
|
+
return nameOrId.toLowerCase();
|
|
55
|
+
if (looksLikeId(nameOrId))
|
|
56
|
+
return nameOrId;
|
|
57
|
+
const folders = await this.listFolders();
|
|
58
|
+
const lower = nameOrId.toLowerCase();
|
|
59
|
+
const match = folders.find((f) => f.path.toLowerCase() === lower) ??
|
|
60
|
+
folders.find((f) => f.name.toLowerCase() === lower) ??
|
|
61
|
+
folders.find((f) => f.wellKnownName?.toLowerCase() === lower);
|
|
62
|
+
if (!match) {
|
|
63
|
+
throw new Error(`Folder "${nameOrId}" not found. Available folders: ${folders.map((f) => f.path).join(", ")}. Use mail_create_folder to create it.`);
|
|
64
|
+
}
|
|
65
|
+
return match.id;
|
|
66
|
+
}
|
|
67
|
+
async createFolder(name, parent) {
|
|
68
|
+
const folders = await this.listFolders();
|
|
69
|
+
const parentId = parent ? await this.resolveFolderId(parent) : undefined;
|
|
70
|
+
const existing = folders.find((f) => f.name.toLowerCase() === name.toLowerCase() && (!parentId || f.parentId === parentId || parent?.toLowerCase() === f.path.split("/").slice(0, -1).join("/").toLowerCase()));
|
|
71
|
+
if (existing)
|
|
72
|
+
return { folder: existing, created: false };
|
|
73
|
+
const path = parentId ? `/me/mailFolders/${parentId}/childFolders` : "/me/mailFolders";
|
|
74
|
+
const created = await this.graph.post(path, { displayName: name, isHidden: false });
|
|
75
|
+
await this.listFolders(true);
|
|
76
|
+
const folder = (await this.listFolders()).find((f) => f.id === created.id) ?? {
|
|
77
|
+
id: created.id,
|
|
78
|
+
name: created.displayName,
|
|
79
|
+
path: created.displayName,
|
|
80
|
+
total: 0,
|
|
81
|
+
unread: 0,
|
|
82
|
+
};
|
|
83
|
+
return { folder, created: true };
|
|
84
|
+
}
|
|
85
|
+
// ---------- Messages ----------
|
|
86
|
+
async search(p) {
|
|
87
|
+
let path;
|
|
88
|
+
if (p.cursor) {
|
|
89
|
+
path = p.cursor;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const folderId = await this.resolveFolderId(p.folder);
|
|
93
|
+
const qs = [`$select=${LIST_SELECT}`, `$top=${p.limit}`];
|
|
94
|
+
if (p.query) {
|
|
95
|
+
qs.push(`$search=${encodeURIComponent(`"${p.query.replace(/"/g, '\\"')}"`)}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const filters = [];
|
|
99
|
+
// receivedDateTime must lead the filter when we also order by it.
|
|
100
|
+
filters.push(`receivedDateTime ge ${p.since ?? "1970-01-01T00:00:00Z"}`);
|
|
101
|
+
if (p.until)
|
|
102
|
+
filters.push(`receivedDateTime le ${p.until}`);
|
|
103
|
+
if (p.from)
|
|
104
|
+
filters.push(`from/emailAddress/address eq ${odataString(p.from.toLowerCase())}`);
|
|
105
|
+
if (p.unreadOnly)
|
|
106
|
+
filters.push("isRead eq false");
|
|
107
|
+
qs.push(`$filter=${encodeURIComponent(filters.join(" and "))}`);
|
|
108
|
+
qs.push(`$orderby=${encodeURIComponent("receivedDateTime desc")}`);
|
|
109
|
+
}
|
|
110
|
+
path = `/me/mailFolders/${folderId}/messages?${qs.join("&")}`;
|
|
111
|
+
}
|
|
112
|
+
const page = await this.graph.page(path);
|
|
113
|
+
return { items: page.items.map(summarizeMessage), ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}) };
|
|
114
|
+
}
|
|
115
|
+
async getMessage(id) {
|
|
116
|
+
const m = await this.graph.get(`/me/messages/${encodeURIComponent(id)}?$select=${LIST_SELECT},toRecipients,hasAttachments,body,internetMessageHeaders,webLink`);
|
|
117
|
+
const { text, truncated } = truncate(bodyToText(m.body));
|
|
118
|
+
return {
|
|
119
|
+
summary: summarizeMessage(m),
|
|
120
|
+
to: (m.toRecipients ?? []).map((r) => r.emailAddress?.address ?? "").filter(Boolean),
|
|
121
|
+
body: text,
|
|
122
|
+
truncated,
|
|
123
|
+
hasAttachments: m.hasAttachments ?? false,
|
|
124
|
+
...(m.webLink ? { webLink: m.webLink } : {}),
|
|
125
|
+
unsubscribe: parseListUnsubscribe(m.internetMessageHeaders),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async headersFor(ids) {
|
|
129
|
+
const reqs = ids.map((id) => ({
|
|
130
|
+
id,
|
|
131
|
+
method: "GET",
|
|
132
|
+
url: `/me/messages/${encodeURIComponent(id)}?$select=id,internetMessageHeaders`,
|
|
133
|
+
}));
|
|
134
|
+
const out = new Map();
|
|
135
|
+
for (const r of await this.graph.batch(reqs)) {
|
|
136
|
+
if (r.status >= 200 && r.status < 300) {
|
|
137
|
+
const body = r.body;
|
|
138
|
+
out.set(r.id, body?.internetMessageHeaders ?? []);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
async sendersSummary(folder, since, top) {
|
|
144
|
+
const folderId = await this.resolveFolderId(folder);
|
|
145
|
+
const key = `${folderId}|${since ?? ""}`;
|
|
146
|
+
let all = this.summaryCache.get(key);
|
|
147
|
+
let scanned = 0;
|
|
148
|
+
if (!all) {
|
|
149
|
+
const byAddress = new Map();
|
|
150
|
+
const qs = [`$select=${SCAN_SELECT}`, `$top=${PAGE_SIZE_SCAN}`];
|
|
151
|
+
if (since)
|
|
152
|
+
qs.push(`$filter=${encodeURIComponent(`receivedDateTime ge ${since}`)}`);
|
|
153
|
+
for await (const m of this.graph.paginate(`/me/mailFolders/${folderId}/messages?${qs.join("&")}`)) {
|
|
154
|
+
scanned++;
|
|
155
|
+
const address = m.from?.emailAddress?.address?.toLowerCase();
|
|
156
|
+
if (!address)
|
|
157
|
+
continue;
|
|
158
|
+
const received = m.receivedDateTime ?? "";
|
|
159
|
+
const cur = byAddress.get(address);
|
|
160
|
+
if (cur) {
|
|
161
|
+
cur.count++;
|
|
162
|
+
if (!m.isRead)
|
|
163
|
+
cur.unreadCount++;
|
|
164
|
+
if (received > cur.lastReceived) {
|
|
165
|
+
cur.lastReceived = received;
|
|
166
|
+
cur.lastSubject = m.subject ?? "";
|
|
167
|
+
cur.lastMessageId = m.id;
|
|
168
|
+
cur.name = m.from?.emailAddress?.name ?? cur.name;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
byAddress.set(address, {
|
|
173
|
+
address,
|
|
174
|
+
name: m.from?.emailAddress?.name ?? "",
|
|
175
|
+
domain: address.split("@")[1] ?? "",
|
|
176
|
+
count: 1,
|
|
177
|
+
unreadCount: m.isRead ? 0 : 1,
|
|
178
|
+
lastReceived: received,
|
|
179
|
+
lastSubject: m.subject ?? "",
|
|
180
|
+
lastMessageId: m.id,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
all = [...byAddress.values()].sort((a, b) => b.count - a.count || (a.lastReceived < b.lastReceived ? 1 : -1));
|
|
185
|
+
this.summaryCache.set(key, all);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
scanned = all.reduce((n, s) => n + s.count, 0);
|
|
189
|
+
}
|
|
190
|
+
const selected = all.slice(0, top);
|
|
191
|
+
const missing = selected.filter((s) => !s.unsubscribe);
|
|
192
|
+
if (missing.length > 0) {
|
|
193
|
+
const headers = await this.headersFor(missing.map((s) => s.lastMessageId));
|
|
194
|
+
for (const s of missing)
|
|
195
|
+
s.unsubscribe = parseListUnsubscribe(headers.get(s.lastMessageId));
|
|
196
|
+
}
|
|
197
|
+
return { senders: selected, scanned, totalSenders: all.length };
|
|
198
|
+
}
|
|
199
|
+
// ---------- Actions ----------
|
|
200
|
+
async runBatch(reqs) {
|
|
201
|
+
if (reqs.length === 0)
|
|
202
|
+
return [];
|
|
203
|
+
const responses = await this.graph.batch(reqs);
|
|
204
|
+
return responses.map((r) => {
|
|
205
|
+
const ok = r.status >= 200 && r.status < 300;
|
|
206
|
+
if (ok) {
|
|
207
|
+
const newId = r.body?.id;
|
|
208
|
+
return newId && newId !== r.id ? { id: r.id, ok, newId } : { id: r.id, ok };
|
|
209
|
+
}
|
|
210
|
+
const body = r.body;
|
|
211
|
+
const detail = body?.error?.code ?? body?.error?.message ?? `HTTP ${r.status}`;
|
|
212
|
+
return { id: r.id, ok, error: detail };
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async move(ids, folder) {
|
|
216
|
+
const destinationId = await this.resolveFolderId(folder);
|
|
217
|
+
return this.runBatch(ids.map((id) => ({ id, method: "POST", url: `/me/messages/${encodeURIComponent(id)}/move`, body: { destinationId }, headers: { "Content-Type": "application/json" } })));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Soft delete = move to the Deleted Items folder (visible in Outlook, restorable).
|
|
221
|
+
* Graph's DELETE would instead put the item in Recoverable Items, invisible to the user, so it is not used.
|
|
222
|
+
* Permanent = permanentDelete (irreversible).
|
|
223
|
+
*/
|
|
224
|
+
async delete(ids, permanent) {
|
|
225
|
+
if (!permanent)
|
|
226
|
+
return this.move(ids, "deleteditems");
|
|
227
|
+
return this.runBatch(ids.map((id) => ({ id, method: "POST", url: `/me/messages/${encodeURIComponent(id)}/permanentDelete` })));
|
|
228
|
+
}
|
|
229
|
+
/** Collect every message id from a sender in a folder. */
|
|
230
|
+
async idsFromSender(from, folder, maxItems = 5000) {
|
|
231
|
+
const folderId = await this.resolveFolderId(folder);
|
|
232
|
+
const filter = encodeURIComponent(`from/emailAddress/address eq ${odataString(from.toLowerCase())}`);
|
|
233
|
+
const ids = [];
|
|
234
|
+
for await (const m of this.graph.paginate(`/me/mailFolders/${folderId}/messages?$select=id&$top=${PAGE_SIZE_SCAN}&$filter=${filter}`, maxItems)) {
|
|
235
|
+
ids.push(m.id);
|
|
236
|
+
}
|
|
237
|
+
return ids;
|
|
238
|
+
}
|
|
239
|
+
invalidateSummaries() {
|
|
240
|
+
this.summaryCache.clear();
|
|
241
|
+
}
|
|
242
|
+
async unsubscribe(id) {
|
|
243
|
+
const m = await this.graph.get(`/me/messages/${encodeURIComponent(id)}?$select=id,subject,from,internetMessageHeaders`);
|
|
244
|
+
const info = parseListUnsubscribe(m.internetMessageHeaders);
|
|
245
|
+
const base = { from: m.from?.emailAddress?.address ?? "", subject: m.subject ?? "" };
|
|
246
|
+
if (info.oneClick && info.https) {
|
|
247
|
+
const r = await oneClickPost(info.https, this.fetchImpl);
|
|
248
|
+
if (r.ok)
|
|
249
|
+
return { ...base, method: "one-click", ok: true, url: info.https, detail: r.detail };
|
|
250
|
+
if (info.mailto) {
|
|
251
|
+
const viaMail = await this.unsubscribeByMail(info);
|
|
252
|
+
return { ...base, ...viaMail, detail: `${r.detail} Email fallback: ${viaMail.detail}` };
|
|
253
|
+
}
|
|
254
|
+
return { ...base, method: "browser", ok: false, url: info.https, detail: `${r.detail}` };
|
|
255
|
+
}
|
|
256
|
+
if (info.mailto) {
|
|
257
|
+
return { ...base, ...(await this.unsubscribeByMail(info)) };
|
|
258
|
+
}
|
|
259
|
+
if (info.https) {
|
|
260
|
+
return { ...base, method: "browser", ok: false, url: info.https, detail: "No one-click support: open this URL in a browser and confirm the unsubscribe." };
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
...base,
|
|
264
|
+
method: "none",
|
|
265
|
+
ok: false,
|
|
266
|
+
detail: "No List-Unsubscribe header. Look for an unsubscribe link in the body via mail_get_message, or remove the sender in bulk with mail_bulk_by_sender.",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async unsubscribeByMail(info) {
|
|
270
|
+
const address = info.mailto;
|
|
271
|
+
const subject = info.mailtoSubject ?? "unsubscribe";
|
|
272
|
+
const content = info.mailtoBody ?? "unsubscribe";
|
|
273
|
+
try {
|
|
274
|
+
await this.graph.post("/me/sendMail", {
|
|
275
|
+
message: { subject, body: { contentType: "text", content }, toRecipients: [{ emailAddress: { address } }] },
|
|
276
|
+
saveToSentItems: false,
|
|
277
|
+
});
|
|
278
|
+
return { method: "mailto", ok: true, mailto: address, detail: `Unsubscribe email sent to ${address} (subject "${subject}").` };
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
282
|
+
return { method: "mailto", ok: false, mailto: address, detail: `Sending to ${address} failed: ${msg}` };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=mail.js.map
|
package/dist/mail.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { getAccessToken } from "./auth.js";
|
|
3
|
+
import { GraphClient } from "./graph.js";
|
|
4
|
+
import { MailService } from "./mail.js";
|
|
5
|
+
import { registerActionTools } from "./tools/actions.js";
|
|
6
|
+
import { registerFolderTools } from "./tools/folders.js";
|
|
7
|
+
import { registerMessageTools } from "./tools/messages.js";
|
|
8
|
+
export function buildServer(mail) {
|
|
9
|
+
const service = mail ?? new MailService(new GraphClient({ getToken: getAccessToken }));
|
|
10
|
+
const server = new McpServer({ name: "mail-mcp", version: "0.1.0" });
|
|
11
|
+
registerFolderTools(server, service);
|
|
12
|
+
registerMessageTools(server, service);
|
|
13
|
+
registerActionTools(server, service);
|
|
14
|
+
return server;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,UAAU,WAAW,CAAC,IAAkB;IAC5C,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,WAAW,CAAC,IAAI,WAAW,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACrE,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { ok, run } from "./shared.js";
|
|
3
|
+
const OutcomeSchema = z.object({
|
|
4
|
+
succeeded: z.number(),
|
|
5
|
+
failed: z.array(z.object({ id: z.string(), error: z.string() })),
|
|
6
|
+
moved: z.array(z.object({ id: z.string(), newId: z.string() })).optional(),
|
|
7
|
+
});
|
|
8
|
+
function summarizeOutcomes(outcomes, verb) {
|
|
9
|
+
const failed = outcomes.filter((o) => !o.ok).map((o) => ({ id: o.id, error: o.error ?? "unknown" }));
|
|
10
|
+
const succeeded = outcomes.length - failed.length;
|
|
11
|
+
const moved = outcomes.filter((o) => o.ok && o.newId).map((o) => ({ id: o.id, newId: o.newId }));
|
|
12
|
+
const text = `${succeeded} message(s) ${verb}.` +
|
|
13
|
+
(failed.length ? ` ${failed.length} failure(s): ${failed.map((f) => `${f.id.slice(0, 12)}… (${f.error})`).join(", ")}` : "") +
|
|
14
|
+
(moved.length ? " Ids changed (see `moved`): the old ids are no longer valid." : "");
|
|
15
|
+
return { text, structured: { succeeded, failed, ...(moved.length ? { moved } : {}) } };
|
|
16
|
+
}
|
|
17
|
+
const Ids = z.array(z.string().min(1)).min(1).max(500).describe("Message ids (1 to 500)");
|
|
18
|
+
export function registerActionTools(server, mail) {
|
|
19
|
+
server.registerTool("mail_move", {
|
|
20
|
+
title: "Move messages",
|
|
21
|
+
description: "Move one or more messages (up to 500 ids) to a folder (name, path or id). Used for filing. " +
|
|
22
|
+
"NOTE: Outlook assigns a new id to every moved message; the `moved` result maps old id → new id. " +
|
|
23
|
+
"To move every message from a sender, prefer mail_bulk_by_sender.",
|
|
24
|
+
inputSchema: z.object({ ids: Ids, folder: z.string().min(1).describe("Destination folder") }).strict(),
|
|
25
|
+
outputSchema: OutcomeSchema,
|
|
26
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
27
|
+
}, async ({ ids, folder }) => run(async () => {
|
|
28
|
+
const outcomes = await mail.move(ids, folder);
|
|
29
|
+
mail.invalidateSummaries();
|
|
30
|
+
const s = summarizeOutcomes(outcomes, `moved to "${folder}"`);
|
|
31
|
+
return ok(s.text, s.structured);
|
|
32
|
+
}));
|
|
33
|
+
server.registerTool("mail_delete", {
|
|
34
|
+
title: "Delete messages",
|
|
35
|
+
description: "Delete one or more messages (up to 500 ids). By default messages go to Deleted Items (recoverable with mail_search folder=deleteditems then mail_move; their id changes). " +
|
|
36
|
+
"`permanent: true` deletes irreversibly: only use it when the user explicitly asks for it.",
|
|
37
|
+
inputSchema: z
|
|
38
|
+
.object({
|
|
39
|
+
ids: Ids,
|
|
40
|
+
permanent: z.boolean().default(false).describe("true = permanent deletion (irreversible). Default: Deleted Items."),
|
|
41
|
+
})
|
|
42
|
+
.strict(),
|
|
43
|
+
outputSchema: OutcomeSchema,
|
|
44
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
45
|
+
}, async ({ ids, permanent }) => run(async () => {
|
|
46
|
+
const outcomes = await mail.delete(ids, permanent);
|
|
47
|
+
mail.invalidateSummaries();
|
|
48
|
+
const s = summarizeOutcomes(outcomes, permanent ? "permanently deleted" : "moved to Deleted Items");
|
|
49
|
+
return ok(s.text, s.structured);
|
|
50
|
+
}));
|
|
51
|
+
server.registerTool("mail_bulk_by_sender", {
|
|
52
|
+
title: "Bulk action by sender",
|
|
53
|
+
description: "Move or delete EVERY message from a sender (exact address) in a folder (Inbox by default). " +
|
|
54
|
+
"Use `dryRun: true` first to learn how many messages match. Deletion goes to Deleted Items unless `permanent: true`.",
|
|
55
|
+
inputSchema: z
|
|
56
|
+
.object({
|
|
57
|
+
from: z.string().email().describe("Exact sender address"),
|
|
58
|
+
folder: z.string().optional().describe("Source folder. Default: inbox"),
|
|
59
|
+
action: z.enum(["move", "delete"]).describe("move = move to targetFolder; delete = delete"),
|
|
60
|
+
targetFolder: z.string().optional().describe("Required when action = move"),
|
|
61
|
+
permanent: z.boolean().default(false).describe("With action = delete: permanent deletion"),
|
|
62
|
+
dryRun: z.boolean().default(false).describe("true = count only, change nothing"),
|
|
63
|
+
})
|
|
64
|
+
.strict(),
|
|
65
|
+
outputSchema: z.object({ matched: z.number(), dryRun: z.boolean(), succeeded: z.number(), failed: z.array(z.object({ id: z.string(), error: z.string() })) }),
|
|
66
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
67
|
+
}, async ({ from, folder, action, targetFolder, permanent, dryRun }) => run(async () => {
|
|
68
|
+
if (action === "move" && !targetFolder)
|
|
69
|
+
throw new Error("`targetFolder` is required when action = move.");
|
|
70
|
+
const ids = await mail.idsFromSender(from, folder);
|
|
71
|
+
if (dryRun || ids.length === 0) {
|
|
72
|
+
return ok(`${ids.length} message(s) from ${from} in "${folder ?? "inbox"}".${dryRun ? " Nothing changed (dryRun)." : ""}`, { matched: ids.length, dryRun, succeeded: 0, failed: [] });
|
|
73
|
+
}
|
|
74
|
+
const outcomes = action === "move" ? await mail.move(ids, targetFolder) : await mail.delete(ids, permanent);
|
|
75
|
+
mail.invalidateSummaries();
|
|
76
|
+
const verb = action === "move" ? `moved to "${targetFolder}"` : permanent ? "permanently deleted" : "moved to Deleted Items";
|
|
77
|
+
const s = summarizeOutcomes(outcomes, verb);
|
|
78
|
+
return ok(`${ids.length} message(s) from ${from}: ${s.text}`, { matched: ids.length, dryRun: false, ...s.structured });
|
|
79
|
+
}));
|
|
80
|
+
server.registerTool("mail_unsubscribe", {
|
|
81
|
+
title: "Unsubscribe",
|
|
82
|
+
description: "Try to unsubscribe from the mailing list of a message, in order: (1) RFC 8058 one-click HTTPS POST, (2) automatic email to the List-Unsubscribe mailto address, " +
|
|
83
|
+
"(3) otherwise returns `method: \"browser\"` with the URL to open in a browser to finish manually, (4) `method: \"none\"` when no header exists. " +
|
|
84
|
+
"Deletes nothing. Preferably pass the `lastMessageId` returned by mail_senders_summary.",
|
|
85
|
+
inputSchema: z.object({ id: z.string().min(1).describe("Id of a representative message of the newsletter") }).strict(),
|
|
86
|
+
outputSchema: z.object({
|
|
87
|
+
method: z.enum(["one-click", "mailto", "browser", "none"]),
|
|
88
|
+
ok: z.boolean(),
|
|
89
|
+
url: z.string().optional(),
|
|
90
|
+
mailto: z.string().optional(),
|
|
91
|
+
detail: z.string(),
|
|
92
|
+
from: z.string(),
|
|
93
|
+
subject: z.string(),
|
|
94
|
+
}),
|
|
95
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
96
|
+
}, async ({ id }) => run(async () => {
|
|
97
|
+
const r = await mail.unsubscribe(id);
|
|
98
|
+
const head = r.ok ? `✅ Unsubscribed from ${r.from} (${r.method}).` : r.method === "browser" ? `🌐 Browser action required for ${r.from}: ${r.url}` : `⚠️ ${r.from}: ${r.method}`;
|
|
99
|
+
return ok(`${head}\n${r.detail}`, { ...r });
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/tools/actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { ok, run } from "./shared.js";
|
|
3
|
+
const FolderSchema = z.object({
|
|
4
|
+
id: z.string(),
|
|
5
|
+
name: z.string(),
|
|
6
|
+
path: z.string(),
|
|
7
|
+
parentId: z.string().optional(),
|
|
8
|
+
total: z.number(),
|
|
9
|
+
unread: z.number(),
|
|
10
|
+
wellKnownName: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
export function registerFolderTools(server, mail) {
|
|
13
|
+
server.registerTool("mail_list_folders", {
|
|
14
|
+
title: "List folders",
|
|
15
|
+
description: "List every folder of the Outlook.com mailbox with its path, total message count and unread count. " +
|
|
16
|
+
"Call it before filing messages to learn which folders exist. Other tools accept a folder by display name (\"Invoices\"), " +
|
|
17
|
+
"by path (\"Archive/2024\"), by well-known name (inbox, deleteditems, junkemail, archive) or by id.",
|
|
18
|
+
inputSchema: z.object({}).strict(),
|
|
19
|
+
outputSchema: z.object({ folders: z.array(FolderSchema) }),
|
|
20
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
21
|
+
}, async () => run(async () => {
|
|
22
|
+
const folders = await mail.listFolders(true);
|
|
23
|
+
const text = folders.map((f) => `- ${f.path} (${f.total} messages, ${f.unread} unread)${f.wellKnownName ? ` [${f.wellKnownName}]` : ""}`).join("\n");
|
|
24
|
+
return ok(text || "_No folders._", { folders });
|
|
25
|
+
}));
|
|
26
|
+
server.registerTool("mail_create_folder", {
|
|
27
|
+
title: "Create folder",
|
|
28
|
+
description: "Create an Outlook folder (at the root or under a parent). Idempotent: if a folder with the same name already exists it is returned without error. " +
|
|
29
|
+
"Example: { name: \"Newsletters\" } or { name: \"2025\", parent: \"Invoices\" }.",
|
|
30
|
+
inputSchema: z
|
|
31
|
+
.object({
|
|
32
|
+
name: z.string().min(1).max(100).describe("Name of the folder to create"),
|
|
33
|
+
parent: z.string().optional().describe("Parent folder (name, path or id). Root if omitted."),
|
|
34
|
+
})
|
|
35
|
+
.strict(),
|
|
36
|
+
outputSchema: z.object({ folder: FolderSchema, created: z.boolean() }),
|
|
37
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
38
|
+
}, async ({ name, parent }) => run(async () => {
|
|
39
|
+
const r = await mail.createFolder(name, parent);
|
|
40
|
+
const text = r.created ? `Folder created: ${r.folder.path} (id ${r.folder.id})` : `Folder already exists: ${r.folder.path} (id ${r.folder.id})`;
|
|
41
|
+
return ok(text, { folder: r.folder, created: r.created });
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=folders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/tools/folders.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { messagesToMarkdown } from "../format.js";
|
|
3
|
+
import { ok, run } from "./shared.js";
|
|
4
|
+
const SummarySchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
from: z.string(),
|
|
7
|
+
fromName: z.string(),
|
|
8
|
+
subject: z.string(),
|
|
9
|
+
receivedDateTime: z.string(),
|
|
10
|
+
isRead: z.boolean(),
|
|
11
|
+
preview: z.string(),
|
|
12
|
+
});
|
|
13
|
+
const UnsubscribeSchema = z.object({
|
|
14
|
+
https: z.string().optional(),
|
|
15
|
+
mailto: z.string().optional(),
|
|
16
|
+
mailtoSubject: z.string().optional(),
|
|
17
|
+
mailtoBody: z.string().optional(),
|
|
18
|
+
oneClick: z.boolean(),
|
|
19
|
+
});
|
|
20
|
+
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}(T[\d:]+(\.\d+)?Z?)?$/, "ISO date expected, e.g. 2025-01-31 or 2025-01-31T00:00:00Z");
|
|
21
|
+
function normalizeDate(d) {
|
|
22
|
+
if (!d)
|
|
23
|
+
return undefined;
|
|
24
|
+
return d.length === 10 ? `${d}T00:00:00Z` : d.endsWith("Z") ? d : `${d}Z`;
|
|
25
|
+
}
|
|
26
|
+
export function registerMessageTools(server, mail) {
|
|
27
|
+
server.registerTool("mail_search", {
|
|
28
|
+
title: "Search messages",
|
|
29
|
+
description: "List messages of a folder (Inbox by default), newest first, with filters: exact sender, date range, unread only. " +
|
|
30
|
+
"`query` runs a full-text search (subject, body, sender) and cannot be combined with the other filters. " +
|
|
31
|
+
"Returns a compact summary (id, sender, subject, date, preview); use mail_get_message for the full content. " +
|
|
32
|
+
"To page, pass `cursor` back verbatim. For a whole-mailbox view by sender, prefer mail_senders_summary.",
|
|
33
|
+
inputSchema: z
|
|
34
|
+
.object({
|
|
35
|
+
folder: z.string().optional().describe("Folder (name, path, well-known name or id). Default: inbox"),
|
|
36
|
+
from: z.string().optional().describe("Exact sender address, e.g. news@example.com"),
|
|
37
|
+
since: isoDate.optional().describe("Received on or after this date (ISO)"),
|
|
38
|
+
until: isoDate.optional().describe("Received on or before this date (ISO)"),
|
|
39
|
+
unreadOnly: z.boolean().optional().describe("Only unread messages"),
|
|
40
|
+
query: z.string().optional().describe("Full-text search (simple KQL). Exclusive with from/since/until/unreadOnly."),
|
|
41
|
+
limit: z.number().int().min(1).max(100).default(25).describe("Results per page (max 100)"),
|
|
42
|
+
cursor: z.string().optional().describe("Page cursor returned by a previous call"),
|
|
43
|
+
})
|
|
44
|
+
.strict(),
|
|
45
|
+
outputSchema: z.object({ items: z.array(SummarySchema), count: z.number(), nextCursor: z.string().optional() }),
|
|
46
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
47
|
+
}, async (p) => run(async () => {
|
|
48
|
+
if (p.query && (p.from || p.since || p.until || p.unreadOnly)) {
|
|
49
|
+
throw new Error("`query` cannot be combined with from/since/until/unreadOnly (Microsoft Graph limitation). Make two calls.");
|
|
50
|
+
}
|
|
51
|
+
const r = await mail.search({
|
|
52
|
+
folder: p.folder,
|
|
53
|
+
from: p.from,
|
|
54
|
+
since: normalizeDate(p.since),
|
|
55
|
+
until: normalizeDate(p.until),
|
|
56
|
+
unreadOnly: p.unreadOnly,
|
|
57
|
+
query: p.query,
|
|
58
|
+
limit: p.limit,
|
|
59
|
+
cursor: p.cursor,
|
|
60
|
+
});
|
|
61
|
+
const text = messagesToMarkdown(r.items) + (r.nextCursor ? `\n\n_More results available: pass \`cursor\` back._` : "");
|
|
62
|
+
return ok(text, { items: r.items, count: r.items.length, ...(r.nextCursor ? { nextCursor: r.nextCursor } : {}) });
|
|
63
|
+
}));
|
|
64
|
+
server.registerTool("mail_get_message", {
|
|
65
|
+
title: "Read a message",
|
|
66
|
+
description: "Return the full content of one message (body converted to text, recipients, attachment flag, web link) " +
|
|
67
|
+
"plus the unsubscribe options found in its headers (https, mailto, one-click). The body is truncated at 25,000 characters.",
|
|
68
|
+
inputSchema: z.object({ id: z.string().min(1).describe("Message id (from mail_search or mail_senders_summary)") }).strict(),
|
|
69
|
+
outputSchema: z.object({
|
|
70
|
+
message: SummarySchema,
|
|
71
|
+
to: z.array(z.string()),
|
|
72
|
+
body: z.string(),
|
|
73
|
+
truncated: z.boolean(),
|
|
74
|
+
hasAttachments: z.boolean(),
|
|
75
|
+
webLink: z.string().optional(),
|
|
76
|
+
unsubscribe: UnsubscribeSchema,
|
|
77
|
+
}),
|
|
78
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
79
|
+
}, async ({ id }) => run(async () => {
|
|
80
|
+
const r = await mail.getMessage(id);
|
|
81
|
+
const u = r.unsubscribe;
|
|
82
|
+
const unsubLine = u.oneClick
|
|
83
|
+
? `One-click unsubscribe available (${u.https})`
|
|
84
|
+
: u.mailto || u.https
|
|
85
|
+
? `Unsubscribe: ${u.https ? "URL " + u.https : ""}${u.https && u.mailto ? " / " : ""}${u.mailto ? "mailto " + u.mailto : ""}`
|
|
86
|
+
: "No unsubscribe header";
|
|
87
|
+
const text = `**${r.summary.subject}**\nFrom: ${r.summary.fromName} <${r.summary.from}>\nTo: ${r.to.join(", ")}\nReceived: ${r.summary.receivedDateTime}\n` +
|
|
88
|
+
`${r.hasAttachments ? "Attachments: yes\n" : ""}${unsubLine}\n\n---\n${r.body}`;
|
|
89
|
+
return ok(text, { message: r.summary, to: r.to, body: r.body, truncated: r.truncated, hasAttachments: r.hasAttachments, ...(r.webLink ? { webLink: r.webLink } : {}), unsubscribe: u });
|
|
90
|
+
}));
|
|
91
|
+
server.registerTool("mail_senders_summary", {
|
|
92
|
+
title: "Summary by sender",
|
|
93
|
+
description: "KEY TOOL FOR TRIAGE. Scans a whole folder (Inbox by default) and aggregates messages by sender: count, unread, latest message, " +
|
|
94
|
+
"and for the `top` senders the available unsubscribe method (oneClick / https / mailto). Sorted by volume. " +
|
|
95
|
+
"The scan is cached for the session. Follow up with mail_unsubscribe(lastMessageId) and mail_bulk_by_sender(address).",
|
|
96
|
+
inputSchema: z
|
|
97
|
+
.object({
|
|
98
|
+
folder: z.string().optional().describe("Folder to analyse. Default: inbox"),
|
|
99
|
+
since: isoDate.optional().describe("Only count messages received since this date"),
|
|
100
|
+
top: z.number().int().min(1).max(200).default(50).describe("Number of senders to return (max 200)"),
|
|
101
|
+
})
|
|
102
|
+
.strict(),
|
|
103
|
+
outputSchema: z.object({
|
|
104
|
+
scanned: z.number(),
|
|
105
|
+
totalSenders: z.number(),
|
|
106
|
+
senders: z.array(z.object({
|
|
107
|
+
address: z.string(),
|
|
108
|
+
name: z.string(),
|
|
109
|
+
domain: z.string(),
|
|
110
|
+
count: z.number(),
|
|
111
|
+
unreadCount: z.number(),
|
|
112
|
+
lastReceived: z.string(),
|
|
113
|
+
lastSubject: z.string(),
|
|
114
|
+
lastMessageId: z.string(),
|
|
115
|
+
unsubscribe: UnsubscribeSchema.optional(),
|
|
116
|
+
})),
|
|
117
|
+
}),
|
|
118
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
119
|
+
}, async ({ folder, since, top }) => run(async () => {
|
|
120
|
+
const r = await mail.sendersSummary(folder, normalizeDate(since), top);
|
|
121
|
+
const lines = r.senders.map((s, i) => {
|
|
122
|
+
const u = s.unsubscribe;
|
|
123
|
+
const method = u?.oneClick ? "one-click" : u?.mailto ? "mailto" : u?.https ? "browser" : "none";
|
|
124
|
+
return `${i + 1}. ${s.name ? s.name + " " : ""}<${s.address}> — ${s.count} msgs (${s.unreadCount} unread), latest ${s.lastReceived.slice(0, 10)} "${s.lastSubject}" | unsubscribe: ${method} | lastMessageId: \`${s.lastMessageId}\``;
|
|
125
|
+
});
|
|
126
|
+
const text = `${r.scanned} messages scanned, ${r.totalSenders} senders. Top ${r.senders.length}:\n${lines.join("\n")}`;
|
|
127
|
+
return ok(text, r);
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=messages.js.map
|