@atengk/mcp-server-email 0.0.0-stage → 1.0.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/dist/index.js ADDED
@@ -0,0 +1,1867 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import http from "http";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+
9
+ // src/config/index.ts
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import dotenv from "dotenv";
13
+ import { z } from "zod";
14
+ dotenv.config();
15
+ var smtpProfileSchema = z.object({
16
+ host: z.string(),
17
+ port: z.union([z.number(), z.string().transform((val) => parseInt(val, 10))]).default(465),
18
+ secure: z.union([z.boolean(), z.string().transform((val) => val === "true" || val === "1")]).default(true),
19
+ user: z.string(),
20
+ pass: z.string(),
21
+ from: z.string().optional()
22
+ });
23
+ var imapProfileSchema = z.object({
24
+ host: z.string(),
25
+ port: z.union([z.number(), z.string().transform((val) => parseInt(val, 10))]).default(993),
26
+ secure: z.union([z.boolean(), z.string().transform((val) => val === "true" || val === "1")]).default(true),
27
+ user: z.string(),
28
+ pass: z.string()
29
+ });
30
+ var accountProfileSchema = z.object({
31
+ id: z.string(),
32
+ name: z.string().optional(),
33
+ email: z.string().optional(),
34
+ smtp: smtpProfileSchema.optional(),
35
+ imap: imapProfileSchema.optional()
36
+ });
37
+ var envSchema = z.object({
38
+ // SMTP 配置 (向下兼容单账户)
39
+ MCP_SMTP_HOST: z.string().optional(),
40
+ MCP_SMTP_PORT: z.string().optional().transform((val) => val ? parseInt(val, 10) : 465),
41
+ MCP_SMTP_SECURE: z.string().optional().transform((val) => val === void 0 || val === "true" || val === "1"),
42
+ MCP_SMTP_USER: z.string().optional(),
43
+ MCP_SMTP_PASS: z.string().optional(),
44
+ MCP_SMTP_FROM: z.string().optional(),
45
+ // IMAP 配置 (向下兼容单账户)
46
+ MCP_IMAP_HOST: z.string().optional(),
47
+ MCP_IMAP_PORT: z.string().optional().transform((val) => val ? parseInt(val, 10) : 993),
48
+ MCP_IMAP_SECURE: z.string().optional().transform((val) => val === void 0 || val === "true" || val === "1"),
49
+ MCP_IMAP_USER: z.string().optional(),
50
+ MCP_IMAP_PASS: z.string().optional(),
51
+ // 多账户配置
52
+ MCP_ACCOUNTS: z.string().optional(),
53
+ MCP_ACCOUNTS_FILE: z.string().optional(),
54
+ MCP_DEFAULT_ACCOUNT: z.string().optional(),
55
+ // MCP 传输与服务配置
56
+ MCP_TRANSPORT: z.enum(["stdio", "sse"]).default("stdio"),
57
+ MCP_PORT: z.string().optional().transform((val) => val ? parseInt(val, 10) : 3e3),
58
+ MCP_LOG_LEVEL: z.string().default("info")
59
+ });
60
+ function parseAccountsFromJson(raw) {
61
+ if (Array.isArray(raw)) {
62
+ return z.array(accountProfileSchema).parse(raw);
63
+ }
64
+ if (typeof raw === "object" && raw !== null) {
65
+ const record = z.record(accountProfileSchema.omit({ id: true })).parse(raw);
66
+ return Object.entries(record).map(([id, profile]) => ({
67
+ ...profile,
68
+ id
69
+ }));
70
+ }
71
+ throw new Error("\u65E0\u6548\u7684\u8D26\u6237\u914D\u7F6E\u683C\u5F0F\uFF0C\u5FC5\u987B\u4E3A JSON \u6570\u7EC4\u6216\u952E\u503C\u5BF9\u8C61");
72
+ }
73
+ function parseAccountProfiles(env = process.env) {
74
+ let accounts = [];
75
+ const accountsFile = env.MCP_ACCOUNTS_FILE;
76
+ if (accountsFile) {
77
+ try {
78
+ const resolvedPath = path.isAbsolute(accountsFile) ? accountsFile : path.resolve(process.cwd(), accountsFile);
79
+ if (fs.existsSync(resolvedPath)) {
80
+ const fileContent = fs.readFileSync(resolvedPath, "utf-8");
81
+ accounts = parseAccountsFromJson(JSON.parse(fileContent));
82
+ } else {
83
+ console.error(`[\u914D\u7F6E\u8B66\u544A] MCP_ACCOUNTS_FILE \u6307\u5B9A\u7684\u6587\u4EF6\u4E0D\u5B58\u5728: ${resolvedPath}`);
84
+ }
85
+ } catch (err) {
86
+ console.error(
87
+ "[\u914D\u7F6E\u9519\u8BEF] \u89E3\u6790 MCP_ACCOUNTS_FILE \u5931\u8D25:",
88
+ err instanceof Error ? err.message : String(err)
89
+ );
90
+ }
91
+ }
92
+ const accountsJson = env.MCP_ACCOUNTS;
93
+ if (accountsJson && accounts.length === 0) {
94
+ try {
95
+ accounts = parseAccountsFromJson(JSON.parse(accountsJson));
96
+ } catch (err) {
97
+ console.error(
98
+ "[\u914D\u7F6E\u9519\u8BEF] \u89E3\u6790 MCP_ACCOUNTS JSON \u5931\u8D25:",
99
+ err instanceof Error ? err.message : String(err)
100
+ );
101
+ }
102
+ }
103
+ if (accounts.length === 0) {
104
+ const hasSmtp = Boolean(env.MCP_SMTP_HOST && env.MCP_SMTP_USER && env.MCP_SMTP_PASS);
105
+ const hasImap = Boolean(env.MCP_IMAP_HOST && env.MCP_IMAP_USER && env.MCP_IMAP_PASS);
106
+ if (hasSmtp || hasImap) {
107
+ const defaultAccount = {
108
+ id: "default",
109
+ name: "\u9ED8\u8BA4\u90AE\u7BB1\u8D26\u6237",
110
+ email: env.MCP_SMTP_USER || env.MCP_IMAP_USER
111
+ };
112
+ if (hasSmtp) {
113
+ defaultAccount.smtp = {
114
+ host: env.MCP_SMTP_HOST,
115
+ port: env.MCP_SMTP_PORT ? parseInt(env.MCP_SMTP_PORT, 10) : 465,
116
+ secure: env.MCP_SMTP_SECURE === void 0 || env.MCP_SMTP_SECURE === "true" || env.MCP_SMTP_SECURE === "1",
117
+ user: env.MCP_SMTP_USER,
118
+ pass: env.MCP_SMTP_PASS,
119
+ from: env.MCP_SMTP_FROM
120
+ };
121
+ }
122
+ if (hasImap) {
123
+ defaultAccount.imap = {
124
+ host: env.MCP_IMAP_HOST,
125
+ port: env.MCP_IMAP_PORT ? parseInt(env.MCP_IMAP_PORT, 10) : 993,
126
+ secure: env.MCP_IMAP_SECURE === void 0 || env.MCP_IMAP_SECURE === "true" || env.MCP_IMAP_SECURE === "1",
127
+ user: env.MCP_IMAP_USER,
128
+ pass: env.MCP_IMAP_PASS
129
+ };
130
+ }
131
+ accounts.push(defaultAccount);
132
+ }
133
+ }
134
+ let defaultAccountId = env.MCP_DEFAULT_ACCOUNT || "";
135
+ if (!defaultAccountId || !accounts.some((a) => a.id === defaultAccountId)) {
136
+ const defaultAcc = accounts.find((a) => a.id === "default");
137
+ defaultAccountId = defaultAcc ? defaultAcc.id : accounts[0]?.id || "default";
138
+ }
139
+ return { accounts, defaultAccountId };
140
+ }
141
+ function loadConfig(env = process.env) {
142
+ const result = envSchema.safeParse(env);
143
+ if (!result.success) {
144
+ console.error("[\u914D\u7F6E\u9519\u8BEF] \u73AF\u5883\u53D8\u91CF\u6821\u9A8C\u5931\u8D25:", result.error.format());
145
+ throw new Error("\u73AF\u5883\u53D8\u91CF\u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5 .env \u914D\u7F6E");
146
+ }
147
+ const { accounts, defaultAccountId } = parseAccountProfiles(env);
148
+ return {
149
+ ...result.data,
150
+ accounts,
151
+ defaultAccountId
152
+ };
153
+ }
154
+ var config = loadConfig();
155
+
156
+ // src/tools/index.ts
157
+ import { z as z2 } from "zod";
158
+
159
+ // src/services/imap.service.ts
160
+ import { ImapFlow } from "imapflow";
161
+ import { simpleParser } from "mailparser";
162
+
163
+ // src/services/mailbox-resolver.ts
164
+ import nodemailer from "nodemailer";
165
+
166
+ // src/services/attachment-sandbox.ts
167
+ import fs2 from "fs/promises";
168
+ import os from "os";
169
+ import path2 from "path";
170
+ import { pathToFileURL } from "url";
171
+ function getAttachmentSandboxDir() {
172
+ const customDir = process.env.MCP_ATTACHMENT_DIR;
173
+ if (customDir && customDir.trim().length > 0) {
174
+ return path2.resolve(customDir.trim());
175
+ }
176
+ return path2.resolve(os.tmpdir(), "mcp-email-attachments");
177
+ }
178
+ function sanitizeFileName(rawName) {
179
+ if (!rawName || typeof rawName !== "string") {
180
+ return `attachment-${Date.now()}`;
181
+ }
182
+ if (rawName.includes("..") || rawName.includes("\0")) {
183
+ throw new Error(`\u62D2\u7EDD\u5904\u7406\uFF1A\u68C0\u6D4B\u5230\u975E\u6CD5\u8DEF\u5F84\u7A7F\u8D8A\u6587\u4EF6\u540D "${rawName}"`);
184
+ }
185
+ const normalized = rawName.replace(/\\/g, "/");
186
+ let cleanName = path2.posix.basename(normalized);
187
+ cleanName = cleanName.replace(/[\x00-\x1f\x7f<>:"/\\|?*]/g, "_").trim();
188
+ if (!cleanName || cleanName === "." || cleanName === "..") {
189
+ cleanName = `attachment-${Date.now()}`;
190
+ }
191
+ return cleanName;
192
+ }
193
+ async function saveAttachmentToSandbox(rawFileName, content, customSandboxDir, uid) {
194
+ const sandboxDir = path2.resolve(customSandboxDir || getAttachmentSandboxDir());
195
+ const targetDir = uid !== void 0 ? path2.join(sandboxDir, String(uid)) : sandboxDir;
196
+ await fs2.mkdir(targetDir, { recursive: true });
197
+ const safeFileName = sanitizeFileName(rawFileName);
198
+ const targetPath = path2.resolve(targetDir, safeFileName);
199
+ const relative = path2.relative(sandboxDir, targetPath);
200
+ if (relative.startsWith("..") || path2.isAbsolute(relative)) {
201
+ throw new Error(`\u5B89\u5168\u9632\u5FA1\u89E6\u53D1\uFF1A\u76EE\u6807\u8DEF\u5F84 "${targetPath}" \u8D8A\u754C\u9003\u9038\u6C99\u7BB1\u6839\u76EE\u5F55 "${sandboxDir}"`);
202
+ }
203
+ await fs2.writeFile(targetPath, content);
204
+ const fileUrl = pathToFileURL(targetPath).toString();
205
+ return {
206
+ filePath: targetPath,
207
+ fileUrl,
208
+ filename: safeFileName,
209
+ size: content.byteLength
210
+ };
211
+ }
212
+ function validateOutboundAttachmentPath(candidatePath) {
213
+ if (!candidatePath || typeof candidatePath !== "string") {
214
+ throw new Error("\u9644\u4EF6\u8DEF\u5F84\u5FC5\u987B\u4E3A\u6709\u6548\u7684\u975E\u7A7A\u5B57\u7B26\u4E32");
215
+ }
216
+ if (candidatePath.includes("\0")) {
217
+ throw new Error("\u975E\u6CD5\u9644\u4EF6\u8DEF\u5F84\uFF1A\u68C0\u6D4B\u5230\u7A7A\u5B57\u8282\u6CE8\u5165");
218
+ }
219
+ const resolvedPath = path2.resolve(candidatePath);
220
+ const baseName = path2.basename(resolvedPath).toLowerCase();
221
+ if (baseName.startsWith(".env") || baseName === "id_rsa" || baseName === "id_ed25519" || baseName.endsWith(".pem") || baseName.endsWith(".key") || baseName === "shadow" || baseName === "passwd" || baseName === "sam" || baseName === "system") {
222
+ throw new Error(`\u5B89\u5168\u62E6\u622A\uFF1A\u4E25\u7981\u5916\u53D1\u7CFB\u7EDF\u5173\u952E\u654F\u611F\u51ED\u636E\u6587\u4EF6 "${baseName}"`);
223
+ }
224
+ const sandboxDir = getAttachmentSandboxDir();
225
+ const cwdDir = process.cwd();
226
+ const relSandbox = path2.relative(sandboxDir, resolvedPath);
227
+ const inSandbox = !relSandbox.startsWith("..") && !path2.isAbsolute(relSandbox);
228
+ const relCwd = path2.relative(cwdDir, resolvedPath);
229
+ const inCwd = !relCwd.startsWith("..") && !path2.isAbsolute(relCwd);
230
+ if (!inSandbox && !inCwd) {
231
+ throw new Error(
232
+ `\u5B89\u5168\u62E6\u622A\uFF1A\u5F85\u5916\u53D1\u9644\u4EF6\u8DEF\u5F84 "${candidatePath}" \u8D85\u51FA\u5141\u8BB8\u7684\u5B89\u5168\u76EE\u5F55\u8FB9\u754C\uFF08\u4EC5\u5141\u8BB8\u5F53\u524D\u5DE5\u4F5C\u533A\u6216\u6C99\u7BB1\u9644\u4EF6\u76EE\u5F55\uFF09`
233
+ );
234
+ }
235
+ return resolvedPath;
236
+ }
237
+
238
+ // src/services/mailbox-resolver.ts
239
+ function resolveSpecialMailbox(logicalAlias, availableMailboxes = []) {
240
+ const normalized = (logicalAlias || "").trim().toLowerCase();
241
+ const items = availableMailboxes.map((m) => {
242
+ if (typeof m === "string") {
243
+ return { path: m, name: m };
244
+ }
245
+ return {
246
+ path: m.path || "",
247
+ name: m.name,
248
+ specialUse: m.specialUse
249
+ };
250
+ });
251
+ if (normalized === "inbox" || normalized === "\u6536\u4EF6\u7BB1") {
252
+ const found = items.find(
253
+ (m) => m.specialUse?.toLowerCase() === "\\inbox" || m.path?.toUpperCase() === "INBOX" || m.name?.toUpperCase() === "INBOX"
254
+ );
255
+ return found ? found.path : "INBOX";
256
+ }
257
+ if (normalized === "drafts" || normalized === "draft" || normalized === "\u8349\u7A3F\u7BB1" || normalized === "\u8349\u7A3F") {
258
+ const bySpecialUse = items.find(
259
+ (m) => m.specialUse?.toLowerCase() === "\\drafts"
260
+ );
261
+ if (bySpecialUse) {
262
+ return bySpecialUse.path;
263
+ }
264
+ const byName = items.find((m) => {
265
+ const p = m.path.toLowerCase();
266
+ const n = (m.name || "").toLowerCase();
267
+ return p === "drafts" || p === "\u8349\u7A3F\u7BB1" || p.endsWith("/drafts") || n === "drafts" || n === "\u8349\u7A3F\u7BB1";
268
+ });
269
+ if (byName) {
270
+ return byName.path;
271
+ }
272
+ return "Drafts";
273
+ }
274
+ if (normalized === "trash" || normalized === "bin" || normalized === "deleted" || normalized === "\u5DF2\u5220\u9664" || normalized === "\u56DE\u6536\u7AD9" || normalized === "\u5E9F\u4EF6\u7BB1") {
275
+ const bySpecialUse = items.find(
276
+ (m) => m.specialUse?.toLowerCase() === "\\trash"
277
+ );
278
+ if (bySpecialUse) {
279
+ return bySpecialUse.path;
280
+ }
281
+ const byName = items.find((m) => {
282
+ const p = m.path.toLowerCase();
283
+ const n = (m.name || "").toLowerCase();
284
+ return p === "trash" || p === "deleted messages" || p === "\u5DF2\u5220\u9664" || p === "\u56DE\u6536\u7AD9" || p.endsWith("/trash") || n === "trash" || n === "\u5DF2\u5220\u9664" || n === "\u56DE\u6536\u7AD9";
285
+ });
286
+ if (byName) {
287
+ return byName.path;
288
+ }
289
+ return "Trash";
290
+ }
291
+ if (normalized === "sent" || normalized === "\u5DF2\u53D1\u9001" || normalized === "sent messages") {
292
+ const bySpecialUse = items.find(
293
+ (m) => m.specialUse?.toLowerCase() === "\\sent"
294
+ );
295
+ if (bySpecialUse) {
296
+ return bySpecialUse.path;
297
+ }
298
+ const byName = items.find((m) => {
299
+ const p = m.path.toLowerCase();
300
+ const n = (m.name || "").toLowerCase();
301
+ return p === "sent" || p === "sent messages" || p === "\u5DF2\u53D1\u9001" || p.endsWith("/sent mail") || p.endsWith("/sent") || n === "sent" || n === "\u5DF2\u53D1\u9001";
302
+ });
303
+ if (byName) {
304
+ return byName.path;
305
+ }
306
+ return "Sent";
307
+ }
308
+ const exactMatch = items.find(
309
+ (m) => m.path.toLowerCase() === normalized || m.name && m.name.toLowerCase() === normalized
310
+ );
311
+ if (exactMatch) {
312
+ return exactMatch.path;
313
+ }
314
+ return logicalAlias;
315
+ }
316
+ async function buildRawMimeMessage(options, from) {
317
+ const transport = nodemailer.createTransport({
318
+ streamTransport: true,
319
+ buffer: true
320
+ });
321
+ let safeAttachments = options.attachments;
322
+ if (options.attachments && options.attachments.length > 0) {
323
+ safeAttachments = options.attachments.map((att) => {
324
+ if (att.path) {
325
+ const validated = validateOutboundAttachmentPath(att.path);
326
+ return { ...att, path: validated };
327
+ }
328
+ return att;
329
+ });
330
+ }
331
+ const mailOptions = {
332
+ from,
333
+ to: Array.isArray(options.to) ? options.to.join(", ") : options.to,
334
+ cc: options.cc ? Array.isArray(options.cc) ? options.cc.join(", ") : options.cc : void 0,
335
+ bcc: options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(", ") : options.bcc : void 0,
336
+ subject: options.subject,
337
+ text: options.text,
338
+ html: options.html,
339
+ attachments: safeAttachments,
340
+ inReplyTo: options.inReplyTo,
341
+ references: options.references
342
+ };
343
+ const info = await transport.sendMail(mailOptions);
344
+ return info.message;
345
+ }
346
+ function extractPreviewSnippet(raw, maxLength = 150) {
347
+ if (!raw) return "";
348
+ let text = typeof raw === "string" ? raw : raw.toString("utf-8");
349
+ text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
350
+ text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
351
+ text = text.replace(/<[^>]+>/g, " ");
352
+ text = text.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'");
353
+ const cleaned = text.replace(/\s+/g, " ").trim();
354
+ if (cleaned.length <= maxLength) {
355
+ return cleaned;
356
+ }
357
+ return cleaned.slice(0, maxLength) + "...";
358
+ }
359
+ function parseBodyFromRawOrParts(bodyParts, source) {
360
+ if (bodyParts) {
361
+ const textPart = bodyParts.get("TEXT") || bodyParts.get("1");
362
+ if (textPart) {
363
+ return textPart.toString("utf-8");
364
+ }
365
+ }
366
+ if (source) {
367
+ const raw = source.toString("utf-8");
368
+ const headerSplit = raw.indexOf("\r\n\r\n");
369
+ if (headerSplit !== -1) {
370
+ return raw.slice(headerSplit + 4);
371
+ }
372
+ const lfSplit = raw.indexOf("\n\n");
373
+ if (lfSplit !== -1) {
374
+ return raw.slice(lfSplit + 2);
375
+ }
376
+ }
377
+ return "";
378
+ }
379
+
380
+ // src/services/content-purifier.ts
381
+ var MAX_EMAIL_BODY_BYTES = 30 * 1024;
382
+ var TRUNCATION_NOTICE = "\n\n[\u63D0\u793A: \u90AE\u4EF6\u6B63\u6587\u5DF2\u8D85\u8FC7 30KB \u5B89\u5168\u9608\u503C\uFF0C\u5DF2\u6267\u884C\u622A\u65AD\u4FDD\u62A4]";
383
+ function htmlToMarkdown(html) {
384
+ if (!html) return "";
385
+ let text = html;
386
+ text = text.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, "");
387
+ text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
388
+ text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
389
+ text = text.replace(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi, "\n# $1\n");
390
+ text = text.replace(/<h2\b[^>]*>([\s\S]*?)<\/h2>/gi, "\n## $1\n");
391
+ text = text.replace(/<h3\b[^>]*>([\s\S]*?)<\/h3>/gi, "\n### $1\n");
392
+ text = text.replace(/<h4\b[^>]*>([\s\S]*?)<\/h4>/gi, "\n#### $1\n");
393
+ text = text.replace(/<h5\b[^>]*>([\s\S]*?)<\/h5>/gi, "\n##### $1\n");
394
+ text = text.replace(/<h6\b[^>]*>([\s\S]*?)<\/h6>/gi, "\n###### $1\n");
395
+ text = text.replace(/<(b|strong)\b[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
396
+ text = text.replace(/<(i|em)\b[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
397
+ text = text.replace(/<a\b[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)");
398
+ text = text.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, "\n- $1");
399
+ text = text.replace(/<hr\b[^>]*\/?>/gi, "\n---\n");
400
+ text = text.replace(/<br\b[^>]*\/?>/gi, "\n");
401
+ text = text.replace(/<\/(p|div|tr)>/gi, "\n");
402
+ text = text.replace(/<[^>]+>/g, "");
403
+ text = text.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'");
404
+ const lines = text.split("\n").map((line) => line.trim());
405
+ const cleanLines = [];
406
+ let prevEmpty = false;
407
+ for (const line of lines) {
408
+ if (line.length === 0) {
409
+ if (!prevEmpty) {
410
+ cleanLines.push("");
411
+ prevEmpty = true;
412
+ }
413
+ } else {
414
+ cleanLines.push(line);
415
+ prevEmpty = false;
416
+ }
417
+ }
418
+ return cleanLines.join("\n").trim();
419
+ }
420
+ function purifyEmailBody(text, html) {
421
+ let rawContent = "";
422
+ if (html && html.trim().length > 0) {
423
+ rawContent = htmlToMarkdown(html);
424
+ } else if (text && text.trim().length > 0) {
425
+ rawContent = text.trim();
426
+ }
427
+ const byteLength = Buffer.byteLength(rawContent, "utf-8");
428
+ if (byteLength > MAX_EMAIL_BODY_BYTES) {
429
+ const buffer = Buffer.from(rawContent, "utf-8");
430
+ const sliced = buffer.subarray(0, MAX_EMAIL_BODY_BYTES);
431
+ const truncatedText = sliced.toString("utf-8");
432
+ return {
433
+ content: truncatedText + TRUNCATION_NOTICE,
434
+ truncated: true
435
+ };
436
+ }
437
+ return {
438
+ content: rawContent,
439
+ truncated: false
440
+ };
441
+ }
442
+
443
+ // src/services/imap.service.ts
444
+ var ImapService = class {
445
+ constructor(imapConfig) {
446
+ this.imapConfig = imapConfig;
447
+ }
448
+ imapConfig;
449
+ /**
450
+ * 创建新的 IMAP 客户端连接
451
+ *
452
+ * @returns 初始化的 ImapFlow 客户端实例
453
+ * @throws Error 当未配置 IMAP 主机或账号时
454
+ */
455
+ createClient() {
456
+ const host = this.imapConfig?.host || config.MCP_IMAP_HOST;
457
+ const port = this.imapConfig?.port ?? config.MCP_IMAP_PORT;
458
+ const secure = this.imapConfig?.secure ?? config.MCP_IMAP_SECURE;
459
+ const user = this.imapConfig?.user || config.MCP_IMAP_USER;
460
+ const pass = this.imapConfig?.pass || config.MCP_IMAP_PASS;
461
+ if (!host || !user || !pass) {
462
+ throw new Error(
463
+ "IMAP \u914D\u7F6E\u4E0D\u5B8C\u6574\uFF1A\u8BF7\u786E\u4FDD\u5DF2\u914D\u7F6E MCP_IMAP_HOST\u3001MCP_IMAP_USER \u548C MCP_IMAP_PASS \u73AF\u5883\u53D8\u91CF"
464
+ );
465
+ }
466
+ return new ImapFlow({
467
+ host,
468
+ port,
469
+ secure,
470
+ auth: {
471
+ user,
472
+ pass
473
+ },
474
+ logger: false
475
+ // 禁用内部控制台输出,保护 MCP Stdio 通信通道
476
+ });
477
+ }
478
+ /**
479
+ * 获取远程邮箱目录列表
480
+ *
481
+ * @returns 邮箱目录路径列表
482
+ */
483
+ async listMailboxes() {
484
+ const client = this.createClient();
485
+ await client.connect();
486
+ try {
487
+ const mailboxes = await client.list();
488
+ return mailboxes.map((item) => item.path);
489
+ } finally {
490
+ await client.logout().catch(() => {
491
+ });
492
+ }
493
+ }
494
+ /**
495
+ * 检索符合条件的邮件摘要列表
496
+ *
497
+ * @param filter 检索与过滤条件
498
+ * @returns 分页检索结果与条目摘要
499
+ */
500
+ async searchEmails(filter) {
501
+ const client = this.createClient();
502
+ await client.connect();
503
+ try {
504
+ const allMailboxes = await client.list();
505
+ const targetMailbox = resolveSpecialMailbox(filter.mailbox || "INBOX", allMailboxes);
506
+ const lock = await client.getMailboxLock(targetMailbox);
507
+ try {
508
+ const query = {};
509
+ if (filter.query) {
510
+ query.body = filter.query;
511
+ }
512
+ if (filter.unseenOnly) {
513
+ query.seen = false;
514
+ }
515
+ if (filter.flaggedOnly) {
516
+ query.flagged = true;
517
+ }
518
+ if (filter.from) {
519
+ query.from = filter.from;
520
+ }
521
+ if (filter.to) {
522
+ query.to = filter.to;
523
+ }
524
+ if (filter.subject) {
525
+ query.subject = filter.subject;
526
+ }
527
+ if (filter.since) {
528
+ query.since = new Date(filter.since);
529
+ }
530
+ if (filter.before) {
531
+ query.before = new Date(filter.before);
532
+ }
533
+ const searchCriteria = Object.keys(query).length > 0 ? query : { all: true };
534
+ const searchResult = await client.search(searchCriteria, { uid: true });
535
+ const uids = Array.isArray(searchResult) ? searchResult : [];
536
+ const total = uids.length;
537
+ const page = filter.page && filter.page > 0 ? filter.page : 1;
538
+ const limit = filter.limit && filter.limit > 0 ? filter.limit : 10;
539
+ if (total === 0) {
540
+ return {
541
+ items: [],
542
+ total: 0,
543
+ page,
544
+ limit,
545
+ hasMore: false
546
+ };
547
+ }
548
+ const sortedUids = [...uids].reverse();
549
+ let selectedUids = [];
550
+ let effectiveTotal = total;
551
+ let hasMore = false;
552
+ if (filter.hasAttachment === void 0) {
553
+ const startIndex = (page - 1) * limit;
554
+ selectedUids = sortedUids.slice(startIndex, startIndex + limit);
555
+ hasMore = startIndex + limit < total;
556
+ } else {
557
+ const maxScanLimit = Math.min(sortedUids.length, 100);
558
+ const scanUids = sortedUids.slice(0, maxScanLimit);
559
+ const matchedUids = [];
560
+ if (scanUids.length > 0) {
561
+ for await (const message of client.fetch(scanUids, {
562
+ uid: true,
563
+ bodyStructure: true
564
+ })) {
565
+ const hasAttachments = Boolean(
566
+ message.bodyStructure?.childNodes && message.bodyStructure.childNodes.length > 1
567
+ );
568
+ if (hasAttachments === filter.hasAttachment) {
569
+ matchedUids.push(message.uid);
570
+ }
571
+ }
572
+ }
573
+ matchedUids.sort((a, b) => b - a);
574
+ effectiveTotal = matchedUids.length;
575
+ const startIndex = (page - 1) * limit;
576
+ selectedUids = matchedUids.slice(startIndex, startIndex + limit);
577
+ hasMore = startIndex + limit < matchedUids.length;
578
+ }
579
+ const items = [];
580
+ if (selectedUids.length > 0) {
581
+ for await (const message of client.fetch(selectedUids, {
582
+ envelope: true,
583
+ flags: true,
584
+ internalDate: true,
585
+ uid: true,
586
+ bodyStructure: true,
587
+ bodyParts: ["TEXT", "1"],
588
+ source: { maxLength: 2048 }
589
+ })) {
590
+ const hasAttachments = Boolean(
591
+ message.bodyStructure?.childNodes && message.bodyStructure.childNodes.length > 1
592
+ );
593
+ const fromAddr = message.envelope?.from?.[0] ? `${message.envelope.from[0].name || ""} <${message.envelope.from[0].address || ""}>`.trim() : "\u672A\u77E5\u53D1\u4EF6\u4EBA";
594
+ const rawBody = parseBodyFromRawOrParts(message.bodyParts, message.source);
595
+ const preview = extractPreviewSnippet(rawBody, 150);
596
+ items.push({
597
+ uid: message.uid,
598
+ seq: message.seq,
599
+ from: fromAddr,
600
+ subject: message.envelope?.subject || "(\u65E0\u4E3B\u9898)",
601
+ date: message.envelope?.date?.toISOString() || (/* @__PURE__ */ new Date()).toISOString(),
602
+ unseen: !message.flags?.has("\\Seen"),
603
+ hasAttachments,
604
+ preview
605
+ });
606
+ }
607
+ items.sort((a, b) => b.uid - a.uid);
608
+ }
609
+ return {
610
+ items,
611
+ total: effectiveTotal,
612
+ page,
613
+ limit,
614
+ hasMore
615
+ };
616
+ } finally {
617
+ lock.release();
618
+ }
619
+ } finally {
620
+ await client.logout().catch(() => {
621
+ });
622
+ }
623
+ }
624
+ /**
625
+ * 根据 UID 获取邮件正文及附件明细(包含 Markdown 提纯与附件元数据清单)
626
+ *
627
+ * @param uid 邮件唯一标识符
628
+ * @param mailbox 所处邮箱文件夹或别名,默认 INBOX
629
+ * @returns 邮件详情数据
630
+ */
631
+ async getEmailDetail(uid, mailbox = "INBOX") {
632
+ const client = this.createClient();
633
+ await client.connect();
634
+ try {
635
+ const mailboxes = await client.list();
636
+ const targetMailbox = resolveSpecialMailbox(mailbox, mailboxes);
637
+ const lock = await client.getMailboxLock(targetMailbox);
638
+ try {
639
+ const download = await client.download(String(uid), void 0, { uid: true });
640
+ if (!download || !download.content) {
641
+ throw new Error(`\u672A\u627E\u5230 UID \u4E3A ${uid} \u7684\u90AE\u4EF6\u5185\u5BB9`);
642
+ }
643
+ const parsed = await simpleParser(download.content);
644
+ const toList = [];
645
+ if (parsed.to) {
646
+ if (Array.isArray(parsed.to)) {
647
+ parsed.to.forEach((addr) => toList.push(addr.text));
648
+ } else {
649
+ toList.push(parsed.to.text);
650
+ }
651
+ }
652
+ const ccList = [];
653
+ if (parsed.cc) {
654
+ if (Array.isArray(parsed.cc)) {
655
+ parsed.cc.forEach((addr) => ccList.push(addr.text));
656
+ } else {
657
+ ccList.push(parsed.cc.text);
658
+ }
659
+ }
660
+ const attachments = (parsed.attachments || []).map((att, idx) => ({
661
+ id: String(idx),
662
+ filename: att.filename || `attachment-${idx}`,
663
+ contentType: att.contentType || "application/octet-stream",
664
+ size: att.size || (att.content ? att.content.length : 0)
665
+ }));
666
+ const fromAddress = parsed.from?.value?.[0]?.address || void 0;
667
+ let references;
668
+ if (parsed.references) {
669
+ references = Array.isArray(parsed.references) ? parsed.references : [parsed.references];
670
+ }
671
+ const purified = purifyEmailBody(
672
+ parsed.text,
673
+ typeof parsed.html === "string" ? parsed.html : void 0
674
+ );
675
+ return {
676
+ uid,
677
+ from: parsed.from?.text || "\u672A\u77E5\u53D1\u4EF6\u4EBA",
678
+ fromAddress,
679
+ to: toList,
680
+ cc: ccList.length > 0 ? ccList : void 0,
681
+ subject: parsed.subject || "(\u65E0\u4E3B\u9898)",
682
+ date: parsed.date ? parsed.date.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
683
+ text: parsed.text,
684
+ html: typeof parsed.html === "string" ? parsed.html : void 0,
685
+ bodyMarkdown: purified.content,
686
+ truncated: purified.truncated,
687
+ messageId: parsed.messageId,
688
+ inReplyTo: parsed.inReplyTo,
689
+ references,
690
+ attachments
691
+ };
692
+ } finally {
693
+ lock.release();
694
+ }
695
+ } finally {
696
+ await client.logout().catch(() => {
697
+ });
698
+ }
699
+ }
700
+ /**
701
+ * 从指定邮件中提取附件并下载保存至受管本地沙箱
702
+ *
703
+ * @param uid 邮件唯一标识符
704
+ * @param attachmentIdentifier 附件唯一标识(ID 序号或文件名)
705
+ * @param mailbox 所在邮箱文件夹,默认 INBOX
706
+ * @returns 下载落盘结果
707
+ * @throws Error 当未找到邮件或目标附件时
708
+ */
709
+ async downloadAttachment(uid, attachmentIdentifier, mailbox = "INBOX") {
710
+ const client = this.createClient();
711
+ await client.connect();
712
+ try {
713
+ const mailboxes = await client.list();
714
+ const targetMailbox = resolveSpecialMailbox(mailbox, mailboxes);
715
+ const lock = await client.getMailboxLock(targetMailbox);
716
+ try {
717
+ const download = await client.download(String(uid), void 0, { uid: true });
718
+ if (!download || !download.content) {
719
+ throw new Error(`\u672A\u627E\u5230 UID \u4E3A ${uid} \u7684\u90AE\u4EF6\u5185\u5BB9`);
720
+ }
721
+ const parsed = await simpleParser(download.content);
722
+ const attachments = parsed.attachments || [];
723
+ const targetIndex = attachments.findIndex(
724
+ (att, idx) => String(idx) === String(attachmentIdentifier) || att.filename === attachmentIdentifier
725
+ );
726
+ if (targetIndex === -1) {
727
+ throw new Error(
728
+ `\u672A\u5728\u90AE\u4EF6 (UID: ${uid}) \u4E2D\u627E\u5230\u6807\u8BC6\u4E3A "${attachmentIdentifier}" \u7684\u9644\u4EF6`
729
+ );
730
+ }
731
+ const targetAttachment = attachments[targetIndex];
732
+ if (!targetAttachment.content) {
733
+ throw new Error(
734
+ `\u9644\u4EF6 "${targetAttachment.filename || attachmentIdentifier}" \u5185\u5BB9\u4E3A\u7A7A\u6216\u65E0\u6CD5\u63D0\u53D6`
735
+ );
736
+ }
737
+ const rawFileName = targetAttachment.filename || `attachment-${targetIndex}`;
738
+ const saved = await saveAttachmentToSandbox(rawFileName, targetAttachment.content, void 0, uid);
739
+ return {
740
+ attachmentId: String(targetIndex),
741
+ filename: saved.filename,
742
+ contentType: targetAttachment.contentType || "application/octet-stream",
743
+ size: saved.size,
744
+ filePath: saved.filePath,
745
+ fileUrl: saved.fileUrl
746
+ };
747
+ } finally {
748
+ lock.release();
749
+ }
750
+ } finally {
751
+ await client.logout().catch(() => {
752
+ });
753
+ }
754
+ }
755
+ /**
756
+ * 验证 IMAP 服务的网络握手与身份认证
757
+ *
758
+ * @returns 是否连接成功
759
+ */
760
+ async verifyConnection() {
761
+ const client = this.createClient();
762
+ try {
763
+ await client.connect();
764
+ return true;
765
+ } finally {
766
+ await client.logout().catch(() => {
767
+ });
768
+ }
769
+ }
770
+ /**
771
+ * 将邮件载荷组装为 RFC 822 MIME 数据并存入草稿箱
772
+ *
773
+ * @param options 邮件选项载荷
774
+ * @param from 发件人地址
775
+ * @returns 存储结果,包含物理草稿箱路径
776
+ */
777
+ async createDraft(options, from) {
778
+ const client = this.createClient();
779
+ await client.connect();
780
+ try {
781
+ const mailboxes = await client.list();
782
+ const draftsPath = resolveSpecialMailbox("drafts", mailboxes);
783
+ const rawMime = await buildRawMimeMessage(options, from);
784
+ await client.append(draftsPath, rawMime, ["\\Draft", "\\Seen"]);
785
+ return {
786
+ success: true,
787
+ mailbox: draftsPath
788
+ };
789
+ } finally {
790
+ await client.logout().catch(() => {
791
+ });
792
+ }
793
+ }
794
+ /**
795
+ * 极速获取邮箱文件夹状态与未读统计看板
796
+ *
797
+ * @param mailboxAlias 可选指定文件夹别名或物理路径,缺省时查询全部可用文件夹
798
+ * @returns 邮箱状态看板概览报告
799
+ */
800
+ async getMailboxStatus(mailboxAlias) {
801
+ const client = this.createClient();
802
+ await client.connect();
803
+ try {
804
+ const allMailboxes = await client.list();
805
+ if (mailboxAlias && mailboxAlias.toUpperCase() !== "ALL") {
806
+ const resolvedPath = resolveSpecialMailbox(mailboxAlias, allMailboxes);
807
+ const status = await client.status(resolvedPath, {
808
+ messages: true,
809
+ unseen: true,
810
+ recent: true
811
+ });
812
+ const item = {
813
+ mailbox: resolvedPath,
814
+ total: status.messages || 0,
815
+ unseen: status.unseen || 0,
816
+ recent: status.recent || 0
817
+ };
818
+ return {
819
+ mailboxes: [item],
820
+ totalUnseen: item.unseen,
821
+ totalMessages: item.total
822
+ };
823
+ }
824
+ const statusPromises = allMailboxes.map(async (box) => {
825
+ try {
826
+ const status = await client.status(box.path, {
827
+ messages: true,
828
+ unseen: true,
829
+ recent: true
830
+ });
831
+ return {
832
+ mailbox: box.path,
833
+ total: status.messages || 0,
834
+ unseen: status.unseen || 0,
835
+ recent: status.recent || 0
836
+ };
837
+ } catch {
838
+ return {
839
+ mailbox: box.path,
840
+ total: 0,
841
+ unseen: 0,
842
+ recent: 0
843
+ };
844
+ }
845
+ });
846
+ const items = await Promise.all(statusPromises);
847
+ let totalUnseen = 0;
848
+ let totalMessages = 0;
849
+ for (const item of items) {
850
+ totalUnseen += item.unseen;
851
+ totalMessages += item.total;
852
+ }
853
+ return {
854
+ mailboxes: items,
855
+ totalUnseen,
856
+ totalMessages
857
+ };
858
+ } finally {
859
+ await client.logout().catch(() => {
860
+ });
861
+ }
862
+ }
863
+ /**
864
+ * 将邮件 UID 或 UID 列表格式化为 IMAP SequenceSet 字符串并执行有效性校验
865
+ *
866
+ * @param uids 单个 UID 或 UID 数组
867
+ * @returns 规整后的 IMAP 查询范围字符串与计数
868
+ * @throws Error 当未提供任何有效 UID 时
869
+ */
870
+ formatUidRange(uids) {
871
+ const list = Array.isArray(uids) ? uids : [uids];
872
+ const valid = list.filter((u) => typeof u === "number" && !isNaN(u) && u > 0);
873
+ if (valid.length === 0) {
874
+ throw new Error("\u8BF7\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u6709\u6548\u7684\u90AE\u4EF6 UID \u6807\u8BC6\u7B26");
875
+ }
876
+ const sorted = Array.from(new Set(valid)).sort((a, b) => a - b);
877
+ return {
878
+ range: sorted.join(","),
879
+ count: sorted.length
880
+ };
881
+ }
882
+ /**
883
+ * 修改邮件的已读/未读状态标记 (\Seen)
884
+ *
885
+ * @param uids 单个邮件 UID 或 UID 列表
886
+ * @param read 是否标记为已读(true 为已读,false 为未读,默认 true)
887
+ * @param mailbox 所在邮箱文件夹别名或物理路径,默认 INBOX
888
+ * @returns 状态流转操作结果
889
+ */
890
+ async markEmailRead(uids, read = true, mailbox = "INBOX") {
891
+ const client = this.createClient();
892
+ await client.connect();
893
+ try {
894
+ const mailboxes = await client.list();
895
+ const resolvedMailbox = resolveSpecialMailbox(mailbox, mailboxes);
896
+ const lock = await client.getMailboxLock(resolvedMailbox);
897
+ try {
898
+ const { range, count } = this.formatUidRange(uids);
899
+ if (read) {
900
+ await client.messageFlagsAdd(range, ["\\Seen"], { uid: true });
901
+ } else {
902
+ await client.messageFlagsRemove(range, ["\\Seen"], { uid: true });
903
+ }
904
+ return {
905
+ success: true,
906
+ count,
907
+ read
908
+ };
909
+ } finally {
910
+ lock.release();
911
+ }
912
+ } finally {
913
+ await client.logout().catch(() => {
914
+ });
915
+ }
916
+ }
917
+ /**
918
+ * 修改邮件的星标/置顶标记 (\Flagged)
919
+ *
920
+ * @param uids 单个邮件 UID 或 UID 列表
921
+ * @param flagged 是否设置星标(true 为星标,false 为取消星标,默认 true)
922
+ * @param mailbox 所在邮箱文件夹别名或物理路径,默认 INBOX
923
+ * @returns 星标设置操作结果
924
+ */
925
+ async flagEmail(uids, flagged = true, mailbox = "INBOX") {
926
+ const client = this.createClient();
927
+ await client.connect();
928
+ try {
929
+ const mailboxes = await client.list();
930
+ const resolvedMailbox = resolveSpecialMailbox(mailbox, mailboxes);
931
+ const lock = await client.getMailboxLock(resolvedMailbox);
932
+ try {
933
+ const { range, count } = this.formatUidRange(uids);
934
+ if (flagged) {
935
+ await client.messageFlagsAdd(range, ["\\Flagged"], { uid: true });
936
+ } else {
937
+ await client.messageFlagsRemove(range, ["\\Flagged"], { uid: true });
938
+ }
939
+ return {
940
+ success: true,
941
+ count,
942
+ flagged
943
+ };
944
+ } finally {
945
+ lock.release();
946
+ }
947
+ } finally {
948
+ await client.logout().catch(() => {
949
+ });
950
+ }
951
+ }
952
+ /**
953
+ * 将邮件跨文件夹移动或转移至回收站进行安全软删除(彻底杜绝物理硬删除 EXPUNGE)
954
+ *
955
+ * @param uids 单个邮件 UID 或 UID 列表
956
+ * @param targetMailbox 目标邮箱文件夹别名或物理路径(如 trash, archive, INBOX)
957
+ * @param sourceMailbox 源邮箱文件夹别名或物理路径,默认 INBOX
958
+ * @returns 移动结果,包含实际物理源路径与目标路径
959
+ */
960
+ async moveEmail(uids, targetMailbox, sourceMailbox = "INBOX") {
961
+ const client = this.createClient();
962
+ await client.connect();
963
+ try {
964
+ const mailboxes = await client.list();
965
+ const resolvedSource = resolveSpecialMailbox(sourceMailbox, mailboxes);
966
+ const resolvedTarget = resolveSpecialMailbox(targetMailbox, mailboxes);
967
+ const lock = await client.getMailboxLock(resolvedSource);
968
+ try {
969
+ const { range, count } = this.formatUidRange(uids);
970
+ await client.messageMove(range, resolvedTarget, { uid: true });
971
+ return {
972
+ success: true,
973
+ count,
974
+ sourceMailbox: resolvedSource,
975
+ targetMailbox: resolvedTarget
976
+ };
977
+ } finally {
978
+ lock.release();
979
+ }
980
+ } finally {
981
+ await client.logout().catch(() => {
982
+ });
983
+ }
984
+ }
985
+ };
986
+ var imapService = new ImapService();
987
+
988
+ // src/services/smtp.service.ts
989
+ import nodemailer2 from "nodemailer";
990
+ var SmtpService = class {
991
+ constructor(smtpConfig) {
992
+ this.smtpConfig = smtpConfig;
993
+ }
994
+ smtpConfig;
995
+ transporter = null;
996
+ /**
997
+ * 初始化并获取 Nodemailer 传输器单例
998
+ *
999
+ * @returns 初始化的 Transporter 实例
1000
+ * @throws Error 当未配置 SMTP 主机或账号时
1001
+ */
1002
+ getTransporter() {
1003
+ if (this.transporter) {
1004
+ return this.transporter;
1005
+ }
1006
+ const host = this.smtpConfig?.host || config.MCP_SMTP_HOST;
1007
+ const port = this.smtpConfig?.port ?? config.MCP_SMTP_PORT;
1008
+ const secure = this.smtpConfig?.secure ?? config.MCP_SMTP_SECURE;
1009
+ const user = this.smtpConfig?.user || config.MCP_SMTP_USER;
1010
+ const pass = this.smtpConfig?.pass || config.MCP_SMTP_PASS;
1011
+ if (!host || !user || !pass) {
1012
+ throw new Error(
1013
+ "SMTP \u914D\u7F6E\u4E0D\u5B8C\u6574\uFF1A\u8BF7\u786E\u4FDD\u5DF2\u914D\u7F6E MCP_SMTP_HOST\u3001MCP_SMTP_USER \u548C MCP_SMTP_PASS \u73AF\u5883\u53D8\u91CF"
1014
+ );
1015
+ }
1016
+ this.transporter = nodemailer2.createTransport({
1017
+ host,
1018
+ port,
1019
+ secure,
1020
+ auth: {
1021
+ user,
1022
+ pass
1023
+ }
1024
+ });
1025
+ return this.transporter;
1026
+ }
1027
+ /**
1028
+ * 发送电子邮件
1029
+ *
1030
+ * @param options 发送邮件配置与内容选项
1031
+ * @returns 发送结果明细,包含 messageId
1032
+ * @throws Error 当发送失败时
1033
+ */
1034
+ async sendEmail(options) {
1035
+ const client = this.getTransporter();
1036
+ let safeAttachments = options.attachments;
1037
+ if (options.attachments && options.attachments.length > 0) {
1038
+ safeAttachments = options.attachments.map((att) => {
1039
+ if (att.path) {
1040
+ const validated = validateOutboundAttachmentPath(att.path);
1041
+ return { ...att, path: validated };
1042
+ }
1043
+ return att;
1044
+ });
1045
+ }
1046
+ const from = options.from || this.smtpConfig?.from || this.smtpConfig?.user || config.MCP_SMTP_FROM || config.MCP_SMTP_USER;
1047
+ const mailOptions = {
1048
+ from,
1049
+ to: Array.isArray(options.to) ? options.to.join(", ") : options.to,
1050
+ cc: options.cc ? Array.isArray(options.cc) ? options.cc.join(", ") : options.cc : void 0,
1051
+ bcc: options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(", ") : options.bcc : void 0,
1052
+ subject: options.subject,
1053
+ text: options.text,
1054
+ html: options.html,
1055
+ attachments: safeAttachments,
1056
+ inReplyTo: options.inReplyTo,
1057
+ references: options.references
1058
+ };
1059
+ const info = await client.sendMail(mailOptions);
1060
+ return {
1061
+ messageId: info.messageId,
1062
+ accepted: Array.isArray(info.accepted) ? info.accepted : []
1063
+ };
1064
+ }
1065
+ /**
1066
+ * 验证 SMTP 服务的网络与认证联通性
1067
+ *
1068
+ * @returns 是否连接成功
1069
+ */
1070
+ async verifyConnection() {
1071
+ const client = this.getTransporter();
1072
+ await client.verify();
1073
+ return true;
1074
+ }
1075
+ };
1076
+ var smtpService = new SmtpService();
1077
+
1078
+ // src/services/account-manager.ts
1079
+ var AccountManager = class {
1080
+ accounts = /* @__PURE__ */ new Map();
1081
+ defaultAccountId;
1082
+ smtpPool = /* @__PURE__ */ new Map();
1083
+ imapPool = /* @__PURE__ */ new Map();
1084
+ /**
1085
+ * 构造账户管理器实例
1086
+ *
1087
+ * @param accountList 预加载的账户画像列表,缺省为 config.accounts
1088
+ * @param defaultId 默认账户标识,缺省为 config.defaultAccountId
1089
+ */
1090
+ constructor(accountList = config.accounts, defaultId = config.defaultAccountId) {
1091
+ accountList.forEach((acc) => {
1092
+ this.accounts.set(acc.id, acc);
1093
+ });
1094
+ this.defaultAccountId = defaultId;
1095
+ }
1096
+ /**
1097
+ * 列出所有已配置的账户脱敏元数据(隐藏密码与授权码)
1098
+ *
1099
+ * @returns 脱敏账户画像列表
1100
+ */
1101
+ listAccounts() {
1102
+ const result = [];
1103
+ for (const [id, acc] of this.accounts.entries()) {
1104
+ result.push({
1105
+ id,
1106
+ name: acc.name,
1107
+ email: acc.email || acc.smtp?.user || acc.imap?.user,
1108
+ isDefault: id === this.defaultAccountId,
1109
+ smtp: acc.smtp ? {
1110
+ host: acc.smtp.host,
1111
+ port: acc.smtp.port,
1112
+ secure: acc.smtp.secure,
1113
+ user: acc.smtp.user,
1114
+ from: acc.smtp.from
1115
+ } : void 0,
1116
+ imap: acc.imap ? {
1117
+ host: acc.imap.host,
1118
+ port: acc.imap.port,
1119
+ secure: acc.imap.secure,
1120
+ user: acc.imap.user
1121
+ } : void 0
1122
+ });
1123
+ }
1124
+ return result;
1125
+ }
1126
+ /**
1127
+ * 获取指定的账户画像,缺省时自动回退至默认账户
1128
+ *
1129
+ * @param accountId 可选的账户标识
1130
+ * @returns 目标账户完整画像
1131
+ * @throws Error 当未找到指定或默认账户配置时
1132
+ */
1133
+ getAccount(accountId) {
1134
+ const targetId = accountId || this.defaultAccountId;
1135
+ const account = this.accounts.get(targetId);
1136
+ if (!account) {
1137
+ if (accountId) {
1138
+ throw new Error(
1139
+ `\u672A\u627E\u5230\u6307\u5B9A\u6807\u8BC6\u4E3A "${accountId}" \u7684\u90AE\u7BB1\u8D26\u6237\u914D\u7F6E\uFF0C\u8BF7\u901A\u8FC7 list_accounts \u67E5\u770B\u53EF\u7528\u753B\u50CF`
1140
+ );
1141
+ }
1142
+ throw new Error(
1143
+ `\u672A\u627E\u5230\u9ED8\u8BA4\u90AE\u7BB1\u8D26\u6237\u914D\u7F6E (defaultId: "${this.defaultAccountId}")\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E`
1144
+ );
1145
+ }
1146
+ return account;
1147
+ }
1148
+ /**
1149
+ * 根据账户标识路由并获取缓存的 SMTP 服务实例
1150
+ *
1151
+ * @param accountId 可选账户标识
1152
+ * @returns SMTP 服务实例
1153
+ */
1154
+ getSmtpService(accountId) {
1155
+ const account = this.getAccount(accountId);
1156
+ if (!account.smtp) {
1157
+ throw new Error(`\u8D26\u6237 "${account.id}" \u672A\u914D\u7F6E SMTP \u5916\u53D1\u670D\u52A1\u53C2\u6570`);
1158
+ }
1159
+ let service = this.smtpPool.get(account.id);
1160
+ if (!service) {
1161
+ service = new SmtpService(account.smtp);
1162
+ this.smtpPool.set(account.id, service);
1163
+ }
1164
+ return service;
1165
+ }
1166
+ /**
1167
+ * 根据账户标识路由并获取缓存的 IMAP 服务实例
1168
+ *
1169
+ * @param accountId 可选账户标识
1170
+ * @returns IMAP 服务实例
1171
+ */
1172
+ getImapService(accountId) {
1173
+ const account = this.getAccount(accountId);
1174
+ if (!account.imap) {
1175
+ throw new Error(`\u8D26\u6237 "${account.id}" \u672A\u914D\u7F6E IMAP \u67E5\u6536\u670D\u52A1\u53C2\u6570`);
1176
+ }
1177
+ let service = this.imapPool.get(account.id);
1178
+ if (!service) {
1179
+ service = new ImapService(account.imap);
1180
+ this.imapPool.set(account.id, service);
1181
+ }
1182
+ return service;
1183
+ }
1184
+ /**
1185
+ * 对指定或默认账户执行网络连通性与认证体检
1186
+ *
1187
+ * @param accountId 可选账户标识
1188
+ * @returns 综合体检诊断报告
1189
+ */
1190
+ async verifyConnection(accountId) {
1191
+ const account = this.getAccount(accountId);
1192
+ const smtpResult = {
1193
+ configured: Boolean(account.smtp),
1194
+ success: false,
1195
+ host: account.smtp?.host,
1196
+ port: account.smtp?.port
1197
+ };
1198
+ if (account.smtp) {
1199
+ try {
1200
+ const smtpClient = this.getSmtpService(account.id);
1201
+ await smtpClient.verifyConnection();
1202
+ smtpResult.success = true;
1203
+ } catch (err) {
1204
+ smtpResult.error = err instanceof Error ? err.message : String(err);
1205
+ }
1206
+ }
1207
+ const imapResult = {
1208
+ configured: Boolean(account.imap),
1209
+ success: false,
1210
+ host: account.imap?.host,
1211
+ port: account.imap?.port
1212
+ };
1213
+ if (account.imap) {
1214
+ try {
1215
+ const imapClient = this.getImapService(account.id);
1216
+ await imapClient.verifyConnection();
1217
+ imapResult.success = true;
1218
+ } catch (err) {
1219
+ imapResult.error = err instanceof Error ? err.message : String(err);
1220
+ }
1221
+ }
1222
+ const overallSuccess = (!smtpResult.configured || smtpResult.success) && (!imapResult.configured || imapResult.success) && (smtpResult.configured || imapResult.configured);
1223
+ return {
1224
+ account: account.id,
1225
+ smtp: smtpResult,
1226
+ imap: imapResult,
1227
+ overallSuccess
1228
+ };
1229
+ }
1230
+ };
1231
+ var accountManager = new AccountManager();
1232
+
1233
+ // src/services/reply.service.ts
1234
+ function extractEmailAddress(input) {
1235
+ const match = input.match(/<([^>]+)>/);
1236
+ if (match && match[1]) {
1237
+ return match[1].trim().toLowerCase();
1238
+ }
1239
+ return input.trim().toLowerCase();
1240
+ }
1241
+ function formatReplySubject(subject) {
1242
+ const cleanSubject = (subject || "").trim();
1243
+ if (/^re:\s*/i.test(cleanSubject)) {
1244
+ return cleanSubject;
1245
+ }
1246
+ return `Re: ${cleanSubject || "(\u65E0\u4E3B\u9898)"}`;
1247
+ }
1248
+ async function replyEmail(options, account, imapService2, smtpService2) {
1249
+ const mailbox = options.mailbox || "INBOX";
1250
+ const original = await imapService2.getEmailDetail(options.originalUid, mailbox);
1251
+ if (!original) {
1252
+ throw new Error(`\u672A\u627E\u5230 UID \u4E3A ${options.originalUid} \u7684\u539F\u59CB\u90AE\u4EF6`);
1253
+ }
1254
+ const to = original.fromAddress || extractEmailAddress(original.from);
1255
+ let cc;
1256
+ if (options.replyAll) {
1257
+ const selfAddresses = /* @__PURE__ */ new Set();
1258
+ if (account.email) {
1259
+ selfAddresses.add(account.email.toLowerCase());
1260
+ }
1261
+ if (account.smtp?.user) {
1262
+ selfAddresses.add(account.smtp.user.toLowerCase());
1263
+ }
1264
+ if (account.imap?.user) {
1265
+ selfAddresses.add(account.imap.user.toLowerCase());
1266
+ }
1267
+ if (account.smtp?.from) {
1268
+ selfAddresses.add(extractEmailAddress(account.smtp.from));
1269
+ }
1270
+ const allOriginalRecipients = [...original.to || [], ...original.cc || []];
1271
+ const ccSet = /* @__PURE__ */ new Set();
1272
+ for (const raw of allOriginalRecipients) {
1273
+ const email = extractEmailAddress(raw);
1274
+ if (email && email !== to && !selfAddresses.has(email)) {
1275
+ ccSet.add(email);
1276
+ }
1277
+ }
1278
+ if (ccSet.size > 0) {
1279
+ cc = Array.from(ccSet);
1280
+ }
1281
+ }
1282
+ const inReplyTo = original.messageId;
1283
+ const references = original.references ? [...original.references] : [];
1284
+ if (original.messageId && !references.includes(original.messageId)) {
1285
+ references.push(original.messageId);
1286
+ }
1287
+ const subject = formatReplySubject(original.subject);
1288
+ const sendResult = await smtpService2.sendEmail({
1289
+ to,
1290
+ cc,
1291
+ subject,
1292
+ text: options.text,
1293
+ html: options.html,
1294
+ attachments: options.attachments,
1295
+ inReplyTo,
1296
+ references: references.length > 0 ? references : void 0
1297
+ });
1298
+ return {
1299
+ messageId: sendResult.messageId,
1300
+ accepted: sendResult.accepted,
1301
+ inReplyTo,
1302
+ references: references.length > 0 ? references : void 0,
1303
+ to,
1304
+ cc,
1305
+ subject
1306
+ };
1307
+ }
1308
+
1309
+ // src/tools/index.ts
1310
+ function registerEmailTools(server) {
1311
+ server.tool(
1312
+ "list_accounts",
1313
+ "\u5217\u51FA\u6240\u6709\u5DF2\u914D\u7F6E\u7684\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u5217\u8868\uFF08\u51ED\u636E\u5B89\u5168\u8131\u654F\uFF0C\u9690\u85CF\u5BC6\u7801\uFF09\uFF0C\u5305\u542B\u8D26\u6237\u6807\u8BC6\u3001\u90AE\u7BB1\u5730\u5740\u3001\u670D\u52A1\u914D\u7F6E\u4E0E\u9ED8\u8BA4\u8D26\u6237\u6807\u8BB0",
1314
+ {},
1315
+ async () => {
1316
+ try {
1317
+ const accounts = accountManager.listAccounts();
1318
+ return {
1319
+ content: [
1320
+ {
1321
+ type: "text",
1322
+ text: JSON.stringify(accounts, null, 2)
1323
+ }
1324
+ ]
1325
+ };
1326
+ } catch (err) {
1327
+ const message = err instanceof Error ? err.message : String(err);
1328
+ return {
1329
+ isError: true,
1330
+ content: [{ type: "text", text: `\u83B7\u53D6\u8D26\u6237\u5217\u8868\u5931\u8D25: ${message}` }]
1331
+ };
1332
+ }
1333
+ }
1334
+ );
1335
+ server.tool(
1336
+ "verify_connection",
1337
+ "\u9A8C\u8BC1\u6307\u5B9A\u6216\u9ED8\u8BA4\u90AE\u7BB1\u8D26\u6237\u7684\u7F51\u7EDC\u8FDE\u901A\u6027\u4E0E\u8BA4\u8BC1\u51ED\u636E\uFF08\u5305\u62EC SMTP \u5916\u53D1\u548C IMAP \u67E5\u6536\u901A\u9053\uFF09\uFF0C\u8FD4\u56DE\u7F51\u7EDC\u63E1\u624B\u4E0E\u8EAB\u4EFD\u8BA4\u8BC1\u4F53\u68C0\u62A5\u544A",
1338
+ {
1339
+ account: z2.string().optional().describe("\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F53\u68C0\u9ED8\u8BA4\u8D26\u6237")
1340
+ },
1341
+ async (args) => {
1342
+ try {
1343
+ const report = await accountManager.verifyConnection(args.account);
1344
+ return {
1345
+ content: [
1346
+ {
1347
+ type: "text",
1348
+ text: JSON.stringify(report, null, 2)
1349
+ }
1350
+ ]
1351
+ };
1352
+ } catch (err) {
1353
+ const message = err instanceof Error ? err.message : String(err);
1354
+ return {
1355
+ isError: true,
1356
+ content: [{ type: "text", text: `\u9A8C\u8BC1\u8D26\u6237\u8FDE\u63A5\u5931\u8D25: ${message}` }]
1357
+ };
1358
+ }
1359
+ }
1360
+ );
1361
+ server.tool(
1362
+ "send_email",
1363
+ "\u53D1\u9001\u7535\u5B50\u90AE\u4EF6\uFF0C\u652F\u6301\u7EAF\u6587\u672C\u3001HTML \u6B63\u6587\u4EE5\u53CA\u6587\u4EF6\u9644\u4EF6",
1364
+ {
1365
+ account: z2.string().optional().describe("\u53D1\u4FE1\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1366
+ to: z2.union([z2.string(), z2.array(z2.string())]).describe("\u6536\u4EF6\u4EBA\u90AE\u7BB1\u5730\u5740\uFF0C\u5355\u4E2A\u6216\u6570\u7EC4"),
1367
+ subject: z2.string().describe("\u90AE\u4EF6\u4E3B\u9898"),
1368
+ text: z2.string().optional().describe("\u7EAF\u6587\u672C\u6B63\u6587\u5185\u5BB9"),
1369
+ html: z2.string().optional().describe("HTML \u683C\u5F0F\u6B63\u6587\u5185\u5BB9"),
1370
+ cc: z2.union([z2.string(), z2.array(z2.string())]).optional().describe("\u6284\u9001\u90AE\u7BB1\u5730\u5740"),
1371
+ bcc: z2.union([z2.string(), z2.array(z2.string())]).optional().describe("\u5BC6\u9001\u90AE\u7BB1\u5730\u5740"),
1372
+ attachments: z2.array(
1373
+ z2.object({
1374
+ filename: z2.string().describe("\u9644\u4EF6\u6587\u4EF6\u540D"),
1375
+ path: z2.string().optional().describe("\u672C\u5730\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84"),
1376
+ content: z2.string().optional().describe("\u9644\u4EF6\u6587\u672C\u5185\u5BB9\u6216 Base64 \u7F16\u7801\u5B57\u7B26\u4E32"),
1377
+ contentType: z2.string().optional().describe("MIME \u7C7B\u578B")
1378
+ })
1379
+ ).optional().describe("\u9644\u4EF6\u5217\u8868")
1380
+ },
1381
+ async (args) => {
1382
+ try {
1383
+ const smtpService2 = accountManager.getSmtpService(args.account);
1384
+ const result = await smtpService2.sendEmail({
1385
+ to: args.to,
1386
+ subject: args.subject,
1387
+ text: args.text,
1388
+ html: args.html,
1389
+ cc: args.cc,
1390
+ bcc: args.bcc,
1391
+ attachments: args.attachments
1392
+ });
1393
+ return {
1394
+ content: [
1395
+ {
1396
+ type: "text",
1397
+ text: `\u90AE\u4EF6\u53D1\u9001\u6210\u529F\uFF01MessageID: ${result.messageId}\uFF0C\u5DF2\u6295\u9012\u81F3: ${result.accepted.join(", ")}`
1398
+ }
1399
+ ]
1400
+ };
1401
+ } catch (err) {
1402
+ const message = err instanceof Error ? err.message : String(err);
1403
+ return {
1404
+ isError: true,
1405
+ content: [{ type: "text", text: `\u53D1\u9001\u90AE\u4EF6\u5931\u8D25: ${message}` }]
1406
+ };
1407
+ }
1408
+ }
1409
+ );
1410
+ server.tool(
1411
+ "reply_email",
1412
+ "\u56DE\u590D\u6307\u5B9A\u90AE\u4EF6\uFF0C\u81EA\u52A8\u4FDD\u6301\u539F\u751F\u4F1A\u8BDD\u6298\u53E0\u6811\uFF08\u81EA\u52A8\u6CE8\u5165 In-Reply-To\u3001References \u90AE\u4EF6\u5934\uFF0C\u6DFB\u52A0 Re: \u4E3B\u9898\u524D\u7F00\uFF0C\u652F\u6301\u4E00\u952E replyAll \u4FDD\u7559\u539F\u6284\u9001\u4EBA\uFF09",
1413
+ {
1414
+ account: z2.string().optional().describe("\u53D1\u9001\u56DE\u590D\u7684\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1415
+ originalUid: z2.number().describe("\u88AB\u56DE\u590D\u539F\u90AE\u4EF6\u7684\u552F\u4E00 UID \u6807\u8BC6\u7B26"),
1416
+ mailbox: z2.string().optional().default("INBOX").describe("\u539F\u90AE\u4EF6\u6240\u5728\u90AE\u7BB1\u6587\u4EF6\u5939\u540D\u79F0\uFF0C\u9ED8\u8BA4\u4E3A INBOX"),
1417
+ text: z2.string().optional().describe("\u56DE\u590D\u7684\u7EAF\u6587\u672C\u6B63\u6587\u5185\u5BB9"),
1418
+ html: z2.string().optional().describe("\u56DE\u590D\u7684 HTML \u683C\u5F0F\u6B63\u6587\u5185\u5BB9"),
1419
+ replyAll: z2.boolean().optional().default(false).describe("\u662F\u5426\u5168\u5458\u56DE\u590D\uFF08\u6284\u9001\u9664\u81EA\u8EAB\u5916\u7684\u539F\u90AE\u4EF6\u6536\u4EF6\u4EBA\u4E0E\u6284\u9001\u4EBA\uFF09"),
1420
+ attachments: z2.array(
1421
+ z2.object({
1422
+ filename: z2.string().describe("\u9644\u4EF6\u6587\u4EF6\u540D"),
1423
+ path: z2.string().optional().describe("\u672C\u5730\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84"),
1424
+ content: z2.string().optional().describe("\u9644\u4EF6\u6587\u672C\u5185\u5BB9\u6216 Base64 \u7F16\u7801\u5B57\u7B26\u4E32"),
1425
+ contentType: z2.string().optional().describe("MIME \u7C7B\u578B")
1426
+ })
1427
+ ).optional().describe("\u9644\u4EF6\u5217\u8868")
1428
+ },
1429
+ async (args) => {
1430
+ try {
1431
+ const account = accountManager.getAccount(args.account);
1432
+ const imapService2 = accountManager.getImapService(args.account);
1433
+ const smtpService2 = accountManager.getSmtpService(args.account);
1434
+ const result = await replyEmail(
1435
+ {
1436
+ originalUid: args.originalUid,
1437
+ mailbox: args.mailbox,
1438
+ text: args.text,
1439
+ html: args.html,
1440
+ replyAll: args.replyAll,
1441
+ attachments: args.attachments
1442
+ },
1443
+ account,
1444
+ imapService2,
1445
+ smtpService2
1446
+ );
1447
+ return {
1448
+ content: [
1449
+ {
1450
+ type: "text",
1451
+ text: `\u90AE\u4EF6\u56DE\u590D\u6210\u529F\uFF01MessageID: ${result.messageId}\uFF0C\u5DF2\u56DE\u590D\u81F3: ${result.to}${result.cc ? ` (\u6284\u9001: ${result.cc.join(", ")})` : ""}\uFF0C\u4E3B\u9898: ${result.subject}`
1452
+ }
1453
+ ]
1454
+ };
1455
+ } catch (err) {
1456
+ const message = err instanceof Error ? err.message : String(err);
1457
+ return {
1458
+ isError: true,
1459
+ content: [{ type: "text", text: `\u56DE\u590D\u90AE\u4EF6\u5931\u8D25: ${message}` }]
1460
+ };
1461
+ }
1462
+ }
1463
+ );
1464
+ server.tool(
1465
+ "create_draft",
1466
+ "\u5C06\u62DF\u5B9A\u90AE\u4EF6\u5B58\u5165\u90AE\u7BB1\u8349\u7A3F\u7BB1\uFF08Drafts\uFF09\uFF0C\u4F9B\u4EBA\u5DE5\u5728\u90AE\u7BB1\u5BA2\u6237\u7AEF\u4E2D\u6700\u7EC8\u5BA1\u9605\u786E\u8BA4\u540E\u53D1\u51FA",
1467
+ {
1468
+ account: z2.string().optional().describe("\u8349\u7A3F\u6240\u5C5E\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1469
+ to: z2.union([z2.string(), z2.array(z2.string())]).optional().describe("\u6536\u4EF6\u4EBA\u90AE\u7BB1\u5730\u5740\uFF0C\u5355\u4E2A\u6216\u6570\u7EC4"),
1470
+ subject: z2.string().optional().default("(\u65E0\u4E3B\u9898)").describe("\u90AE\u4EF6\u4E3B\u9898"),
1471
+ text: z2.string().optional().describe("\u7EAF\u6587\u672C\u6B63\u6587\u5185\u5BB9"),
1472
+ html: z2.string().optional().describe("HTML \u683C\u5F0F\u6B63\u6587\u5185\u5BB9"),
1473
+ cc: z2.union([z2.string(), z2.array(z2.string())]).optional().describe("\u6284\u9001\u90AE\u7BB1\u5730\u5740"),
1474
+ bcc: z2.union([z2.string(), z2.array(z2.string())]).optional().describe("\u5BC6\u9001\u90AE\u7BB1\u5730\u5740"),
1475
+ attachments: z2.array(
1476
+ z2.object({
1477
+ filename: z2.string().describe("\u9644\u4EF6\u6587\u4EF6\u540D"),
1478
+ path: z2.string().optional().describe("\u672C\u5730\u6587\u4EF6\u7EDD\u5BF9\u8DEF\u5F84"),
1479
+ content: z2.string().optional().describe("\u9644\u4EF6\u6587\u672C\u5185\u5BB9\u6216 Base64 \u7F16\u7801\u5B57\u7B26\u4E32"),
1480
+ contentType: z2.string().optional().describe("MIME \u7C7B\u578B")
1481
+ })
1482
+ ).optional().describe("\u9644\u4EF6\u5217\u8868")
1483
+ },
1484
+ async (args) => {
1485
+ try {
1486
+ const account = accountManager.getAccount(args.account);
1487
+ const imapService2 = accountManager.getImapService(args.account);
1488
+ const from = account.smtp?.from || account.smtp?.user || account.email || "user@example.com";
1489
+ const result = await imapService2.createDraft(
1490
+ {
1491
+ to: args.to || "",
1492
+ subject: args.subject || "(\u65E0\u4E3B\u9898)",
1493
+ text: args.text,
1494
+ html: args.html,
1495
+ cc: args.cc,
1496
+ bcc: args.bcc,
1497
+ attachments: args.attachments
1498
+ },
1499
+ from
1500
+ );
1501
+ return {
1502
+ content: [
1503
+ {
1504
+ type: "text",
1505
+ text: `\u8349\u7A3F\u5DF2\u6210\u529F\u4FDD\u5B58\u81F3 [${result.mailbox}]\uFF01\u4E3B\u9898: ${args.subject || "(\u65E0\u4E3B\u9898)"}`
1506
+ }
1507
+ ]
1508
+ };
1509
+ } catch (err) {
1510
+ const message = err instanceof Error ? err.message : String(err);
1511
+ return {
1512
+ isError: true,
1513
+ content: [{ type: "text", text: `\u4FDD\u5B58\u8349\u7A3F\u7BB1\u5931\u8D25: ${message}` }]
1514
+ };
1515
+ }
1516
+ }
1517
+ );
1518
+ server.tool(
1519
+ "get_mailbox_status",
1520
+ "\u6781\u901F\u83B7\u53D6\u6307\u5B9A\u6216\u5168\u90E8\u90AE\u7BB1\u6587\u4EF6\u5939\u7684\u72B6\u6001\u770B\u677F\uFF08\u5305\u542B\u603B\u90AE\u4EF6\u6570\u3001\u672A\u8BFB\u90AE\u4EF6\u6570\u4E0E\u6700\u8FD1\u90AE\u4EF6\u6570\uFF09\uFF0C\u6BEB\u79D2\u7EA7\u54CD\u5E94",
1521
+ {
1522
+ account: z2.string().optional().describe("\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1523
+ mailbox: z2.string().optional().describe(
1524
+ "\u76EE\u6807\u90AE\u7BB1\u6587\u4EF6\u5939\u522B\u540D\u6216\u7269\u7406\u8DEF\u5F84\uFF08\u5982 inbox, drafts, trash \u6216 INBOX\uFF09\uFF0C\u7F3A\u7701\u65F6\u7EDF\u8BA1\u5168\u90E8\u53EF\u7528\u6587\u4EF6\u5939"
1525
+ )
1526
+ },
1527
+ async (args) => {
1528
+ try {
1529
+ const imapService2 = accountManager.getImapService(args.account);
1530
+ const report = await imapService2.getMailboxStatus(args.mailbox);
1531
+ return {
1532
+ content: [
1533
+ {
1534
+ type: "text",
1535
+ text: JSON.stringify(report, null, 2)
1536
+ }
1537
+ ]
1538
+ };
1539
+ } catch (err) {
1540
+ const message = err instanceof Error ? err.message : String(err);
1541
+ return {
1542
+ isError: true,
1543
+ content: [{ type: "text", text: `\u83B7\u53D6\u90AE\u7BB1\u72B6\u6001\u5931\u8D25: ${message}` }]
1544
+ };
1545
+ }
1546
+ }
1547
+ );
1548
+ server.tool(
1549
+ "search_emails",
1550
+ "\u5728\u90AE\u7BB1\u4E2D\u6309\u591A\u7EF4\u6761\u4EF6\u68C0\u7D22\u90AE\u4EF6\uFF0C\u8FD4\u56DE\u5305\u542B 150 \u5B57\u7B26 Preview \u7EAF\u6587\u672C\u6458\u8981\u7684\u5206\u9875\u5217\u8868",
1551
+ {
1552
+ account: z2.string().optional().describe("\u6536\u4FE1\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1553
+ mailbox: z2.string().optional().default("INBOX").describe("\u90AE\u7BB1\u6587\u4EF6\u5939\u540D\u79F0\u6216\u522B\u540D\uFF08\u5982 inbox, drafts, trash\uFF09\uFF0C\u9ED8\u8BA4\u4E3A INBOX"),
1554
+ query: z2.string().optional().describe("\u5168\u6587\u68C0\u7D22\u5173\u952E\u5B57\uFF08\u5339\u914D\u53D1\u4EF6\u4EBA\u3001\u4E3B\u9898\u6216\u6B63\u6587\uFF09"),
1555
+ from: z2.string().optional().describe("\u6309\u53D1\u4EF6\u4EBA\u5730\u5740\u6216\u5173\u952E\u5B57\u7B5B\u9009"),
1556
+ to: z2.string().optional().describe("\u6309\u6536\u4EF6\u4EBA\u5730\u5740\u6216\u5173\u952E\u5B57\u7B5B\u9009"),
1557
+ subject: z2.string().optional().describe("\u6309\u90AE\u4EF6\u4E3B\u9898\u5173\u952E\u5B57\u7B5B\u9009"),
1558
+ unseenOnly: z2.boolean().optional().default(false).describe("\u662F\u5426\u4EC5\u67E5\u8BE2\u672A\u8BFB\u90AE\u4EF6"),
1559
+ flaggedOnly: z2.boolean().optional().default(false).describe("\u662F\u5426\u4EC5\u67E5\u8BE2\u661F\u6807/\u7F6E\u9876\u90AE\u4EF6"),
1560
+ hasAttachment: z2.boolean().optional().describe("\u662F\u5426\u5FC5\u987B\u5305\u542B\u9644\u4EF6"),
1561
+ since: z2.string().optional().describe("\u8D77\u59CB\u65E5\u671F\u7B5B\u9009\uFF0C\u683C\u5F0F\u5982 YYYY-MM-DD"),
1562
+ before: z2.string().optional().describe("\u622A\u6B62\u65E5\u671F\u7B5B\u9009\uFF0C\u683C\u5F0F\u5982 YYYY-MM-DD"),
1563
+ page: z2.number().optional().default(1).describe("\u5F53\u524D\u9875\u7801\uFF0C\u4ECE 1 \u5F00\u59CB\uFF0C\u9ED8\u8BA4 1"),
1564
+ limit: z2.number().optional().default(10).describe("\u6BCF\u9875\u62C9\u53D6\u6761\u6570\uFF0C\u9ED8\u8BA4 10")
1565
+ },
1566
+ async (args) => {
1567
+ try {
1568
+ const imapService2 = accountManager.getImapService(args.account);
1569
+ const result = await imapService2.searchEmails({
1570
+ mailbox: args.mailbox,
1571
+ query: args.query,
1572
+ from: args.from,
1573
+ to: args.to,
1574
+ subject: args.subject,
1575
+ unseenOnly: args.unseenOnly,
1576
+ flaggedOnly: args.flaggedOnly,
1577
+ hasAttachment: args.hasAttachment,
1578
+ since: args.since,
1579
+ before: args.before,
1580
+ page: args.page,
1581
+ limit: args.limit
1582
+ });
1583
+ return {
1584
+ content: [
1585
+ {
1586
+ type: "text",
1587
+ text: JSON.stringify(result, null, 2)
1588
+ }
1589
+ ]
1590
+ };
1591
+ } catch (err) {
1592
+ const message = err instanceof Error ? err.message : String(err);
1593
+ return {
1594
+ isError: true,
1595
+ content: [{ type: "text", text: `\u68C0\u7D22\u90AE\u4EF6\u5217\u8868\u5931\u8D25: ${message}` }]
1596
+ };
1597
+ }
1598
+ }
1599
+ );
1600
+ server.tool(
1601
+ "get_email_detail",
1602
+ "\u6839\u636E\u90AE\u4EF6\u5168\u5C40 UID \u83B7\u53D6\u5355\u5C01\u90AE\u4EF6\u7684\u5B8C\u6574\u6B63\u6587\u5185\u5BB9\u4E0E\u9644\u4EF6\u6E05\u5355",
1603
+ {
1604
+ account: z2.string().optional().describe("\u6536\u4FE1\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1605
+ uid: z2.number().describe("\u90AE\u4EF6\u7684\u552F\u4E00 UID \u6807\u8BC6\u7B26"),
1606
+ mailbox: z2.string().optional().default("INBOX").describe("\u90AE\u7BB1\u6587\u4EF6\u5939\u540D\u79F0\uFF0C\u9ED8\u8BA4\u4E3A INBOX")
1607
+ },
1608
+ async (args) => {
1609
+ try {
1610
+ const imapService2 = accountManager.getImapService(args.account);
1611
+ const detail = await imapService2.getEmailDetail(args.uid, args.mailbox);
1612
+ return {
1613
+ content: [
1614
+ {
1615
+ type: "text",
1616
+ text: JSON.stringify(detail, null, 2)
1617
+ }
1618
+ ]
1619
+ };
1620
+ } catch (err) {
1621
+ const message = err instanceof Error ? err.message : String(err);
1622
+ return {
1623
+ isError: true,
1624
+ content: [{ type: "text", text: `\u83B7\u53D6\u90AE\u4EF6\u8BE6\u60C5\u5931\u8D25: ${message}` }]
1625
+ };
1626
+ }
1627
+ }
1628
+ );
1629
+ server.tool(
1630
+ "list_mailboxes",
1631
+ "\u83B7\u53D6\u5F53\u524D\u90AE\u7BB1\u670D\u52A1\u7684\u6240\u6709\u53EF\u7528\u6587\u4EF6\u5939/\u76EE\u5F55\u5217\u8868\uFF08\u5982 INBOX, Sent, Trash \u7B49\uFF09",
1632
+ {
1633
+ account: z2.string().optional().describe("\u6536\u4FE1\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237")
1634
+ },
1635
+ async (args) => {
1636
+ try {
1637
+ const imapService2 = accountManager.getImapService(args.account);
1638
+ const mailboxes = await imapService2.listMailboxes();
1639
+ return {
1640
+ content: [
1641
+ {
1642
+ type: "text",
1643
+ text: JSON.stringify(mailboxes, null, 2)
1644
+ }
1645
+ ]
1646
+ };
1647
+ } catch (err) {
1648
+ const message = err instanceof Error ? err.message : String(err);
1649
+ return {
1650
+ isError: true,
1651
+ content: [{ type: "text", text: `\u83B7\u53D6\u90AE\u7BB1\u76EE\u5F55\u5931\u8D25: ${message}` }]
1652
+ };
1653
+ }
1654
+ }
1655
+ );
1656
+ server.tool(
1657
+ "download_attachment",
1658
+ "\u5C06\u6307\u5B9A\u90AE\u4EF6\u7684\u9644\u4EF6\u6309\u9700\u63D0\u53D6\u5E76\u5B89\u5168\u4FDD\u5B58\u81F3\u672C\u5730\u53D7\u7BA1\u6C99\u7BB1\u76EE\u5F55\uFF0C\u8FD4\u56DE\u7269\u7406\u7EDD\u5BF9\u8DEF\u5F84\u4E0E file:/// \u76F4\u8FBE URI",
1659
+ {
1660
+ account: z2.string().optional().describe("\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1661
+ uid: z2.number().describe("\u90AE\u4EF6\u7684\u552F\u4E00 UID \u6807\u8BC6\u7B26"),
1662
+ attachmentId: z2.string().describe("\u5F85\u4E0B\u8F7D\u9644\u4EF6\u7684\u6807\u8BC6 ID\uFF08\u5BF9\u5E94 get_email_detail \u8FD4\u56DE\u7684\u9644\u4EF6 id\uFF0C\u5982 '0'\uFF09\u6216\u9644\u4EF6\u6587\u4EF6\u540D"),
1663
+ mailbox: z2.string().optional().default("INBOX").describe("\u539F\u90AE\u4EF6\u6240\u5728\u90AE\u7BB1\u6587\u4EF6\u5939\u540D\u79F0\u6216\u522B\u540D\uFF0C\u9ED8\u8BA4\u4E3A INBOX")
1664
+ },
1665
+ async (args) => {
1666
+ try {
1667
+ const imapService2 = accountManager.getImapService(args.account);
1668
+ const result = await imapService2.downloadAttachment(
1669
+ args.uid,
1670
+ args.attachmentId,
1671
+ args.mailbox
1672
+ );
1673
+ return {
1674
+ content: [
1675
+ {
1676
+ type: "text",
1677
+ text: JSON.stringify(result, null, 2)
1678
+ }
1679
+ ]
1680
+ };
1681
+ } catch (err) {
1682
+ const message = err instanceof Error ? err.message : String(err);
1683
+ return {
1684
+ isError: true,
1685
+ content: [{ type: "text", text: `\u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25: ${message}` }]
1686
+ };
1687
+ }
1688
+ }
1689
+ );
1690
+ server.tool(
1691
+ "mark_email_read",
1692
+ "\u5BF9\u5355\u5C01\u6216\u6279\u91CF\u90AE\u4EF6\u8BBE\u7F6E\u5DF2\u8BFB\u6216\u672A\u8BFB\u6807\u8BB0\uFF08\\Seen\uFF09\uFF0C\u534F\u52A9\u5B8C\u6210\u5F85\u529E\u90AE\u4EF6\u5206\u8BCA\u6D41\u8F6C",
1693
+ {
1694
+ account: z2.string().optional().describe("\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1695
+ uids: z2.union([z2.number(), z2.array(z2.number())]).describe("\u5355\u5C01\u90AE\u4EF6 UID\uFF08\u6570\u5B57\uFF09\u6216\u6279\u91CF\u90AE\u4EF6 UID \u6570\u7EC4\uFF08\u5982 [101, 102]\uFF09"),
1696
+ read: z2.boolean().optional().default(true).describe("\u662F\u5426\u6807\u8BB0\u4E3A\u5DF2\u8BFB\uFF08true \u4E3A\u5DF2\u8BFB\uFF0Cfalse \u4E3A\u672A\u8BFB\uFF09\uFF0C\u9ED8\u8BA4\u4E3A true"),
1697
+ mailbox: z2.string().optional().default("INBOX").describe("\u76EE\u6807\u90AE\u7BB1\u6587\u4EF6\u5939\u522B\u540D\u6216\u7269\u7406\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A INBOX")
1698
+ },
1699
+ async (args) => {
1700
+ try {
1701
+ const imapService2 = accountManager.getImapService(args.account);
1702
+ const result = await imapService2.markEmailRead(
1703
+ args.uids,
1704
+ args.read,
1705
+ args.mailbox
1706
+ );
1707
+ return {
1708
+ content: [
1709
+ {
1710
+ type: "text",
1711
+ text: JSON.stringify(result, null, 2)
1712
+ }
1713
+ ]
1714
+ };
1715
+ } catch (err) {
1716
+ const message = err instanceof Error ? err.message : String(err);
1717
+ return {
1718
+ isError: true,
1719
+ content: [{ type: "text", text: `\u6807\u8BB0\u5DF2\u8BFB\u72B6\u6001\u5931\u8D25: ${message}` }]
1720
+ };
1721
+ }
1722
+ }
1723
+ );
1724
+ server.tool(
1725
+ "flag_email",
1726
+ "\u5BF9\u5355\u5C01\u6216\u6279\u91CF\u90AE\u4EF6\u8BBE\u7F6E\u6216\u53D6\u6D88\u91CD\u8981\u661F\u6807\u6807\u8BB0\uFF08\\Flagged\uFF09\uFF0C\u7528\u4E8E\u91CD\u70B9\u5173\u6CE8\u6216\u5F85\u529E\u6807\u8BB0",
1727
+ {
1728
+ account: z2.string().optional().describe("\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1729
+ uids: z2.union([z2.number(), z2.array(z2.number())]).describe("\u5355\u5C01\u90AE\u4EF6 UID\uFF08\u6570\u5B57\uFF09\u6216\u6279\u91CF\u90AE\u4EF6 UID \u6570\u7EC4\uFF08\u5982 [101, 102]\uFF09"),
1730
+ flagged: z2.boolean().optional().default(true).describe("\u662F\u5426\u8BBE\u7F6E\u661F\u6807\uFF08true \u4E3A\u661F\u6807\u7F6E\u9876\uFF0Cfalse \u4E3A\u53D6\u6D88\u661F\u6807\uFF09\uFF0C\u9ED8\u8BA4\u4E3A true"),
1731
+ mailbox: z2.string().optional().default("INBOX").describe("\u76EE\u6807\u90AE\u7BB1\u6587\u4EF6\u5939\u522B\u540D\u6216\u7269\u7406\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A INBOX")
1732
+ },
1733
+ async (args) => {
1734
+ try {
1735
+ const imapService2 = accountManager.getImapService(args.account);
1736
+ const result = await imapService2.flagEmail(
1737
+ args.uids,
1738
+ args.flagged,
1739
+ args.mailbox
1740
+ );
1741
+ return {
1742
+ content: [
1743
+ {
1744
+ type: "text",
1745
+ text: JSON.stringify(result, null, 2)
1746
+ }
1747
+ ]
1748
+ };
1749
+ } catch (err) {
1750
+ const message = err instanceof Error ? err.message : String(err);
1751
+ return {
1752
+ isError: true,
1753
+ content: [{ type: "text", text: `\u8BBE\u7F6E\u661F\u6807\u72B6\u6001\u5931\u8D25: ${message}` }]
1754
+ };
1755
+ }
1756
+ }
1757
+ );
1758
+ server.tool(
1759
+ "move_email",
1760
+ "\u5C06\u5355\u5C01\u6216\u6279\u91CF\u90AE\u4EF6\u79FB\u52A8\u81F3\u76EE\u6807\u6587\u4EF6\u5939\uFF08\u5982\u5F52\u6863\u81F3 archive\uFF09\uFF1B\u5F7B\u5E95\u7981\u7528\u7269\u7406\u786C\u5220\u9664\uFF0C\u5220\u9664\u90AE\u4EF6\u8BF7\u6307\u5B9A targetMailbox \u4E3A trash \u8FDB\u884C\u5B89\u5168\u8F6F\u5220\u9664",
1761
+ {
1762
+ account: z2.string().optional().describe("\u90AE\u7BB1\u8D26\u6237\u753B\u50CF\u6807\u8BC6\uFF0C\u7F3A\u7701\u65F6\u4F7F\u7528\u9ED8\u8BA4\u8D26\u6237"),
1763
+ uids: z2.union([z2.number(), z2.array(z2.number())]).describe("\u5355\u5C01\u90AE\u4EF6 UID\uFF08\u6570\u5B57\uFF09\u6216\u6279\u91CF\u90AE\u4EF6 UID \u6570\u7EC4\uFF08\u5982 [101, 102]\uFF09"),
1764
+ targetMailbox: z2.string().describe("\u76EE\u6807\u90AE\u7BB1\u6587\u4EF6\u5939\u522B\u540D\u6216\u7269\u7406\u8DEF\u5F84\uFF08\u5982 trash \u8FDB\u884C\u8F6F\u5220\u9664\uFF0C\u6216 archive \u5F52\u6863\uFF09"),
1765
+ sourceMailbox: z2.string().optional().default("INBOX").describe("\u6E90\u90AE\u7BB1\u6587\u4EF6\u5939\u522B\u540D\u6216\u7269\u7406\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A INBOX")
1766
+ },
1767
+ async (args) => {
1768
+ try {
1769
+ const imapService2 = accountManager.getImapService(args.account);
1770
+ const result = await imapService2.moveEmail(
1771
+ args.uids,
1772
+ args.targetMailbox,
1773
+ args.sourceMailbox
1774
+ );
1775
+ return {
1776
+ content: [
1777
+ {
1778
+ type: "text",
1779
+ text: JSON.stringify(result, null, 2)
1780
+ }
1781
+ ]
1782
+ };
1783
+ } catch (err) {
1784
+ const message = err instanceof Error ? err.message : String(err);
1785
+ return {
1786
+ isError: true,
1787
+ content: [{ type: "text", text: `\u79FB\u52A8\u90AE\u4EF6\u5931\u8D25: ${message}` }]
1788
+ };
1789
+ }
1790
+ }
1791
+ );
1792
+ }
1793
+
1794
+ // src/index.ts
1795
+ async function main() {
1796
+ const server = new McpServer({
1797
+ name: "mcp-server-email",
1798
+ version: "1.0.1"
1799
+ });
1800
+ registerEmailTools(server);
1801
+ const args = process.argv.slice(2);
1802
+ let transportMode = config.MCP_TRANSPORT;
1803
+ let port = config.MCP_PORT;
1804
+ for (let i = 0; i < args.length; i++) {
1805
+ if (args[i] === "--transport" && args[i + 1]) {
1806
+ transportMode = args[i + 1] === "sse" ? "sse" : "stdio";
1807
+ i++;
1808
+ } else if (args[i] === "--port" && args[i + 1]) {
1809
+ port = parseInt(args[i + 1], 10) || 3e3;
1810
+ i++;
1811
+ }
1812
+ }
1813
+ if (transportMode === "sse") {
1814
+ const sessions = /* @__PURE__ */ new Map();
1815
+ const httpServer = http.createServer(async (req, res) => {
1816
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
1817
+ res.setHeader("Access-Control-Allow-Origin", "*");
1818
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1819
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1820
+ if (req.method === "OPTIONS") {
1821
+ res.writeHead(204).end();
1822
+ return;
1823
+ }
1824
+ if (url.pathname === "/sse") {
1825
+ const transport = new SSEServerTransport("/messages", res);
1826
+ sessions.set(transport.sessionId, transport);
1827
+ res.on("close", () => {
1828
+ sessions.delete(transport.sessionId);
1829
+ console.error(`[MCP Email Server] SSE \u4F1A\u8BDD\u5DF2\u65AD\u5F00: ${transport.sessionId}`);
1830
+ });
1831
+ await server.connect(transport);
1832
+ console.error(`[MCP Email Server] SSE \u4F20\u8F93\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: ${transport.sessionId}`);
1833
+ } else if (url.pathname === "/messages" && req.method === "POST") {
1834
+ const sessionId = url.searchParams.get("sessionId");
1835
+ const transport = sessionId ? sessions.get(sessionId) : sessions.values().next().value || null;
1836
+ if (transport) {
1837
+ await transport.handlePostMessage(req, res);
1838
+ } else {
1839
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" }).end("\u65E0\u6548\u6216\u672A\u627E\u5230\u5BF9\u5E94\u7684 SSE \u4F20\u8F93\u4F1A\u8BDD");
1840
+ }
1841
+ } else if (url.pathname === "/health") {
1842
+ res.writeHead(200, { "Content-Type": "application/json" });
1843
+ res.end(
1844
+ JSON.stringify({
1845
+ status: "ok",
1846
+ activeSessions: sessions.size,
1847
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1848
+ })
1849
+ );
1850
+ } else {
1851
+ res.writeHead(404).end("Not Found");
1852
+ }
1853
+ });
1854
+ httpServer.listen(port, () => {
1855
+ console.error(`[MCP Email Server] SSE \u6A21\u5F0F\u6B63\u5728\u76D1\u542C\u7AEF\u53E3: ${port}`);
1856
+ });
1857
+ } else {
1858
+ const transport = new StdioServerTransport();
1859
+ await server.connect(transport);
1860
+ console.error("[MCP Email Server] Stdio \u4F20\u8F93\u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u76D1\u542C\u6807\u51C6\u8F93\u5165\u8F93\u51FA");
1861
+ }
1862
+ }
1863
+ main().catch((err) => {
1864
+ console.error("[MCP Email Server \u5D29\u6E83]", err);
1865
+ process.exit(1);
1866
+ });
1867
+ //# sourceMappingURL=index.js.map