hypermail-mcp 0.7.8 → 0.7.10
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/README.md +31 -57
- package/dist/cli.js +470 -470
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13965,6 +13965,7 @@ var AccountStore = class _AccountStore {
|
|
|
13965
13965
|
filePath;
|
|
13966
13966
|
key;
|
|
13967
13967
|
data;
|
|
13968
|
+
writeLocks = /* @__PURE__ */ new Map();
|
|
13968
13969
|
static async open(opts = {}) {
|
|
13969
13970
|
const dataDir = resolveDataDir(opts.dataDir);
|
|
13970
13971
|
await fs2.mkdir(dataDir, { recursive: true, mode: 448 });
|
|
@@ -13992,21 +13993,79 @@ var AccountStore = class _AccountStore {
|
|
|
13992
13993
|
return rec ? { ...rec } : void 0;
|
|
13993
13994
|
}
|
|
13994
13995
|
async upsertAccount(rec) {
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
13996
|
+
return this.runSerial(rec.email, async () => {
|
|
13997
|
+
const norm = rec.email.trim().toLowerCase();
|
|
13998
|
+
const next = { ...rec, email: norm };
|
|
13999
|
+
const idx = this.data.accounts.findIndex((a) => a.email.toLowerCase() === norm);
|
|
14000
|
+
if (idx >= 0) this.data.accounts[idx] = next;
|
|
14001
|
+
else this.data.accounts.push(next);
|
|
14002
|
+
await this.flush();
|
|
14003
|
+
return { ...next };
|
|
14004
|
+
});
|
|
14005
|
+
}
|
|
14006
|
+
async updateTokens(email, tokens) {
|
|
14007
|
+
return this.runSerial(email, async () => {
|
|
14008
|
+
const norm = email.trim().toLowerCase();
|
|
14009
|
+
const idx = this.data.accounts.findIndex((a) => a.email.toLowerCase() === norm);
|
|
14010
|
+
if (idx < 0) return void 0;
|
|
14011
|
+
const current = this.data.accounts[idx];
|
|
14012
|
+
const next = { ...current, tokens };
|
|
14013
|
+
this.data.accounts[idx] = next;
|
|
14014
|
+
await this.flush();
|
|
14015
|
+
return { ...next };
|
|
14016
|
+
});
|
|
14017
|
+
}
|
|
14018
|
+
async updateNewEmailCheckpoint(email, checkpoint) {
|
|
14019
|
+
return this.runSerial(email, async () => {
|
|
14020
|
+
const norm = email.trim().toLowerCase();
|
|
14021
|
+
const idx = this.data.accounts.findIndex((a) => a.email.toLowerCase() === norm);
|
|
14022
|
+
if (idx < 0) return void 0;
|
|
14023
|
+
const current = this.data.accounts[idx];
|
|
14024
|
+
const currentCheckpoint = current.newEmailCheckpoint;
|
|
14025
|
+
const mergedCheckpoint = currentCheckpoint?.receivedAt === checkpoint.receivedAt ? {
|
|
14026
|
+
receivedAt: checkpoint.receivedAt,
|
|
14027
|
+
deliveredIdsAtReceivedAt: [
|
|
14028
|
+
.../* @__PURE__ */ new Set([
|
|
14029
|
+
...currentCheckpoint.deliveredIdsAtReceivedAt ?? [],
|
|
14030
|
+
...checkpoint.deliveredIdsAtReceivedAt ?? []
|
|
14031
|
+
])
|
|
14032
|
+
]
|
|
14033
|
+
} : checkpoint;
|
|
14034
|
+
const next = {
|
|
14035
|
+
...current,
|
|
14036
|
+
newEmailCheckpoint: mergedCheckpoint
|
|
14037
|
+
};
|
|
14038
|
+
this.data.accounts[idx] = next;
|
|
14039
|
+
await this.flush();
|
|
14040
|
+
return { ...next };
|
|
14041
|
+
});
|
|
14002
14042
|
}
|
|
14003
14043
|
async removeAccount(email) {
|
|
14044
|
+
return this.runSerial(email, async () => {
|
|
14045
|
+
const norm = email.trim().toLowerCase();
|
|
14046
|
+
const before = this.data.accounts.length;
|
|
14047
|
+
this.data.accounts = this.data.accounts.filter((a) => a.email.toLowerCase() !== norm);
|
|
14048
|
+
if (this.data.accounts.length === before) return false;
|
|
14049
|
+
await this.flush();
|
|
14050
|
+
return true;
|
|
14051
|
+
});
|
|
14052
|
+
}
|
|
14053
|
+
async runSerial(email, task) {
|
|
14004
14054
|
const norm = email.trim().toLowerCase();
|
|
14005
|
-
const
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14055
|
+
const previous = this.writeLocks.get(norm) ?? Promise.resolve();
|
|
14056
|
+
const run = previous.catch(() => void 0).then(task);
|
|
14057
|
+
const lock = run.then(
|
|
14058
|
+
() => void 0,
|
|
14059
|
+
() => void 0
|
|
14060
|
+
);
|
|
14061
|
+
this.writeLocks.set(norm, lock);
|
|
14062
|
+
try {
|
|
14063
|
+
return await run;
|
|
14064
|
+
} finally {
|
|
14065
|
+
if (this.writeLocks.get(norm) === lock) {
|
|
14066
|
+
this.writeLocks.delete(norm);
|
|
14067
|
+
}
|
|
14068
|
+
}
|
|
14010
14069
|
}
|
|
14011
14070
|
async flush() {
|
|
14012
14071
|
const buf = encrypt(this.data, this.key);
|
|
@@ -14203,10 +14262,10 @@ var OutlookClientFactory = class {
|
|
|
14203
14262
|
this.tenantId
|
|
14204
14263
|
);
|
|
14205
14264
|
if (nextTokens.msalCache !== tokens.msalCache) {
|
|
14206
|
-
store.
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14265
|
+
store.updateTokens(
|
|
14266
|
+
account.email,
|
|
14267
|
+
nextTokens
|
|
14268
|
+
).catch(() => {
|
|
14210
14269
|
});
|
|
14211
14270
|
}
|
|
14212
14271
|
return accessToken;
|
|
@@ -15826,10 +15885,10 @@ var GmailClientFactory = class {
|
|
|
15826
15885
|
expiryDate: updated.expiry_date ?? currentTokens.expiryDate,
|
|
15827
15886
|
scopes: updated.scope ? updated.scope.split(" ") : currentTokens.scopes
|
|
15828
15887
|
};
|
|
15829
|
-
await store.
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15888
|
+
await store.updateTokens(
|
|
15889
|
+
account.email,
|
|
15890
|
+
nextTokens
|
|
15891
|
+
).catch(() => {
|
|
15833
15892
|
});
|
|
15834
15893
|
});
|
|
15835
15894
|
persistLocks.set(key, chain);
|
|
@@ -17038,7 +17097,7 @@ function registerAccountTools(server, ctx) {
|
|
|
17038
17097
|
}
|
|
17039
17098
|
|
|
17040
17099
|
// src/tools/browse.ts
|
|
17041
|
-
import { z as
|
|
17100
|
+
import { z as z5 } from "zod";
|
|
17042
17101
|
|
|
17043
17102
|
// src/html-to-markdown.ts
|
|
17044
17103
|
import TurndownService from "turndown";
|
|
@@ -17066,32 +17125,275 @@ function selectBody(msg, format) {
|
|
|
17066
17125
|
}
|
|
17067
17126
|
}
|
|
17068
17127
|
|
|
17069
|
-
// src/tools/
|
|
17070
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
17128
|
+
// src/tools/new-emails.ts
|
|
17129
|
+
import { z as z4 } from "zod";
|
|
17130
|
+
var DEFAULT_LIMIT = 10;
|
|
17131
|
+
var BODY_LIMIT = 2e4;
|
|
17132
|
+
var PAGE_SIZE = 100;
|
|
17133
|
+
var MISSING_RECEIVED_AT = "1970-01-01T00:00:00.000Z";
|
|
17134
|
+
function registerNewEmailTool(server, ctx) {
|
|
17135
|
+
const { store, registry, tools } = ctx;
|
|
17136
|
+
if (!shouldRegister("get_new_emails", tools)) return;
|
|
17137
|
+
const newEmailOutputSchema = z4.object({
|
|
17073
17138
|
account: z4.string(),
|
|
17074
|
-
|
|
17075
|
-
|
|
17076
|
-
|
|
17077
|
-
|
|
17139
|
+
id: z4.string(),
|
|
17140
|
+
subject: z4.string(),
|
|
17141
|
+
from: emailAddrOutputSchema.optional(),
|
|
17142
|
+
to: z4.array(emailAddrOutputSchema).optional(),
|
|
17143
|
+
cc: z4.array(emailAddrOutputSchema).optional(),
|
|
17144
|
+
bcc: z4.array(emailAddrOutputSchema).optional(),
|
|
17145
|
+
receivedAt: z4.string().optional(),
|
|
17146
|
+
preview: z4.string().optional(),
|
|
17147
|
+
isRead: z4.boolean().optional(),
|
|
17148
|
+
hasAttachments: z4.boolean().optional(),
|
|
17149
|
+
folder: z4.string().optional(),
|
|
17150
|
+
attachments: z4.array(attachmentMetaOutputSchema).optional(),
|
|
17151
|
+
body: z4.string(),
|
|
17152
|
+
bodyFormat: z4.literal("markdown"),
|
|
17153
|
+
bodyTruncated: z4.boolean(),
|
|
17154
|
+
bodyOriginalLength: z4.number()
|
|
17078
17155
|
});
|
|
17079
|
-
const
|
|
17080
|
-
account: z4.string(),
|
|
17156
|
+
const outputSchema = z4.object({
|
|
17081
17157
|
count: z4.number(),
|
|
17082
|
-
|
|
17158
|
+
emails: z4.array(newEmailOutputSchema),
|
|
17159
|
+
errors: z4.array(z4.object({ account: z4.string(), message: z4.string() }))
|
|
17160
|
+
});
|
|
17161
|
+
server.registerTool(
|
|
17162
|
+
"get_new_emails",
|
|
17163
|
+
{
|
|
17164
|
+
description: "Fetch a bounded batch of inbox emails not previously returned by this tool. Agents should call this on their own schedule. Bodies are returned as markdown and may be truncated.",
|
|
17165
|
+
inputSchema: z4.object({
|
|
17166
|
+
account: z4.string().email().optional().describe(
|
|
17167
|
+
"Email account to poll. If omitted, polls all accounts with one global limit."
|
|
17168
|
+
),
|
|
17169
|
+
limit: z4.number().int().min(0).default(DEFAULT_LIMIT).optional().describe(
|
|
17170
|
+
"Maximum emails to return. Defaults to 10. Use 0 to initialize/check without fetching bodies."
|
|
17171
|
+
)
|
|
17172
|
+
}),
|
|
17173
|
+
outputSchema
|
|
17174
|
+
},
|
|
17175
|
+
async (args) => {
|
|
17176
|
+
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
17177
|
+
if (args.account) {
|
|
17178
|
+
try {
|
|
17179
|
+
const { provider, account } = registry.resolveByEmail(args.account);
|
|
17180
|
+
const result = await collectCandidatesForAccount(store, provider, account);
|
|
17181
|
+
const selected2 = oldestCandidatesFirst(result.candidates).slice(0, limit);
|
|
17182
|
+
const emails2 = limit === 0 ? [] : await hydrateAndAdvance(store, provider, result.account, selected2);
|
|
17183
|
+
const data2 = { count: emails2.length, emails: emails2, errors: [] };
|
|
17184
|
+
return ok(data2, data2);
|
|
17185
|
+
} catch (err) {
|
|
17186
|
+
return fail(errMsg(err));
|
|
17187
|
+
}
|
|
17188
|
+
}
|
|
17189
|
+
const accounts = store.listAccounts();
|
|
17190
|
+
if (accounts.length === 0) {
|
|
17191
|
+
return fail("no accounts registered. Call add_account first.");
|
|
17192
|
+
}
|
|
17193
|
+
const errors = [];
|
|
17194
|
+
const collected = [];
|
|
17195
|
+
const providersByEmail = /* @__PURE__ */ new Map();
|
|
17196
|
+
const accountsByEmail = /* @__PURE__ */ new Map();
|
|
17197
|
+
for (const stored of accounts) {
|
|
17198
|
+
try {
|
|
17199
|
+
const { provider, account } = registry.resolveByEmail(stored.email);
|
|
17200
|
+
const result = await collectCandidatesForAccount(store, provider, account);
|
|
17201
|
+
providersByEmail.set(result.account.email, provider);
|
|
17202
|
+
accountsByEmail.set(result.account.email, result.account);
|
|
17203
|
+
collected.push(...result.candidates);
|
|
17204
|
+
} catch (err) {
|
|
17205
|
+
errors.push({ account: stored.email, message: errMsg(err) });
|
|
17206
|
+
}
|
|
17207
|
+
}
|
|
17208
|
+
const selected = oldestCandidatesFirst(collected).slice(0, limit);
|
|
17209
|
+
const emails = [];
|
|
17210
|
+
if (limit > 0) {
|
|
17211
|
+
const byAccount = /* @__PURE__ */ new Map();
|
|
17212
|
+
for (const candidate of selected) {
|
|
17213
|
+
const items = byAccount.get(candidate.account.email) ?? [];
|
|
17214
|
+
items.push(candidate);
|
|
17215
|
+
byAccount.set(candidate.account.email, items);
|
|
17216
|
+
}
|
|
17217
|
+
for (const [email, accountCandidates] of byAccount) {
|
|
17218
|
+
const provider = providersByEmail.get(email);
|
|
17219
|
+
const account = accountsByEmail.get(email);
|
|
17220
|
+
if (!provider || !account) continue;
|
|
17221
|
+
try {
|
|
17222
|
+
emails.push(
|
|
17223
|
+
...await hydrateAndAdvance(
|
|
17224
|
+
store,
|
|
17225
|
+
provider,
|
|
17226
|
+
account,
|
|
17227
|
+
accountCandidates
|
|
17228
|
+
)
|
|
17229
|
+
);
|
|
17230
|
+
} catch (err) {
|
|
17231
|
+
errors.push({ account: email, message: errMsg(err) });
|
|
17232
|
+
}
|
|
17233
|
+
}
|
|
17234
|
+
}
|
|
17235
|
+
const orderedEmails = emails.sort(compareNewEmailOutputOldestFirst);
|
|
17236
|
+
const data = { count: orderedEmails.length, emails: orderedEmails, errors };
|
|
17237
|
+
return ok(data, data);
|
|
17238
|
+
}
|
|
17239
|
+
);
|
|
17240
|
+
}
|
|
17241
|
+
async function collectCandidatesForAccount(store, provider, account) {
|
|
17242
|
+
const checkpoint = normalizeCheckpoint(account.newEmailCheckpoint);
|
|
17243
|
+
if (!checkpoint) {
|
|
17244
|
+
await initializeCheckpoint(store, provider, account);
|
|
17245
|
+
return { account, candidates: [] };
|
|
17246
|
+
}
|
|
17247
|
+
const deliveredAtCheckpoint = new Set(checkpoint.deliveredIdsAtReceivedAt ?? []);
|
|
17248
|
+
const candidates = [];
|
|
17249
|
+
let skip = 0;
|
|
17250
|
+
while (true) {
|
|
17251
|
+
const { items, hasMore } = await provider.listEmails(account, {
|
|
17252
|
+
folder: "inbox",
|
|
17253
|
+
limit: PAGE_SIZE,
|
|
17254
|
+
skip
|
|
17255
|
+
});
|
|
17256
|
+
if (items.length === 0) break;
|
|
17257
|
+
let sawOlderThanCheckpoint = false;
|
|
17258
|
+
for (const item of items) {
|
|
17259
|
+
const timestamp = effectiveReceivedAt(item.receivedAt);
|
|
17260
|
+
const comparison = compareTimestamp(timestamp, checkpoint.receivedAt);
|
|
17261
|
+
if (comparison > 0) {
|
|
17262
|
+
candidates.push({ account, summary: item, timestamp });
|
|
17263
|
+
} else if (comparison === 0) {
|
|
17264
|
+
if (!deliveredAtCheckpoint.has(item.id)) {
|
|
17265
|
+
candidates.push({ account, summary: item, timestamp });
|
|
17266
|
+
}
|
|
17267
|
+
} else {
|
|
17268
|
+
sawOlderThanCheckpoint = true;
|
|
17269
|
+
}
|
|
17270
|
+
}
|
|
17271
|
+
if (sawOlderThanCheckpoint || !hasMore) break;
|
|
17272
|
+
skip += items.length;
|
|
17273
|
+
}
|
|
17274
|
+
return { account, candidates };
|
|
17275
|
+
}
|
|
17276
|
+
async function initializeCheckpoint(store, provider, account) {
|
|
17277
|
+
const { items } = await provider.listEmails(account, {
|
|
17278
|
+
folder: "inbox",
|
|
17279
|
+
limit: PAGE_SIZE
|
|
17280
|
+
});
|
|
17281
|
+
const first = items[0];
|
|
17282
|
+
const receivedAt = first ? effectiveReceivedAt(first.receivedAt) : (/* @__PURE__ */ new Date()).toISOString();
|
|
17283
|
+
const deliveredIdsAtReceivedAt = items.filter((item) => effectiveReceivedAt(item.receivedAt) === receivedAt).map((item) => item.id);
|
|
17284
|
+
await store.updateNewEmailCheckpoint(account.email, {
|
|
17285
|
+
receivedAt,
|
|
17286
|
+
deliveredIdsAtReceivedAt
|
|
17287
|
+
});
|
|
17288
|
+
}
|
|
17289
|
+
async function hydrateAndAdvance(store, provider, account, selected) {
|
|
17290
|
+
if (selected.length === 0) return [];
|
|
17291
|
+
const emails = [];
|
|
17292
|
+
for (const candidate of selected) {
|
|
17293
|
+
const full = await provider.readEmail(account, candidate.summary.id);
|
|
17294
|
+
emails.push(formatNewEmail(account.email, full, candidate.summary));
|
|
17295
|
+
}
|
|
17296
|
+
await advanceCheckpoint(store, account, selected);
|
|
17297
|
+
return emails;
|
|
17298
|
+
}
|
|
17299
|
+
function formatNewEmail(account, msg, summary) {
|
|
17300
|
+
const body = selectBody(msg, "markdown");
|
|
17301
|
+
const bodyTruncated = body.length > BODY_LIMIT;
|
|
17302
|
+
return {
|
|
17303
|
+
account,
|
|
17304
|
+
id: msg.id,
|
|
17305
|
+
subject: msg.subject,
|
|
17306
|
+
from: msg.from,
|
|
17307
|
+
to: msg.to,
|
|
17308
|
+
cc: msg.cc,
|
|
17309
|
+
bcc: msg.bcc,
|
|
17310
|
+
receivedAt: msg.receivedAt ?? summary.receivedAt,
|
|
17311
|
+
preview: msg.preview,
|
|
17312
|
+
isRead: msg.isRead,
|
|
17313
|
+
hasAttachments: msg.hasAttachments,
|
|
17314
|
+
folder: msg.folder,
|
|
17315
|
+
attachments: msg.attachments,
|
|
17316
|
+
body: bodyTruncated ? body.slice(0, BODY_LIMIT) : body,
|
|
17317
|
+
bodyFormat: "markdown",
|
|
17318
|
+
bodyTruncated,
|
|
17319
|
+
bodyOriginalLength: body.length
|
|
17320
|
+
};
|
|
17321
|
+
}
|
|
17322
|
+
async function advanceCheckpoint(store, account, selected) {
|
|
17323
|
+
const ordered = oldestCandidatesFirst(selected);
|
|
17324
|
+
const newest = ordered[ordered.length - 1];
|
|
17325
|
+
if (!newest) return;
|
|
17326
|
+
const newestTimestamp = newest.timestamp;
|
|
17327
|
+
const idsAtNewest = ordered.filter((candidate) => candidate.timestamp === newestTimestamp).map((candidate) => candidate.summary.id);
|
|
17328
|
+
await store.updateNewEmailCheckpoint(account.email, {
|
|
17329
|
+
receivedAt: newestTimestamp,
|
|
17330
|
+
deliveredIdsAtReceivedAt: idsAtNewest
|
|
17331
|
+
});
|
|
17332
|
+
}
|
|
17333
|
+
function normalizeCheckpoint(checkpoint) {
|
|
17334
|
+
const receivedAt = normalizeTimestamp(checkpoint?.receivedAt);
|
|
17335
|
+
if (!receivedAt) return null;
|
|
17336
|
+
return {
|
|
17337
|
+
receivedAt,
|
|
17338
|
+
deliveredIdsAtReceivedAt: checkpoint?.deliveredIdsAtReceivedAt ?? []
|
|
17339
|
+
};
|
|
17340
|
+
}
|
|
17341
|
+
function effectiveReceivedAt(receivedAt) {
|
|
17342
|
+
return normalizeTimestamp(receivedAt) ?? MISSING_RECEIVED_AT;
|
|
17343
|
+
}
|
|
17344
|
+
function normalizeTimestamp(value) {
|
|
17345
|
+
if (!value) return null;
|
|
17346
|
+
const ms = Date.parse(value);
|
|
17347
|
+
if (!Number.isFinite(ms)) return null;
|
|
17348
|
+
return new Date(ms).toISOString();
|
|
17349
|
+
}
|
|
17350
|
+
function oldestCandidatesFirst(items) {
|
|
17351
|
+
return [...items].sort((a, b) => {
|
|
17352
|
+
const byTimestamp = compareTimestamp(a.timestamp, b.timestamp);
|
|
17353
|
+
if (byTimestamp !== 0) return byTimestamp;
|
|
17354
|
+
return a.summary.id.localeCompare(b.summary.id);
|
|
17355
|
+
});
|
|
17356
|
+
}
|
|
17357
|
+
function compareNewEmailOutputOldestFirst(a, b) {
|
|
17358
|
+
const byTimestamp = compareTimestamp(
|
|
17359
|
+
effectiveReceivedAt(a.receivedAt),
|
|
17360
|
+
effectiveReceivedAt(b.receivedAt)
|
|
17361
|
+
);
|
|
17362
|
+
if (byTimestamp !== 0) return byTimestamp;
|
|
17363
|
+
return a.id.localeCompare(b.id);
|
|
17364
|
+
}
|
|
17365
|
+
function compareTimestamp(a, b) {
|
|
17366
|
+
if (a < b) return -1;
|
|
17367
|
+
if (a > b) return 1;
|
|
17368
|
+
return 0;
|
|
17369
|
+
}
|
|
17370
|
+
|
|
17371
|
+
// src/tools/browse.ts
|
|
17372
|
+
function registerBrowseTools(server, ctx) {
|
|
17373
|
+
const { store, registry, tools } = ctx;
|
|
17374
|
+
const emailListOutputSchema = z5.object({
|
|
17375
|
+
account: z5.string(),
|
|
17376
|
+
count: z5.number(),
|
|
17377
|
+
items: z5.array(emailSummaryOutputSchema),
|
|
17378
|
+
skip: z5.number(),
|
|
17379
|
+
hasMore: z5.boolean()
|
|
17380
|
+
});
|
|
17381
|
+
const searchEmailsOutputSchema = z5.object({
|
|
17382
|
+
account: z5.string(),
|
|
17383
|
+
count: z5.number(),
|
|
17384
|
+
items: z5.array(emailSummaryOutputSchema)
|
|
17083
17385
|
});
|
|
17084
17386
|
if (shouldRegister("list_emails", tools)) {
|
|
17085
17387
|
server.registerTool(
|
|
17086
17388
|
"list_emails",
|
|
17087
17389
|
{
|
|
17088
17390
|
description: "List recent emails in a folder of the given account. Pass the user's email address as `account`; the server routes to the correct backend automatically.",
|
|
17089
|
-
inputSchema:
|
|
17090
|
-
account:
|
|
17091
|
-
folder:
|
|
17092
|
-
limit:
|
|
17093
|
-
unreadOnly:
|
|
17094
|
-
skip:
|
|
17391
|
+
inputSchema: z5.object({
|
|
17392
|
+
account: z5.string().email(),
|
|
17393
|
+
folder: z5.string().default("inbox").optional(),
|
|
17394
|
+
limit: z5.number().int().positive().max(100).optional(),
|
|
17395
|
+
unreadOnly: z5.boolean().optional(),
|
|
17396
|
+
skip: z5.number().int().min(0).optional()
|
|
17095
17397
|
}),
|
|
17096
17398
|
outputSchema: emailListOutputSchema
|
|
17097
17399
|
},
|
|
@@ -17118,15 +17420,16 @@ function registerBrowseTools(server, ctx) {
|
|
|
17118
17420
|
}
|
|
17119
17421
|
);
|
|
17120
17422
|
}
|
|
17423
|
+
registerNewEmailTool(server, { store, registry, tools });
|
|
17121
17424
|
if (shouldRegister("search_emails", tools)) {
|
|
17122
17425
|
server.registerTool(
|
|
17123
17426
|
"search_emails",
|
|
17124
17427
|
{
|
|
17125
17428
|
description: "Search emails by free-text query (KQL on Outlook). Returns lightweight summaries.",
|
|
17126
|
-
inputSchema:
|
|
17127
|
-
account:
|
|
17128
|
-
query:
|
|
17129
|
-
limit:
|
|
17429
|
+
inputSchema: z5.object({
|
|
17430
|
+
account: z5.string().email(),
|
|
17431
|
+
query: z5.string().min(1),
|
|
17432
|
+
limit: z5.number().int().positive().max(100).optional()
|
|
17130
17433
|
}),
|
|
17131
17434
|
outputSchema: searchEmailsOutputSchema
|
|
17132
17435
|
},
|
|
@@ -17148,31 +17451,31 @@ function registerBrowseTools(server, ctx) {
|
|
|
17148
17451
|
}
|
|
17149
17452
|
);
|
|
17150
17453
|
}
|
|
17151
|
-
const readEmailOutputSchema =
|
|
17152
|
-
id:
|
|
17153
|
-
subject:
|
|
17454
|
+
const readEmailOutputSchema = z5.object({
|
|
17455
|
+
id: z5.string(),
|
|
17456
|
+
subject: z5.string(),
|
|
17154
17457
|
from: emailAddrOutputSchema.optional(),
|
|
17155
|
-
to:
|
|
17156
|
-
cc:
|
|
17157
|
-
bcc:
|
|
17158
|
-
receivedAt:
|
|
17159
|
-
preview:
|
|
17160
|
-
isRead:
|
|
17161
|
-
hasAttachments:
|
|
17162
|
-
folder:
|
|
17163
|
-
attachments:
|
|
17164
|
-
body:
|
|
17165
|
-
bodyFormat:
|
|
17458
|
+
to: z5.array(emailAddrOutputSchema).optional(),
|
|
17459
|
+
cc: z5.array(emailAddrOutputSchema).optional(),
|
|
17460
|
+
bcc: z5.array(emailAddrOutputSchema).optional(),
|
|
17461
|
+
receivedAt: z5.string().optional(),
|
|
17462
|
+
preview: z5.string().optional(),
|
|
17463
|
+
isRead: z5.boolean().optional(),
|
|
17464
|
+
hasAttachments: z5.boolean().optional(),
|
|
17465
|
+
folder: z5.string().optional(),
|
|
17466
|
+
attachments: z5.array(attachmentMetaOutputSchema).optional(),
|
|
17467
|
+
body: z5.string(),
|
|
17468
|
+
bodyFormat: z5.enum(["markdown", "html", "text"])
|
|
17166
17469
|
});
|
|
17167
17470
|
if (shouldRegister("read_email", tools)) {
|
|
17168
17471
|
server.registerTool(
|
|
17169
17472
|
"read_email",
|
|
17170
17473
|
{
|
|
17171
17474
|
description: "Fetch a single email with full body and recipients by id. Body is returned as `body` with `bodyFormat` indicating the format. Default format is 'markdown' \u2014 HTML is automatically converted to save context tokens.",
|
|
17172
|
-
inputSchema:
|
|
17173
|
-
account:
|
|
17174
|
-
id:
|
|
17175
|
-
format:
|
|
17475
|
+
inputSchema: z5.object({
|
|
17476
|
+
account: z5.string().email(),
|
|
17477
|
+
id: z5.string().min(1),
|
|
17478
|
+
format: z5.enum(["markdown", "html", "text"]).default("markdown").optional().describe(
|
|
17176
17479
|
"Output body format. 'markdown' converts HTML to Markdown (default), 'html' returns the raw HTML, 'text' returns plain text."
|
|
17177
17480
|
)
|
|
17178
17481
|
}),
|
|
@@ -17207,20 +17510,20 @@ function registerBrowseTools(server, ctx) {
|
|
|
17207
17510
|
}
|
|
17208
17511
|
);
|
|
17209
17512
|
}
|
|
17210
|
-
const readAttachmentOutputSchema =
|
|
17211
|
-
name:
|
|
17212
|
-
contentType:
|
|
17213
|
-
path:
|
|
17513
|
+
const readAttachmentOutputSchema = z5.object({
|
|
17514
|
+
name: z5.string(),
|
|
17515
|
+
contentType: z5.string().optional(),
|
|
17516
|
+
path: z5.string()
|
|
17214
17517
|
});
|
|
17215
17518
|
if (shouldRegister("read_attachment", tools)) {
|
|
17216
17519
|
server.registerTool(
|
|
17217
17520
|
"read_attachment",
|
|
17218
17521
|
{
|
|
17219
17522
|
description: "Download an email attachment to a temporary file and return its path. Use messageId and attachmentId from a prior read_email call.",
|
|
17220
|
-
inputSchema:
|
|
17221
|
-
account:
|
|
17222
|
-
messageId:
|
|
17223
|
-
attachmentId:
|
|
17523
|
+
inputSchema: z5.object({
|
|
17524
|
+
account: z5.string().email(),
|
|
17525
|
+
messageId: z5.string().min(1),
|
|
17526
|
+
attachmentId: z5.string().min(1)
|
|
17224
17527
|
}),
|
|
17225
17528
|
outputSchema: readAttachmentOutputSchema
|
|
17226
17529
|
},
|
|
@@ -17242,22 +17545,22 @@ function registerBrowseTools(server, ctx) {
|
|
|
17242
17545
|
}
|
|
17243
17546
|
|
|
17244
17547
|
// src/tools/folders.ts
|
|
17245
|
-
import { z as
|
|
17548
|
+
import { z as z6 } from "zod";
|
|
17246
17549
|
function registerFolderTools(server, ctx) {
|
|
17247
17550
|
const { registry, tools } = ctx;
|
|
17248
|
-
const listFoldersOutputSchema =
|
|
17249
|
-
account:
|
|
17250
|
-
count:
|
|
17251
|
-
items:
|
|
17551
|
+
const listFoldersOutputSchema = z6.object({
|
|
17552
|
+
account: z6.string(),
|
|
17553
|
+
count: z6.number(),
|
|
17554
|
+
items: z6.array(folderInfoOutputSchema)
|
|
17252
17555
|
});
|
|
17253
17556
|
if (shouldRegister("list_folders", tools)) {
|
|
17254
17557
|
server.registerTool(
|
|
17255
17558
|
"list_folders",
|
|
17256
17559
|
{
|
|
17257
17560
|
description: "List available mail folders. Returns top-level folders by default, or child folders of the given parent when `parentFolderId` is provided.",
|
|
17258
|
-
inputSchema:
|
|
17259
|
-
account:
|
|
17260
|
-
parentFolderId:
|
|
17561
|
+
inputSchema: z6.object({
|
|
17562
|
+
account: z6.string().email(),
|
|
17563
|
+
parentFolderId: z6.string().optional().describe(
|
|
17261
17564
|
"When provided, lists child folders of this folder. When omitted, lists top-level folders (children of the root)."
|
|
17262
17565
|
)
|
|
17263
17566
|
}),
|
|
@@ -17281,8 +17584,8 @@ function registerFolderTools(server, ctx) {
|
|
|
17281
17584
|
}
|
|
17282
17585
|
);
|
|
17283
17586
|
}
|
|
17284
|
-
const createFolderOutputSchema =
|
|
17285
|
-
created:
|
|
17587
|
+
const createFolderOutputSchema = z6.object({
|
|
17588
|
+
created: z6.literal(true),
|
|
17286
17589
|
folder: folderInfoOutputSchema
|
|
17287
17590
|
});
|
|
17288
17591
|
if (shouldRegister("create_folder", tools)) {
|
|
@@ -17290,10 +17593,10 @@ function registerFolderTools(server, ctx) {
|
|
|
17290
17593
|
"create_folder",
|
|
17291
17594
|
{
|
|
17292
17595
|
description: "Create a new mail folder. Creates under the root folder by default, or under the specified parent when `parentFolderId` is provided. Disabled in --read-only mode.",
|
|
17293
|
-
inputSchema:
|
|
17294
|
-
account:
|
|
17295
|
-
displayName:
|
|
17296
|
-
parentFolderId:
|
|
17596
|
+
inputSchema: z6.object({
|
|
17597
|
+
account: z6.string().email(),
|
|
17598
|
+
displayName: z6.string().min(1).describe("Name of the new folder"),
|
|
17599
|
+
parentFolderId: z6.string().optional().describe(
|
|
17297
17600
|
"When provided, creates the folder as a child of this folder. When omitted, creates under the root folder."
|
|
17298
17601
|
)
|
|
17299
17602
|
}),
|
|
@@ -17314,18 +17617,18 @@ function registerFolderTools(server, ctx) {
|
|
|
17314
17617
|
}
|
|
17315
17618
|
);
|
|
17316
17619
|
}
|
|
17317
|
-
const deleteFolderOutputSchema =
|
|
17318
|
-
deleted:
|
|
17319
|
-
id:
|
|
17620
|
+
const deleteFolderOutputSchema = z6.object({
|
|
17621
|
+
deleted: z6.literal(true),
|
|
17622
|
+
id: z6.string()
|
|
17320
17623
|
});
|
|
17321
17624
|
if (shouldRegister("delete_folder", tools)) {
|
|
17322
17625
|
server.registerTool(
|
|
17323
17626
|
"delete_folder",
|
|
17324
17627
|
{
|
|
17325
17628
|
description: "Delete a mail folder by ID. Disabled in --read-only mode.",
|
|
17326
|
-
inputSchema:
|
|
17327
|
-
account:
|
|
17328
|
-
folderId:
|
|
17629
|
+
inputSchema: z6.object({
|
|
17630
|
+
account: z6.string().email(),
|
|
17631
|
+
folderId: z6.string().min(1).describe("ID of the folder to delete")
|
|
17329
17632
|
}),
|
|
17330
17633
|
outputSchema: deleteFolderOutputSchema
|
|
17331
17634
|
},
|
|
@@ -17341,8 +17644,8 @@ function registerFolderTools(server, ctx) {
|
|
|
17341
17644
|
}
|
|
17342
17645
|
);
|
|
17343
17646
|
}
|
|
17344
|
-
const renameFolderOutputSchema =
|
|
17345
|
-
renamed:
|
|
17647
|
+
const renameFolderOutputSchema = z6.object({
|
|
17648
|
+
renamed: z6.literal(true),
|
|
17346
17649
|
folder: folderInfoOutputSchema
|
|
17347
17650
|
});
|
|
17348
17651
|
if (shouldRegister("rename_folder", tools)) {
|
|
@@ -17350,10 +17653,10 @@ function registerFolderTools(server, ctx) {
|
|
|
17350
17653
|
"rename_folder",
|
|
17351
17654
|
{
|
|
17352
17655
|
description: "Rename an existing mail folder. Disabled in --read-only mode.",
|
|
17353
|
-
inputSchema:
|
|
17354
|
-
account:
|
|
17355
|
-
folderId:
|
|
17356
|
-
newName:
|
|
17656
|
+
inputSchema: z6.object({
|
|
17657
|
+
account: z6.string().email(),
|
|
17658
|
+
folderId: z6.string().min(1).describe("ID of the folder to rename"),
|
|
17659
|
+
newName: z6.string().min(1).describe("New display name for the folder")
|
|
17357
17660
|
}),
|
|
17358
17661
|
outputSchema: renameFolderOutputSchema
|
|
17359
17662
|
},
|
|
@@ -17376,7 +17679,7 @@ function registerFolderTools(server, ctx) {
|
|
|
17376
17679
|
}
|
|
17377
17680
|
|
|
17378
17681
|
// src/tools/organize.ts
|
|
17379
|
-
import { z as
|
|
17682
|
+
import { z as z7 } from "zod";
|
|
17380
17683
|
function registerOrganizeTools(server, ctx) {
|
|
17381
17684
|
const { registry, tools } = ctx;
|
|
17382
17685
|
async function moveToWellKnown(args, destination, resultKey) {
|
|
@@ -17392,13 +17695,13 @@ function registerOrganizeTools(server, ctx) {
|
|
|
17392
17695
|
const data = { marked: true, id: args.id, isRead };
|
|
17393
17696
|
return ok(data, data);
|
|
17394
17697
|
}
|
|
17395
|
-
const archiveMoveSchema =
|
|
17396
|
-
account:
|
|
17397
|
-
id:
|
|
17698
|
+
const archiveMoveSchema = z7.object({
|
|
17699
|
+
account: z7.string().email(),
|
|
17700
|
+
id: z7.string().min(1).describe("Message ID to move")
|
|
17398
17701
|
});
|
|
17399
|
-
const archiveOutputSchema =
|
|
17400
|
-
archived:
|
|
17401
|
-
id:
|
|
17702
|
+
const archiveOutputSchema = z7.object({
|
|
17703
|
+
archived: z7.literal(true),
|
|
17704
|
+
id: z7.string()
|
|
17402
17705
|
});
|
|
17403
17706
|
if (shouldRegister("archive_email", tools)) {
|
|
17404
17707
|
server.registerTool(
|
|
@@ -17417,9 +17720,9 @@ function registerOrganizeTools(server, ctx) {
|
|
|
17417
17720
|
}
|
|
17418
17721
|
);
|
|
17419
17722
|
}
|
|
17420
|
-
const trashOutputSchema =
|
|
17421
|
-
trashed:
|
|
17422
|
-
id:
|
|
17723
|
+
const trashOutputSchema = z7.object({
|
|
17724
|
+
trashed: z7.literal(true),
|
|
17725
|
+
id: z7.string()
|
|
17423
17726
|
});
|
|
17424
17727
|
if (shouldRegister("trash_email", tools)) {
|
|
17425
17728
|
server.registerTool(
|
|
@@ -17438,20 +17741,20 @@ function registerOrganizeTools(server, ctx) {
|
|
|
17438
17741
|
}
|
|
17439
17742
|
);
|
|
17440
17743
|
}
|
|
17441
|
-
const moveEmailOutputSchema =
|
|
17442
|
-
moved:
|
|
17443
|
-
id:
|
|
17444
|
-
destination:
|
|
17744
|
+
const moveEmailOutputSchema = z7.object({
|
|
17745
|
+
moved: z7.literal(true),
|
|
17746
|
+
id: z7.string(),
|
|
17747
|
+
destination: z7.string()
|
|
17445
17748
|
});
|
|
17446
17749
|
if (shouldRegister("move_email", tools)) {
|
|
17447
17750
|
server.registerTool(
|
|
17448
17751
|
"move_email",
|
|
17449
17752
|
{
|
|
17450
17753
|
description: "Move a message to any folder by well-known name (e.g. 'inbox', 'drafts', 'junkemail', 'sentitems', 'outbox') or custom folder ID. Disabled in --read-only mode.",
|
|
17451
|
-
inputSchema:
|
|
17452
|
-
account:
|
|
17453
|
-
id:
|
|
17454
|
-
destination:
|
|
17754
|
+
inputSchema: z7.object({
|
|
17755
|
+
account: z7.string().email(),
|
|
17756
|
+
id: z7.string().min(1).describe("Message ID to move"),
|
|
17757
|
+
destination: z7.string().min(1).describe(
|
|
17455
17758
|
"Destination folder \u2014 a well-known folder name ('archive', 'deleteditems', 'inbox', 'drafts', 'junkemail', 'sentitems', 'outbox') or a raw folder ID."
|
|
17456
17759
|
)
|
|
17457
17760
|
}),
|
|
@@ -17473,14 +17776,14 @@ function registerOrganizeTools(server, ctx) {
|
|
|
17473
17776
|
}
|
|
17474
17777
|
);
|
|
17475
17778
|
}
|
|
17476
|
-
const markReadInputSchema =
|
|
17477
|
-
account:
|
|
17478
|
-
id:
|
|
17779
|
+
const markReadInputSchema = z7.object({
|
|
17780
|
+
account: z7.string().email(),
|
|
17781
|
+
id: z7.string().min(1).describe("Message ID to mark as read")
|
|
17479
17782
|
});
|
|
17480
|
-
const markReadOutputSchema =
|
|
17481
|
-
marked:
|
|
17482
|
-
id:
|
|
17483
|
-
isRead:
|
|
17783
|
+
const markReadOutputSchema = z7.object({
|
|
17784
|
+
marked: z7.literal(true),
|
|
17785
|
+
id: z7.string(),
|
|
17786
|
+
isRead: z7.boolean()
|
|
17484
17787
|
});
|
|
17485
17788
|
if (shouldRegister("mark_read", tools)) {
|
|
17486
17789
|
server.registerTool(
|
|
@@ -17519,7 +17822,7 @@ function registerOrganizeTools(server, ctx) {
|
|
|
17519
17822
|
}
|
|
17520
17823
|
|
|
17521
17824
|
// src/tools/compose.ts
|
|
17522
|
-
import { z as
|
|
17825
|
+
import { z as z8 } from "zod";
|
|
17523
17826
|
import { readFileSync } from "fs";
|
|
17524
17827
|
import { basename, extname } from "path";
|
|
17525
17828
|
function registerComposeTools(server, ctx) {
|
|
@@ -17549,32 +17852,32 @@ function registerComposeTools(server, ctx) {
|
|
|
17549
17852
|
".mp3": "audio/mpeg",
|
|
17550
17853
|
".mp4": "video/mp4"
|
|
17551
17854
|
};
|
|
17552
|
-
const sendEmailSchema =
|
|
17553
|
-
account:
|
|
17554
|
-
to:
|
|
17555
|
-
cc:
|
|
17556
|
-
bcc:
|
|
17557
|
-
subject:
|
|
17558
|
-
body:
|
|
17559
|
-
format:
|
|
17855
|
+
const sendEmailSchema = z8.object({
|
|
17856
|
+
account: z8.string().email(),
|
|
17857
|
+
to: z8.array(emailAddrSchema).min(1),
|
|
17858
|
+
cc: z8.array(emailAddrSchema).optional(),
|
|
17859
|
+
bcc: z8.array(emailAddrSchema).optional(),
|
|
17860
|
+
subject: z8.string(),
|
|
17861
|
+
body: z8.string(),
|
|
17862
|
+
format: z8.enum(["html", "markdown"]).describe(
|
|
17560
17863
|
"Body format. 'html' sends the body as-is (must be valid HTML). 'markdown' converts the body from Markdown to HTML for clean rendering on the recipient side."
|
|
17561
17864
|
),
|
|
17562
|
-
include_signature:
|
|
17865
|
+
include_signature: z8.boolean().describe(
|
|
17563
17866
|
"Whether to append the account's saved HTML signature to the email. If true, don't include a signature in the body param to avoid double signature. Returns an error if true but no signature is configured for this account."
|
|
17564
17867
|
),
|
|
17565
|
-
inReplyTo:
|
|
17868
|
+
inReplyTo: z8.union([z8.string(), z8.literal(false)]).describe(
|
|
17566
17869
|
"Message ID to reply to. When set, sends as a threaded reply which includes the quoted thread history automatically. Set to `false` for a new email (not a reply)."
|
|
17567
17870
|
),
|
|
17568
|
-
replyAll:
|
|
17871
|
+
replyAll: z8.boolean().default(false).optional().describe(
|
|
17569
17872
|
"When true and `inReplyTo` is set, reply to all recipients instead of just the sender."
|
|
17570
17873
|
),
|
|
17571
|
-
forwardMessageId:
|
|
17874
|
+
forwardMessageId: z8.string().optional().describe(
|
|
17572
17875
|
"Message ID to forward. When set, sends as a forward of the specified message, preserving the original content. Mutually exclusive with `inReplyTo`."
|
|
17573
17876
|
),
|
|
17574
|
-
attachments:
|
|
17575
|
-
|
|
17576
|
-
filePath:
|
|
17577
|
-
name:
|
|
17877
|
+
attachments: z8.array(
|
|
17878
|
+
z8.object({
|
|
17879
|
+
filePath: z8.string().min(1).describe("Absolute path to a local file"),
|
|
17880
|
+
name: z8.string().optional().describe(
|
|
17578
17881
|
"Attachment filename. Defaults to the file's basename."
|
|
17579
17882
|
)
|
|
17580
17883
|
})
|
|
@@ -17637,8 +17940,8 @@ function registerComposeTools(server, ctx) {
|
|
|
17637
17940
|
}
|
|
17638
17941
|
}
|
|
17639
17942
|
const sendEmailOutputSchema = {
|
|
17640
|
-
sent:
|
|
17641
|
-
id:
|
|
17943
|
+
sent: z8.literal(true),
|
|
17944
|
+
id: z8.string()
|
|
17642
17945
|
};
|
|
17643
17946
|
if (shouldRegister("send_email", tools)) {
|
|
17644
17947
|
server.registerTool(
|
|
@@ -17657,9 +17960,9 @@ function registerComposeTools(server, ctx) {
|
|
|
17657
17960
|
);
|
|
17658
17961
|
}
|
|
17659
17962
|
const draftEmailOutputSchema = {
|
|
17660
|
-
draft:
|
|
17661
|
-
id:
|
|
17662
|
-
draftHtml:
|
|
17963
|
+
draft: z8.literal(true),
|
|
17964
|
+
id: z8.string(),
|
|
17965
|
+
draftHtml: z8.string().optional()
|
|
17663
17966
|
};
|
|
17664
17967
|
if (shouldRegister("draft_email", tools)) {
|
|
17665
17968
|
server.registerTool(
|
|
@@ -17677,38 +17980,38 @@ function registerComposeTools(server, ctx) {
|
|
|
17677
17980
|
)
|
|
17678
17981
|
);
|
|
17679
17982
|
}
|
|
17680
|
-
const editDraftSchema =
|
|
17681
|
-
account:
|
|
17682
|
-
id:
|
|
17683
|
-
to:
|
|
17684
|
-
cc:
|
|
17685
|
-
bcc:
|
|
17686
|
-
subject:
|
|
17687
|
-
body:
|
|
17688
|
-
format:
|
|
17983
|
+
const editDraftSchema = z8.object({
|
|
17984
|
+
account: z8.string().email(),
|
|
17985
|
+
id: z8.string().min(1).describe("Draft message ID to edit"),
|
|
17986
|
+
to: z8.array(emailAddrSchema).optional(),
|
|
17987
|
+
cc: z8.array(emailAddrSchema).optional(),
|
|
17988
|
+
bcc: z8.array(emailAddrSchema).optional(),
|
|
17989
|
+
subject: z8.string().optional(),
|
|
17990
|
+
body: z8.string().optional(),
|
|
17991
|
+
format: z8.enum(["html", "markdown"]).optional().describe(
|
|
17689
17992
|
"Body format. Only meaningful when `body` is also provided. 'html' sends the body as-is (must be valid HTML). 'markdown' converts the body from Markdown to HTML for clean rendering on the recipient side."
|
|
17690
17993
|
),
|
|
17691
|
-
include_signature:
|
|
17994
|
+
include_signature: z8.boolean().optional().describe(
|
|
17692
17995
|
"Whether to re-apply the account's saved HTML signature to the body. If true, don't include a signature in the body param. Only meaningful when `body` is also provided. Returns an error if true but no signature is configured for this account."
|
|
17693
17996
|
),
|
|
17694
|
-
new_attachments:
|
|
17695
|
-
|
|
17696
|
-
filePath:
|
|
17697
|
-
name:
|
|
17997
|
+
new_attachments: z8.array(
|
|
17998
|
+
z8.object({
|
|
17999
|
+
filePath: z8.string().min(1).describe("Absolute path to a local file"),
|
|
18000
|
+
name: z8.string().optional().describe(
|
|
17698
18001
|
"Attachment filename. Defaults to the file's basename."
|
|
17699
18002
|
)
|
|
17700
18003
|
})
|
|
17701
18004
|
).optional().describe(
|
|
17702
18005
|
"New file attachments to add to the draft. The server reads the files from disk and base64-encodes them automatically."
|
|
17703
18006
|
),
|
|
17704
|
-
remove_attachments:
|
|
18007
|
+
remove_attachments: z8.array(z8.string().min(1)).optional().describe(
|
|
17705
18008
|
"Attachment IDs to remove from the draft. Get attachment IDs from read_email."
|
|
17706
18009
|
)
|
|
17707
18010
|
});
|
|
17708
18011
|
const editDraftOutputSchema = {
|
|
17709
|
-
edited:
|
|
17710
|
-
id:
|
|
17711
|
-
draftHtml:
|
|
18012
|
+
edited: z8.literal(true),
|
|
18013
|
+
id: z8.string(),
|
|
18014
|
+
draftHtml: z8.string().optional()
|
|
17712
18015
|
};
|
|
17713
18016
|
if (shouldRegister("edit_draft", tools)) {
|
|
17714
18017
|
server.registerTool(
|
|
@@ -17800,8 +18103,8 @@ function registerComposeTools(server, ctx) {
|
|
|
17800
18103
|
);
|
|
17801
18104
|
}
|
|
17802
18105
|
const sendDraftOutputSchema = {
|
|
17803
|
-
sent:
|
|
17804
|
-
id:
|
|
18106
|
+
sent: z8.literal(true),
|
|
18107
|
+
id: z8.string()
|
|
17805
18108
|
};
|
|
17806
18109
|
if (shouldRegister("send_draft", tools)) {
|
|
17807
18110
|
server.registerTool(
|
|
@@ -17809,8 +18112,8 @@ function registerComposeTools(server, ctx) {
|
|
|
17809
18112
|
{
|
|
17810
18113
|
description: "Send an existing draft email by ID. Use this with draft IDs returned by `draft_email` or `edit_draft`. Disabled in --read-only mode.",
|
|
17811
18114
|
inputSchema: {
|
|
17812
|
-
account:
|
|
17813
|
-
id:
|
|
18115
|
+
account: z8.string().email(),
|
|
18116
|
+
id: z8.string().min(1).describe("Draft message ID to send")
|
|
17814
18117
|
},
|
|
17815
18118
|
outputSchema: sendDraftOutputSchema
|
|
17816
18119
|
},
|
|
@@ -17832,7 +18135,7 @@ function registerComposeTools(server, ctx) {
|
|
|
17832
18135
|
function registerTools(server, opts) {
|
|
17833
18136
|
const { store, registry, tools } = opts;
|
|
17834
18137
|
registerAccountTools(server, { store, registry, tools });
|
|
17835
|
-
registerBrowseTools(server, { registry, tools });
|
|
18138
|
+
registerBrowseTools(server, { store, registry, tools });
|
|
17836
18139
|
registerFolderTools(server, { registry, tools });
|
|
17837
18140
|
registerOrganizeTools(server, { registry, tools });
|
|
17838
18141
|
registerComposeTools(server, { store, registry, tools });
|
|
@@ -17841,7 +18144,7 @@ function registerTools(server, opts) {
|
|
|
17841
18144
|
// package.json
|
|
17842
18145
|
var package_default = {
|
|
17843
18146
|
name: "hypermail-mcp",
|
|
17844
|
-
version: "0.7.
|
|
18147
|
+
version: "0.7.10",
|
|
17845
18148
|
description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
|
|
17846
18149
|
type: "module",
|
|
17847
18150
|
bin: {
|
|
@@ -17913,193 +18216,6 @@ var package_default = {
|
|
|
17913
18216
|
// src/version.ts
|
|
17914
18217
|
var VERSION = package_default.version;
|
|
17915
18218
|
|
|
17916
|
-
// src/watcher/webhook.ts
|
|
17917
|
-
async function postWebhook(email, config) {
|
|
17918
|
-
if (!config.webhook) return false;
|
|
17919
|
-
const { url, retry } = config.webhook;
|
|
17920
|
-
const maxAttempts = retry.maxAttempts;
|
|
17921
|
-
const baseDelayMs = retry.baseDelayMs;
|
|
17922
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
17923
|
-
if (attempt > 0) {
|
|
17924
|
-
const delay = baseDelayMs * 2 ** (attempt - 1);
|
|
17925
|
-
await sleep(delay);
|
|
17926
|
-
}
|
|
17927
|
-
try {
|
|
17928
|
-
const res = await fetch(url, {
|
|
17929
|
-
method: "POST",
|
|
17930
|
-
headers: { "content-type": "application/json" },
|
|
17931
|
-
body: JSON.stringify(email)
|
|
17932
|
-
});
|
|
17933
|
-
if (res.ok) return true;
|
|
17934
|
-
console.error(
|
|
17935
|
-
`[hypermail-watch] webhook POST ${email.id} attempt ${attempt + 1}/${maxAttempts}: HTTP ${res.status}`
|
|
17936
|
-
);
|
|
17937
|
-
} catch (err) {
|
|
17938
|
-
const code = err.code ?? "";
|
|
17939
|
-
console.error(
|
|
17940
|
-
`[hypermail-watch] webhook POST ${email.id} attempt ${attempt + 1}/${maxAttempts}: ${code || String(err)}`
|
|
17941
|
-
);
|
|
17942
|
-
}
|
|
17943
|
-
}
|
|
17944
|
-
console.error(
|
|
17945
|
-
`[hypermail-watch] webhook delivery failed after ${maxAttempts} retries for ${email.id}`
|
|
17946
|
-
);
|
|
17947
|
-
return false;
|
|
17948
|
-
}
|
|
17949
|
-
function sleep(ms) {
|
|
17950
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17951
|
-
}
|
|
17952
|
-
|
|
17953
|
-
// src/watcher/script.ts
|
|
17954
|
-
import { spawn } from "child_process";
|
|
17955
|
-
async function runNotifyCommand(email, config) {
|
|
17956
|
-
if (!config.notifyCommand) return false;
|
|
17957
|
-
const { command, timeoutMs, retry } = config.notifyCommand;
|
|
17958
|
-
const maxAttempts = retry.maxAttempts;
|
|
17959
|
-
const baseDelayMs = retry.baseDelayMs;
|
|
17960
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
17961
|
-
if (attempt > 0) {
|
|
17962
|
-
const delay = baseDelayMs * 2 ** (attempt - 1);
|
|
17963
|
-
await sleep2(delay);
|
|
17964
|
-
}
|
|
17965
|
-
try {
|
|
17966
|
-
const ok2 = await spawnWithTimeout(
|
|
17967
|
-
command,
|
|
17968
|
-
JSON.stringify(email),
|
|
17969
|
-
timeoutMs
|
|
17970
|
-
);
|
|
17971
|
-
if (ok2) return true;
|
|
17972
|
-
console.error(
|
|
17973
|
-
`[hypermail-watch] notify command ${email.id} attempt ${attempt + 1}/${maxAttempts}: non-zero exit code`
|
|
17974
|
-
);
|
|
17975
|
-
} catch (err) {
|
|
17976
|
-
console.error(
|
|
17977
|
-
`[hypermail-watch] notify command ${email.id} attempt ${attempt + 1}/${maxAttempts}: ${String(err)}`
|
|
17978
|
-
);
|
|
17979
|
-
}
|
|
17980
|
-
}
|
|
17981
|
-
console.error(
|
|
17982
|
-
`[hypermail-watch] notify command delivery failed after ${maxAttempts} retries for ${email.id}`
|
|
17983
|
-
);
|
|
17984
|
-
return false;
|
|
17985
|
-
}
|
|
17986
|
-
function spawnWithTimeout(command, stdinData, timeoutMs) {
|
|
17987
|
-
return new Promise((resolve) => {
|
|
17988
|
-
const child = spawn(command, {
|
|
17989
|
-
shell: true,
|
|
17990
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
17991
|
-
});
|
|
17992
|
-
let stderr = "";
|
|
17993
|
-
child.stderr?.on("data", (chunk) => {
|
|
17994
|
-
stderr += chunk.toString("utf-8");
|
|
17995
|
-
});
|
|
17996
|
-
const timer = setTimeout(() => {
|
|
17997
|
-
child.kill("SIGTERM");
|
|
17998
|
-
if (stderr) {
|
|
17999
|
-
console.error(`[hypermail-watch] notify command timed out after ${timeoutMs}ms. stderr:
|
|
18000
|
-
${stderr}`);
|
|
18001
|
-
}
|
|
18002
|
-
resolve(false);
|
|
18003
|
-
}, timeoutMs);
|
|
18004
|
-
child.on("close", (code) => {
|
|
18005
|
-
clearTimeout(timer);
|
|
18006
|
-
if (stderr && code !== 0) {
|
|
18007
|
-
console.error(`[hypermail-watch] notify command stderr:
|
|
18008
|
-
${stderr}`);
|
|
18009
|
-
}
|
|
18010
|
-
resolve(code === 0);
|
|
18011
|
-
});
|
|
18012
|
-
child.on("error", (err) => {
|
|
18013
|
-
clearTimeout(timer);
|
|
18014
|
-
console.error(`[hypermail-watch] notify command spawn error: ${err.message}`);
|
|
18015
|
-
resolve(false);
|
|
18016
|
-
});
|
|
18017
|
-
child.stdin?.end(stdinData);
|
|
18018
|
-
});
|
|
18019
|
-
}
|
|
18020
|
-
function sleep2(ms) {
|
|
18021
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18022
|
-
}
|
|
18023
|
-
|
|
18024
|
-
// src/watcher/manager.ts
|
|
18025
|
-
var WatcherManager = class {
|
|
18026
|
-
constructor(store, registry, config) {
|
|
18027
|
-
this.store = store;
|
|
18028
|
-
this.registry = registry;
|
|
18029
|
-
this.config = config;
|
|
18030
|
-
}
|
|
18031
|
-
store;
|
|
18032
|
-
registry;
|
|
18033
|
-
config;
|
|
18034
|
-
intervalId = null;
|
|
18035
|
-
/** Start the poll loop. Fires immediately on the first tick, then every
|
|
18036
|
-
* `pollIntervalSeconds`. Safe to call multiple times — subsequent calls
|
|
18037
|
-
* are no-ops. */
|
|
18038
|
-
start() {
|
|
18039
|
-
if (this.intervalId !== null) return;
|
|
18040
|
-
this.poll();
|
|
18041
|
-
this.intervalId = setInterval(
|
|
18042
|
-
() => this.poll(),
|
|
18043
|
-
this.config.pollIntervalSeconds * 1e3
|
|
18044
|
-
);
|
|
18045
|
-
}
|
|
18046
|
-
/** Stop the poll loop and release the interval. Safe to call when already
|
|
18047
|
-
* stopped. */
|
|
18048
|
-
stop() {
|
|
18049
|
-
if (this.intervalId !== null) {
|
|
18050
|
-
clearInterval(this.intervalId);
|
|
18051
|
-
this.intervalId = null;
|
|
18052
|
-
}
|
|
18053
|
-
}
|
|
18054
|
-
// ── private ──
|
|
18055
|
-
async poll() {
|
|
18056
|
-
const accounts = this.store.listAccounts();
|
|
18057
|
-
for (const acct of accounts) {
|
|
18058
|
-
try {
|
|
18059
|
-
await this.pollAccount(acct.email);
|
|
18060
|
-
} catch (err) {
|
|
18061
|
-
console.error(
|
|
18062
|
-
`[hypermail-watch] poll failed for ${acct.email}:`,
|
|
18063
|
-
err
|
|
18064
|
-
);
|
|
18065
|
-
}
|
|
18066
|
-
}
|
|
18067
|
-
}
|
|
18068
|
-
async pollAccount(email) {
|
|
18069
|
-
const { provider, account } = this.registry.resolveByEmail(email);
|
|
18070
|
-
const result = await provider.listEmails(account, {
|
|
18071
|
-
folder: "inbox",
|
|
18072
|
-
limit: 50
|
|
18073
|
-
});
|
|
18074
|
-
const knownIds = [...account.lastSeenIds ?? []];
|
|
18075
|
-
const newEmails = result.items.filter((e) => !knownIds.includes(e.id));
|
|
18076
|
-
if (newEmails.length === 0) return;
|
|
18077
|
-
for (const summary of newEmails) {
|
|
18078
|
-
try {
|
|
18079
|
-
const full = await provider.readEmail(account, summary.id);
|
|
18080
|
-
await this.emit(full);
|
|
18081
|
-
knownIds.unshift(summary.id);
|
|
18082
|
-
} catch (err) {
|
|
18083
|
-
console.error(
|
|
18084
|
-
`[hypermail-watch] emission failed for ${email}/${summary.id}:`,
|
|
18085
|
-
err
|
|
18086
|
-
);
|
|
18087
|
-
}
|
|
18088
|
-
}
|
|
18089
|
-
const capped = knownIds.slice(0, 200);
|
|
18090
|
-
await this.store.upsertAccount({ ...account, lastSeenIds: capped });
|
|
18091
|
-
}
|
|
18092
|
-
async emit(full) {
|
|
18093
|
-
await postWebhook(full, this.config);
|
|
18094
|
-
runNotifyCommand(full, this.config).catch((err) => {
|
|
18095
|
-
console.error(
|
|
18096
|
-
`[hypermail-watch] notify command unhandled error for ${full.id}:`,
|
|
18097
|
-
err
|
|
18098
|
-
);
|
|
18099
|
-
});
|
|
18100
|
-
}
|
|
18101
|
-
};
|
|
18102
|
-
|
|
18103
18219
|
// src/config/load.ts
|
|
18104
18220
|
var ENV_DATA_DIR = "HYPERMAIL_DATA_DIR";
|
|
18105
18221
|
var ENV_KEY = "HYPERMAIL_KEY";
|
|
@@ -18113,22 +18229,9 @@ var ENV_OUTLOOK_TENANT_ID = "HYPERMAIL_OUTLOOK_TENANT_ID";
|
|
|
18113
18229
|
var ENV_GMAIL_CLIENT_ID = "HYPERMAIL_GMAIL_CLIENT_ID";
|
|
18114
18230
|
var ENV_GMAIL_CLIENT_SECRET = "HYPERMAIL_GMAIL_CLIENT_SECRET";
|
|
18115
18231
|
var ENV_GMAIL_REDIRECT_URI = "HYPERMAIL_GMAIL_REDIRECT_URI";
|
|
18116
|
-
var ENV_WATCH_ENABLED = "HYPERMAIL_WATCH_ENABLED";
|
|
18117
|
-
var ENV_WATCH_POLL_SECONDS = "HYPERMAIL_WATCH_POLL_SECONDS";
|
|
18118
|
-
var ENV_WATCH_WEBHOOK_URL = "HYPERMAIL_WATCH_WEBHOOK_URL";
|
|
18119
|
-
var ENV_WATCH_WEBHOOK_RETRY_ATTEMPTS = "HYPERMAIL_WATCH_WEBHOOK_RETRY_ATTEMPTS";
|
|
18120
|
-
var ENV_WATCH_WEBHOOK_RETRY_DELAY_MS = "HYPERMAIL_WATCH_WEBHOOK_RETRY_DELAY_MS";
|
|
18121
|
-
var ENV_WATCH_NOTIFY_COMMAND = "HYPERMAIL_WATCH_NOTIFY_COMMAND";
|
|
18122
|
-
var ENV_WATCH_NOTIFY_TIMEOUT_MS = "HYPERMAIL_WATCH_NOTIFY_TIMEOUT_MS";
|
|
18123
|
-
var ENV_WATCH_NOTIFY_RETRY_ATTEMPTS = "HYPERMAIL_WATCH_NOTIFY_RETRY_ATTEMPTS";
|
|
18124
|
-
var ENV_WATCH_NOTIFY_RETRY_DELAY_MS = "HYPERMAIL_WATCH_NOTIFY_RETRY_DELAY_MS";
|
|
18125
18232
|
var DEFAULT_TRANSPORT = "stdio";
|
|
18126
18233
|
var DEFAULT_HTTP_PORT = 3e3;
|
|
18127
18234
|
var DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
18128
|
-
var DEFAULT_WATCH_POLL_SECONDS = 10;
|
|
18129
|
-
var DEFAULT_RETRY_ATTEMPTS = 5;
|
|
18130
|
-
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
18131
|
-
var DEFAULT_NOTIFY_TIMEOUT_MS = 3e4;
|
|
18132
18235
|
function envRaw(name) {
|
|
18133
18236
|
return process.env[name];
|
|
18134
18237
|
}
|
|
@@ -18138,14 +18241,6 @@ function optionalEnvString(name) {
|
|
|
18138
18241
|
const trimmed = value.trim();
|
|
18139
18242
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
18140
18243
|
}
|
|
18141
|
-
function parseBoolEnv(name) {
|
|
18142
|
-
const value = envRaw(name);
|
|
18143
|
-
if (value === void 0) return void 0;
|
|
18144
|
-
const lower = value.trim().toLowerCase();
|
|
18145
|
-
if (lower === "true") return true;
|
|
18146
|
-
if (lower === "false") return false;
|
|
18147
|
-
throw new Error(`${name} must be either "true" or "false"`);
|
|
18148
|
-
}
|
|
18149
18244
|
function parseTransportEnv() {
|
|
18150
18245
|
const value = envRaw(ENV_TRANSPORT);
|
|
18151
18246
|
if (value === void 0) return void 0;
|
|
@@ -18163,15 +18258,6 @@ function parsePositiveInteger(value) {
|
|
|
18163
18258
|
const parsed = Number(trimmed);
|
|
18164
18259
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
18165
18260
|
}
|
|
18166
|
-
function parsePositiveIntegerEnv(name, defaultValue) {
|
|
18167
|
-
const value = envRaw(name);
|
|
18168
|
-
if (value === void 0) return defaultValue;
|
|
18169
|
-
const parsed = parsePositiveInteger(value);
|
|
18170
|
-
if (parsed === void 0) {
|
|
18171
|
-
throw new Error(`${name} must be a positive integer`);
|
|
18172
|
-
}
|
|
18173
|
-
return parsed;
|
|
18174
|
-
}
|
|
18175
18261
|
function parseStringArray(value) {
|
|
18176
18262
|
if (value === void 0) return void 0;
|
|
18177
18263
|
const trimmed = value.trim();
|
|
@@ -18189,17 +18275,6 @@ function validateToolNames(toolNames, envName) {
|
|
|
18189
18275
|
}
|
|
18190
18276
|
}
|
|
18191
18277
|
}
|
|
18192
|
-
function validateWebhookUrl(raw) {
|
|
18193
|
-
try {
|
|
18194
|
-
const url = new URL(raw);
|
|
18195
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
18196
|
-
throw new Error("unsupported protocol");
|
|
18197
|
-
}
|
|
18198
|
-
return raw;
|
|
18199
|
-
} catch {
|
|
18200
|
-
throw new Error(`${ENV_WATCH_WEBHOOK_URL} must be a valid http(s) URL`);
|
|
18201
|
-
}
|
|
18202
|
-
}
|
|
18203
18278
|
function resolveHttpConfig(transport, cliOverrides, warnings) {
|
|
18204
18279
|
const portSource = cliOverrides.port !== void 0 ? "--port" : ENV_HTTP_PORT;
|
|
18205
18280
|
const rawPort = cliOverrides.port ?? envRaw(ENV_HTTP_PORT);
|
|
@@ -18263,68 +18338,12 @@ function resolveProvidersConfig() {
|
|
|
18263
18338
|
}
|
|
18264
18339
|
return providers;
|
|
18265
18340
|
}
|
|
18266
|
-
function resolveRetryConfig(attemptsEnv, delayEnv) {
|
|
18267
|
-
return {
|
|
18268
|
-
maxAttempts: parsePositiveIntegerEnv(attemptsEnv, DEFAULT_RETRY_ATTEMPTS),
|
|
18269
|
-
baseDelayMs: parsePositiveIntegerEnv(delayEnv, DEFAULT_RETRY_DELAY_MS)
|
|
18270
|
-
};
|
|
18271
|
-
}
|
|
18272
|
-
function resolveWatchConfig() {
|
|
18273
|
-
const enabled = parseBoolEnv(ENV_WATCH_ENABLED) ?? false;
|
|
18274
|
-
if (!enabled) return void 0;
|
|
18275
|
-
const pollIntervalSeconds = parsePositiveIntegerEnv(
|
|
18276
|
-
ENV_WATCH_POLL_SECONDS,
|
|
18277
|
-
DEFAULT_WATCH_POLL_SECONDS
|
|
18278
|
-
);
|
|
18279
|
-
const webhookUrl = optionalEnvString(ENV_WATCH_WEBHOOK_URL);
|
|
18280
|
-
let webhook;
|
|
18281
|
-
if (webhookUrl) {
|
|
18282
|
-
webhook = {
|
|
18283
|
-
url: validateWebhookUrl(webhookUrl),
|
|
18284
|
-
retry: resolveRetryConfig(
|
|
18285
|
-
ENV_WATCH_WEBHOOK_RETRY_ATTEMPTS,
|
|
18286
|
-
ENV_WATCH_WEBHOOK_RETRY_DELAY_MS
|
|
18287
|
-
)
|
|
18288
|
-
};
|
|
18289
|
-
}
|
|
18290
|
-
const rawNotifyCommand = envRaw(ENV_WATCH_NOTIFY_COMMAND);
|
|
18291
|
-
let notifyCommand;
|
|
18292
|
-
if (rawNotifyCommand !== void 0) {
|
|
18293
|
-
const command = rawNotifyCommand.trim();
|
|
18294
|
-
if (!command) {
|
|
18295
|
-
throw new Error(`${ENV_WATCH_NOTIFY_COMMAND} must not be empty when watch is enabled`);
|
|
18296
|
-
}
|
|
18297
|
-
notifyCommand = {
|
|
18298
|
-
command,
|
|
18299
|
-
timeoutMs: parsePositiveIntegerEnv(
|
|
18300
|
-
ENV_WATCH_NOTIFY_TIMEOUT_MS,
|
|
18301
|
-
DEFAULT_NOTIFY_TIMEOUT_MS
|
|
18302
|
-
),
|
|
18303
|
-
retry: resolveRetryConfig(
|
|
18304
|
-
ENV_WATCH_NOTIFY_RETRY_ATTEMPTS,
|
|
18305
|
-
ENV_WATCH_NOTIFY_RETRY_DELAY_MS
|
|
18306
|
-
)
|
|
18307
|
-
};
|
|
18308
|
-
}
|
|
18309
|
-
if (!webhook && !notifyCommand) {
|
|
18310
|
-
throw new Error(
|
|
18311
|
-
`${ENV_WATCH_ENABLED}=true requires ${ENV_WATCH_WEBHOOK_URL} or ${ENV_WATCH_NOTIFY_COMMAND}`
|
|
18312
|
-
);
|
|
18313
|
-
}
|
|
18314
|
-
return {
|
|
18315
|
-
enabled: true,
|
|
18316
|
-
pollIntervalSeconds,
|
|
18317
|
-
webhook,
|
|
18318
|
-
notifyCommand
|
|
18319
|
-
};
|
|
18320
|
-
}
|
|
18321
18341
|
function loadConfig(cliOverrides = {}) {
|
|
18322
18342
|
const warnings = [];
|
|
18323
18343
|
const transport = cliOverrides.transport ?? parseTransportEnv() ?? DEFAULT_TRANSPORT;
|
|
18324
18344
|
const http = resolveHttpConfig(transport, cliOverrides, warnings);
|
|
18325
18345
|
const tools = resolveToolsConfig();
|
|
18326
18346
|
const providers = resolveProvidersConfig();
|
|
18327
|
-
const watch = resolveWatchConfig();
|
|
18328
18347
|
const dataDir = cliOverrides.dataDir ?? optionalEnvString(ENV_DATA_DIR);
|
|
18329
18348
|
if (!optionalEnvString(ENV_KEY)) {
|
|
18330
18349
|
warnings.push(
|
|
@@ -18337,8 +18356,7 @@ function loadConfig(cliOverrides = {}) {
|
|
|
18337
18356
|
transport,
|
|
18338
18357
|
http,
|
|
18339
18358
|
tools,
|
|
18340
|
-
providers
|
|
18341
|
-
watch
|
|
18359
|
+
providers
|
|
18342
18360
|
},
|
|
18343
18361
|
warnings
|
|
18344
18362
|
};
|
|
@@ -18353,6 +18371,7 @@ var KNOWN_TOOLS = [
|
|
|
18353
18371
|
"set_account_settings",
|
|
18354
18372
|
"remove_account",
|
|
18355
18373
|
"list_emails",
|
|
18374
|
+
"get_new_emails",
|
|
18356
18375
|
"search_emails",
|
|
18357
18376
|
"read_email",
|
|
18358
18377
|
"read_attachment",
|
|
@@ -18386,14 +18405,6 @@ async function startServer(opts) {
|
|
|
18386
18405
|
const store = await AccountStore.open({ dataDir: config.dataDir });
|
|
18387
18406
|
const registry = buildRegistry({ store, providers: config.providers });
|
|
18388
18407
|
const tools = resolveTools(config);
|
|
18389
|
-
let watcher;
|
|
18390
|
-
if (config.watch?.enabled) {
|
|
18391
|
-
watcher = new WatcherManager(store, registry, config.watch);
|
|
18392
|
-
watcher.start();
|
|
18393
|
-
const stop = () => watcher?.stop();
|
|
18394
|
-
process.on("SIGTERM", stop);
|
|
18395
|
-
process.on("SIGINT", stop);
|
|
18396
|
-
}
|
|
18397
18408
|
const createServer = () => {
|
|
18398
18409
|
const s = new McpServer(
|
|
18399
18410
|
{ name: "hypermail-mcp", version: VERSION },
|
|
@@ -18598,17 +18609,6 @@ Provider environment variables:
|
|
|
18598
18609
|
HYPERMAIL_GMAIL_CLIENT_SECRET
|
|
18599
18610
|
HYPERMAIL_GMAIL_REDIRECT_URI
|
|
18600
18611
|
|
|
18601
|
-
Watcher environment variables:
|
|
18602
|
-
HYPERMAIL_WATCH_ENABLED=true|false
|
|
18603
|
-
HYPERMAIL_WATCH_POLL_SECONDS
|
|
18604
|
-
HYPERMAIL_WATCH_WEBHOOK_URL
|
|
18605
|
-
HYPERMAIL_WATCH_WEBHOOK_RETRY_ATTEMPTS
|
|
18606
|
-
HYPERMAIL_WATCH_WEBHOOK_RETRY_DELAY_MS
|
|
18607
|
-
HYPERMAIL_WATCH_NOTIFY_COMMAND
|
|
18608
|
-
HYPERMAIL_WATCH_NOTIFY_TIMEOUT_MS
|
|
18609
|
-
HYPERMAIL_WATCH_NOTIFY_RETRY_ATTEMPTS
|
|
18610
|
-
HYPERMAIL_WATCH_NOTIFY_RETRY_DELAY_MS
|
|
18611
|
-
|
|
18612
18612
|
Example:
|
|
18613
18613
|
HYPERMAIL_TRANSPORT=http \\
|
|
18614
18614
|
HYPERMAIL_HTTP_PORT=8080 \\
|