hypermail-mcp 0.7.19 → 0.7.20
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 +18 -1
- package/dist/cli.js +206 -53
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
A **Model Context Protocol** server that lets an agent operate any of the user's
|
|
4
4
|
inboxes through a single, unified tool surface.
|
|
5
5
|
|
|
6
|
+
> **v0.7.20** — Hardened long-running IMAP and new-email polling paths:
|
|
7
|
+
> IMAP connections now use explicit connection/greeting/socket/operation
|
|
8
|
+
> timeouts, reset stuck connections, and report post-TLS authentication
|
|
9
|
+
> failures with account-specific remediation guidance. `get_new_emails` now
|
|
10
|
+
> applies per-account timeouts during candidate collection and hydration so one
|
|
11
|
+
> hanging account does not block all-account polling; partial timeout failures
|
|
12
|
+
> are returned alongside successful accounts' emails.
|
|
13
|
+
>
|
|
6
14
|
> **v0.7.19** — Hardened Outlook ID handling when Microsoft Graph `$search`
|
|
7
15
|
> returns IDs that `/me/messages/{id}` rejects as malformed: `search_emails`
|
|
8
16
|
> now returns a translated readable immutable ID when available, and
|
|
@@ -344,7 +352,9 @@ tool on their own schedule, for example every 30–60 seconds.
|
|
|
344
352
|
- `limit: 0` can initialize/check state without fetching message bodies.
|
|
345
353
|
|
|
346
354
|
All-account calls return partial failures as `errors: [{ account, message }]`
|
|
347
|
-
and still return successful accounts' emails.
|
|
355
|
+
and still return successful accounts' emails. Pollers should continue processing
|
|
356
|
+
returned emails when `errors` is non-empty, and use those entries to notify or
|
|
357
|
+
log which accounts are failing.
|
|
348
358
|
|
|
349
359
|
See [`examples/hermes/`](examples/hermes/) for a Hermes scheduler integration
|
|
350
360
|
that polls this tool and hands new-email payloads to a Hermes agent.
|
|
@@ -373,6 +383,13 @@ that polls this tool and hands new-email payloads to a Hermes agent.
|
|
|
373
383
|
it returns `{ "status": "ready", "account": {...} }`.
|
|
374
384
|
5. From then on, any tool can be called with `account: "<that-email>"`.
|
|
375
385
|
|
|
386
|
+
### IMAP
|
|
387
|
+
|
|
388
|
+
IMAP accounts are added synchronously with host/user/password settings. If the
|
|
389
|
+
server accepts TCP/TLS but closes during login, Hypermail reports an IMAP
|
|
390
|
+
authentication failure. Check the mailbox password or app-password, confirm IMAP
|
|
391
|
+
access is enabled by the provider, then re-add or update the account.
|
|
392
|
+
|
|
376
393
|
### Gmail
|
|
377
394
|
|
|
378
395
|
Gmail uses Google OAuth 2.0, matching the official Gmail MCP model. Google's
|
package/dist/cli.js
CHANGED
|
@@ -8625,19 +8625,19 @@ var getRefs = (options) => {
|
|
|
8625
8625
|
};
|
|
8626
8626
|
|
|
8627
8627
|
// node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@4.4.3/node_modules/zod-to-json-schema/dist/esm/errorMessages.js
|
|
8628
|
-
function addErrorMessage(res, key,
|
|
8628
|
+
function addErrorMessage(res, key, errorMessage2, refs) {
|
|
8629
8629
|
if (!refs?.errorMessages)
|
|
8630
8630
|
return;
|
|
8631
|
-
if (
|
|
8631
|
+
if (errorMessage2) {
|
|
8632
8632
|
res.errorMessage = {
|
|
8633
8633
|
...res.errorMessage,
|
|
8634
|
-
[key]:
|
|
8634
|
+
[key]: errorMessage2
|
|
8635
8635
|
};
|
|
8636
8636
|
}
|
|
8637
8637
|
}
|
|
8638
|
-
function setResponseValueAndErrors(res, key, value,
|
|
8638
|
+
function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
|
|
8639
8639
|
res[key] = value;
|
|
8640
|
-
addErrorMessage(res, key,
|
|
8640
|
+
addErrorMessage(res, key, errorMessage2, refs);
|
|
8641
8641
|
}
|
|
8642
8642
|
|
|
8643
8643
|
// node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@4.4.3/node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
|
|
@@ -9955,8 +9955,8 @@ var Protocol = class {
|
|
|
9955
9955
|
if (queuedMessage.type === "response") {
|
|
9956
9956
|
resolver(message);
|
|
9957
9957
|
} else {
|
|
9958
|
-
const
|
|
9959
|
-
const error = new McpError(
|
|
9958
|
+
const errorMessage2 = message;
|
|
9959
|
+
const error = new McpError(errorMessage2.error.code, errorMessage2.error.message, errorMessage2.error.data);
|
|
9960
9960
|
resolver(error);
|
|
9961
9961
|
}
|
|
9962
9962
|
} else {
|
|
@@ -11256,23 +11256,23 @@ var Server = class extends Protocol {
|
|
|
11256
11256
|
const wrappedHandler = async (request, extra) => {
|
|
11257
11257
|
const validatedRequest = safeParse2(CallToolRequestSchema, request);
|
|
11258
11258
|
if (!validatedRequest.success) {
|
|
11259
|
-
const
|
|
11260
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${
|
|
11259
|
+
const errorMessage2 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
11260
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage2}`);
|
|
11261
11261
|
}
|
|
11262
11262
|
const { params } = validatedRequest.data;
|
|
11263
11263
|
const result = await Promise.resolve(handler(request, extra));
|
|
11264
11264
|
if (params.task) {
|
|
11265
11265
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
11266
11266
|
if (!taskValidationResult.success) {
|
|
11267
|
-
const
|
|
11268
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${
|
|
11267
|
+
const errorMessage2 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
11268
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage2}`);
|
|
11269
11269
|
}
|
|
11270
11270
|
return taskValidationResult.data;
|
|
11271
11271
|
}
|
|
11272
11272
|
const validationResult = safeParse2(CallToolResultSchema, result);
|
|
11273
11273
|
if (!validationResult.success) {
|
|
11274
|
-
const
|
|
11275
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${
|
|
11274
|
+
const errorMessage2 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
11275
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage2}`);
|
|
11276
11276
|
}
|
|
11277
11277
|
return validationResult.data;
|
|
11278
11278
|
};
|
|
@@ -11767,12 +11767,12 @@ var McpServer = class {
|
|
|
11767
11767
|
* @param errorMessage - The error message.
|
|
11768
11768
|
* @returns The tool error result.
|
|
11769
11769
|
*/
|
|
11770
|
-
createToolError(
|
|
11770
|
+
createToolError(errorMessage2) {
|
|
11771
11771
|
return {
|
|
11772
11772
|
content: [
|
|
11773
11773
|
{
|
|
11774
11774
|
type: "text",
|
|
11775
|
-
text:
|
|
11775
|
+
text: errorMessage2
|
|
11776
11776
|
}
|
|
11777
11777
|
],
|
|
11778
11778
|
isError: true
|
|
@@ -11790,8 +11790,8 @@ var McpServer = class {
|
|
|
11790
11790
|
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
11791
11791
|
if (!parseResult.success) {
|
|
11792
11792
|
const error = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
11793
|
-
const
|
|
11794
|
-
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${
|
|
11793
|
+
const errorMessage2 = getParseErrorMessage(error);
|
|
11794
|
+
throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage2}`);
|
|
11795
11795
|
}
|
|
11796
11796
|
return parseResult.data;
|
|
11797
11797
|
}
|
|
@@ -11815,8 +11815,8 @@ var McpServer = class {
|
|
|
11815
11815
|
const parseResult = await safeParseAsync2(outputObj, result.structuredContent);
|
|
11816
11816
|
if (!parseResult.success) {
|
|
11817
11817
|
const error = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
11818
|
-
const
|
|
11819
|
-
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${
|
|
11818
|
+
const errorMessage2 = getParseErrorMessage(error);
|
|
11819
|
+
throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage2}`);
|
|
11820
11820
|
}
|
|
11821
11821
|
}
|
|
11822
11822
|
/**
|
|
@@ -12028,8 +12028,8 @@ var McpServer = class {
|
|
|
12028
12028
|
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
12029
12029
|
if (!parseResult.success) {
|
|
12030
12030
|
const error = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
12031
|
-
const
|
|
12032
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${
|
|
12031
|
+
const errorMessage2 = getParseErrorMessage(error);
|
|
12032
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage2}`);
|
|
12033
12033
|
}
|
|
12034
12034
|
const args = parseResult.data;
|
|
12035
12035
|
const cb = prompt.callback;
|
|
@@ -15276,6 +15276,10 @@ var OutlookProvider = class {
|
|
|
15276
15276
|
// src/providers/imap/client.ts
|
|
15277
15277
|
import { ImapFlow } from "imapflow";
|
|
15278
15278
|
import nodemailer from "nodemailer";
|
|
15279
|
+
var IMAP_CONNECTION_TIMEOUT_MS = 15e3;
|
|
15280
|
+
var IMAP_GREETING_TIMEOUT_MS = 15e3;
|
|
15281
|
+
var IMAP_SOCKET_TIMEOUT_MS = 45e3;
|
|
15282
|
+
var IMAP_OPERATION_TIMEOUT_MS = 45e3;
|
|
15279
15283
|
function isImapTokens(obj) {
|
|
15280
15284
|
if (typeof obj !== "object" || obj === null) return false;
|
|
15281
15285
|
const o = obj;
|
|
@@ -15290,10 +15294,12 @@ function extractTokens(account) {
|
|
|
15290
15294
|
return account.tokens;
|
|
15291
15295
|
}
|
|
15292
15296
|
var ImapClient = class {
|
|
15293
|
-
constructor(tokens) {
|
|
15297
|
+
constructor(tokens, opts = {}) {
|
|
15294
15298
|
this.tokens = tokens;
|
|
15299
|
+
this.opts = opts;
|
|
15295
15300
|
}
|
|
15296
15301
|
tokens;
|
|
15302
|
+
opts;
|
|
15297
15303
|
imap = null;
|
|
15298
15304
|
transporter = null;
|
|
15299
15305
|
connecting = null;
|
|
@@ -15309,16 +15315,28 @@ var ImapClient = class {
|
|
|
15309
15315
|
user: this.tokens.user,
|
|
15310
15316
|
pass: this.tokens.password
|
|
15311
15317
|
},
|
|
15312
|
-
logger: false
|
|
15318
|
+
logger: false,
|
|
15319
|
+
connectionTimeout: IMAP_CONNECTION_TIMEOUT_MS,
|
|
15320
|
+
greetingTimeout: IMAP_GREETING_TIMEOUT_MS,
|
|
15321
|
+
socketTimeout: IMAP_SOCKET_TIMEOUT_MS
|
|
15313
15322
|
});
|
|
15314
15323
|
if (!this.connecting) {
|
|
15315
|
-
this.
|
|
15316
|
-
|
|
15317
|
-
this.
|
|
15318
|
-
|
|
15324
|
+
this.log("connectStart");
|
|
15325
|
+
this.connecting = this.withOperationTimeout("connect", this.imap.connect()).then(() => {
|
|
15326
|
+
this.log("connectEnd");
|
|
15327
|
+
}).catch((err) => {
|
|
15328
|
+
const normalized = this.normalizeConnectError(err);
|
|
15329
|
+
this.log("connectError", {
|
|
15330
|
+
message: normalized.message,
|
|
15331
|
+
code: imapErrorCode(err) ?? null,
|
|
15332
|
+
authenticationFailed: hasAuthenticationFailedFlag(err)
|
|
15333
|
+
});
|
|
15334
|
+
this.resetImap();
|
|
15335
|
+
throw normalized;
|
|
15319
15336
|
});
|
|
15320
15337
|
}
|
|
15321
15338
|
await this.connecting;
|
|
15339
|
+
if (!this.imap) throw new Error("IMAP connection unavailable after connect");
|
|
15322
15340
|
return this.imap;
|
|
15323
15341
|
}
|
|
15324
15342
|
/** Get (or create) a nodemailer SMTP transporter. */
|
|
@@ -15345,7 +15363,17 @@ var ImapClient = class {
|
|
|
15345
15363
|
() => void 0,
|
|
15346
15364
|
() => void 0
|
|
15347
15365
|
);
|
|
15348
|
-
|
|
15366
|
+
try {
|
|
15367
|
+
return await this.withOperationTimeout("operation", run, () => {
|
|
15368
|
+
this.imapQueue = Promise.resolve();
|
|
15369
|
+
this.resetImap();
|
|
15370
|
+
});
|
|
15371
|
+
} catch (err) {
|
|
15372
|
+
if (err instanceof ImapTimeoutError) {
|
|
15373
|
+
this.log("operationTimeout", { message: err.message });
|
|
15374
|
+
}
|
|
15375
|
+
throw err;
|
|
15376
|
+
}
|
|
15349
15377
|
}
|
|
15350
15378
|
/**
|
|
15351
15379
|
* Acquire a mailbox lock and run `fn` with the mailbox selected.
|
|
@@ -15376,15 +15404,101 @@ var ImapClient = class {
|
|
|
15376
15404
|
this.transporter = null;
|
|
15377
15405
|
}
|
|
15378
15406
|
}
|
|
15407
|
+
resetImap() {
|
|
15408
|
+
const imap = this.imap;
|
|
15409
|
+
this.imap = null;
|
|
15410
|
+
this.connecting = null;
|
|
15411
|
+
if (imap) {
|
|
15412
|
+
imap.logout().catch(() => {
|
|
15413
|
+
});
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
15416
|
+
async withOperationTimeout(operation, promise, onTimeout) {
|
|
15417
|
+
let timer;
|
|
15418
|
+
const timeout = new Promise((_, reject) => {
|
|
15419
|
+
timer = setTimeout(() => {
|
|
15420
|
+
onTimeout?.();
|
|
15421
|
+
reject(new ImapTimeoutError(this.timeoutMessage(operation)));
|
|
15422
|
+
}, IMAP_OPERATION_TIMEOUT_MS);
|
|
15423
|
+
});
|
|
15424
|
+
try {
|
|
15425
|
+
return await Promise.race([promise, timeout]);
|
|
15426
|
+
} finally {
|
|
15427
|
+
if (timer) clearTimeout(timer);
|
|
15428
|
+
}
|
|
15429
|
+
}
|
|
15430
|
+
timeoutMessage(operation) {
|
|
15431
|
+
return `IMAP ${operation} timed out after ${IMAP_OPERATION_TIMEOUT_MS}ms for account ${this.accountLabel()} (${this.tokens.host}:${this.tokens.port})`;
|
|
15432
|
+
}
|
|
15433
|
+
accountLabel() {
|
|
15434
|
+
return this.opts.account ?? this.tokens.user;
|
|
15435
|
+
}
|
|
15436
|
+
normalizeConnectError(err) {
|
|
15437
|
+
if (!isImapAuthenticationError(err)) {
|
|
15438
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
15439
|
+
}
|
|
15440
|
+
const code = imapErrorCode(err);
|
|
15441
|
+
const codeSuffix = code ? ` Provider error code: ${code}.` : "";
|
|
15442
|
+
return new Error(
|
|
15443
|
+
`IMAP authentication failed for account ${this.accountLabel()} (${this.tokens.host}:${this.tokens.port}). Verify the password/app-password and IMAP access policy, then re-add or update the account.` + codeSuffix,
|
|
15444
|
+
{ cause: err }
|
|
15445
|
+
);
|
|
15446
|
+
}
|
|
15447
|
+
log(event, fields = {}) {
|
|
15448
|
+
(this.opts.logger ?? noopLogger).debug("imap", event, {
|
|
15449
|
+
account: this.accountLabel(),
|
|
15450
|
+
host: this.tokens.host,
|
|
15451
|
+
port: this.tokens.port,
|
|
15452
|
+
...fields
|
|
15453
|
+
});
|
|
15454
|
+
}
|
|
15379
15455
|
};
|
|
15456
|
+
var ImapTimeoutError = class extends Error {
|
|
15457
|
+
constructor(message) {
|
|
15458
|
+
super(message);
|
|
15459
|
+
this.name = "ImapTimeoutError";
|
|
15460
|
+
}
|
|
15461
|
+
};
|
|
15462
|
+
function errorMessage(err) {
|
|
15463
|
+
return err instanceof Error ? err.message : String(err);
|
|
15464
|
+
}
|
|
15465
|
+
function isImapAuthenticationError(err) {
|
|
15466
|
+
if (hasAuthenticationFailedFlag(err)) return true;
|
|
15467
|
+
const code = imapErrorCode(err);
|
|
15468
|
+
if (code === "ClosedAfterConnectTLS") return true;
|
|
15469
|
+
const message = errorMessage(err).toLowerCase();
|
|
15470
|
+
return message.includes("authentication failed") || message.includes("invalid credentials") || message.includes("login failed");
|
|
15471
|
+
}
|
|
15472
|
+
function hasAuthenticationFailedFlag(err) {
|
|
15473
|
+
return readErrorField(err, "authenticationFailed") === true;
|
|
15474
|
+
}
|
|
15475
|
+
function imapErrorCode(err) {
|
|
15476
|
+
const direct = readErrorField(err, "code");
|
|
15477
|
+
if (typeof direct === "string" && direct !== "NoConnection") return direct;
|
|
15478
|
+
const nested = readErrorField(readErrorField(err, "error"), "code");
|
|
15479
|
+
if (typeof nested === "string") return nested;
|
|
15480
|
+
if (typeof direct === "string") return direct;
|
|
15481
|
+
return void 0;
|
|
15482
|
+
}
|
|
15483
|
+
function readErrorField(err, field) {
|
|
15484
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
15485
|
+
return err[field];
|
|
15486
|
+
}
|
|
15380
15487
|
var ImapClientFactory = class {
|
|
15488
|
+
constructor(logger = noopLogger) {
|
|
15489
|
+
this.logger = logger;
|
|
15490
|
+
}
|
|
15491
|
+
logger;
|
|
15381
15492
|
cache = /* @__PURE__ */ new Map();
|
|
15382
15493
|
get(account) {
|
|
15383
15494
|
const key = account.email.toLowerCase();
|
|
15384
15495
|
const existing = this.cache.get(key);
|
|
15385
15496
|
if (existing) return existing;
|
|
15386
15497
|
const tokens = extractTokens(account);
|
|
15387
|
-
const client = new ImapClient(tokens
|
|
15498
|
+
const client = new ImapClient(tokens, {
|
|
15499
|
+
account: account.email,
|
|
15500
|
+
logger: this.logger
|
|
15501
|
+
});
|
|
15388
15502
|
this.cache.set(key, client);
|
|
15389
15503
|
return client;
|
|
15390
15504
|
}
|
|
@@ -16155,12 +16269,13 @@ async function deleteFolder2(clients, account, folderId) {
|
|
|
16155
16269
|
|
|
16156
16270
|
// src/providers/imap/index.ts
|
|
16157
16271
|
var ImapProvider = class {
|
|
16158
|
-
constructor(store) {
|
|
16272
|
+
constructor(store, logger = noopLogger) {
|
|
16159
16273
|
this.store = store;
|
|
16274
|
+
this.clients = new ImapClientFactory(logger);
|
|
16160
16275
|
}
|
|
16161
16276
|
store;
|
|
16162
16277
|
id = "imap";
|
|
16163
|
-
clients
|
|
16278
|
+
clients;
|
|
16164
16279
|
// ---------- account lifecycle ----------
|
|
16165
16280
|
async addAccount(input) {
|
|
16166
16281
|
if (!this.store) throw new Error("IMAP provider requires an AccountStore");
|
|
@@ -17345,7 +17460,7 @@ function buildRegistry(opts) {
|
|
|
17345
17460
|
clientId: outlookCfg?.clientId,
|
|
17346
17461
|
tenantId: outlookCfg?.tenantId
|
|
17347
17462
|
}));
|
|
17348
|
-
providers.set("imap", new ImapProvider(opts.store));
|
|
17463
|
+
providers.set("imap", new ImapProvider(opts.store, opts.logger));
|
|
17349
17464
|
const gmailCfg = opts.providers?.gmail;
|
|
17350
17465
|
providers.set("gmail", new GmailProvider({
|
|
17351
17466
|
store: opts.store,
|
|
@@ -17820,10 +17935,46 @@ function selectBody(msg, format) {
|
|
|
17820
17935
|
|
|
17821
17936
|
// src/tools/new-emails.ts
|
|
17822
17937
|
import { z as z4 } from "zod";
|
|
17938
|
+
|
|
17939
|
+
// src/tools/new-emails-timeout.ts
|
|
17940
|
+
var ACCOUNT_POLL_TIMEOUT_MS = 45e3;
|
|
17941
|
+
var MISSING_RECEIVED_AT = "1970-01-01T00:00:00.000Z";
|
|
17942
|
+
function effectiveReceivedAt(receivedAt) {
|
|
17943
|
+
return normalizeTimestamp2(receivedAt) ?? MISSING_RECEIVED_AT;
|
|
17944
|
+
}
|
|
17945
|
+
function normalizeTimestamp2(value) {
|
|
17946
|
+
if (!value) return null;
|
|
17947
|
+
const ms = Date.parse(value);
|
|
17948
|
+
if (!Number.isFinite(ms)) return null;
|
|
17949
|
+
return new Date(ms).toISOString();
|
|
17950
|
+
}
|
|
17951
|
+
function compareTimestamp2(a, b) {
|
|
17952
|
+
if (a < b) return -1;
|
|
17953
|
+
if (a > b) return 1;
|
|
17954
|
+
return 0;
|
|
17955
|
+
}
|
|
17956
|
+
async function withAccountPollTimeout(account, operation, promise) {
|
|
17957
|
+
let timer;
|
|
17958
|
+
const timeout = new Promise((_, reject) => {
|
|
17959
|
+
timer = setTimeout(() => {
|
|
17960
|
+
reject(
|
|
17961
|
+
new Error(
|
|
17962
|
+
`${operation} timed out after ${ACCOUNT_POLL_TIMEOUT_MS}ms for account ${account}`
|
|
17963
|
+
)
|
|
17964
|
+
);
|
|
17965
|
+
}, ACCOUNT_POLL_TIMEOUT_MS);
|
|
17966
|
+
});
|
|
17967
|
+
try {
|
|
17968
|
+
return await Promise.race([promise, timeout]);
|
|
17969
|
+
} finally {
|
|
17970
|
+
if (timer) clearTimeout(timer);
|
|
17971
|
+
}
|
|
17972
|
+
}
|
|
17973
|
+
|
|
17974
|
+
// src/tools/new-emails.ts
|
|
17823
17975
|
var DEFAULT_LIMIT = 10;
|
|
17824
17976
|
var BODY_LIMIT = 2e4;
|
|
17825
17977
|
var PAGE_SIZE = 100;
|
|
17826
|
-
var MISSING_RECEIVED_AT = "1970-01-01T00:00:00.000Z";
|
|
17827
17978
|
function registerNewEmailTool(server, ctx) {
|
|
17828
17979
|
const { store, registry, tools, logger = noopLogger } = ctx;
|
|
17829
17980
|
if (!shouldRegister("get_new_emails", tools)) return;
|
|
@@ -17874,7 +18025,11 @@ function registerNewEmailTool(server, ctx) {
|
|
|
17874
18025
|
if (args.account) {
|
|
17875
18026
|
try {
|
|
17876
18027
|
const { provider, account } = registry.resolveByEmail(args.account);
|
|
17877
|
-
const result = await
|
|
18028
|
+
const result = await withAccountPollTimeout(
|
|
18029
|
+
account.email,
|
|
18030
|
+
"collect new-email candidates",
|
|
18031
|
+
collectCandidatesForAccount(store, provider, account, logger)
|
|
18032
|
+
);
|
|
17878
18033
|
const selected2 = oldestCandidatesFirst(result.candidates).slice(0, limit);
|
|
17879
18034
|
logger.debug("get-new-emails", "selected", {
|
|
17880
18035
|
account: result.account.email,
|
|
@@ -17884,7 +18039,11 @@ function registerNewEmailTool(server, ctx) {
|
|
|
17884
18039
|
selectedReceivedAt: selected2.map((candidate) => candidate.timestamp),
|
|
17885
18040
|
limit
|
|
17886
18041
|
});
|
|
17887
|
-
const emails2 = limit === 0 ? [] : await
|
|
18042
|
+
const emails2 = limit === 0 ? [] : await withAccountPollTimeout(
|
|
18043
|
+
result.account.email,
|
|
18044
|
+
"hydrate new emails",
|
|
18045
|
+
hydrateAndAdvance(store, provider, result.account, selected2, logger)
|
|
18046
|
+
);
|
|
17888
18047
|
const data2 = { count: emails2.length, emails: emails2, errors: [] };
|
|
17889
18048
|
logger.debug("get-new-emails", "end", {
|
|
17890
18049
|
account: result.account.email,
|
|
@@ -17917,7 +18076,11 @@ function registerNewEmailTool(server, ctx) {
|
|
|
17917
18076
|
accounts.map(async (stored) => {
|
|
17918
18077
|
try {
|
|
17919
18078
|
const { provider, account } = registry.resolveByEmail(stored.email);
|
|
17920
|
-
const result = await
|
|
18079
|
+
const result = await withAccountPollTimeout(
|
|
18080
|
+
account.email,
|
|
18081
|
+
"collect new-email candidates",
|
|
18082
|
+
collectCandidatesForAccount(store, provider, account, logger)
|
|
18083
|
+
);
|
|
17921
18084
|
return {
|
|
17922
18085
|
status: "ok",
|
|
17923
18086
|
account: result.account,
|
|
@@ -17969,7 +18132,11 @@ function registerNewEmailTool(server, ctx) {
|
|
|
17969
18132
|
try {
|
|
17970
18133
|
return {
|
|
17971
18134
|
status: "ok",
|
|
17972
|
-
emails: await
|
|
18135
|
+
emails: await withAccountPollTimeout(
|
|
18136
|
+
email,
|
|
18137
|
+
"hydrate new emails",
|
|
18138
|
+
hydrateAndAdvance(store, provider, account, accountCandidates, logger)
|
|
18139
|
+
)
|
|
17973
18140
|
};
|
|
17974
18141
|
} catch (err) {
|
|
17975
18142
|
const message = errMsg(err);
|
|
@@ -18138,15 +18305,6 @@ function normalizeCheckpoint2(checkpoint) {
|
|
|
18138
18305
|
deliveredIdsAtReceivedAt: checkpoint?.deliveredIdsAtReceivedAt ?? []
|
|
18139
18306
|
};
|
|
18140
18307
|
}
|
|
18141
|
-
function effectiveReceivedAt(receivedAt) {
|
|
18142
|
-
return normalizeTimestamp2(receivedAt) ?? MISSING_RECEIVED_AT;
|
|
18143
|
-
}
|
|
18144
|
-
function normalizeTimestamp2(value) {
|
|
18145
|
-
if (!value) return null;
|
|
18146
|
-
const ms = Date.parse(value);
|
|
18147
|
-
if (!Number.isFinite(ms)) return null;
|
|
18148
|
-
return new Date(ms).toISOString();
|
|
18149
|
-
}
|
|
18150
18308
|
function oldestCandidatesFirst(items) {
|
|
18151
18309
|
return [...items].sort((a, b) => {
|
|
18152
18310
|
const byTimestamp = compareTimestamp2(a.timestamp, b.timestamp);
|
|
@@ -18162,11 +18320,6 @@ function compareNewEmailOutputOldestFirst(a, b) {
|
|
|
18162
18320
|
if (byTimestamp !== 0) return byTimestamp;
|
|
18163
18321
|
return a.id.localeCompare(b.id);
|
|
18164
18322
|
}
|
|
18165
|
-
function compareTimestamp2(a, b) {
|
|
18166
|
-
if (a < b) return -1;
|
|
18167
|
-
if (a > b) return 1;
|
|
18168
|
-
return 0;
|
|
18169
|
-
}
|
|
18170
18323
|
|
|
18171
18324
|
// src/tools/browse.ts
|
|
18172
18325
|
function registerBrowseTools(server, ctx) {
|
|
@@ -19138,7 +19291,7 @@ function registerTools(server, opts) {
|
|
|
19138
19291
|
// package.json
|
|
19139
19292
|
var package_default = {
|
|
19140
19293
|
name: "hypermail-mcp",
|
|
19141
|
-
version: "0.7.
|
|
19294
|
+
version: "0.7.20",
|
|
19142
19295
|
description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
|
|
19143
19296
|
type: "module",
|
|
19144
19297
|
bin: {
|
|
@@ -19419,7 +19572,7 @@ async function startServer(opts) {
|
|
|
19419
19572
|
toolsDisabled: config.tools?.disabled ?? null
|
|
19420
19573
|
});
|
|
19421
19574
|
const store = await AccountStore.open({ dataDir, logger });
|
|
19422
|
-
const registry = buildRegistry({ store, providers: config.providers });
|
|
19575
|
+
const registry = buildRegistry({ store, providers: config.providers, logger });
|
|
19423
19576
|
const tools = resolveTools(config);
|
|
19424
19577
|
const createServer = () => {
|
|
19425
19578
|
const s = new McpServer(
|