hypermail-mcp 0.7.9 → 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 +4 -0
- package/dist/cli.js +86 -40
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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.10** — Fixed an account-store race where Gmail/Outlook token
|
|
7
|
+
> refreshes could overwrite `get_new_emails` checkpoints. Checkpoint updates now
|
|
8
|
+
> preserve token data and merge delivered IDs at the same timestamp.
|
|
9
|
+
>
|
|
6
10
|
> **v0.7.9** — Replaced server-side email watch/webhook/script delivery with
|
|
7
11
|
> the pull-based `get_new_emails` tool. Agents schedule their own repeated calls
|
|
8
12
|
> and fetch bounded batches of new inbox email.
|
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);
|
|
@@ -17222,9 +17281,9 @@ async function initializeCheckpoint(store, provider, account) {
|
|
|
17222
17281
|
const first = items[0];
|
|
17223
17282
|
const receivedAt = first ? effectiveReceivedAt(first.receivedAt) : (/* @__PURE__ */ new Date()).toISOString();
|
|
17224
17283
|
const deliveredIdsAtReceivedAt = items.filter((item) => effectiveReceivedAt(item.receivedAt) === receivedAt).map((item) => item.id);
|
|
17225
|
-
await store.
|
|
17226
|
-
|
|
17227
|
-
|
|
17284
|
+
await store.updateNewEmailCheckpoint(account.email, {
|
|
17285
|
+
receivedAt,
|
|
17286
|
+
deliveredIdsAtReceivedAt
|
|
17228
17287
|
});
|
|
17229
17288
|
}
|
|
17230
17289
|
async function hydrateAndAdvance(store, provider, account, selected) {
|
|
@@ -17264,24 +17323,11 @@ async function advanceCheckpoint(store, account, selected) {
|
|
|
17264
17323
|
const ordered = oldestCandidatesFirst(selected);
|
|
17265
17324
|
const newest = ordered[ordered.length - 1];
|
|
17266
17325
|
if (!newest) return;
|
|
17267
|
-
const previous = normalizeCheckpoint(account.newEmailCheckpoint);
|
|
17268
17326
|
const newestTimestamp = newest.timestamp;
|
|
17269
17327
|
const idsAtNewest = ordered.filter((candidate) => candidate.timestamp === newestTimestamp).map((candidate) => candidate.summary.id);
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
deliveredIdsAtReceivedAt
|
|
17273
|
-
.../* @__PURE__ */ new Set([
|
|
17274
|
-
...previous.deliveredIdsAtReceivedAt ?? [],
|
|
17275
|
-
...idsAtNewest
|
|
17276
|
-
])
|
|
17277
|
-
];
|
|
17278
|
-
}
|
|
17279
|
-
await store.upsertAccount({
|
|
17280
|
-
...account,
|
|
17281
|
-
newEmailCheckpoint: {
|
|
17282
|
-
receivedAt: newestTimestamp,
|
|
17283
|
-
deliveredIdsAtReceivedAt
|
|
17284
|
-
}
|
|
17328
|
+
await store.updateNewEmailCheckpoint(account.email, {
|
|
17329
|
+
receivedAt: newestTimestamp,
|
|
17330
|
+
deliveredIdsAtReceivedAt: idsAtNewest
|
|
17285
17331
|
});
|
|
17286
17332
|
}
|
|
17287
17333
|
function normalizeCheckpoint(checkpoint) {
|
|
@@ -18098,7 +18144,7 @@ function registerTools(server, opts) {
|
|
|
18098
18144
|
// package.json
|
|
18099
18145
|
var package_default = {
|
|
18100
18146
|
name: "hypermail-mcp",
|
|
18101
|
-
version: "0.7.
|
|
18147
|
+
version: "0.7.10",
|
|
18102
18148
|
description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
|
|
18103
18149
|
type: "module",
|
|
18104
18150
|
bin: {
|