@marlinjai/email-mcp 1.10.0 → 1.11.0
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 +7 -1
- package/dist/index.js +269 -52
- package/dist/index.js.map +4 -4
- package/dist/setup/wizard.js +143 -20
- package/dist/setup/wizard.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -141,7 +141,13 @@ npx -y -p @marlinjai/email-mcp@latest email-mcp-setup
|
|
|
141
141
|
|
|
142
142
|
### Sending & Drafts (6)
|
|
143
143
|
|
|
144
|
-
`email_send`, `email_reply`, `email_forward`, `email_draft_create` and `email_draft_update` take an optional `attachments` list. Each entry is either `{ path }` (a file in the attachments folder, see below) or `{ content, filename }` (base64), with an optional `contentType` (inferred from the extension otherwise). The total is capped at 25 MB; Outlook accepts up to 3 MB per message through this server. On `email_draft_update` a list replaces the draft's files
|
|
144
|
+
`email_send`, `email_reply`, `email_forward`, `email_draft_create` and `email_draft_update` take an optional `attachments` list. Each entry is either `{ path }` (a file in the attachments folder, see below) or `{ content, filename }` (base64), with an optional `contentType` (inferred from the extension otherwise). The total is capped at 25 MB; Outlook accepts up to 3 MB per message through this server. On `email_draft_update` a list replaces the draft's files, an empty list removes them, and no list keeps the files the draft has.
|
|
145
|
+
|
|
146
|
+
**Gmail adds your signature, like Gmail's own editor.** The Gmail API never appends one, so `email_send`, `email_reply`, `email_forward`, `email_draft_create` and `email_draft_update` read the signature of the send-as address the mail goes out from (Gmail settings, read on every call, so a changed signature applies at once) and add it in Gmail's own markup (`gmail_signature`), above any quoted history, plus a plain-text rendering in the text part. A text-only body gains an HTML part for it, as the signature's logo and links only exist as HTML. A body that already holds a signature (a `gmail_signature` block, or a `-- ` line in text) is left alone, so passing a draft's body back to `email_draft_update` never stacks a second one. `signature: false` turns it off for one call. Each of these tools returns a `signature` field saying what happened: `added`, `already_present`, `disabled`, `none_configured`, `unsupported`, or `skipped` with the reason (for example a missing permission); a failed read never blocks the mail or draft.
|
|
147
|
+
|
|
148
|
+
**Outlook, iCloud and IMAP add no signature.** Microsoft Graph has no signature API for Outlook.com accounts (signatures live in the Outlook clients, out of reach for a server), and an IMAP or iCloud server keeps no signature at all. The tools report `unsupported` there instead of inventing one; put the signature in the body when the mail needs it. A signature stored in email-mcp's own account settings was considered and left out on purpose: it would be a second copy that drifts from the one in the mail client, which is exactly how old mail ends up with an outdated signature.
|
|
149
|
+
|
|
150
|
+
**From names you.** The From header carries a display name, not just the address: on Gmail the send-as display name from Gmail's settings, otherwise the account's name in email-mcp (unless that is only a label: a provider name such as "Gmail" or "iCloud", or the address's local part such as "founder"). Gmail often keeps no display name for an account's primary address (its web editor then uses the Google profile name, which this server cannot read); set one under Settings, Accounts, "Send mail as", edit info, and the tools use it. This also holds for a draft sent later from another mail program, which keeps the draft's From. Outlook sets From itself.
|
|
145
151
|
|
|
146
152
|
Replies stay in their thread: `email_reply` sends inside the original conversation on Gmail and Outlook (and sets the `In-Reply-To` and `References` headers everywhere), `email_draft_create` with `inReplyToEmailId` saves a reply draft in that thread, and `email_draft_update` keeps a reply draft there.
|
|
147
153
|
|
package/dist/index.js
CHANGED
|
@@ -7181,6 +7181,16 @@ function normalizeBody(body) {
|
|
|
7181
7181
|
function toAddress(contact) {
|
|
7182
7182
|
return contact.name ? { name: contact.name, address: contact.email } : contact.email;
|
|
7183
7183
|
}
|
|
7184
|
+
function fromAddress(email3, displayName, accountName) {
|
|
7185
|
+
const own = displayName?.trim();
|
|
7186
|
+
if (own) return { name: own, address: email3 };
|
|
7187
|
+
const fallback = accountName?.trim();
|
|
7188
|
+
const label = (name) => PROVIDER_LABELS.has(name) || name === email3.toLowerCase() || name === email3.toLowerCase().split("@")[0];
|
|
7189
|
+
if (fallback && !label(fallback.toLowerCase())) {
|
|
7190
|
+
return { name: fallback, address: email3 };
|
|
7191
|
+
}
|
|
7192
|
+
return email3;
|
|
7193
|
+
}
|
|
7184
7194
|
function toAddressList(contacts) {
|
|
7185
7195
|
return contacts?.length ? contacts.map(toAddress) : void 0;
|
|
7186
7196
|
}
|
|
@@ -7206,9 +7216,11 @@ async function buildMimeMessage(from, params) {
|
|
|
7206
7216
|
node.keepBcc = true;
|
|
7207
7217
|
return node.build();
|
|
7208
7218
|
}
|
|
7219
|
+
var PROVIDER_LABELS;
|
|
7209
7220
|
var init_mime = __esm({
|
|
7210
7221
|
"src/providers/mime.ts"() {
|
|
7211
7222
|
"use strict";
|
|
7223
|
+
PROVIDER_LABELS = /* @__PURE__ */ new Set(["gmail", "outlook", "icloud", "imap"]);
|
|
7212
7224
|
}
|
|
7213
7225
|
});
|
|
7214
7226
|
|
|
@@ -7228,12 +7240,14 @@ var init_adapter = __esm({
|
|
|
7228
7240
|
gmail = null;
|
|
7229
7241
|
accountId = "";
|
|
7230
7242
|
email = "";
|
|
7243
|
+
accountName = "";
|
|
7231
7244
|
async connect(credentials) {
|
|
7232
7245
|
if (!credentials.oauth) {
|
|
7233
7246
|
throw new Error("Gmail adapter requires OAuth credentials");
|
|
7234
7247
|
}
|
|
7235
7248
|
this.accountId = credentials.id;
|
|
7236
7249
|
this.email = credentials.email;
|
|
7250
|
+
this.accountName = credentials.name;
|
|
7237
7251
|
const auth = new OAuth2Client();
|
|
7238
7252
|
auth.setCredentials({
|
|
7239
7253
|
access_token: credentials.oauth.access_token,
|
|
@@ -7386,27 +7400,39 @@ var init_adapter = __esm({
|
|
|
7386
7400
|
return { id: res.data.id || "" };
|
|
7387
7401
|
}
|
|
7388
7402
|
/**
|
|
7389
|
-
* drafts.update replaces the whole message, so
|
|
7390
|
-
*
|
|
7391
|
-
*
|
|
7403
|
+
* drafts.update replaces the whole message, so what the caller does not
|
|
7404
|
+
* name would be lost: a reply draft would fall out of its thread and a
|
|
7405
|
+
* draft's files would be dropped. So when the caller names no thread, the
|
|
7406
|
+
* thread and the reply headers of the stored draft are carried over, and
|
|
7407
|
+
* when the caller names no attachments, the stored draft's files are. A
|
|
7408
|
+
* given list (an empty one included) replaces the files.
|
|
7392
7409
|
*/
|
|
7393
7410
|
async updateDraft(draftId, update) {
|
|
7394
7411
|
const gmail = this.ensureConnected();
|
|
7395
7412
|
let params = update;
|
|
7396
|
-
|
|
7413
|
+
const keepThread = update.threadId === void 0;
|
|
7414
|
+
const keepFiles = update.attachments === void 0;
|
|
7415
|
+
if (keepThread || keepFiles) {
|
|
7397
7416
|
const existing = await gmail.users.drafts.get({
|
|
7398
7417
|
userId: "me",
|
|
7399
7418
|
id: draftId,
|
|
7400
|
-
//
|
|
7401
|
-
// single headers
|
|
7402
|
-
|
|
7419
|
+
// 'metadata' is ids, labels and headers, no body (drafts.get has no
|
|
7420
|
+
// way to ask for single headers, so all of them come back). The parts
|
|
7421
|
+
// that list the files only come with 'full'.
|
|
7422
|
+
format: keepFiles ? "full" : "metadata"
|
|
7403
7423
|
});
|
|
7404
7424
|
const message = existing.data.message;
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7425
|
+
if (keepThread) {
|
|
7426
|
+
const header = (name) => (message?.payload?.headers || []).find((h) => (h.name || "").toLowerCase() === name)?.value || void 0;
|
|
7427
|
+
const inReplyTo = update.inReplyTo ?? header("in-reply-to");
|
|
7428
|
+
const references = update.references ?? header("references")?.split(/\s+/).filter(Boolean);
|
|
7429
|
+
if (message?.threadId && inReplyTo) {
|
|
7430
|
+
params = { ...params, threadId: message.threadId, inReplyTo, references: references?.length ? references : [inReplyTo] };
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7433
|
+
if (keepFiles && message?.id) {
|
|
7434
|
+
const stored = await this.storedAttachments(message.id, message.payload);
|
|
7435
|
+
if (stored.length) params = { ...params, attachments: stored };
|
|
7410
7436
|
}
|
|
7411
7437
|
}
|
|
7412
7438
|
const raw = await this.buildRaw(params);
|
|
@@ -7419,6 +7445,43 @@ var init_adapter = __esm({
|
|
|
7419
7445
|
});
|
|
7420
7446
|
return { id: res.data.id || draftId };
|
|
7421
7447
|
}
|
|
7448
|
+
/**
|
|
7449
|
+
* The signature Gmail's own editor would add: the one of the send-as
|
|
7450
|
+
* address the From header names (`this.email`, see buildRaw), or the
|
|
7451
|
+
* default send-as when that address is not listed. Read on every call, so
|
|
7452
|
+
* a signature changed in Gmail's settings applies to the next mail.
|
|
7453
|
+
*/
|
|
7454
|
+
async getSignature() {
|
|
7455
|
+
const entries = await this.sendAsList();
|
|
7456
|
+
const entry = this.ownSendAs(entries) || entries.find((e) => e.isDefault);
|
|
7457
|
+
if (!entry) throw new Error(`Gmail lists no send-as address for ${this.email}`);
|
|
7458
|
+
const html = (entry.signature || "").trim();
|
|
7459
|
+
return { sendAs: entry.sendAsEmail || this.email, html: html || null };
|
|
7460
|
+
}
|
|
7461
|
+
async sendAsList() {
|
|
7462
|
+
const gmail = this.ensureConnected();
|
|
7463
|
+
const res = await gmail.users.settings.sendAs.list({ userId: "me" });
|
|
7464
|
+
return res.data.sendAs || [];
|
|
7465
|
+
}
|
|
7466
|
+
ownSendAs(entries) {
|
|
7467
|
+
const own = this.email.toLowerCase();
|
|
7468
|
+
return entries.find((e) => (e.sendAsEmail || "").toLowerCase() === own);
|
|
7469
|
+
}
|
|
7470
|
+
/**
|
|
7471
|
+
* The From header: the account's address with the display name Gmail
|
|
7472
|
+
* keeps for it as a send-as address. When the settings cannot be read the
|
|
7473
|
+
* mail still goes out, named after the account (see `fromAddress`); the
|
|
7474
|
+
* signature outcome in the tool result already reports that failure.
|
|
7475
|
+
*/
|
|
7476
|
+
async from() {
|
|
7477
|
+
let displayName;
|
|
7478
|
+
try {
|
|
7479
|
+
displayName = this.ownSendAs(await this.sendAsList())?.displayName || void 0;
|
|
7480
|
+
} catch {
|
|
7481
|
+
displayName = void 0;
|
|
7482
|
+
}
|
|
7483
|
+
return fromAddress(this.email, displayName, this.accountName);
|
|
7484
|
+
}
|
|
7422
7485
|
async listDrafts(limit, _offset) {
|
|
7423
7486
|
const gmail = this.ensureConnected();
|
|
7424
7487
|
const res = await gmail.users.drafts.list({
|
|
@@ -7803,6 +7866,41 @@ var init_adapter = __esm({
|
|
|
7803
7866
|
const belongs = params.threadId && params.inReplyTo && params.references?.length && params.subject.trim();
|
|
7804
7867
|
return belongs ? { threadId: params.threadId } : {};
|
|
7805
7868
|
}
|
|
7869
|
+
/**
|
|
7870
|
+
* The files of a stored message, with their bytes: every part that has a
|
|
7871
|
+
* file name, at any depth. Large files are referenced by an attachment id
|
|
7872
|
+
* and fetched; small ones can come inline in the part. A fetch that fails
|
|
7873
|
+
* is not swallowed: a draft update built on it would drop that file.
|
|
7874
|
+
*/
|
|
7875
|
+
async storedAttachments(messageId, payload) {
|
|
7876
|
+
const gmail = this.ensureConnected();
|
|
7877
|
+
const files = [];
|
|
7878
|
+
const html = (extractBody(payload).html || "").toLowerCase();
|
|
7879
|
+
const visit = async (part) => {
|
|
7880
|
+
if (!part) return;
|
|
7881
|
+
const partHeader = (name) => (part.headers || []).find((h) => (h.name || "").toLowerCase() === name)?.value || "";
|
|
7882
|
+
const contentId = partHeader("content-id").trim().replace(/^<|>$/g, "").toLowerCase();
|
|
7883
|
+
const embedded = /^\s*inline/i.test(partHeader("content-disposition")) && contentId !== "" && html.includes(`cid:${contentId}`);
|
|
7884
|
+
if (part.filename && !embedded) {
|
|
7885
|
+
let data = part.body?.data;
|
|
7886
|
+
if (!data && part.body?.attachmentId) {
|
|
7887
|
+
const res = await gmail.users.messages.attachments.get({ userId: "me", messageId, id: part.body.attachmentId });
|
|
7888
|
+
data = res.data.data;
|
|
7889
|
+
}
|
|
7890
|
+
if (data === void 0 || data === null) {
|
|
7891
|
+
throw new Error(`Could not read the draft's attachment ${part.filename}; the draft was not changed.`);
|
|
7892
|
+
}
|
|
7893
|
+
files.push({
|
|
7894
|
+
filename: part.filename,
|
|
7895
|
+
content: Buffer.from(data, "base64url"),
|
|
7896
|
+
contentType: part.mimeType || "application/octet-stream"
|
|
7897
|
+
});
|
|
7898
|
+
}
|
|
7899
|
+
for (const child of part.parts || []) await visit(child);
|
|
7900
|
+
};
|
|
7901
|
+
await visit(payload);
|
|
7902
|
+
return files;
|
|
7903
|
+
}
|
|
7806
7904
|
/**
|
|
7807
7905
|
* Builds the base64url `raw` payload Gmail expects for send, draft create and
|
|
7808
7906
|
* draft update: a full MIME message with a text part, an HTML part when one
|
|
@@ -7810,7 +7908,7 @@ var init_adapter = __esm({
|
|
|
7810
7908
|
* and any attachments. See `buildMimeMessage`.
|
|
7811
7909
|
*/
|
|
7812
7910
|
async buildRaw(params) {
|
|
7813
|
-
const message = await buildMimeMessage(this.
|
|
7911
|
+
const message = await buildMimeMessage(await this.from(), params);
|
|
7814
7912
|
return message.toString("base64url");
|
|
7815
7913
|
}
|
|
7816
7914
|
};
|
|
@@ -9081,6 +9179,7 @@ var init_adapter3 = __esm({
|
|
|
9081
9179
|
client = null;
|
|
9082
9180
|
accountId = "";
|
|
9083
9181
|
email = "";
|
|
9182
|
+
accountName = "";
|
|
9084
9183
|
passwordCreds = null;
|
|
9085
9184
|
connectingPromise = null;
|
|
9086
9185
|
async connect(credentials) {
|
|
@@ -9089,6 +9188,7 @@ var init_adapter3 = __esm({
|
|
|
9089
9188
|
}
|
|
9090
9189
|
this.accountId = credentials.id;
|
|
9091
9190
|
this.email = credentials.email;
|
|
9191
|
+
this.accountName = credentials.name;
|
|
9092
9192
|
this.passwordCreds = credentials.password;
|
|
9093
9193
|
await this.ensureConnected();
|
|
9094
9194
|
}
|
|
@@ -9521,7 +9621,7 @@ var init_adapter3 = __esm({
|
|
|
9521
9621
|
async sendEmail(params) {
|
|
9522
9622
|
if (!this.passwordCreds) throw new Error("No credentials available");
|
|
9523
9623
|
const transport = createSmtpTransport(this.email, this.passwordCreds);
|
|
9524
|
-
const messageId = await sendViaSmtp(transport, this.
|
|
9624
|
+
const messageId = await sendViaSmtp(transport, this.from(), params);
|
|
9525
9625
|
return { id: messageId };
|
|
9526
9626
|
}
|
|
9527
9627
|
/**
|
|
@@ -9530,7 +9630,12 @@ var init_adapter3 = __esm({
|
|
|
9530
9630
|
* attachments. See `buildMimeMessage`.
|
|
9531
9631
|
*/
|
|
9532
9632
|
buildDraftMessage(params) {
|
|
9533
|
-
return buildMimeMessage(this.
|
|
9633
|
+
return buildMimeMessage(this.from(), params);
|
|
9634
|
+
}
|
|
9635
|
+
/** IMAP keeps no display name on the server, so the account's name is the
|
|
9636
|
+
* only one there is (see `fromAddress`). */
|
|
9637
|
+
from() {
|
|
9638
|
+
return fromAddress(this.email, void 0, this.accountName);
|
|
9534
9639
|
}
|
|
9535
9640
|
async createDraft(params) {
|
|
9536
9641
|
const client = await this.ensureConnected();
|
|
@@ -9548,7 +9653,11 @@ var init_adapter3 = __esm({
|
|
|
9548
9653
|
*
|
|
9549
9654
|
* A reply draft keeps its place in the thread: when the caller passes no
|
|
9550
9655
|
* `inReplyTo`, the In-Reply-To and References headers of the old revision
|
|
9551
|
-
* are copied into the new one.
|
|
9656
|
+
* are copied into the new one. The same goes for files: when the caller
|
|
9657
|
+
* passes no `attachments`, the old revision's attachments are copied over
|
|
9658
|
+
* (a given list, an empty one included, replaces them). Images embedded in
|
|
9659
|
+
* the old HTML body are not files and are not copied: the new body replaces
|
|
9660
|
+
* the old one.
|
|
9552
9661
|
*/
|
|
9553
9662
|
async updateDraft(draftId, params, sourceFolder) {
|
|
9554
9663
|
const client = await this.ensureConnected();
|
|
@@ -9562,13 +9671,28 @@ var init_adapter3 = __esm({
|
|
|
9562
9671
|
let rawMessage;
|
|
9563
9672
|
try {
|
|
9564
9673
|
let next = params;
|
|
9565
|
-
|
|
9674
|
+
const keepThread = params.inReplyTo === void 0;
|
|
9675
|
+
const keepFiles = params.attachments === void 0;
|
|
9676
|
+
if (keepThread || keepFiles) {
|
|
9566
9677
|
const old = await client.fetchOne(String(draftId), { source: true, uid: true }, { uid: true });
|
|
9567
9678
|
if (old && old.source) {
|
|
9568
9679
|
const parsed = await simpleParser(old.source);
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
next = { ...
|
|
9680
|
+
if (keepThread && parsed.inReplyTo) {
|
|
9681
|
+
const references = [].concat(parsed.references ?? []);
|
|
9682
|
+
next = { ...next, inReplyTo: parsed.inReplyTo, references: params.references ?? references };
|
|
9683
|
+
}
|
|
9684
|
+
if (keepFiles) {
|
|
9685
|
+
const html = (parsed.html || "").toLowerCase();
|
|
9686
|
+
const files = (parsed.attachments || []).filter((att) => {
|
|
9687
|
+
if (!att.related) return true;
|
|
9688
|
+
const cid = String(att.cid || att.contentId || "").replace(/^<|>$/g, "").toLowerCase();
|
|
9689
|
+
return !cid || !html.includes(`cid:${cid}`);
|
|
9690
|
+
}).map((att, i) => ({
|
|
9691
|
+
filename: att.filename || `attachment-${i + 1}`,
|
|
9692
|
+
content: att.content,
|
|
9693
|
+
contentType: att.contentType || "application/octet-stream"
|
|
9694
|
+
}));
|
|
9695
|
+
if (files.length) next = { ...next, attachments: files };
|
|
9572
9696
|
}
|
|
9573
9697
|
}
|
|
9574
9698
|
}
|
|
@@ -34246,6 +34370,78 @@ function assertAttachmentTotal(totalBytes) {
|
|
|
34246
34370
|
}
|
|
34247
34371
|
}
|
|
34248
34372
|
|
|
34373
|
+
// src/tools/signature.ts
|
|
34374
|
+
init_mime();
|
|
34375
|
+
function escapeHtml(text) {
|
|
34376
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
34377
|
+
}
|
|
34378
|
+
function textToHtml(text) {
|
|
34379
|
+
return `<div dir="ltr">${escapeHtml(text).replace(/\r?\n/g, "<br>")}</div>`;
|
|
34380
|
+
}
|
|
34381
|
+
var TEXT_DELIMITER = /^-- \r?$/m;
|
|
34382
|
+
var HTML_QUOTE = /<(?:div|blockquote)\b[^>]*\bclass="[^"]*\bgmail_quote/i;
|
|
34383
|
+
var TEXT_QUOTE = /^(?:-{10} Forwarded message -{10}|On .+ wrote:)\s*$/m;
|
|
34384
|
+
function hasSignature(body) {
|
|
34385
|
+
return /\bgmail_signature\b/.test(body.html ?? "") || TEXT_DELIMITER.test(body.text ?? "");
|
|
34386
|
+
}
|
|
34387
|
+
function renderSignature(signatureHtml) {
|
|
34388
|
+
return {
|
|
34389
|
+
html: `<span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature">${signatureHtml}</div>`,
|
|
34390
|
+
text: `--
|
|
34391
|
+
${htmlToText(signatureHtml)}`
|
|
34392
|
+
};
|
|
34393
|
+
}
|
|
34394
|
+
function insertBefore(content, quote, block, separator) {
|
|
34395
|
+
const match = quote.exec(content);
|
|
34396
|
+
const at = match ? match.index : content.length;
|
|
34397
|
+
const before = content.slice(0, at).replace(/\s+$/, "");
|
|
34398
|
+
const after = content.slice(at);
|
|
34399
|
+
return [before, block, after].filter(Boolean).join(separator);
|
|
34400
|
+
}
|
|
34401
|
+
function withSignature(body, signatureHtml) {
|
|
34402
|
+
const signature = renderSignature(signatureHtml);
|
|
34403
|
+
const html = body.html ?? (body.text ? textToHtml(body.text) : "");
|
|
34404
|
+
const text = body.text ?? (body.html ? htmlToText(body.html) : "");
|
|
34405
|
+
return {
|
|
34406
|
+
html: insertBefore(html, HTML_QUOTE, `<div>${signature.html}</div>`, "<br>"),
|
|
34407
|
+
text: insertBefore(text, TEXT_QUOTE, signature.text, "\n\n")
|
|
34408
|
+
};
|
|
34409
|
+
}
|
|
34410
|
+
function unsupportedReason(provider) {
|
|
34411
|
+
if (provider === "outlook") {
|
|
34412
|
+
return "Outlook: Microsoft Graph has no signature API for Outlook.com accounts, so no signature was added. Put it in the body if the mail needs one.";
|
|
34413
|
+
}
|
|
34414
|
+
return `${provider === "icloud" ? "iCloud" : "IMAP"}: the server keeps no signature a client can read, so no signature was added. Put it in the body if the mail needs one.`;
|
|
34415
|
+
}
|
|
34416
|
+
async function resolveSignature(provider, body, enabled) {
|
|
34417
|
+
if (enabled === false) return { body, signature: { status: "disabled" } };
|
|
34418
|
+
if (hasSignature(body)) return { body, signature: { status: "already_present" } };
|
|
34419
|
+
if (!provider.getSignature) {
|
|
34420
|
+
return { body, signature: { status: "unsupported", reason: unsupportedReason(provider.providerType) } };
|
|
34421
|
+
}
|
|
34422
|
+
let found;
|
|
34423
|
+
try {
|
|
34424
|
+
found = await provider.getSignature();
|
|
34425
|
+
} catch (error48) {
|
|
34426
|
+
const message = error48?.message || String(error48);
|
|
34427
|
+
return {
|
|
34428
|
+
body,
|
|
34429
|
+
signature: { status: "skipped", reason: `The signature could not be read, so none was added: ${message}` }
|
|
34430
|
+
};
|
|
34431
|
+
}
|
|
34432
|
+
if (!found.html) {
|
|
34433
|
+
return {
|
|
34434
|
+
body,
|
|
34435
|
+
signature: {
|
|
34436
|
+
status: "none_configured",
|
|
34437
|
+
sendAs: found.sendAs,
|
|
34438
|
+
reason: `${found.sendAs} has no signature in Gmail's settings.`
|
|
34439
|
+
}
|
|
34440
|
+
};
|
|
34441
|
+
}
|
|
34442
|
+
return { body: withSignature(body, found.html), signature: { status: "added", sendAs: found.sendAs } };
|
|
34443
|
+
}
|
|
34444
|
+
|
|
34249
34445
|
// src/tools/sending.ts
|
|
34250
34446
|
var ContactSchema = external_exports3.object({
|
|
34251
34447
|
email: external_exports3.string(),
|
|
@@ -34255,6 +34451,9 @@ var BodySchema = external_exports3.object({
|
|
|
34255
34451
|
text: external_exports3.string().optional(),
|
|
34256
34452
|
html: external_exports3.string().optional()
|
|
34257
34453
|
});
|
|
34454
|
+
var SignatureSchema = external_exports3.boolean().optional().describe(
|
|
34455
|
+
"Add the account's signature (default true). Gmail: the signature of the send-as address the mail goes out from, read from Gmail's settings, in Gmail's own markup above any quoted history; a body that already holds one is left alone. Outlook.com, iCloud and IMAP have no signature a client can read, so nothing is added there. The result's `signature` field says what happened"
|
|
34456
|
+
);
|
|
34258
34457
|
function jsonResult3(data) {
|
|
34259
34458
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
34260
34459
|
}
|
|
@@ -34294,21 +34493,24 @@ function registerSendingTools(server, accountManager) {
|
|
|
34294
34493
|
bcc: external_exports3.array(ContactSchema).optional(),
|
|
34295
34494
|
subject: external_exports3.string(),
|
|
34296
34495
|
body: BodySchema,
|
|
34297
|
-
attachments: AttachmentsSchema
|
|
34496
|
+
attachments: AttachmentsSchema,
|
|
34497
|
+
signature: SignatureSchema
|
|
34298
34498
|
},
|
|
34299
34499
|
async (args) => {
|
|
34300
34500
|
try {
|
|
34301
34501
|
const provider = await accountManager.getProvider(args.accountId);
|
|
34502
|
+
const attachments = resolveAttachments(args.attachments);
|
|
34503
|
+
const { body, signature } = await resolveSignature(provider, args.body, args.signature);
|
|
34302
34504
|
const params = {
|
|
34303
34505
|
to: args.to,
|
|
34304
34506
|
cc: args.cc,
|
|
34305
34507
|
bcc: args.bcc,
|
|
34306
34508
|
subject: args.subject,
|
|
34307
|
-
body
|
|
34308
|
-
attachments
|
|
34509
|
+
body,
|
|
34510
|
+
attachments
|
|
34309
34511
|
};
|
|
34310
34512
|
const result = await provider.sendEmail(params);
|
|
34311
|
-
return jsonResult3(result);
|
|
34513
|
+
return jsonResult3({ ...result, signature });
|
|
34312
34514
|
} catch (error48) {
|
|
34313
34515
|
return jsonResult3({ error: error48.message });
|
|
34314
34516
|
}
|
|
@@ -34327,7 +34529,8 @@ function registerSendingTools(server, accountManager) {
|
|
|
34327
34529
|
cc: external_exports3.array(ContactSchema).optional().describe("Cc recipients. Replaces any Cc carried over by replyAll"),
|
|
34328
34530
|
bcc: external_exports3.array(ContactSchema).optional(),
|
|
34329
34531
|
additionalRecipients: external_exports3.array(ContactSchema).optional().describe("Extra To recipients added to the default or given To (deduplicated by address)"),
|
|
34330
|
-
attachments: AttachmentsSchema
|
|
34532
|
+
attachments: AttachmentsSchema,
|
|
34533
|
+
signature: SignatureSchema
|
|
34331
34534
|
},
|
|
34332
34535
|
async (args) => {
|
|
34333
34536
|
try {
|
|
@@ -34355,17 +34558,19 @@ function registerSendingTools(server, accountManager) {
|
|
|
34355
34558
|
}
|
|
34356
34559
|
if (args.cc !== void 0) cc = args.cc;
|
|
34357
34560
|
if (args.replyAll) ({ to, cc } = withoutDuplicates(to, cc));
|
|
34561
|
+
const attachments = resolveAttachments(args.attachments);
|
|
34562
|
+
const { body, signature } = await resolveSignature(provider, args.body, args.signature);
|
|
34358
34563
|
const params = {
|
|
34359
34564
|
to,
|
|
34360
34565
|
cc,
|
|
34361
34566
|
bcc: args.bcc,
|
|
34362
34567
|
subject,
|
|
34363
|
-
body
|
|
34568
|
+
body,
|
|
34364
34569
|
...replyThreading(original),
|
|
34365
|
-
attachments
|
|
34570
|
+
attachments
|
|
34366
34571
|
};
|
|
34367
34572
|
const result = await provider.sendEmail(params);
|
|
34368
|
-
return jsonResult3(result);
|
|
34573
|
+
return jsonResult3({ ...result, signature });
|
|
34369
34574
|
} catch (error48) {
|
|
34370
34575
|
return jsonResult3({ error: error48.message });
|
|
34371
34576
|
}
|
|
@@ -34383,12 +34588,14 @@ function registerSendingTools(server, accountManager) {
|
|
|
34383
34588
|
body: BodySchema.optional(),
|
|
34384
34589
|
attachments: AttachmentsSchema.describe("Files to attach to the forward. Total size, together with forwarded original attachments, is capped at 25 MB"),
|
|
34385
34590
|
includeOriginalAttachments: external_exports3.boolean().optional().describe("Also forward the original message's attachments (default false). Leave it off unless the user asked for the files to go along"),
|
|
34386
|
-
sourceFolder: external_exports3.string().optional().describe("Source folder (required for IMAP/iCloud when email is not in INBOX)")
|
|
34591
|
+
sourceFolder: external_exports3.string().optional().describe("Source folder (required for IMAP/iCloud when email is not in INBOX)"),
|
|
34592
|
+
signature: SignatureSchema
|
|
34387
34593
|
},
|
|
34388
34594
|
async (args) => {
|
|
34389
34595
|
try {
|
|
34390
34596
|
const provider = await accountManager.getProvider(args.accountId);
|
|
34391
34597
|
const original = await provider.getEmail(args.emailId, args.sourceFolder);
|
|
34598
|
+
const { body: note, signature } = await resolveSignature(provider, args.body ?? {}, args.signature);
|
|
34392
34599
|
const subject = original.subject.startsWith("Fwd: ") ? original.subject : `Fwd: ${original.subject}`;
|
|
34393
34600
|
const forwardHeader = [
|
|
34394
34601
|
"---------- Forwarded message ----------",
|
|
@@ -34398,7 +34605,7 @@ function registerSendingTools(server, accountManager) {
|
|
|
34398
34605
|
`To: ${original.to.map((c) => c.name ? `${c.name} <${c.email}>` : c.email).join(", ")}`,
|
|
34399
34606
|
""
|
|
34400
34607
|
].join("\n");
|
|
34401
|
-
const additionalText =
|
|
34608
|
+
const additionalText = note.text ?? "";
|
|
34402
34609
|
const originalText = original.body.text ?? "";
|
|
34403
34610
|
const forwardedText = [
|
|
34404
34611
|
additionalText,
|
|
@@ -34407,19 +34614,22 @@ function registerSendingTools(server, accountManager) {
|
|
|
34407
34614
|
originalText
|
|
34408
34615
|
].join("");
|
|
34409
34616
|
let forwardedHtml;
|
|
34410
|
-
if (original.body.html ||
|
|
34411
|
-
const additionalHtml =
|
|
34412
|
-
const originalHtml = original.body.html ?? "";
|
|
34617
|
+
if (original.body.html || note.html) {
|
|
34618
|
+
const additionalHtml = note.html ?? "";
|
|
34619
|
+
const originalHtml = original.body.html ?? textToHtml(original.body.text ?? "");
|
|
34620
|
+
const contact = (c) => escapeHtml(c.name ? `${c.name} <${c.email}>` : c.email);
|
|
34413
34621
|
forwardedHtml = [
|
|
34414
34622
|
additionalHtml,
|
|
34415
34623
|
"<br/><hr/>",
|
|
34624
|
+
'<div class="gmail_quote">',
|
|
34416
34625
|
`<b>---------- Forwarded message ----------</b><br/>`,
|
|
34417
|
-
`From: ${original.from
|
|
34418
|
-
`Date: ${original.date}<br/>`,
|
|
34419
|
-
`Subject: ${original.subject}<br/>`,
|
|
34420
|
-
`To: ${original.to.map(
|
|
34626
|
+
`From: ${contact(original.from)}<br/>`,
|
|
34627
|
+
`Date: ${escapeHtml(original.date)}<br/>`,
|
|
34628
|
+
`Subject: ${escapeHtml(original.subject)}<br/>`,
|
|
34629
|
+
`To: ${original.to.map(contact).join(", ")}<br/>`,
|
|
34421
34630
|
"<br/>",
|
|
34422
|
-
originalHtml
|
|
34631
|
+
originalHtml,
|
|
34632
|
+
"</div>"
|
|
34423
34633
|
].join("");
|
|
34424
34634
|
}
|
|
34425
34635
|
const extra = resolveAttachments(args.attachments) ?? [];
|
|
@@ -34445,7 +34655,7 @@ function registerSendingTools(server, accountManager) {
|
|
|
34445
34655
|
attachments: attachments.length ? attachments : void 0
|
|
34446
34656
|
};
|
|
34447
34657
|
const result = await provider.sendEmail(params);
|
|
34448
|
-
return jsonResult3(result);
|
|
34658
|
+
return jsonResult3({ ...result, signature });
|
|
34449
34659
|
} catch (error48) {
|
|
34450
34660
|
return jsonResult3({ error: error48.message });
|
|
34451
34661
|
}
|
|
@@ -34461,22 +34671,24 @@ function registerSendingTools(server, accountManager) {
|
|
|
34461
34671
|
body: BodySchema,
|
|
34462
34672
|
attachments: AttachmentsSchema,
|
|
34463
34673
|
inReplyToEmailId: external_exports3.string().optional().describe("Id of the email this draft replies to. The draft joins that thread (Gmail threadId, Outlook createReply, In-Reply-To/References headers elsewhere)"),
|
|
34464
|
-
inReplyToSourceFolder: external_exports3.string().optional().describe("Folder of the email named in inReplyToEmailId (required for IMAP/iCloud when that email is not in INBOX). The draft itself is saved to Drafts")
|
|
34674
|
+
inReplyToSourceFolder: external_exports3.string().optional().describe("Folder of the email named in inReplyToEmailId (required for IMAP/iCloud when that email is not in INBOX). The draft itself is saved to Drafts"),
|
|
34675
|
+
signature: SignatureSchema
|
|
34465
34676
|
},
|
|
34466
34677
|
async (args) => {
|
|
34467
34678
|
try {
|
|
34468
34679
|
const provider = await accountManager.getProvider(args.accountId);
|
|
34680
|
+
const attachments = resolveAttachments(args.attachments);
|
|
34681
|
+
const threading = args.inReplyToEmailId ? replyThreading(await provider.getEmail(args.inReplyToEmailId, args.inReplyToSourceFolder)) : {};
|
|
34682
|
+
const { body, signature } = await resolveSignature(provider, args.body, args.signature);
|
|
34469
34683
|
const params = {
|
|
34470
34684
|
to: args.to,
|
|
34471
34685
|
subject: args.subject,
|
|
34472
|
-
body
|
|
34473
|
-
attachments
|
|
34686
|
+
body,
|
|
34687
|
+
attachments,
|
|
34688
|
+
...threading
|
|
34474
34689
|
};
|
|
34475
|
-
if (args.inReplyToEmailId) {
|
|
34476
|
-
Object.assign(params, replyThreading(await provider.getEmail(args.inReplyToEmailId, args.inReplyToSourceFolder)));
|
|
34477
|
-
}
|
|
34478
34690
|
const result = await provider.createDraft(params);
|
|
34479
|
-
return jsonResult3(result);
|
|
34691
|
+
return jsonResult3({ ...result, signature });
|
|
34480
34692
|
} catch (error48) {
|
|
34481
34693
|
return jsonResult3({ error: error48.message });
|
|
34482
34694
|
}
|
|
@@ -34492,21 +34704,26 @@ function registerSendingTools(server, accountManager) {
|
|
|
34492
34704
|
subject: external_exports3.string(),
|
|
34493
34705
|
body: BodySchema,
|
|
34494
34706
|
attachments: AttachmentsSchema.describe(
|
|
34495
|
-
"Files to attach. When given, they replace the draft's current attachments; an empty list removes them all. When omitted,
|
|
34707
|
+
"Files to attach. When given, they replace the draft's current attachments; an empty list removes them all. When omitted, the draft keeps the files it has."
|
|
34496
34708
|
),
|
|
34497
|
-
sourceFolder: external_exports3.string().optional().describe("Source folder (required for IMAP/iCloud when the draft is not in the default Drafts folder)")
|
|
34709
|
+
sourceFolder: external_exports3.string().optional().describe("Source folder (required for IMAP/iCloud when the draft is not in the default Drafts folder)"),
|
|
34710
|
+
signature: SignatureSchema.describe(
|
|
34711
|
+
"Add the account's signature (default true), as on email_draft_create. The body given here replaces the draft's body, so the signature is added to it once; a body that already holds one (passed back from the draft) is left alone, so repeated updates never stack a second one"
|
|
34712
|
+
)
|
|
34498
34713
|
},
|
|
34499
34714
|
async (args) => {
|
|
34500
34715
|
try {
|
|
34501
34716
|
const provider = await accountManager.getProvider(args.accountId);
|
|
34717
|
+
const attachments = resolveAttachments(args.attachments);
|
|
34718
|
+
const { body, signature } = await resolveSignature(provider, args.body, args.signature);
|
|
34502
34719
|
const params = {
|
|
34503
34720
|
to: args.to,
|
|
34504
34721
|
subject: args.subject,
|
|
34505
|
-
body
|
|
34506
|
-
attachments
|
|
34722
|
+
body,
|
|
34723
|
+
attachments
|
|
34507
34724
|
};
|
|
34508
34725
|
const result = await provider.updateDraft(args.draftId, params, args.sourceFolder);
|
|
34509
|
-
return jsonResult3(result);
|
|
34726
|
+
return jsonResult3({ ...result, signature });
|
|
34510
34727
|
} catch (error48) {
|
|
34511
34728
|
return jsonResult3({ error: error48.message });
|
|
34512
34729
|
}
|
|
@@ -35156,7 +35373,7 @@ function registerForwardingTools(server, accountManager) {
|
|
|
35156
35373
|
}
|
|
35157
35374
|
|
|
35158
35375
|
// src/server.ts
|
|
35159
|
-
var SERVER_VERSION = "1.
|
|
35376
|
+
var SERVER_VERSION = "1.11.0";
|
|
35160
35377
|
async function createServer(accountManager) {
|
|
35161
35378
|
const mgr = accountManager ?? new AccountManager();
|
|
35162
35379
|
const server = new McpServer({
|