@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/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -175,6 +175,18 @@ var init_credential_store = __esm({
|
|
|
175
175
|
const data = this.read();
|
|
176
176
|
return Object.values(data.accounts);
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* The account already configured for this mailbox, if any. Provider plus
|
|
180
|
+
* address identifies a mailbox; the address comparison is case-insensitive
|
|
181
|
+
* because providers report it in varying case.
|
|
182
|
+
*/
|
|
183
|
+
async findByProviderAndEmail(provider, email3) {
|
|
184
|
+
const wanted = email3.trim().toLowerCase();
|
|
185
|
+
const data = this.read();
|
|
186
|
+
return Object.values(data.accounts).find(
|
|
187
|
+
(a) => a.provider === provider && a.email.trim().toLowerCase() === wanted
|
|
188
|
+
) ?? null;
|
|
189
|
+
}
|
|
178
190
|
async remove(id) {
|
|
179
191
|
const data = this.read();
|
|
180
192
|
delete data.accounts[id];
|
|
@@ -7710,15 +7722,17 @@ var init_auth = __esm({
|
|
|
7710
7722
|
getAuthUrl(redirectUri, scopeMode) {
|
|
7711
7723
|
const codeVerifier = crypto2.randomBytes(32).toString("base64url");
|
|
7712
7724
|
const codeChallenge = crypto2.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
7725
|
+
const state = crypto2.randomBytes(16).toString("base64url");
|
|
7713
7726
|
this.oauth2Client = new OAuth2Client2(this.clientId, this.clientSecret, redirectUri);
|
|
7714
7727
|
const url2 = this.oauth2Client.generateAuthUrl({
|
|
7715
7728
|
access_type: "offline",
|
|
7716
7729
|
scope: GMAIL_SCOPES[resolveScopeMode(scopeMode)],
|
|
7717
7730
|
prompt: "consent",
|
|
7718
7731
|
code_challenge: codeChallenge,
|
|
7719
|
-
code_challenge_method: "S256"
|
|
7732
|
+
code_challenge_method: "S256",
|
|
7733
|
+
state
|
|
7720
7734
|
});
|
|
7721
|
-
return { url: url2, codeVerifier };
|
|
7735
|
+
return { url: url2, codeVerifier, state };
|
|
7722
7736
|
}
|
|
7723
7737
|
async exchangeCode(code, codeVerifier) {
|
|
7724
7738
|
const { tokens } = await this.oauth2Client.getToken({ code, codeVerifier });
|
|
@@ -8369,6 +8383,7 @@ var init_adapter2 = __esm({
|
|
|
8369
8383
|
});
|
|
8370
8384
|
|
|
8371
8385
|
// src/providers/outlook/auth.ts
|
|
8386
|
+
import crypto3 from "node:crypto";
|
|
8372
8387
|
import fs2 from "node:fs";
|
|
8373
8388
|
import path2 from "node:path";
|
|
8374
8389
|
import os2 from "node:os";
|
|
@@ -8423,13 +8438,15 @@ var init_auth2 = __esm({
|
|
|
8423
8438
|
}
|
|
8424
8439
|
async getAuthUrl(redirectUri) {
|
|
8425
8440
|
const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
|
|
8441
|
+
const state = crypto3.randomBytes(16).toString("base64url");
|
|
8426
8442
|
const url2 = await this.pca.getAuthCodeUrl({
|
|
8427
8443
|
scopes: _OutlookAuth.SCOPES,
|
|
8428
8444
|
redirectUri,
|
|
8429
8445
|
codeChallenge: challenge,
|
|
8430
|
-
codeChallengeMethod: "S256"
|
|
8446
|
+
codeChallengeMethod: "S256",
|
|
8447
|
+
state
|
|
8431
8448
|
});
|
|
8432
|
-
return { url: url2, codeVerifier: verifier };
|
|
8449
|
+
return { url: url2, codeVerifier: verifier, state };
|
|
8433
8450
|
}
|
|
8434
8451
|
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
8435
8452
|
const result = await this.pca.acquireTokenByCode({
|
|
@@ -9660,6 +9677,7 @@ var init_oauth_server = __esm({
|
|
|
9660
9677
|
rejectCode = null;
|
|
9661
9678
|
timeoutMs;
|
|
9662
9679
|
timeoutHandle = null;
|
|
9680
|
+
expectedState = null;
|
|
9663
9681
|
constructor(timeoutMs = 12e4) {
|
|
9664
9682
|
this.timeoutMs = timeoutMs;
|
|
9665
9683
|
}
|
|
@@ -9670,7 +9688,12 @@ var init_oauth_server = __esm({
|
|
|
9670
9688
|
if (url2.pathname === "/callback" || url2.pathname === "/") {
|
|
9671
9689
|
const code = url2.searchParams.get("code");
|
|
9672
9690
|
const error48 = url2.searchParams.get("error");
|
|
9673
|
-
|
|
9691
|
+
const state = url2.searchParams.get("state");
|
|
9692
|
+
if (code && this.expectedState !== null && state !== this.expectedState) {
|
|
9693
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
9694
|
+
res.end(ERROR_HTML);
|
|
9695
|
+
this.rejectCode?.(new Error("OAuth error: state mismatch, callback did not come from the authorization request we started"));
|
|
9696
|
+
} else if (code) {
|
|
9674
9697
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
9675
9698
|
res.end(SUCCESS_HTML);
|
|
9676
9699
|
this.resolveCode?.(code);
|
|
@@ -9695,7 +9718,13 @@ var init_oauth_server = __esm({
|
|
|
9695
9718
|
this.server.on("error", reject);
|
|
9696
9719
|
});
|
|
9697
9720
|
}
|
|
9698
|
-
|
|
9721
|
+
/**
|
|
9722
|
+
* Resolve with the authorization code once the browser is redirected back.
|
|
9723
|
+
* Pass the `state` that was put into the authorization URL: callbacks whose
|
|
9724
|
+
* state does not match are rejected instead of resolved (CSRF protection).
|
|
9725
|
+
*/
|
|
9726
|
+
waitForCode(expectedState) {
|
|
9727
|
+
this.expectedState = expectedState ?? null;
|
|
9699
9728
|
return new Promise((resolve, reject) => {
|
|
9700
9729
|
this.resolveCode = (code) => {
|
|
9701
9730
|
if (this.timeoutHandle) clearTimeout(this.timeoutHandle);
|
|
@@ -9727,7 +9756,7 @@ var init_oauth_server = __esm({
|
|
|
9727
9756
|
|
|
9728
9757
|
// src/setup/wizard.ts
|
|
9729
9758
|
var wizard_exports = {};
|
|
9730
|
-
import
|
|
9759
|
+
import crypto5 from "node:crypto";
|
|
9731
9760
|
import { exec } from "node:child_process";
|
|
9732
9761
|
function openBrowser(url2) {
|
|
9733
9762
|
const platform = process.platform;
|
|
@@ -9771,6 +9800,14 @@ async function testAndSave(creds) {
|
|
|
9771
9800
|
async function promptAccountName(defaultName) {
|
|
9772
9801
|
return askText("Account name:", defaultName);
|
|
9773
9802
|
}
|
|
9803
|
+
async function resolveAccountIdentity(provider, email3, defaultName) {
|
|
9804
|
+
const existing = await store.findByProviderAndEmail(provider, email3);
|
|
9805
|
+
if (existing) {
|
|
9806
|
+
console.log(`An account for ${email3} already exists ("${existing.name}"), updating it in place.`);
|
|
9807
|
+
return { id: existing.id, name: await promptAccountName(existing.name) };
|
|
9808
|
+
}
|
|
9809
|
+
return { id: crypto5.randomUUID(), name: await promptAccountName(defaultName) };
|
|
9810
|
+
}
|
|
9774
9811
|
async function setupGmail(scopeModeFlag) {
|
|
9775
9812
|
console.log("\n--- Gmail Setup ---");
|
|
9776
9813
|
if (process.env.EMAIL_MCP_GMAIL_CLIENT_ID) {
|
|
@@ -9794,7 +9831,7 @@ async function setupGmail(scopeModeFlag) {
|
|
|
9794
9831
|
const port = await server.start();
|
|
9795
9832
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
9796
9833
|
const gmailAuth = new GmailAuth(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, redirectUri);
|
|
9797
|
-
const { url: url2, codeVerifier } = gmailAuth.getAuthUrl(redirectUri, scopeMode);
|
|
9834
|
+
const { url: url2, codeVerifier, state } = gmailAuth.getAuthUrl(redirectUri, scopeMode);
|
|
9798
9835
|
console.log("\nOpening browser for Google authorization...");
|
|
9799
9836
|
console.log(`If the browser does not open, visit:
|
|
9800
9837
|
${url2}
|
|
@@ -9802,7 +9839,7 @@ ${url2}
|
|
|
9802
9839
|
openBrowser(url2);
|
|
9803
9840
|
let code;
|
|
9804
9841
|
try {
|
|
9805
|
-
code = await server.waitForCode();
|
|
9842
|
+
code = await server.waitForCode(state);
|
|
9806
9843
|
} catch (err) {
|
|
9807
9844
|
server.shutdown();
|
|
9808
9845
|
throw new Error(`OAuth flow failed: ${err.message}`);
|
|
@@ -9817,8 +9854,7 @@ ${url2}
|
|
|
9817
9854
|
const email3 = profile.emailAddress ?? "";
|
|
9818
9855
|
if (!email3) throw new Error("Could not retrieve Gmail address from profile");
|
|
9819
9856
|
console.log(`Authenticated as ${email3}`);
|
|
9820
|
-
const name = await
|
|
9821
|
-
const id = crypto4.randomUUID();
|
|
9857
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.Gmail, email3.trim(), "Gmail");
|
|
9822
9858
|
const creds = {
|
|
9823
9859
|
id,
|
|
9824
9860
|
name,
|
|
@@ -9835,7 +9871,7 @@ async function setupOutlook() {
|
|
|
9835
9871
|
const port = await server.start();
|
|
9836
9872
|
const redirectUri = `http://localhost:${port}`;
|
|
9837
9873
|
const outlookAuth = new OutlookAuth(OUTLOOK_CLIENT_ID);
|
|
9838
|
-
const { url: url2, codeVerifier } = await outlookAuth.getAuthUrl(redirectUri);
|
|
9874
|
+
const { url: url2, codeVerifier, state } = await outlookAuth.getAuthUrl(redirectUri);
|
|
9839
9875
|
console.log("\nOpening browser for Microsoft authorization...");
|
|
9840
9876
|
console.log(`If the browser does not open, visit:
|
|
9841
9877
|
${url2}
|
|
@@ -9843,7 +9879,7 @@ ${url2}
|
|
|
9843
9879
|
openBrowser(url2);
|
|
9844
9880
|
let code;
|
|
9845
9881
|
try {
|
|
9846
|
-
code = await server.waitForCode();
|
|
9882
|
+
code = await server.waitForCode(state);
|
|
9847
9883
|
} catch (err) {
|
|
9848
9884
|
server.shutdown();
|
|
9849
9885
|
throw new Error(`OAuth flow failed: ${err.message}`);
|
|
@@ -9855,8 +9891,7 @@ ${url2}
|
|
|
9855
9891
|
const email3 = account?.username ?? "";
|
|
9856
9892
|
if (!email3) throw new Error("Could not retrieve email from Microsoft account");
|
|
9857
9893
|
console.log(`Authenticated as ${email3}`);
|
|
9858
|
-
const name = await
|
|
9859
|
-
const id = crypto4.randomUUID();
|
|
9894
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.Outlook, email3.trim(), "Outlook");
|
|
9860
9895
|
const creds = {
|
|
9861
9896
|
id,
|
|
9862
9897
|
name,
|
|
@@ -9887,8 +9922,7 @@ async function setupICloud() {
|
|
|
9887
9922
|
console.log(" Password is required");
|
|
9888
9923
|
password = await askPassword("App-specific password:");
|
|
9889
9924
|
}
|
|
9890
|
-
const name = await
|
|
9891
|
-
const id = crypto4.randomUUID();
|
|
9925
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.ICloud, email3.trim(), "iCloud");
|
|
9892
9926
|
const creds = {
|
|
9893
9927
|
id,
|
|
9894
9928
|
name,
|
|
@@ -9928,8 +9962,7 @@ async function setupIMAP() {
|
|
|
9928
9962
|
const smtpPortStr = await askValidated("SMTP port:", "587", portValidator);
|
|
9929
9963
|
smtpPort = parseInt(smtpPortStr, 10);
|
|
9930
9964
|
}
|
|
9931
|
-
const name = await
|
|
9932
|
-
const id = crypto4.randomUUID();
|
|
9965
|
+
const { id, name } = await resolveAccountIdentity(ProviderType.IMAP, email3.trim(), "IMAP");
|
|
9933
9966
|
const creds = {
|
|
9934
9967
|
id,
|
|
9935
9968
|
name,
|
|
@@ -33215,7 +33248,7 @@ var StdioServerTransport = class {
|
|
|
33215
33248
|
init_account_manager();
|
|
33216
33249
|
|
|
33217
33250
|
// src/tools/accounts.ts
|
|
33218
|
-
import
|
|
33251
|
+
import crypto4 from "node:crypto";
|
|
33219
33252
|
function jsonResult(data) {
|
|
33220
33253
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
33221
33254
|
}
|
|
@@ -33270,7 +33303,7 @@ function registerAccountTools(server, accountManager) {
|
|
|
33270
33303
|
error: "IMAP host and port are required for non-iCloud providers"
|
|
33271
33304
|
});
|
|
33272
33305
|
}
|
|
33273
|
-
const accountId =
|
|
33306
|
+
const accountId = crypto4.randomUUID();
|
|
33274
33307
|
const creds = {
|
|
33275
33308
|
id: accountId,
|
|
33276
33309
|
name: args.name,
|