hypermail-mcp 0.7.17 → 0.7.19
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 -3
- package/dist/cli.js +202 -63
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
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.19** — Hardened Outlook ID handling when Microsoft Graph `$search`
|
|
7
|
+
> returns IDs that `/me/messages/{id}` rejects as malformed: `search_emails`
|
|
8
|
+
> now returns a translated readable immutable ID when available, and
|
|
9
|
+
> `read_email` retries malformed/stale IDs through `translateExchangeIds`
|
|
10
|
+
> before failing. `list_emails` also falls back from localized folder display
|
|
11
|
+
> names such as `Éléments envoyés` to the matching Graph folder ID.
|
|
12
|
+
>
|
|
13
|
+
> **v0.7.18** — `search_emails` can now omit `account` to search all
|
|
14
|
+
> registered accounts in parallel, returning account-annotated results plus
|
|
15
|
+
> per-account errors without discarding successful matches. All-account
|
|
16
|
+
> `get_new_emails` polling also collects and hydrates per-account batches in
|
|
17
|
+
> parallel while preserving the global oldest-first limit.
|
|
18
|
+
>
|
|
6
19
|
> **v0.7.17** — Hardened Outlook message IDs returned by `list_emails` and
|
|
7
20
|
> `search_emails`: Outlook reads now request Microsoft Graph immutable IDs,
|
|
8
21
|
> `search_emails` probes results and marks stale/not-found messages with
|
|
@@ -289,10 +302,10 @@ account store.
|
|
|
289
302
|
| `get_account_settings` | `account` | Get signature (HTML) and style preferences for an account. |
|
|
290
303
|
| `set_account_settings` | `account`, `signature?`, `signaturePath?`, `style?` | Set signature HTML (inline or via file path) and font preferences. |
|
|
291
304
|
| `remove_account` | `email` | Deletes tokens for the account. |
|
|
292
|
-
| `list_emails` | `account`, `folder?`, `limit?`, `unreadOnly?`, `skip?` | Defaults: folder=`inbox`, limit=25. Supports pagination via `skip` — response includes `hasMore`. |
|
|
305
|
+
| `list_emails` | `account`, `folder?`, `limit?`, `unreadOnly?`, `skip?` | Defaults: folder=`inbox`, limit=25. Supports pagination via `skip` — response includes `hasMore`. Outlook accepts well-known folder names, folder IDs, and falls back from localized display names to matching folder IDs. |
|
|
293
306
|
| `get_new_emails` | `account?`, `limit?` | Pull new inbox emails not previously returned by this tool. `limit` defaults to 10 and is global when `account` is omitted. Returns full markdown bodies with attachment metadata; bodies may be truncated. |
|
|
294
|
-
| `search_emails` | `account
|
|
295
|
-
| `read_email` | `account`, `id`, `format?` | Returns full body + recipients + attachment metadata. `format`: `markdown` (default), `html`, or `text`. |
|
|
307
|
+
| `search_emails` | `account?`, `query`, `limit?` | Search one account when `account` is provided, or omit it to search all registered accounts in parallel. Returns account-annotated summaries and partial per-account errors. Uses KQL on Outlook, and normalizes malformed Outlook search-result IDs to readable immutable IDs when possible. |
|
|
308
|
+
| `read_email` | `account`, `id`, `format?` | Returns full body + recipients + attachment metadata. `format`: `markdown` (default), `html`, or `text`. Outlook retries malformed/stale IDs through Graph ID translation before failing. |
|
|
296
309
|
| `read_attachment` | `account`, `messageId`, `attachmentId` | Download an attachment to a temporary file and return its path. |
|
|
297
310
|
| `archive_email` | `account`, `id` | Move a message to the Archive folder. |
|
|
298
311
|
| `trash_email` | `account`, `id` | Move a message to Deleted Items (trash). |
|
|
@@ -319,6 +332,8 @@ tool on their own schedule, for example every 30–60 seconds.
|
|
|
319
332
|
- `account` is optional. When omitted, the tool checks all registered accounts.
|
|
320
333
|
- `limit` defaults to `10`. In all-account mode, the limit is a global total
|
|
321
334
|
across accounts, selected by oldest `receivedAt` first.
|
|
335
|
+
- All-account polling performs per-account candidate collection and hydration in
|
|
336
|
+
parallel, then returns the combined batch in oldest-first order.
|
|
322
337
|
- First use for an account initializes its checkpoint to the newest inbox email
|
|
323
338
|
and returns no emails for that account.
|
|
324
339
|
- Later calls return emails not previously returned by this tool, oldest first.
|
package/dist/cli.js
CHANGED
|
@@ -14870,6 +14870,10 @@ var FULL_MESSAGE_SELECT = [
|
|
|
14870
14870
|
"hasAttachments",
|
|
14871
14871
|
"body"
|
|
14872
14872
|
].join(",");
|
|
14873
|
+
var EXCHANGE_ID_TRANSLATION_SOURCE_TYPES = [
|
|
14874
|
+
"ewsId",
|
|
14875
|
+
"restId"
|
|
14876
|
+
];
|
|
14873
14877
|
function graphErrorFields(err) {
|
|
14874
14878
|
if (typeof err !== "object" || err === null) {
|
|
14875
14879
|
return [String(err)];
|
|
@@ -14910,6 +14914,23 @@ function isStaleMessageIdError(err) {
|
|
|
14910
14914
|
return normalized.includes("erroritemnotfound") || normalized.includes("invalidid") || normalized.includes("object was not found in the store") || normalized.includes("not found in the store");
|
|
14911
14915
|
});
|
|
14912
14916
|
}
|
|
14917
|
+
async function translateMessageIdToImmutable(client, id) {
|
|
14918
|
+
for (const sourceIdType of EXCHANGE_ID_TRANSLATION_SOURCE_TYPES) {
|
|
14919
|
+
try {
|
|
14920
|
+
const res = await client.api("/me/translateExchangeIds").post({
|
|
14921
|
+
inputIds: [id],
|
|
14922
|
+
sourceIdType,
|
|
14923
|
+
targetIdType: "restImmutableEntryId"
|
|
14924
|
+
});
|
|
14925
|
+
const targetId = res.value?.find((item) => item.targetId)?.targetId;
|
|
14926
|
+
if (typeof targetId === "string" && targetId !== "" && targetId !== id) {
|
|
14927
|
+
return targetId;
|
|
14928
|
+
}
|
|
14929
|
+
} catch {
|
|
14930
|
+
}
|
|
14931
|
+
}
|
|
14932
|
+
return void 0;
|
|
14933
|
+
}
|
|
14913
14934
|
async function probeMessage(client, id, useImmutableIds) {
|
|
14914
14935
|
let req = client.api(`/me/messages/${encodeURIComponent(id)}`).select("id");
|
|
14915
14936
|
if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
|
|
@@ -14918,27 +14939,58 @@ async function probeMessage(client, id, useImmutableIds) {
|
|
|
14918
14939
|
async function probeSearchResult(client, id) {
|
|
14919
14940
|
try {
|
|
14920
14941
|
await probeMessage(client, id, true);
|
|
14921
|
-
return
|
|
14942
|
+
return id;
|
|
14922
14943
|
} catch (err) {
|
|
14923
14944
|
if (!isStaleMessageIdError(err)) throw err;
|
|
14924
14945
|
}
|
|
14925
14946
|
try {
|
|
14926
14947
|
await probeMessage(client, id, false);
|
|
14927
|
-
return
|
|
14948
|
+
return id;
|
|
14928
14949
|
} catch (err) {
|
|
14929
|
-
if (isStaleMessageIdError(err))
|
|
14930
|
-
|
|
14950
|
+
if (!isStaleMessageIdError(err)) throw err;
|
|
14951
|
+
}
|
|
14952
|
+
const translatedId = await translateMessageIdToImmutable(client, id);
|
|
14953
|
+
if (!translatedId) return void 0;
|
|
14954
|
+
try {
|
|
14955
|
+
await probeMessage(client, translatedId, true);
|
|
14956
|
+
return translatedId;
|
|
14957
|
+
} catch (err) {
|
|
14958
|
+
if (!isStaleMessageIdError(err)) throw err;
|
|
14959
|
+
return void 0;
|
|
14931
14960
|
}
|
|
14932
14961
|
}
|
|
14962
|
+
async function getMessageById(client, id, useImmutableIds) {
|
|
14963
|
+
let req = client.api(`/me/messages/${encodeURIComponent(id)}`).select(FULL_MESSAGE_SELECT);
|
|
14964
|
+
if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
|
|
14965
|
+
return await req.get();
|
|
14966
|
+
}
|
|
14933
14967
|
async function getMessage(client, id) {
|
|
14968
|
+
let lastStaleError;
|
|
14934
14969
|
try {
|
|
14935
|
-
|
|
14936
|
-
return { message: message2, useImmutableIds: true };
|
|
14970
|
+
return { message: await getMessageById(client, id, true), useImmutableIds: true };
|
|
14937
14971
|
} catch (err) {
|
|
14938
14972
|
if (!isStaleMessageIdError(err)) throw err;
|
|
14973
|
+
lastStaleError = err;
|
|
14939
14974
|
}
|
|
14940
|
-
|
|
14941
|
-
|
|
14975
|
+
try {
|
|
14976
|
+
return { message: await getMessageById(client, id, false), useImmutableIds: false };
|
|
14977
|
+
} catch (err) {
|
|
14978
|
+
if (!isStaleMessageIdError(err)) throw err;
|
|
14979
|
+
lastStaleError = err;
|
|
14980
|
+
}
|
|
14981
|
+
const translatedId = await translateMessageIdToImmutable(client, id);
|
|
14982
|
+
if (translatedId) {
|
|
14983
|
+
try {
|
|
14984
|
+
return {
|
|
14985
|
+
message: await getMessageById(client, translatedId, true),
|
|
14986
|
+
useImmutableIds: true
|
|
14987
|
+
};
|
|
14988
|
+
} catch (err) {
|
|
14989
|
+
if (!isStaleMessageIdError(err)) throw err;
|
|
14990
|
+
lastStaleError = err;
|
|
14991
|
+
}
|
|
14992
|
+
}
|
|
14993
|
+
throw lastStaleError ?? new Error("Message not found");
|
|
14942
14994
|
}
|
|
14943
14995
|
async function listAttachments(client, messageId, useImmutableIds) {
|
|
14944
14996
|
let req = client.api(`/me/messages/${encodeURIComponent(messageId)}/attachments`).select("id,name,contentType,size");
|
|
@@ -14956,14 +15008,36 @@ async function getAttachmentValue(client, messageId, attachmentId, useImmutableI
|
|
|
14956
15008
|
if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
|
|
14957
15009
|
return await req.get();
|
|
14958
15010
|
}
|
|
15011
|
+
async function listMessagePage(client, folder, limit, skip, filterParts) {
|
|
15012
|
+
let req = client.api(`/me/mailFolders/${encodeURIComponent(folder)}/messages`).header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER).top(limit).skip(skip).select(MESSAGE_SELECT).orderby("receivedDateTime DESC");
|
|
15013
|
+
if (filterParts.length > 0) req = req.filter(filterParts.join(" and "));
|
|
15014
|
+
return await req.get();
|
|
15015
|
+
}
|
|
15016
|
+
function normalizeFolderDisplayName(value) {
|
|
15017
|
+
return value.trim().normalize("NFD").replace(new RegExp("\\p{Diacritic}", "gu"), "").toLowerCase();
|
|
15018
|
+
}
|
|
15019
|
+
async function resolveFolderIdByDisplayName(client, displayName) {
|
|
15020
|
+
const wanted = normalizeFolderDisplayName(displayName);
|
|
15021
|
+
if (!wanted) return void 0;
|
|
15022
|
+
const res = await client.api("/me/mailFolders").top(100).select("id,displayName").get();
|
|
15023
|
+
return (res.value ?? []).find(
|
|
15024
|
+
(folder) => normalizeFolderDisplayName(folder.displayName) === wanted
|
|
15025
|
+
)?.id;
|
|
15026
|
+
}
|
|
14959
15027
|
async function listEmails(client, account, opts) {
|
|
14960
15028
|
const limit = clampLimit(opts.limit, 25, 100);
|
|
14961
15029
|
const folder = opts.folder ?? "inbox";
|
|
14962
15030
|
const filterParts = [];
|
|
14963
15031
|
if (opts.unreadOnly) filterParts.push("isRead eq false");
|
|
14964
|
-
let
|
|
14965
|
-
|
|
14966
|
-
|
|
15032
|
+
let res;
|
|
15033
|
+
try {
|
|
15034
|
+
res = await listMessagePage(client, folder, limit, opts.skip ?? 0, filterParts);
|
|
15035
|
+
} catch (err) {
|
|
15036
|
+
if (!isStaleMessageIdError(err)) throw err;
|
|
15037
|
+
const resolvedFolderId = await resolveFolderIdByDisplayName(client, folder);
|
|
15038
|
+
if (!resolvedFolderId || resolvedFolderId === folder) throw err;
|
|
15039
|
+
res = await listMessagePage(client, resolvedFolderId, limit, opts.skip ?? 0, filterParts);
|
|
15040
|
+
}
|
|
14967
15041
|
return {
|
|
14968
15042
|
items: res.value.map((m) => mapSummary(m, folder)),
|
|
14969
15043
|
hasMore: !!res["@odata.nextLink"]
|
|
@@ -14975,8 +15049,8 @@ async function searchEmails(client, account, query, opts) {
|
|
|
14975
15049
|
const summaries = res.value.map((m) => mapSummary(m));
|
|
14976
15050
|
return Promise.all(
|
|
14977
15051
|
summaries.map(async (summary) => {
|
|
14978
|
-
const
|
|
14979
|
-
return
|
|
15052
|
+
const readableId = await probeSearchResult(client, summary.id);
|
|
15053
|
+
return readableId ? { ...summary, id: readableId } : { ...summary, stale: true };
|
|
14980
15054
|
})
|
|
14981
15055
|
);
|
|
14982
15056
|
}
|
|
@@ -17839,20 +17913,36 @@ function registerNewEmailTool(server, ctx) {
|
|
|
17839
17913
|
const collected = [];
|
|
17840
17914
|
const providersByEmail = /* @__PURE__ */ new Map();
|
|
17841
17915
|
const accountsByEmail = /* @__PURE__ */ new Map();
|
|
17842
|
-
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
})
|
|
17854
|
-
|
|
17916
|
+
const collectedByAccount = await Promise.all(
|
|
17917
|
+
accounts.map(async (stored) => {
|
|
17918
|
+
try {
|
|
17919
|
+
const { provider, account } = registry.resolveByEmail(stored.email);
|
|
17920
|
+
const result = await collectCandidatesForAccount(store, provider, account, logger);
|
|
17921
|
+
return {
|
|
17922
|
+
status: "ok",
|
|
17923
|
+
account: result.account,
|
|
17924
|
+
provider,
|
|
17925
|
+
candidates: result.candidates
|
|
17926
|
+
};
|
|
17927
|
+
} catch (err) {
|
|
17928
|
+
const message = errMsg(err);
|
|
17929
|
+
logger.debug("get-new-emails", "accountError", { account: stored.email, message });
|
|
17930
|
+
return {
|
|
17931
|
+
status: "error",
|
|
17932
|
+
account: stored.email,
|
|
17933
|
+
message
|
|
17934
|
+
};
|
|
17935
|
+
}
|
|
17936
|
+
})
|
|
17937
|
+
);
|
|
17938
|
+
for (const result of collectedByAccount) {
|
|
17939
|
+
if (result.status === "error") {
|
|
17940
|
+
errors.push({ account: result.account, message: result.message });
|
|
17941
|
+
continue;
|
|
17855
17942
|
}
|
|
17943
|
+
providersByEmail.set(result.account.email, result.provider);
|
|
17944
|
+
accountsByEmail.set(result.account.email, result.account);
|
|
17945
|
+
collected.push(...result.candidates);
|
|
17856
17946
|
}
|
|
17857
17947
|
const selected = oldestCandidatesFirst(collected).slice(0, limit);
|
|
17858
17948
|
logger.debug("get-new-emails", "selected", {
|
|
@@ -17871,27 +17961,33 @@ function registerNewEmailTool(server, ctx) {
|
|
|
17871
17961
|
items.push(candidate);
|
|
17872
17962
|
byAccount.set(candidate.account.email, items);
|
|
17873
17963
|
}
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
provider,
|
|
17883
|
-
|
|
17884
|
-
|
|
17885
|
-
|
|
17886
|
-
)
|
|
17887
|
-
|
|
17888
|
-
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
}
|
|
17893
|
-
|
|
17964
|
+
const hydratedByAccount = await Promise.all(
|
|
17965
|
+
[...byAccount].map(async ([email, accountCandidates]) => {
|
|
17966
|
+
const provider = providersByEmail.get(email);
|
|
17967
|
+
const account = accountsByEmail.get(email);
|
|
17968
|
+
if (!provider || !account) return { status: "ok", emails: [] };
|
|
17969
|
+
try {
|
|
17970
|
+
return {
|
|
17971
|
+
status: "ok",
|
|
17972
|
+
emails: await hydrateAndAdvance(store, provider, account, accountCandidates, logger)
|
|
17973
|
+
};
|
|
17974
|
+
} catch (err) {
|
|
17975
|
+
const message = errMsg(err);
|
|
17976
|
+
logger.debug("get-new-emails", "accountError", { account: email, message });
|
|
17977
|
+
return {
|
|
17978
|
+
status: "error",
|
|
17979
|
+
account: email,
|
|
17980
|
+
message
|
|
17981
|
+
};
|
|
17982
|
+
}
|
|
17983
|
+
})
|
|
17984
|
+
);
|
|
17985
|
+
for (const result of hydratedByAccount) {
|
|
17986
|
+
if (result.status === "error") {
|
|
17987
|
+
errors.push({ account: result.account, message: result.message });
|
|
17988
|
+
continue;
|
|
17894
17989
|
}
|
|
17990
|
+
emails.push(...result.emails);
|
|
17895
17991
|
}
|
|
17896
17992
|
}
|
|
17897
17993
|
const orderedEmails = emails.sort(compareNewEmailOutputOldestFirst);
|
|
@@ -18082,10 +18178,15 @@ function registerBrowseTools(server, ctx) {
|
|
|
18082
18178
|
skip: z5.number(),
|
|
18083
18179
|
hasMore: z5.boolean()
|
|
18084
18180
|
});
|
|
18181
|
+
const searchEmailSummaryOutputSchema = emailSummaryOutputSchema.extend({
|
|
18182
|
+
account: z5.string()
|
|
18183
|
+
});
|
|
18085
18184
|
const searchEmailsOutputSchema = z5.object({
|
|
18086
18185
|
account: z5.string(),
|
|
18087
18186
|
count: z5.number(),
|
|
18088
|
-
items: z5.array(
|
|
18187
|
+
items: z5.array(searchEmailSummaryOutputSchema),
|
|
18188
|
+
accounts: z5.array(z5.string()).optional(),
|
|
18189
|
+
errors: z5.array(z5.object({ account: z5.string(), message: z5.string() })).optional()
|
|
18089
18190
|
});
|
|
18090
18191
|
if (shouldRegister("list_emails", tools)) {
|
|
18091
18192
|
server.registerTool(
|
|
@@ -18129,29 +18230,67 @@ function registerBrowseTools(server, ctx) {
|
|
|
18129
18230
|
server.registerTool(
|
|
18130
18231
|
"search_emails",
|
|
18131
18232
|
{
|
|
18132
|
-
description: "Search emails by free-text query (KQL on Outlook). Returns lightweight summaries.",
|
|
18233
|
+
description: "Search emails by free-text query (KQL on Outlook). Returns lightweight summaries. Pass `account` to search one account, or omit it to search all registered accounts in parallel.",
|
|
18133
18234
|
inputSchema: z5.object({
|
|
18134
|
-
account: z5.string().email(),
|
|
18235
|
+
account: z5.string().email().optional(),
|
|
18135
18236
|
query: z5.string().min(1),
|
|
18136
18237
|
limit: z5.number().int().positive().max(100).optional()
|
|
18137
18238
|
}),
|
|
18138
18239
|
outputSchema: searchEmailsOutputSchema
|
|
18139
18240
|
},
|
|
18140
18241
|
async (args) => {
|
|
18141
|
-
|
|
18142
|
-
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18242
|
+
if (args.account) {
|
|
18243
|
+
try {
|
|
18244
|
+
const { provider, account } = registry.resolveByEmail(args.account);
|
|
18245
|
+
const items2 = await provider.searchEmails(account, args.query, {
|
|
18246
|
+
limit: args.limit
|
|
18247
|
+
});
|
|
18248
|
+
const data2 = {
|
|
18249
|
+
account: account.email,
|
|
18250
|
+
count: items2.length,
|
|
18251
|
+
items: items2.map((item) => ({ ...item, account: account.email })),
|
|
18252
|
+
errors: []
|
|
18253
|
+
};
|
|
18254
|
+
return ok(data2, data2);
|
|
18255
|
+
} catch (err) {
|
|
18256
|
+
return fail(errMsg(err));
|
|
18257
|
+
}
|
|
18258
|
+
}
|
|
18259
|
+
const accounts = store.listAccounts();
|
|
18260
|
+
if (accounts.length === 0) {
|
|
18261
|
+
return fail("no accounts registered. Call add_account first.");
|
|
18154
18262
|
}
|
|
18263
|
+
const results = await Promise.all(
|
|
18264
|
+
accounts.map(async (stored) => {
|
|
18265
|
+
try {
|
|
18266
|
+
const { provider, account } = registry.resolveByEmail(stored.email);
|
|
18267
|
+
const items2 = await provider.searchEmails(account, args.query, {
|
|
18268
|
+
limit: args.limit
|
|
18269
|
+
});
|
|
18270
|
+
return {
|
|
18271
|
+
account: account.email,
|
|
18272
|
+
items: items2.map((item) => ({ ...item, account: account.email }))
|
|
18273
|
+
};
|
|
18274
|
+
} catch (err) {
|
|
18275
|
+
return {
|
|
18276
|
+
account: stored.email,
|
|
18277
|
+
error: errMsg(err)
|
|
18278
|
+
};
|
|
18279
|
+
}
|
|
18280
|
+
})
|
|
18281
|
+
);
|
|
18282
|
+
const items = results.flatMap((result) => result.items ?? []);
|
|
18283
|
+
const errors = results.filter(
|
|
18284
|
+
(result) => "error" in result
|
|
18285
|
+
).map((result) => ({ account: result.account, message: result.error }));
|
|
18286
|
+
const data = {
|
|
18287
|
+
account: "all",
|
|
18288
|
+
accounts: accounts.map((account) => account.email),
|
|
18289
|
+
count: items.length,
|
|
18290
|
+
items,
|
|
18291
|
+
errors
|
|
18292
|
+
};
|
|
18293
|
+
return ok(data, data);
|
|
18155
18294
|
}
|
|
18156
18295
|
);
|
|
18157
18296
|
}
|
|
@@ -18999,7 +19138,7 @@ function registerTools(server, opts) {
|
|
|
18999
19138
|
// package.json
|
|
19000
19139
|
var package_default = {
|
|
19001
19140
|
name: "hypermail-mcp",
|
|
19002
|
-
version: "0.7.
|
|
19141
|
+
version: "0.7.19",
|
|
19003
19142
|
description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
|
|
19004
19143
|
type: "module",
|
|
19005
19144
|
bin: {
|