@marlinjai/email-mcp 1.5.0 → 1.7.1
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 +15 -4
- package/dist/index.js +240 -37
- package/dist/index.js.map +3 -3
- package/dist/setup/wizard.js +173 -35
- package/dist/setup/wizard.js.map +3 -3
- package/package.json +1 -1
package/dist/setup/wizard.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
// src/setup/wizard.ts
|
|
5
|
-
import
|
|
5
|
+
import crypto4 from "node:crypto";
|
|
6
6
|
import { exec } from "node:child_process";
|
|
7
7
|
|
|
8
8
|
// src/setup/prompts.ts
|
|
@@ -276,6 +276,7 @@ var OAuthCallbackServer = class {
|
|
|
276
276
|
rejectCode = null;
|
|
277
277
|
timeoutMs;
|
|
278
278
|
timeoutHandle = null;
|
|
279
|
+
expectedState = null;
|
|
279
280
|
constructor(timeoutMs = 12e4) {
|
|
280
281
|
this.timeoutMs = timeoutMs;
|
|
281
282
|
}
|
|
@@ -286,7 +287,12 @@ var OAuthCallbackServer = class {
|
|
|
286
287
|
if (url.pathname === "/callback" || url.pathname === "/") {
|
|
287
288
|
const code = url.searchParams.get("code");
|
|
288
289
|
const error = url.searchParams.get("error");
|
|
289
|
-
|
|
290
|
+
const state = url.searchParams.get("state");
|
|
291
|
+
if (code && this.expectedState !== null && state !== this.expectedState) {
|
|
292
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
293
|
+
res.end(ERROR_HTML);
|
|
294
|
+
this.rejectCode?.(new Error("OAuth error: state mismatch, callback did not come from the authorization request we started"));
|
|
295
|
+
} else if (code) {
|
|
290
296
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
291
297
|
res.end(SUCCESS_HTML);
|
|
292
298
|
this.resolveCode?.(code);
|
|
@@ -311,7 +317,13 @@ var OAuthCallbackServer = class {
|
|
|
311
317
|
this.server.on("error", reject);
|
|
312
318
|
});
|
|
313
319
|
}
|
|
314
|
-
|
|
320
|
+
/**
|
|
321
|
+
* Resolve with the authorization code once the browser is redirected back.
|
|
322
|
+
* Pass the `state` that was put into the authorization URL: callbacks whose
|
|
323
|
+
* state does not match are rejected instead of resolved (CSRF protection).
|
|
324
|
+
*/
|
|
325
|
+
waitForCode(expectedState) {
|
|
326
|
+
this.expectedState = expectedState ?? null;
|
|
315
327
|
return new Promise((resolve, reject) => {
|
|
316
328
|
this.resolveCode = (code) => {
|
|
317
329
|
if (this.timeoutHandle) clearTimeout(this.timeoutHandle);
|
|
@@ -342,14 +354,33 @@ var OAuthCallbackServer = class {
|
|
|
342
354
|
// src/providers/gmail/auth.ts
|
|
343
355
|
import { OAuth2Client } from "google-auth-library";
|
|
344
356
|
import crypto2 from "node:crypto";
|
|
345
|
-
var GMAIL_SCOPES =
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
357
|
+
var GMAIL_SCOPES = {
|
|
358
|
+
full: [
|
|
359
|
+
// Gmail's maximum-permission scope. Superset of gmail.modify below;
|
|
360
|
+
// the only capability it adds is immediate, trash-bypassing permanent
|
|
361
|
+
// deletion (messages.delete / batchDelete with permanent: true).
|
|
362
|
+
"https://mail.google.com/",
|
|
363
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
364
|
+
// Mailbox content access (above) does NOT cover the Settings API —
|
|
365
|
+
// Google treats them as separate permission domains. Needed for
|
|
366
|
+
// users.settings.filters.* (email_create_block_rule/list/delete).
|
|
367
|
+
"https://www.googleapis.com/auth/gmail.settings.basic"
|
|
368
|
+
],
|
|
369
|
+
// Drops https://mail.google.com/ so an AI-triggered permanent delete
|
|
370
|
+
// can't happen: gmail.modify already covers every other read/write
|
|
371
|
+
// operation this server performs (read, send, label, archive, trash,
|
|
372
|
+
// drafts, filters). Choosing this mode means deleteEmail(..., permanent:
|
|
373
|
+
// true) and batchDelete(..., permanent: true) will fail with a Gmail API
|
|
374
|
+
// 403 instead of succeeding — everything else behaves identically.
|
|
375
|
+
restricted: [
|
|
376
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
377
|
+
"https://www.googleapis.com/auth/gmail.settings.basic"
|
|
378
|
+
]
|
|
379
|
+
};
|
|
380
|
+
function resolveScopeMode(explicit) {
|
|
381
|
+
if (explicit) return explicit;
|
|
382
|
+
return process.env.EMAIL_MCP_GMAIL_SCOPE?.toLowerCase() === "restricted" ? "restricted" : "full";
|
|
383
|
+
}
|
|
353
384
|
var GmailAuth = class {
|
|
354
385
|
clientId;
|
|
355
386
|
clientSecret;
|
|
@@ -359,18 +390,20 @@ var GmailAuth = class {
|
|
|
359
390
|
this.clientSecret = clientSecret;
|
|
360
391
|
this.oauth2Client = new OAuth2Client(clientId, clientSecret, redirectUri);
|
|
361
392
|
}
|
|
362
|
-
getAuthUrl(redirectUri) {
|
|
393
|
+
getAuthUrl(redirectUri, scopeMode) {
|
|
363
394
|
const codeVerifier = crypto2.randomBytes(32).toString("base64url");
|
|
364
395
|
const codeChallenge = crypto2.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
396
|
+
const state = crypto2.randomBytes(16).toString("base64url");
|
|
365
397
|
this.oauth2Client = new OAuth2Client(this.clientId, this.clientSecret, redirectUri);
|
|
366
398
|
const url = this.oauth2Client.generateAuthUrl({
|
|
367
399
|
access_type: "offline",
|
|
368
|
-
scope: GMAIL_SCOPES,
|
|
400
|
+
scope: GMAIL_SCOPES[resolveScopeMode(scopeMode)],
|
|
369
401
|
prompt: "consent",
|
|
370
402
|
code_challenge: codeChallenge,
|
|
371
|
-
code_challenge_method: "S256"
|
|
403
|
+
code_challenge_method: "S256",
|
|
404
|
+
state
|
|
372
405
|
});
|
|
373
|
-
return { url, codeVerifier };
|
|
406
|
+
return { url, codeVerifier, state };
|
|
374
407
|
}
|
|
375
408
|
async exchangeCode(code, codeVerifier) {
|
|
376
409
|
const { tokens } = await this.oauth2Client.getToken({ code, codeVerifier });
|
|
@@ -402,6 +435,7 @@ var GmailAuth = class {
|
|
|
402
435
|
};
|
|
403
436
|
|
|
404
437
|
// src/providers/outlook/auth.ts
|
|
438
|
+
import crypto3 from "node:crypto";
|
|
405
439
|
import fs2 from "node:fs";
|
|
406
440
|
import path2 from "node:path";
|
|
407
441
|
import os2 from "node:os";
|
|
@@ -452,13 +486,15 @@ var OutlookAuth = class _OutlookAuth {
|
|
|
452
486
|
}
|
|
453
487
|
async getAuthUrl(redirectUri) {
|
|
454
488
|
const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
|
|
489
|
+
const state = crypto3.randomBytes(16).toString("base64url");
|
|
455
490
|
const url = await this.pca.getAuthCodeUrl({
|
|
456
491
|
scopes: _OutlookAuth.SCOPES,
|
|
457
492
|
redirectUri,
|
|
458
493
|
codeChallenge: challenge,
|
|
459
|
-
codeChallengeMethod: "S256"
|
|
494
|
+
codeChallengeMethod: "S256",
|
|
495
|
+
state
|
|
460
496
|
});
|
|
461
|
-
return { url, codeVerifier: verifier };
|
|
497
|
+
return { url, codeVerifier: verifier, state };
|
|
462
498
|
}
|
|
463
499
|
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
464
500
|
const result = await this.pca.acquireTokenByCode({
|
|
@@ -728,11 +764,12 @@ var GmailAdapter = class {
|
|
|
728
764
|
const labels = res.data.labels || [];
|
|
729
765
|
return labels.map(mapGmailLabel);
|
|
730
766
|
}
|
|
731
|
-
async createFolder(name) {
|
|
767
|
+
async createFolder(name, parentPath) {
|
|
732
768
|
const gmail = this.ensureConnected();
|
|
769
|
+
const fullName = parentPath ? `${parentPath}/${name}` : name;
|
|
733
770
|
const res = await gmail.users.labels.create({
|
|
734
771
|
userId: "me",
|
|
735
|
-
requestBody: { name }
|
|
772
|
+
requestBody: { name: fullName }
|
|
736
773
|
});
|
|
737
774
|
return mapGmailLabel(res.data);
|
|
738
775
|
}
|
|
@@ -849,6 +886,18 @@ var GmailAdapter = class {
|
|
|
849
886
|
});
|
|
850
887
|
return { id: res.data.id || "" };
|
|
851
888
|
}
|
|
889
|
+
async updateDraft(draftId, params) {
|
|
890
|
+
const gmail = this.ensureConnected();
|
|
891
|
+
const raw = this.buildRfc2822(params);
|
|
892
|
+
const res = await gmail.users.drafts.update({
|
|
893
|
+
userId: "me",
|
|
894
|
+
id: draftId,
|
|
895
|
+
requestBody: {
|
|
896
|
+
message: { raw }
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
return { id: res.data.id || draftId };
|
|
900
|
+
}
|
|
852
901
|
async listDrafts(limit, _offset) {
|
|
853
902
|
const gmail = this.ensureConnected();
|
|
854
903
|
const res = await gmail.users.drafts.list({
|
|
@@ -1012,6 +1061,34 @@ var GmailAdapter = class {
|
|
|
1012
1061
|
}
|
|
1013
1062
|
return result;
|
|
1014
1063
|
}
|
|
1064
|
+
async batchLabel(emailIds, addLabels, removeLabels) {
|
|
1065
|
+
const gmail = this.ensureConnected();
|
|
1066
|
+
const result = { succeeded: [], failed: [] };
|
|
1067
|
+
const addLabelIds = addLabels ?? [];
|
|
1068
|
+
const removeLabelIds = removeLabels ?? [];
|
|
1069
|
+
if (addLabelIds.length === 0 && removeLabelIds.length === 0) {
|
|
1070
|
+
result.succeeded = [...emailIds];
|
|
1071
|
+
return result;
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
await gmail.users.messages.batchModify({
|
|
1075
|
+
userId: "me",
|
|
1076
|
+
requestBody: { ids: emailIds, addLabelIds, removeLabelIds }
|
|
1077
|
+
});
|
|
1078
|
+
result.succeeded = [...emailIds];
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
for (const id of emailIds) {
|
|
1081
|
+
try {
|
|
1082
|
+
if (addLabelIds.length > 0) await this.addLabels(id, addLabelIds);
|
|
1083
|
+
if (removeLabelIds.length > 0) await this.removeLabels(id, removeLabelIds);
|
|
1084
|
+
result.succeeded.push(id);
|
|
1085
|
+
} catch (e) {
|
|
1086
|
+
result.failed.push({ id, error: e.message });
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return result;
|
|
1091
|
+
}
|
|
1015
1092
|
async getRawMessage(emailId, _sourceFolder) {
|
|
1016
1093
|
const gmail = this.ensureConnected();
|
|
1017
1094
|
const res = await gmail.users.messages.get({
|
|
@@ -1475,6 +1552,12 @@ var OutlookAdapter = class {
|
|
|
1475
1552
|
const result = await client.api("/me/messages").post(message);
|
|
1476
1553
|
return { id: result.id };
|
|
1477
1554
|
}
|
|
1555
|
+
async updateDraft(draftId, params) {
|
|
1556
|
+
const client = this.ensureClient();
|
|
1557
|
+
const message = this.buildGraphMessage(params);
|
|
1558
|
+
await client.api(`/me/messages/${encodeURIComponent(draftId)}`).patch(message);
|
|
1559
|
+
return { id: draftId };
|
|
1560
|
+
}
|
|
1478
1561
|
async listDrafts(limit, offset) {
|
|
1479
1562
|
const client = this.ensureClient();
|
|
1480
1563
|
let request = client.api("/me/mailFolders/drafts/messages");
|
|
@@ -2317,8 +2400,7 @@ var ImapAdapter = class {
|
|
|
2317
2400
|
const messageId = await sendViaSmtp(transport, this.email, params);
|
|
2318
2401
|
return { id: messageId };
|
|
2319
2402
|
}
|
|
2320
|
-
|
|
2321
|
-
const client = await this.ensureConnected();
|
|
2403
|
+
buildDraftRfc2822(params) {
|
|
2322
2404
|
const lines = [];
|
|
2323
2405
|
lines.push(`From: ${this.email}`);
|
|
2324
2406
|
lines.push(`To: ${params.to.map((c) => c.name ? `"${c.name}" <${c.email}>` : c.email).join(", ")}`);
|
|
@@ -2333,12 +2415,41 @@ var ImapAdapter = class {
|
|
|
2333
2415
|
lines.push("Content-Type: text/plain; charset=utf-8");
|
|
2334
2416
|
lines.push("");
|
|
2335
2417
|
lines.push(params.body.text || "");
|
|
2336
|
-
|
|
2418
|
+
return lines.join("\r\n");
|
|
2419
|
+
}
|
|
2420
|
+
async createDraft(params) {
|
|
2421
|
+
const client = await this.ensureConnected();
|
|
2422
|
+
const rawMessage = this.buildDraftRfc2822(params);
|
|
2337
2423
|
const draftsFolder = await this.resolveFolder("Drafts");
|
|
2338
2424
|
const result = await client.append(draftsFolder, rawMessage, ["\\Draft", "\\Seen"]);
|
|
2339
2425
|
if (!result) throw new Error("Failed to append draft");
|
|
2340
2426
|
return { id: String(result.uid || result) };
|
|
2341
2427
|
}
|
|
2428
|
+
/**
|
|
2429
|
+
* IMAP has no in-place message update — a draft is just a message in the
|
|
2430
|
+
* Drafts folder, and IMAP messages are immutable once appended. This
|
|
2431
|
+
* deletes the old revision and appends a new one, so the returned `id`
|
|
2432
|
+
* is a NEW uid; the old id no longer resolves after this call.
|
|
2433
|
+
*/
|
|
2434
|
+
async updateDraft(draftId, params, sourceFolder) {
|
|
2435
|
+
const client = await this.ensureConnected();
|
|
2436
|
+
const draftsFolder = sourceFolder ? await this.resolveFolder(sourceFolder) : await this.resolveFolder("Drafts");
|
|
2437
|
+
let lock;
|
|
2438
|
+
try {
|
|
2439
|
+
lock = await client.getMailboxLock(draftsFolder);
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
throw formatImapError(error, `Failed to open folder "${draftsFolder}"`);
|
|
2442
|
+
}
|
|
2443
|
+
try {
|
|
2444
|
+
await client.messageDelete(draftId, { uid: true });
|
|
2445
|
+
} finally {
|
|
2446
|
+
lock.release();
|
|
2447
|
+
}
|
|
2448
|
+
const rawMessage = this.buildDraftRfc2822(params);
|
|
2449
|
+
const result = await client.append(draftsFolder, rawMessage, ["\\Draft", "\\Seen"]);
|
|
2450
|
+
if (!result) throw new Error("Failed to append updated draft");
|
|
2451
|
+
return { id: String(result.uid || result) };
|
|
2452
|
+
}
|
|
2342
2453
|
async listDrafts(limit, offset) {
|
|
2343
2454
|
return this.search({
|
|
2344
2455
|
folder: "Drafts",
|
|
@@ -2580,9 +2691,9 @@ var ICloudAdapter = class extends ImapAdapter {
|
|
|
2580
2691
|
};
|
|
2581
2692
|
|
|
2582
2693
|
// src/oauth-config.ts
|
|
2583
|
-
var GMAIL_CLIENT_ID = "1062266096576-lu2ukplhc3lcm0bvheml5i18urptdp4t.apps.googleusercontent.com";
|
|
2584
|
-
var GMAIL_CLIENT_SECRET = "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";
|
|
2585
|
-
var OUTLOOK_CLIENT_ID = "76d32225-ef10-4b49-951c-c80ae906595d";
|
|
2694
|
+
var GMAIL_CLIENT_ID = process.env.EMAIL_MCP_GMAIL_CLIENT_ID || "1062266096576-lu2ukplhc3lcm0bvheml5i18urptdp4t.apps.googleusercontent.com";
|
|
2695
|
+
var GMAIL_CLIENT_SECRET = process.env.EMAIL_MCP_GMAIL_CLIENT_SECRET || "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";
|
|
2696
|
+
var OUTLOOK_CLIENT_ID = process.env.EMAIL_MCP_OUTLOOK_CLIENT_ID || "76d32225-ef10-4b49-951c-c80ae906595d";
|
|
2586
2697
|
|
|
2587
2698
|
// src/account-manager.ts
|
|
2588
2699
|
function createProvider(provider) {
|
|
@@ -2770,14 +2881,30 @@ async function testAndSave(creds) {
|
|
|
2770
2881
|
async function promptAccountName(defaultName) {
|
|
2771
2882
|
return askText("Account name:", defaultName);
|
|
2772
2883
|
}
|
|
2773
|
-
async function setupGmail() {
|
|
2884
|
+
async function setupGmail(scopeModeFlag) {
|
|
2774
2885
|
console.log("\n--- Gmail Setup ---");
|
|
2886
|
+
if (process.env.EMAIL_MCP_GMAIL_CLIENT_ID) {
|
|
2887
|
+
console.log("Using your own Google OAuth Client ID (EMAIL_MCP_GMAIL_CLIENT_ID).");
|
|
2888
|
+
}
|
|
2775
2889
|
console.log("A browser window will open for Google authorization.\n");
|
|
2890
|
+
let scopeMode = scopeModeFlag;
|
|
2891
|
+
if (!scopeMode) {
|
|
2892
|
+
scopeMode = await askChoice("Gmail permission scope:", [
|
|
2893
|
+
{
|
|
2894
|
+
name: "Full (default) \u2014 includes permanent delete, bypassing Trash",
|
|
2895
|
+
value: "full"
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
name: "Restricted \u2014 read/send/label/archive/trash only, no permanent delete",
|
|
2899
|
+
value: "restricted"
|
|
2900
|
+
}
|
|
2901
|
+
]);
|
|
2902
|
+
}
|
|
2776
2903
|
const server = new OAuthCallbackServer();
|
|
2777
2904
|
const port = await server.start();
|
|
2778
2905
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
2779
2906
|
const gmailAuth = new GmailAuth(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, redirectUri);
|
|
2780
|
-
const { url, codeVerifier } = gmailAuth.getAuthUrl(redirectUri);
|
|
2907
|
+
const { url, codeVerifier, state } = gmailAuth.getAuthUrl(redirectUri, scopeMode);
|
|
2781
2908
|
console.log("\nOpening browser for Google authorization...");
|
|
2782
2909
|
console.log(`If the browser does not open, visit:
|
|
2783
2910
|
${url}
|
|
@@ -2785,7 +2912,7 @@ ${url}
|
|
|
2785
2912
|
openBrowser(url);
|
|
2786
2913
|
let code;
|
|
2787
2914
|
try {
|
|
2788
|
-
code = await server.waitForCode();
|
|
2915
|
+
code = await server.waitForCode(state);
|
|
2789
2916
|
} catch (err) {
|
|
2790
2917
|
server.shutdown();
|
|
2791
2918
|
throw new Error(`OAuth flow failed: ${err.message}`);
|
|
@@ -2801,7 +2928,7 @@ ${url}
|
|
|
2801
2928
|
if (!email) throw new Error("Could not retrieve Gmail address from profile");
|
|
2802
2929
|
console.log(`Authenticated as ${email}`);
|
|
2803
2930
|
const name = await promptAccountName("Gmail");
|
|
2804
|
-
const id =
|
|
2931
|
+
const id = crypto4.randomUUID();
|
|
2805
2932
|
const creds = {
|
|
2806
2933
|
id,
|
|
2807
2934
|
name,
|
|
@@ -2818,7 +2945,7 @@ async function setupOutlook() {
|
|
|
2818
2945
|
const port = await server.start();
|
|
2819
2946
|
const redirectUri = `http://localhost:${port}`;
|
|
2820
2947
|
const outlookAuth = new OutlookAuth(OUTLOOK_CLIENT_ID);
|
|
2821
|
-
const { url, codeVerifier } = await outlookAuth.getAuthUrl(redirectUri);
|
|
2948
|
+
const { url, codeVerifier, state } = await outlookAuth.getAuthUrl(redirectUri);
|
|
2822
2949
|
console.log("\nOpening browser for Microsoft authorization...");
|
|
2823
2950
|
console.log(`If the browser does not open, visit:
|
|
2824
2951
|
${url}
|
|
@@ -2826,7 +2953,7 @@ ${url}
|
|
|
2826
2953
|
openBrowser(url);
|
|
2827
2954
|
let code;
|
|
2828
2955
|
try {
|
|
2829
|
-
code = await server.waitForCode();
|
|
2956
|
+
code = await server.waitForCode(state);
|
|
2830
2957
|
} catch (err) {
|
|
2831
2958
|
server.shutdown();
|
|
2832
2959
|
throw new Error(`OAuth flow failed: ${err.message}`);
|
|
@@ -2839,7 +2966,7 @@ ${url}
|
|
|
2839
2966
|
if (!email) throw new Error("Could not retrieve email from Microsoft account");
|
|
2840
2967
|
console.log(`Authenticated as ${email}`);
|
|
2841
2968
|
const name = await promptAccountName("Outlook");
|
|
2842
|
-
const id =
|
|
2969
|
+
const id = crypto4.randomUUID();
|
|
2843
2970
|
const creds = {
|
|
2844
2971
|
id,
|
|
2845
2972
|
name,
|
|
@@ -2871,7 +2998,7 @@ async function setupICloud() {
|
|
|
2871
2998
|
password = await askPassword("App-specific password:");
|
|
2872
2999
|
}
|
|
2873
3000
|
const name = await promptAccountName("iCloud");
|
|
2874
|
-
const id =
|
|
3001
|
+
const id = crypto4.randomUUID();
|
|
2875
3002
|
const creds = {
|
|
2876
3003
|
id,
|
|
2877
3004
|
name,
|
|
@@ -2912,7 +3039,7 @@ async function setupIMAP() {
|
|
|
2912
3039
|
smtpPort = parseInt(smtpPortStr, 10);
|
|
2913
3040
|
}
|
|
2914
3041
|
const name = await promptAccountName("IMAP");
|
|
2915
|
-
const id =
|
|
3042
|
+
const id = crypto4.randomUUID();
|
|
2916
3043
|
const creds = {
|
|
2917
3044
|
id,
|
|
2918
3045
|
name,
|
|
@@ -2976,6 +3103,17 @@ async function main() {
|
|
|
2976
3103
|
await removeAccount(id);
|
|
2977
3104
|
return;
|
|
2978
3105
|
}
|
|
3106
|
+
const scopeIdx = args.indexOf("--scope");
|
|
3107
|
+
let scopeModeFlag;
|
|
3108
|
+
if (scopeIdx !== -1) {
|
|
3109
|
+
const value = args[scopeIdx + 1];
|
|
3110
|
+
if (value === "full" || value === "restricted") {
|
|
3111
|
+
scopeModeFlag = value;
|
|
3112
|
+
} else {
|
|
3113
|
+
console.error("Usage: --scope <full|restricted>");
|
|
3114
|
+
process.exit(1);
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
2979
3117
|
console.log("@marlinjai/email-mcp Account Setup\n");
|
|
2980
3118
|
let addMore = true;
|
|
2981
3119
|
while (addMore) {
|
|
@@ -2987,7 +3125,7 @@ async function main() {
|
|
|
2987
3125
|
]);
|
|
2988
3126
|
switch (provider) {
|
|
2989
3127
|
case ProviderType.Gmail:
|
|
2990
|
-
await setupGmail();
|
|
3128
|
+
await setupGmail(scopeModeFlag);
|
|
2991
3129
|
break;
|
|
2992
3130
|
case ProviderType.Outlook:
|
|
2993
3131
|
await setupOutlook();
|