@marlinjai/email-mcp 1.7.0 → 1.7.2
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 +2 -0
- package/dist/index.js +54 -21
- package/dist/index.js.map +3 -3
- package/dist/setup/wizard.js +52 -19
- 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
|
|
@@ -245,6 +245,18 @@ var CredentialStore = class {
|
|
|
245
245
|
const data = this.read();
|
|
246
246
|
return Object.values(data.accounts);
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* The account already configured for this mailbox, if any. Provider plus
|
|
250
|
+
* address identifies a mailbox; the address comparison is case-insensitive
|
|
251
|
+
* because providers report it in varying case.
|
|
252
|
+
*/
|
|
253
|
+
async findByProviderAndEmail(provider, email) {
|
|
254
|
+
const wanted = email.trim().toLowerCase();
|
|
255
|
+
const data = this.read();
|
|
256
|
+
return Object.values(data.accounts).find(
|
|
257
|
+
(a) => a.provider === provider && a.email.trim().toLowerCase() === wanted
|
|
258
|
+
) ?? null;
|
|
259
|
+
}
|
|
248
260
|
async remove(id) {
|
|
249
261
|
const data = this.read();
|
|
250
262
|
delete data.accounts[id];
|
|
@@ -276,6 +288,7 @@ var OAuthCallbackServer = class {
|
|
|
276
288
|
rejectCode = null;
|
|
277
289
|
timeoutMs;
|
|
278
290
|
timeoutHandle = null;
|
|
291
|
+
expectedState = null;
|
|
279
292
|
constructor(timeoutMs = 12e4) {
|
|
280
293
|
this.timeoutMs = timeoutMs;
|
|
281
294
|
}
|
|
@@ -286,7 +299,12 @@ var OAuthCallbackServer = class {
|
|
|
286
299
|
if (url.pathname === "/callback" || url.pathname === "/") {
|
|
287
300
|
const code = url.searchParams.get("code");
|
|
288
301
|
const error = url.searchParams.get("error");
|
|
289
|
-
|
|
302
|
+
const state = url.searchParams.get("state");
|
|
303
|
+
if (code && this.expectedState !== null && state !== this.expectedState) {
|
|
304
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
305
|
+
res.end(ERROR_HTML);
|
|
306
|
+
this.rejectCode?.(new Error("OAuth error: state mismatch, callback did not come from the authorization request we started"));
|
|
307
|
+
} else if (code) {
|
|
290
308
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
291
309
|
res.end(SUCCESS_HTML);
|
|
292
310
|
this.resolveCode?.(code);
|
|
@@ -311,7 +329,13 @@ var OAuthCallbackServer = class {
|
|
|
311
329
|
this.server.on("error", reject);
|
|
312
330
|
});
|
|
313
331
|
}
|
|
314
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Resolve with the authorization code once the browser is redirected back.
|
|
334
|
+
* Pass the `state` that was put into the authorization URL: callbacks whose
|
|
335
|
+
* state does not match are rejected instead of resolved (CSRF protection).
|
|
336
|
+
*/
|
|
337
|
+
waitForCode(expectedState) {
|
|
338
|
+
this.expectedState = expectedState ?? null;
|
|
315
339
|
return new Promise((resolve, reject) => {
|
|
316
340
|
this.resolveCode = (code) => {
|
|
317
341
|
if (this.timeoutHandle) clearTimeout(this.timeoutHandle);
|
|
@@ -381,15 +405,17 @@ var GmailAuth = class {
|
|
|
381
405
|
getAuthUrl(redirectUri, scopeMode) {
|
|
382
406
|
const codeVerifier = crypto2.randomBytes(32).toString("base64url");
|
|
383
407
|
const codeChallenge = crypto2.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
408
|
+
const state = crypto2.randomBytes(16).toString("base64url");
|
|
384
409
|
this.oauth2Client = new OAuth2Client(this.clientId, this.clientSecret, redirectUri);
|
|
385
410
|
const url = this.oauth2Client.generateAuthUrl({
|
|
386
411
|
access_type: "offline",
|
|
387
412
|
scope: GMAIL_SCOPES[resolveScopeMode(scopeMode)],
|
|
388
413
|
prompt: "consent",
|
|
389
414
|
code_challenge: codeChallenge,
|
|
390
|
-
code_challenge_method: "S256"
|
|
415
|
+
code_challenge_method: "S256",
|
|
416
|
+
state
|
|
391
417
|
});
|
|
392
|
-
return { url, codeVerifier };
|
|
418
|
+
return { url, codeVerifier, state };
|
|
393
419
|
}
|
|
394
420
|
async exchangeCode(code, codeVerifier) {
|
|
395
421
|
const { tokens } = await this.oauth2Client.getToken({ code, codeVerifier });
|
|
@@ -421,6 +447,7 @@ var GmailAuth = class {
|
|
|
421
447
|
};
|
|
422
448
|
|
|
423
449
|
// src/providers/outlook/auth.ts
|
|
450
|
+
import crypto3 from "node:crypto";
|
|
424
451
|
import fs2 from "node:fs";
|
|
425
452
|
import path2 from "node:path";
|
|
426
453
|
import os2 from "node:os";
|
|
@@ -471,13 +498,15 @@ var OutlookAuth = class _OutlookAuth {
|
|
|
471
498
|
}
|
|
472
499
|
async getAuthUrl(redirectUri) {
|
|
473
500
|
const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
|
|
501
|
+
const state = crypto3.randomBytes(16).toString("base64url");
|
|
474
502
|
const url = await this.pca.getAuthCodeUrl({
|
|
475
503
|
scopes: _OutlookAuth.SCOPES,
|
|
476
504
|
redirectUri,
|
|
477
505
|
codeChallenge: challenge,
|
|
478
|
-
codeChallengeMethod: "S256"
|
|
506
|
+
codeChallengeMethod: "S256",
|
|
507
|
+
state
|
|
479
508
|
});
|
|
480
|
-
return { url, codeVerifier: verifier };
|
|
509
|
+
return { url, codeVerifier: verifier, state };
|
|
481
510
|
}
|
|
482
511
|
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
483
512
|
const result = await this.pca.acquireTokenByCode({
|
|
@@ -2864,6 +2893,14 @@ async function testAndSave(creds) {
|
|
|
2864
2893
|
async function promptAccountName(defaultName) {
|
|
2865
2894
|
return askText("Account name:", defaultName);
|
|
2866
2895
|
}
|
|
2896
|
+
async function resolveAccountIdentity(provider, email, defaultName) {
|
|
2897
|
+
const existing = await store.findByProviderAndEmail(provider, email);
|
|
2898
|
+
if (existing) {
|
|
2899
|
+
console.log(`An account for ${email} already exists ("${existing.name}"), updating it in place.`);
|
|
2900
|
+
return { id: existing.id, name: await promptAccountName(existing.name) };
|
|
2901
|
+
}
|
|
2902
|
+
return { id: crypto4.randomUUID(), name: await promptAccountName(defaultName) };
|
|
2903
|
+
}
|
|
2867
2904
|
async function setupGmail(scopeModeFlag) {
|
|
2868
2905
|
console.log("\n--- Gmail Setup ---");
|
|
2869
2906
|
if (process.env.EMAIL_MCP_GMAIL_CLIENT_ID) {
|
|
@@ -2887,7 +2924,7 @@ async function setupGmail(scopeModeFlag) {
|
|
|
2887
2924
|
const port = await server.start();
|
|
2888
2925
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
2889
2926
|
const gmailAuth = new GmailAuth(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, redirectUri);
|
|
2890
|
-
const { url, codeVerifier } = gmailAuth.getAuthUrl(redirectUri, scopeMode);
|
|
2927
|
+
const { url, codeVerifier, state } = gmailAuth.getAuthUrl(redirectUri, scopeMode);
|
|
2891
2928
|
console.log("\nOpening browser for Google authorization...");
|
|
2892
2929
|
console.log(`If the browser does not open, visit:
|
|
2893
2930
|
${url}
|
|
@@ -2895,7 +2932,7 @@ ${url}
|
|
|
2895
2932
|
openBrowser(url);
|
|
2896
2933
|
let code;
|
|
2897
2934
|
try {
|
|
2898
|
-
code = await server.waitForCode();
|
|
2935
|
+
code = await server.waitForCode(state);
|
|
2899
2936
|
} catch (err) {
|
|
2900
2937
|
server.shutdown();
|
|
2901
2938
|
throw new Error(`OAuth flow failed: ${err.message}`);
|
|
@@ -2910,8 +2947,7 @@ ${url}
|
|
|
2910
2947
|
const email = profile.emailAddress ?? "";
|
|
2911
2948
|
if (!email) throw new Error("Could not retrieve Gmail address from profile");
|
|
2912
2949
|
console.log(`Authenticated as ${email}`);
|
|
2913
|
-
const name = await
|
|
2914
|
-
const id = crypto3.randomUUID();
|
|
2950
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.Gmail, email.trim(), "Gmail");
|
|
2915
2951
|
const creds = {
|
|
2916
2952
|
id,
|
|
2917
2953
|
name,
|
|
@@ -2928,7 +2964,7 @@ async function setupOutlook() {
|
|
|
2928
2964
|
const port = await server.start();
|
|
2929
2965
|
const redirectUri = `http://localhost:${port}`;
|
|
2930
2966
|
const outlookAuth = new OutlookAuth(OUTLOOK_CLIENT_ID);
|
|
2931
|
-
const { url, codeVerifier } = await outlookAuth.getAuthUrl(redirectUri);
|
|
2967
|
+
const { url, codeVerifier, state } = await outlookAuth.getAuthUrl(redirectUri);
|
|
2932
2968
|
console.log("\nOpening browser for Microsoft authorization...");
|
|
2933
2969
|
console.log(`If the browser does not open, visit:
|
|
2934
2970
|
${url}
|
|
@@ -2936,7 +2972,7 @@ ${url}
|
|
|
2936
2972
|
openBrowser(url);
|
|
2937
2973
|
let code;
|
|
2938
2974
|
try {
|
|
2939
|
-
code = await server.waitForCode();
|
|
2975
|
+
code = await server.waitForCode(state);
|
|
2940
2976
|
} catch (err) {
|
|
2941
2977
|
server.shutdown();
|
|
2942
2978
|
throw new Error(`OAuth flow failed: ${err.message}`);
|
|
@@ -2948,8 +2984,7 @@ ${url}
|
|
|
2948
2984
|
const email = account?.username ?? "";
|
|
2949
2985
|
if (!email) throw new Error("Could not retrieve email from Microsoft account");
|
|
2950
2986
|
console.log(`Authenticated as ${email}`);
|
|
2951
|
-
const name = await
|
|
2952
|
-
const id = crypto3.randomUUID();
|
|
2987
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.Outlook, email.trim(), "Outlook");
|
|
2953
2988
|
const creds = {
|
|
2954
2989
|
id,
|
|
2955
2990
|
name,
|
|
@@ -2980,8 +3015,7 @@ async function setupICloud() {
|
|
|
2980
3015
|
console.log(" Password is required");
|
|
2981
3016
|
password = await askPassword("App-specific password:");
|
|
2982
3017
|
}
|
|
2983
|
-
const name = await
|
|
2984
|
-
const id = crypto3.randomUUID();
|
|
3018
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.ICloud, email.trim(), "iCloud");
|
|
2985
3019
|
const creds = {
|
|
2986
3020
|
id,
|
|
2987
3021
|
name,
|
|
@@ -3021,8 +3055,7 @@ async function setupIMAP() {
|
|
|
3021
3055
|
const smtpPortStr = await askValidated("SMTP port:", "587", portValidator);
|
|
3022
3056
|
smtpPort = parseInt(smtpPortStr, 10);
|
|
3023
3057
|
}
|
|
3024
|
-
const name = await
|
|
3025
|
-
const id = crypto3.randomUUID();
|
|
3058
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.IMAP, email.trim(), "IMAP");
|
|
3026
3059
|
const creds = {
|
|
3027
3060
|
id,
|
|
3028
3061
|
name,
|