hypermail-mcp 0.7.10 → 0.7.12
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 +13 -2
- package/dist/cli.js +125 -80
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,20 @@
|
|
|
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.12** — Hardened Outlook reply/forward draft formatting when
|
|
7
|
+
> Microsoft Graph labels generated thread history as HTML but returns
|
|
8
|
+
> plain/unstructured text. Such histories are now defensively normalized with
|
|
9
|
+
> escaped content and line breaks so quoted messages remain readable.
|
|
10
|
+
>
|
|
11
|
+
> **v0.7.11** — Fixed Outlook reply/forward drafts whose Graph-generated
|
|
12
|
+
> quoted bodies are plain text by escaping them and patching the draft as HTML,
|
|
13
|
+
> so newly composed HTML replies no longer render as raw text.
|
|
14
|
+
>
|
|
6
15
|
> **v0.7.10** — Fixed an account-store race where Gmail/Outlook token
|
|
7
16
|
> refreshes could overwrite `get_new_emails` checkpoints. Checkpoint updates now
|
|
8
|
-
> preserve token data and merge delivered IDs at the same timestamp.
|
|
17
|
+
> preserve token data and merge delivered IDs at the same timestamp. `edit_draft`
|
|
18
|
+
> body edits now require exact selected-section replacement (`old_text` +
|
|
19
|
+
> `new_text`) so reply/forward history is preserved instead of overwritten.
|
|
9
20
|
>
|
|
10
21
|
> **v0.7.9** — Replaced server-side email watch/webhook/script delivery with
|
|
11
22
|
> the pull-based `get_new_emails` tool. Agents schedule their own repeated calls
|
|
@@ -249,7 +260,7 @@ account store.
|
|
|
249
260
|
| `move_email` | `account`, `id`, `destination` | Move to any folder by well-known name (`inbox`, `drafts`, etc.) or custom folder ID. |
|
|
250
261
|
| `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Send an email. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Appends signature when `include_signature` is true. `inReplyTo` sends as threaded reply; `forwardMessageId` sends as forward. `inReplyTo` is required — set to `false` for new emails. `attachments` is an optional array of `{filePath, name?}` — files are read from disk and encoded automatically. |
|
|
251
262
|
| `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Save as draft instead of sending. Same params as `send_email` including `attachments`. Returns the draft message ID and HTML body (`draftHtml`). `inReplyTo` is required — set to `false` for new emails. |
|
|
252
|
-
| `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `format?`, `include_signature?`, `new_attachments?`, `remove_attachments?` | Edit an existing draft by ID.
|
|
263
|
+
| `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `old_text?`, `new_text?`, `body?`, `format?`, `include_signature?`, `new_attachments?`, `remove_attachments?` | Edit an existing draft by ID. Body edits require exact selected-section replacement: copy `old_text` from the current draft HTML (`draftHtml` or `read_email` with `format: "html"`) and provide `new_text`; the match must occur exactly once, and unselected content such as reply/forward history is preserved. Deprecated `body` is only an alias for `new_text` when `old_text` is also provided; body-only full replacement is rejected. `new_attachments` adds files (`{filePath, name?}[]`); `remove_attachments` removes by attachment ID (`string[]`). Returns the updated draft ID, HTML body (`draftHtml`), and attachment metadata. |
|
|
253
264
|
| `send_draft` | `account`, `id` | Send an existing draft email by ID. Use with draft IDs returned by `draft_email` or `edit_draft`. |
|
|
254
265
|
| `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
|
|
255
266
|
| `create_folder` | `account`, `displayName`, `parentFolderId?` | Create a new mail folder under root (default) or the given parent. |
|
package/dist/cli.js
CHANGED
|
@@ -14359,16 +14359,43 @@ function clampLimit(v, dflt, max) {
|
|
|
14359
14359
|
|
|
14360
14360
|
// src/providers/outlook/write-ops.ts
|
|
14361
14361
|
var THREAD_MARKER = "<!-- hypermail-thread-boundary -->";
|
|
14362
|
+
function isTextBody(contentType) {
|
|
14363
|
+
return contentType?.toLowerCase() === "text";
|
|
14364
|
+
}
|
|
14365
|
+
function textToHtml(text) {
|
|
14366
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/\r\n|\r|\n/g, "<br>");
|
|
14367
|
+
}
|
|
14368
|
+
function hasMeaningfulHtmlTag(html) {
|
|
14369
|
+
return /<\/?(?:p|div|br|blockquote|table|thead|tbody|tfoot|tr|td|th|ul|ol|li|a|span|font|b|strong|em|i|u|img|hr|pre)\b/i.test(html);
|
|
14370
|
+
}
|
|
14371
|
+
function wrapQuotedHistory(content) {
|
|
14372
|
+
return `<blockquote style="margin:0 0 0 .8ex;border-left:1px solid #ccc;padding-left:1ex">${content}</blockquote>`;
|
|
14373
|
+
}
|
|
14374
|
+
function normalizeDraftBody(content, contentType) {
|
|
14375
|
+
if (isTextBody(contentType)) return textToHtml(content);
|
|
14376
|
+
if (contentType?.toLowerCase() !== "html") return content;
|
|
14377
|
+
const bodyMatch = /(<body\b[^>]*>)([\s\S]*?)(<\/body>)/i.exec(content);
|
|
14378
|
+
const inner = bodyMatch ? bodyMatch[2] ?? "" : content;
|
|
14379
|
+
if (inner.trim() === "" || hasMeaningfulHtmlTag(inner)) return content;
|
|
14380
|
+
const normalized = wrapQuotedHistory(textToHtml(inner));
|
|
14381
|
+
if (!bodyMatch) return normalized;
|
|
14382
|
+
const bodyOpen = bodyMatch[1] ?? "";
|
|
14383
|
+
const bodyClose = bodyMatch[3] ?? "";
|
|
14384
|
+
return content.slice(0, bodyMatch.index) + bodyOpen + normalized + bodyClose + content.slice(bodyMatch.index + bodyMatch[0].length);
|
|
14385
|
+
}
|
|
14362
14386
|
async function buildDraftFromReference(client, createEndpoint, createPayload, converted) {
|
|
14363
14387
|
const draft = await client.api(createEndpoint).post(createPayload);
|
|
14364
14388
|
const draftMsg = await client.api(`/me/messages/${draft.id}`).select("body").get();
|
|
14365
|
-
const
|
|
14366
|
-
const
|
|
14389
|
+
const rawDraftBody = draftMsg.body?.content ?? "";
|
|
14390
|
+
const draftBody = normalizeDraftBody(
|
|
14391
|
+
rawDraftBody,
|
|
14392
|
+
draftMsg.body?.contentType
|
|
14393
|
+
);
|
|
14367
14394
|
const spacer = '<div style="line-height:12px"><br></div>';
|
|
14368
14395
|
const prepend = converted.body + spacer + THREAD_MARKER;
|
|
14369
14396
|
const finalBody = draftBody.includes("<body") ? draftBody.replace(/(<body[^>]*>)/i, `$1${prepend}`) : prepend + draftBody;
|
|
14370
14397
|
await client.api(`/me/messages/${draft.id}`).patch({
|
|
14371
|
-
body: { contentType:
|
|
14398
|
+
body: { contentType: "HTML", content: finalBody }
|
|
14372
14399
|
});
|
|
14373
14400
|
for (const att of converted.attachments) {
|
|
14374
14401
|
await client.api(`/me/messages/${draft.id}/attachments`).post(att);
|
|
@@ -16840,30 +16867,21 @@ function buildStyleAttr(style) {
|
|
|
16840
16867
|
if (style.fontColor) parts.push(`color: ${style.fontColor}`);
|
|
16841
16868
|
return parts.join("; ");
|
|
16842
16869
|
}
|
|
16843
|
-
function
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
const before = html.slice(0, markerIdx);
|
|
16847
|
-
let answerEnd = before.lastIndexOf(
|
|
16848
|
-
'<div style="line-height:12px"><br></div>'
|
|
16849
|
-
);
|
|
16850
|
-
if (answerEnd === -1) {
|
|
16851
|
-
const re = /<div\s+style=["'][^"']*line-height\s*:\s*12px[^"']*["']\s*>\s*<br\s*\/?>\s*<\/div>/gi;
|
|
16852
|
-
let m;
|
|
16853
|
-
while ((m = re.exec(before)) !== null) answerEnd = m.index;
|
|
16854
|
-
}
|
|
16855
|
-
return { threadStart: markerIdx, answerEnd: Math.max(answerEnd, 0) };
|
|
16870
|
+
function applyExactTextEdit(content, oldText, newText) {
|
|
16871
|
+
if (oldText.length === 0) {
|
|
16872
|
+
throw new Error("old_text must not be empty");
|
|
16856
16873
|
}
|
|
16857
|
-
const
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
return { threadStart: spacerMatch.index, answerEnd: spacerMatch.index };
|
|
16874
|
+
const first = content.indexOf(oldText);
|
|
16875
|
+
if (first === -1) {
|
|
16876
|
+
throw new Error("old_text was not found in the current draft body");
|
|
16861
16877
|
}
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16878
|
+
const second = content.indexOf(oldText, first + oldText.length);
|
|
16879
|
+
if (second !== -1) {
|
|
16880
|
+
throw new Error(
|
|
16881
|
+
"old_text matched multiple sections in the current draft body; provide a more specific selection"
|
|
16882
|
+
);
|
|
16865
16883
|
}
|
|
16866
|
-
return
|
|
16884
|
+
return content.slice(0, first) + newText + content.slice(first + oldText.length);
|
|
16867
16885
|
}
|
|
16868
16886
|
function shouldRegister(name, tools) {
|
|
16869
16887
|
if (tools.enabledTools) {
|
|
@@ -17825,33 +17843,37 @@ function registerOrganizeTools(server, ctx) {
|
|
|
17825
17843
|
import { z as z8 } from "zod";
|
|
17826
17844
|
import { readFileSync } from "fs";
|
|
17827
17845
|
import { basename, extname } from "path";
|
|
17846
|
+
|
|
17847
|
+
// src/tools/mime-types.ts
|
|
17848
|
+
var MIME_TYPES = {
|
|
17849
|
+
".pdf": "application/pdf",
|
|
17850
|
+
".png": "image/png",
|
|
17851
|
+
".jpg": "image/jpeg",
|
|
17852
|
+
".jpeg": "image/jpeg",
|
|
17853
|
+
".gif": "image/gif",
|
|
17854
|
+
".svg": "image/svg+xml",
|
|
17855
|
+
".webp": "image/webp",
|
|
17856
|
+
".txt": "text/plain",
|
|
17857
|
+
".html": "text/html",
|
|
17858
|
+
".css": "text/css",
|
|
17859
|
+
".csv": "text/csv",
|
|
17860
|
+
".json": "application/json",
|
|
17861
|
+
".xml": "application/xml",
|
|
17862
|
+
".zip": "application/zip",
|
|
17863
|
+
".gz": "application/gzip",
|
|
17864
|
+
".tar": "application/x-tar",
|
|
17865
|
+
".doc": "application/msword",
|
|
17866
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
17867
|
+
".xls": "application/vnd.ms-excel",
|
|
17868
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
17869
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
17870
|
+
".mp3": "audio/mpeg",
|
|
17871
|
+
".mp4": "video/mp4"
|
|
17872
|
+
};
|
|
17873
|
+
|
|
17874
|
+
// src/tools/compose.ts
|
|
17828
17875
|
function registerComposeTools(server, ctx) {
|
|
17829
17876
|
const { store, registry, tools } = ctx;
|
|
17830
|
-
const MIME_TYPES = {
|
|
17831
|
-
".pdf": "application/pdf",
|
|
17832
|
-
".png": "image/png",
|
|
17833
|
-
".jpg": "image/jpeg",
|
|
17834
|
-
".jpeg": "image/jpeg",
|
|
17835
|
-
".gif": "image/gif",
|
|
17836
|
-
".svg": "image/svg+xml",
|
|
17837
|
-
".webp": "image/webp",
|
|
17838
|
-
".txt": "text/plain",
|
|
17839
|
-
".html": "text/html",
|
|
17840
|
-
".css": "text/css",
|
|
17841
|
-
".csv": "text/csv",
|
|
17842
|
-
".json": "application/json",
|
|
17843
|
-
".xml": "application/xml",
|
|
17844
|
-
".zip": "application/zip",
|
|
17845
|
-
".gz": "application/gzip",
|
|
17846
|
-
".tar": "application/x-tar",
|
|
17847
|
-
".doc": "application/msword",
|
|
17848
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
17849
|
-
".xls": "application/vnd.ms-excel",
|
|
17850
|
-
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
17851
|
-
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
17852
|
-
".mp3": "audio/mpeg",
|
|
17853
|
-
".mp4": "video/mp4"
|
|
17854
|
-
};
|
|
17855
17877
|
const sendEmailSchema = z8.object({
|
|
17856
17878
|
account: z8.string().email(),
|
|
17857
17879
|
to: z8.array(emailAddrSchema).min(1),
|
|
@@ -17987,12 +18009,20 @@ function registerComposeTools(server, ctx) {
|
|
|
17987
18009
|
cc: z8.array(emailAddrSchema).optional(),
|
|
17988
18010
|
bcc: z8.array(emailAddrSchema).optional(),
|
|
17989
18011
|
subject: z8.string().optional(),
|
|
17990
|
-
|
|
18012
|
+
old_text: z8.string().min(1).optional().describe(
|
|
18013
|
+
"Exact current HTML section to replace in the draft body. Copy this from `draftHtml` or from `read_email` with format='html'. Must match exactly once; unselected content is preserved."
|
|
18014
|
+
),
|
|
18015
|
+
new_text: z8.string().optional().describe(
|
|
18016
|
+
"Replacement content for `old_text`. The replacement is composed using `format` and `include_signature`, then inserted exactly where `old_text` matched."
|
|
18017
|
+
),
|
|
18018
|
+
body: z8.string().optional().describe(
|
|
18019
|
+
"Deprecated alias for `new_text`. Body-only full replacement is not supported; provide `old_text` with this field."
|
|
18020
|
+
),
|
|
17991
18021
|
format: z8.enum(["html", "markdown"]).optional().describe(
|
|
17992
|
-
"
|
|
18022
|
+
"Replacement format. Only meaningful when `new_text` or deprecated `body` is also provided. 'html' inserts the replacement as-is (must be valid HTML). 'markdown' converts the replacement from Markdown to HTML."
|
|
17993
18023
|
),
|
|
17994
18024
|
include_signature: z8.boolean().optional().describe(
|
|
17995
|
-
"Whether to
|
|
18025
|
+
"Whether to append the account's saved HTML signature to the replacement section. If true, don't include a signature in `new_text`/`body`. Only meaningful when replacement content is provided. Returns an error if true but no signature is configured for this account."
|
|
17996
18026
|
),
|
|
17997
18027
|
new_attachments: z8.array(
|
|
17998
18028
|
z8.object({
|
|
@@ -18017,7 +18047,7 @@ function registerComposeTools(server, ctx) {
|
|
|
18017
18047
|
server.registerTool(
|
|
18018
18048
|
"edit_draft",
|
|
18019
18049
|
{
|
|
18020
|
-
description: "Edit an existing draft email by ID. Only the fields you provide are updated \u2014 unmentioned fields stay unchanged.
|
|
18050
|
+
description: "Edit an existing draft email by ID. Only the fields you provide are updated \u2014 unmentioned fields stay unchanged. Body edits work like an exact text edit: provide `old_text` copied from the current draft HTML and `new_text` to replace that exact section. The match must occur exactly once, and all unselected content \u2014 including reply/forward history \u2014 is preserved. Deprecated `body` is accepted only as an alias for `new_text` when `old_text` is also provided. Returns the draft ID and the draft's updated HTML body content (`draftHtml`). Before sending, inspect `draftHtml` to verify the draft looks correct. Does not support changing `inReplyTo` or `forwardMessageId` \u2014 those are set at creation time via `draft_email`. Disabled in --read-only mode.",
|
|
18021
18051
|
inputSchema: editDraftSchema,
|
|
18022
18052
|
outputSchema: editDraftOutputSchema
|
|
18023
18053
|
},
|
|
@@ -18025,43 +18055,57 @@ function registerComposeTools(server, ctx) {
|
|
|
18025
18055
|
const a = args;
|
|
18026
18056
|
try {
|
|
18027
18057
|
const { provider, account } = registry.resolveByEmail(a.account);
|
|
18028
|
-
|
|
18058
|
+
const hasNewText = a.new_text !== void 0;
|
|
18059
|
+
const hasBodyAlias = a.body !== void 0;
|
|
18060
|
+
const hasOldText = a.old_text !== void 0;
|
|
18061
|
+
if (hasNewText && hasBodyAlias) {
|
|
18062
|
+
return fail("Provide only one of new_text or deprecated body, not both.");
|
|
18063
|
+
}
|
|
18064
|
+
if (hasBodyAlias && !hasOldText) {
|
|
18065
|
+
return fail(
|
|
18066
|
+
"Body-only full replacement is no longer supported. Provide old_text copied from the current draft HTML and use body as the replacement, or use new_text."
|
|
18067
|
+
);
|
|
18068
|
+
}
|
|
18069
|
+
if (hasNewText && !hasOldText) {
|
|
18070
|
+
return fail("new_text requires old_text so only the selected section is edited.");
|
|
18071
|
+
}
|
|
18072
|
+
if (hasOldText && !hasNewText && !hasBodyAlias) {
|
|
18073
|
+
return fail("old_text requires new_text with the replacement content.");
|
|
18074
|
+
}
|
|
18075
|
+
const replacementText = a.new_text ?? a.body;
|
|
18076
|
+
if (replacementText !== void 0 && a.include_signature && !account.signature) {
|
|
18029
18077
|
return fail(
|
|
18030
18078
|
"include_signature is true but no signature is configured for this account. Set up a signature first with set_account_settings."
|
|
18031
18079
|
);
|
|
18032
18080
|
}
|
|
18033
18081
|
let bodyPayload;
|
|
18034
18082
|
let isHtmlPayload;
|
|
18035
|
-
if (
|
|
18083
|
+
if (replacementText !== void 0) {
|
|
18084
|
+
const existing = await provider.readEmail(account, a.id);
|
|
18085
|
+
const existingBody = existing.bodyHtml ?? existing.bodyText ?? "";
|
|
18036
18086
|
const composed = composeBody({
|
|
18037
|
-
body:
|
|
18087
|
+
body: replacementText,
|
|
18038
18088
|
format: a.format ?? "html",
|
|
18039
18089
|
signature: account.signature,
|
|
18040
18090
|
style: account.style,
|
|
18041
18091
|
includeSignature: !!a.include_signature
|
|
18042
18092
|
});
|
|
18043
|
-
bodyPayload = composed.body;
|
|
18093
|
+
bodyPayload = applyExactTextEdit(existingBody, a.old_text ?? "", composed.body);
|
|
18044
18094
|
isHtmlPayload = composed.isHtml;
|
|
18045
18095
|
}
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18096
|
+
const hasDraftUpdate = a.to !== void 0 || a.cc !== void 0 || a.bcc !== void 0 || a.subject !== void 0 || bodyPayload !== void 0;
|
|
18097
|
+
let currentId = a.id;
|
|
18098
|
+
if (hasDraftUpdate) {
|
|
18099
|
+
const res = await provider.updateDraft(account, currentId, {
|
|
18100
|
+
to: a.to,
|
|
18101
|
+
cc: a.cc,
|
|
18102
|
+
bcc: a.bcc,
|
|
18103
|
+
subject: a.subject,
|
|
18104
|
+
body: bodyPayload,
|
|
18105
|
+
isHtml: isHtmlPayload
|
|
18106
|
+
});
|
|
18107
|
+
currentId = res.id;
|
|
18056
18108
|
}
|
|
18057
|
-
const res = await provider.updateDraft(account, a.id, {
|
|
18058
|
-
to: a.to,
|
|
18059
|
-
cc: a.cc,
|
|
18060
|
-
bcc: a.bcc,
|
|
18061
|
-
subject: a.subject,
|
|
18062
|
-
body: bodyPayload,
|
|
18063
|
-
isHtml: isHtmlPayload
|
|
18064
|
-
});
|
|
18065
18109
|
const newAttachmentIds = [];
|
|
18066
18110
|
if (a.new_attachments && a.new_attachments.length > 0) {
|
|
18067
18111
|
for (const att of a.new_attachments) {
|
|
@@ -18070,11 +18114,12 @@ function registerComposeTools(server, ctx) {
|
|
|
18070
18114
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
18071
18115
|
const attRes = await provider.addAttachmentToDraft(
|
|
18072
18116
|
account,
|
|
18073
|
-
|
|
18117
|
+
currentId,
|
|
18074
18118
|
att.name ?? basename(att.filePath),
|
|
18075
18119
|
fileData.toString("base64"),
|
|
18076
18120
|
contentType
|
|
18077
18121
|
);
|
|
18122
|
+
currentId = attRes.id;
|
|
18078
18123
|
newAttachmentIds.push(attRes.attachment.id);
|
|
18079
18124
|
}
|
|
18080
18125
|
}
|
|
@@ -18083,16 +18128,16 @@ function registerComposeTools(server, ctx) {
|
|
|
18083
18128
|
for (const attId of a.remove_attachments) {
|
|
18084
18129
|
await provider.removeAttachmentFromDraft(
|
|
18085
18130
|
account,
|
|
18086
|
-
|
|
18131
|
+
currentId,
|
|
18087
18132
|
attId
|
|
18088
18133
|
);
|
|
18089
18134
|
removedIds.push(attId);
|
|
18090
18135
|
}
|
|
18091
18136
|
}
|
|
18092
|
-
const draft = await provider.readEmail(account,
|
|
18137
|
+
const draft = await provider.readEmail(account, currentId);
|
|
18093
18138
|
const result = {
|
|
18094
18139
|
edited: true,
|
|
18095
|
-
id:
|
|
18140
|
+
id: currentId,
|
|
18096
18141
|
draftHtml: draft.bodyHtml ?? ""
|
|
18097
18142
|
};
|
|
18098
18143
|
return ok(result, result);
|
|
@@ -18144,7 +18189,7 @@ function registerTools(server, opts) {
|
|
|
18144
18189
|
// package.json
|
|
18145
18190
|
var package_default = {
|
|
18146
18191
|
name: "hypermail-mcp",
|
|
18147
|
-
version: "0.7.
|
|
18192
|
+
version: "0.7.12",
|
|
18148
18193
|
description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
|
|
18149
18194
|
type: "module",
|
|
18150
18195
|
bin: {
|