@gnidreve/classic-imap-smtp-mcp 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js ADDED
@@ -0,0 +1,2924 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin/main.ts
4
+ import { existsSync, mkdirSync, writeFileSync as writeFileSync5 } from "fs";
5
+ import { dirname } from "path";
6
+
7
+ // src/config/loader.ts
8
+ import { readFileSync, statSync } from "fs";
9
+ import { parse as parseToml } from "smol-toml";
10
+
11
+ // src/lib/errors.ts
12
+ var McpMailError = class extends Error {
13
+ constructor(code, message, details, imapResponse) {
14
+ super(message);
15
+ this.code = code;
16
+ this.details = details;
17
+ this.imapResponse = imapResponse;
18
+ this.name = new.target.name;
19
+ }
20
+ code;
21
+ details;
22
+ imapResponse;
23
+ toResult() {
24
+ return {
25
+ code: this.code,
26
+ message: this.message,
27
+ ...this.details ? { details: this.details } : {},
28
+ ...this.imapResponse ? { imap_response: this.imapResponse } : {}
29
+ };
30
+ }
31
+ };
32
+ var AuthError = class extends McpMailError {
33
+ constructor(message = "Authentication failed", details) {
34
+ super("AUTH_FAILED", message, details);
35
+ }
36
+ };
37
+ var MailboxNotFoundError = class extends McpMailError {
38
+ constructor(mailbox) {
39
+ super("MAILBOX_NOT_FOUND", `Mailbox not found: ${mailbox}`, { mailbox });
40
+ }
41
+ };
42
+ var UidNotFoundError = class extends McpMailError {
43
+ constructor(uid, mailbox) {
44
+ super("UID_NOT_FOUND", `UID ${uid} not found in ${mailbox}`, { uid, mailbox });
45
+ }
46
+ };
47
+ var AttachmentNotFoundError = class extends McpMailError {
48
+ constructor(ref) {
49
+ super("ATTACHMENT_NOT_FOUND", `Attachment not found: ${ref}`, { ref });
50
+ }
51
+ };
52
+ var AccountNotFoundError = class extends McpMailError {
53
+ constructor(account) {
54
+ super("ACCOUNT_NOT_FOUND", `Account not configured: ${account}`, { account });
55
+ }
56
+ };
57
+ var RateLimitError = class extends McpMailError {
58
+ constructor(message = "Rate limit exceeded") {
59
+ super("RATE_LIMITED", message);
60
+ }
61
+ };
62
+ var TlsError = class extends McpMailError {
63
+ constructor(message, details) {
64
+ super("TLS_ERROR", message, details);
65
+ }
66
+ };
67
+ var ConfigError = class extends McpMailError {
68
+ constructor(message, details) {
69
+ super("CONFIG_ERROR", message, details);
70
+ }
71
+ };
72
+ var PermissionError = class extends McpMailError {
73
+ constructor(message, details) {
74
+ super("PERMISSION_DENIED", message, details);
75
+ }
76
+ };
77
+ var ImapProtocolError = class extends McpMailError {
78
+ constructor(message, imapResponse) {
79
+ super("IMAP_PROTOCOL_ERROR", message, void 0, imapResponse);
80
+ }
81
+ };
82
+ var SmtpRelayError = class extends McpMailError {
83
+ constructor(message, details) {
84
+ super("SMTP_RELAY_ERROR", message, details);
85
+ }
86
+ };
87
+
88
+ // src/config/providers.ts
89
+ var PROVIDERS = {
90
+ "gmail.com": {
91
+ imap_host: "imap.gmail.com",
92
+ imap_port: 993,
93
+ imap_tls: "implicit",
94
+ smtp_host: "smtp.gmail.com",
95
+ smtp_port: 465,
96
+ smtp_tls: "implicit"
97
+ },
98
+ "outlook.com": {
99
+ imap_host: "outlook.office365.com",
100
+ imap_port: 993,
101
+ imap_tls: "implicit",
102
+ smtp_host: "smtp.office365.com",
103
+ smtp_port: 587,
104
+ smtp_tls: "starttls"
105
+ },
106
+ "hotmail.com": {
107
+ imap_host: "outlook.office365.com",
108
+ imap_port: 993,
109
+ imap_tls: "implicit",
110
+ smtp_host: "smtp.office365.com",
111
+ smtp_port: 587,
112
+ smtp_tls: "starttls"
113
+ },
114
+ "live.com": {
115
+ imap_host: "outlook.office365.com",
116
+ imap_port: 993,
117
+ imap_tls: "implicit",
118
+ smtp_host: "smtp.office365.com",
119
+ smtp_port: 587,
120
+ smtp_tls: "starttls"
121
+ },
122
+ "icloud.com": {
123
+ imap_host: "imap.mail.me.com",
124
+ imap_port: 993,
125
+ imap_tls: "implicit",
126
+ smtp_host: "smtp.mail.me.com",
127
+ smtp_port: 587,
128
+ smtp_tls: "starttls"
129
+ },
130
+ "me.com": {
131
+ imap_host: "imap.mail.me.com",
132
+ imap_port: 993,
133
+ imap_tls: "implicit",
134
+ smtp_host: "smtp.mail.me.com",
135
+ smtp_port: 587,
136
+ smtp_tls: "starttls"
137
+ },
138
+ "yahoo.com": {
139
+ imap_host: "imap.mail.yahoo.com",
140
+ imap_port: 993,
141
+ imap_tls: "implicit",
142
+ smtp_host: "smtp.mail.yahoo.com",
143
+ smtp_port: 465,
144
+ smtp_tls: "implicit"
145
+ },
146
+ "fastmail.com": {
147
+ imap_host: "imap.fastmail.com",
148
+ imap_port: 993,
149
+ imap_tls: "implicit",
150
+ smtp_host: "smtp.fastmail.com",
151
+ smtp_port: 465,
152
+ smtp_tls: "implicit"
153
+ },
154
+ "posteo.de": {
155
+ imap_host: "posteo.de",
156
+ imap_port: 993,
157
+ imap_tls: "implicit",
158
+ smtp_host: "posteo.de",
159
+ smtp_port: 465,
160
+ smtp_tls: "implicit"
161
+ },
162
+ "mailbox.org": {
163
+ imap_host: "imap.mailbox.org",
164
+ imap_port: 993,
165
+ imap_tls: "implicit",
166
+ smtp_host: "smtp.mailbox.org",
167
+ smtp_port: 465,
168
+ smtp_tls: "implicit"
169
+ },
170
+ "gmx.net": {
171
+ imap_host: "imap.gmx.net",
172
+ imap_port: 993,
173
+ imap_tls: "implicit",
174
+ smtp_host: "mail.gmx.net",
175
+ smtp_port: 465,
176
+ smtp_tls: "implicit"
177
+ },
178
+ "gmx.de": {
179
+ imap_host: "imap.gmx.net",
180
+ imap_port: 993,
181
+ imap_tls: "implicit",
182
+ smtp_host: "mail.gmx.net",
183
+ smtp_port: 465,
184
+ smtp_tls: "implicit"
185
+ },
186
+ "web.de": {
187
+ imap_host: "imap.web.de",
188
+ imap_port: 993,
189
+ imap_tls: "implicit",
190
+ smtp_host: "smtp.web.de",
191
+ smtp_port: 587,
192
+ smtp_tls: "starttls"
193
+ },
194
+ "proton.me": {
195
+ imap_host: "127.0.0.1",
196
+ imap_port: 1143,
197
+ imap_tls: "starttls",
198
+ smtp_host: "127.0.0.1",
199
+ smtp_port: 1025,
200
+ smtp_tls: "starttls"
201
+ }
202
+ };
203
+ function detectProvider(email) {
204
+ const domain = email.split("@")[1]?.toLowerCase();
205
+ return domain ? PROVIDERS[domain] : void 0;
206
+ }
207
+
208
+ // src/config/schema.ts
209
+ import { z } from "zod";
210
+ var tlsModeSchema = z.union([
211
+ z.literal("implicit"),
212
+ z.literal("starttls"),
213
+ z.literal("none")
214
+ ]);
215
+ var accountSchema = z.object({
216
+ name: z.string().min(1),
217
+ user: z.string().min(1),
218
+ pass: z.string().min(1),
219
+ from_name: z.string().optional(),
220
+ imap_host: z.string().optional(),
221
+ // optional bei Auto-Detect-Providern
222
+ imap_port: z.number().int().positive().default(993),
223
+ imap_tls: tlsModeSchema.default("implicit"),
224
+ smtp_host: z.string().optional(),
225
+ smtp_port: z.number().int().positive().default(465),
226
+ smtp_tls: tlsModeSchema.default("implicit"),
227
+ verify_tls: z.boolean().default(true)
228
+ });
229
+ var limitsSchema = z.object({
230
+ smtp_per_minute: z.number().int().positive().default(10),
231
+ imap_ops_per_second: z.number().int().positive().default(100)
232
+ });
233
+ var fileConfigSchema = z.object({
234
+ default_account: z.string().optional(),
235
+ limits: limitsSchema.default({ smtp_per_minute: 10, imap_ops_per_second: 100 }),
236
+ accounts: z.array(accountSchema).min(1)
237
+ });
238
+
239
+ // src/config/xdg.ts
240
+ import { homedir, platform } from "os";
241
+ import { join } from "path";
242
+ var APP = "classic-imap-smtp-mcp";
243
+ function defaultConfigPath() {
244
+ if (platform() === "win32") {
245
+ const appData = process.env.APPDATA ?? join(homedir(), "AppData", "Roaming");
246
+ return join(appData, APP, "config.toml");
247
+ }
248
+ const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
249
+ return join(xdg, APP, "config.toml");
250
+ }
251
+
252
+ // src/config/loader.ts
253
+ var ENV = "CLASSIC_IMAP_SMTP_";
254
+ function loadFromEnv() {
255
+ const user = process.env[`${ENV}USER`];
256
+ const pass = process.env[`${ENV}PASS`];
257
+ if (!user || !pass) return null;
258
+ const preset = detectProvider(user);
259
+ const raw = {
260
+ name: "default",
261
+ user,
262
+ pass,
263
+ from_name: process.env[`${ENV}FROM_NAME`],
264
+ imap_host: process.env[`${ENV}IMAP_HOST`] ?? preset?.imap_host,
265
+ imap_port: numEnv(`${ENV}IMAP_PORT`) ?? preset?.imap_port ?? 993,
266
+ imap_tls: process.env[`${ENV}IMAP_TLS`] ?? preset?.imap_tls ?? "implicit",
267
+ smtp_host: process.env[`${ENV}SMTP_HOST`] ?? preset?.smtp_host,
268
+ smtp_port: numEnv(`${ENV}SMTP_PORT`) ?? preset?.smtp_port ?? 465,
269
+ smtp_tls: process.env[`${ENV}SMTP_TLS`] ?? preset?.smtp_tls ?? "implicit",
270
+ verify_tls: process.env[`${ENV}VERIFY_TLS`] !== "false"
271
+ };
272
+ const account = accountSchema.parse(normalizeTls(raw));
273
+ return { mode: "env", defaultAccount: "default", accounts: /* @__PURE__ */ new Map([["default", account]]) };
274
+ }
275
+ function loadFromFile(path) {
276
+ let text;
277
+ try {
278
+ checkPermissions(path);
279
+ text = readFileSync(path, "utf8");
280
+ } catch (err) {
281
+ if (err instanceof PermissionError) throw err;
282
+ throw new ConfigError(`Cannot read config file: ${path}`, { cause: String(err) });
283
+ }
284
+ let parsed;
285
+ try {
286
+ parsed = fileConfigSchema.parse(parseToml(text));
287
+ } catch (err) {
288
+ throw new ConfigError("Invalid config file", { cause: String(err) });
289
+ }
290
+ const accounts = /* @__PURE__ */ new Map();
291
+ for (const acc of parsed.accounts) {
292
+ const preset = detectProvider(acc.user);
293
+ accounts.set(acc.name, {
294
+ ...acc,
295
+ imap_host: acc.imap_host ?? preset?.imap_host,
296
+ smtp_host: acc.smtp_host ?? preset?.smtp_host
297
+ });
298
+ }
299
+ const defaultAccount = parsed.default_account ?? parsed.accounts[0].name;
300
+ return { mode: "config-file", defaultAccount, accounts, configPath: path };
301
+ }
302
+ function loadConfig(explicitPath) {
303
+ const fromEnv = loadFromEnv();
304
+ if (fromEnv) return fromEnv;
305
+ const path = explicitPath ?? defaultConfigPath();
306
+ return loadFromFile(path);
307
+ }
308
+ function checkPermissions(path) {
309
+ if (process.platform === "win32") return;
310
+ const mode = statSync(path).mode & 511;
311
+ if (mode & 63) {
312
+ throw new PermissionError(
313
+ `Config file ${path} is too permissive (mode ${mode.toString(8)}); expected 0600`,
314
+ { path, mode: mode.toString(8) }
315
+ );
316
+ }
317
+ }
318
+ function numEnv(key) {
319
+ const v = process.env[key];
320
+ return v ? Number.parseInt(v, 10) : void 0;
321
+ }
322
+ function normalizeTls(raw) {
323
+ const map = (v) => v === "true" || v === true ? "implicit" : v === "false" || v === false ? "none" : v;
324
+ return { ...raw, imap_tls: map(raw.imap_tls), smtp_tls: map(raw.smtp_tls) };
325
+ }
326
+
327
+ // src/connections/imap-pool.ts
328
+ import { ImapFlow } from "imapflow";
329
+ var MAX_RETRIES = 5;
330
+ var BASE_DELAY_MS = 1e3;
331
+ var MAX_DELAY_MS = 6e4;
332
+ var IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
333
+ var ImapPool = class {
334
+ constructor(config, logger) {
335
+ this.config = config;
336
+ this.logger = logger;
337
+ }
338
+ config;
339
+ logger;
340
+ connections = /* @__PURE__ */ new Map();
341
+ async acquire(account) {
342
+ const existing = this.connections.get(account);
343
+ if (existing?.client.usable) {
344
+ existing.lastUsed = Date.now();
345
+ return existing.client;
346
+ }
347
+ if (existing) {
348
+ try {
349
+ await existing.client.logout();
350
+ } catch {
351
+ }
352
+ this.connections.delete(account);
353
+ }
354
+ const accConfig = this.config.accounts.get(account);
355
+ if (!accConfig) {
356
+ throw new ImapProtocolError(`Account ${account} not configured`);
357
+ }
358
+ const client = await this.connectWithRetry(accConfig);
359
+ this.connections.set(account, { client, lastUsed: Date.now() });
360
+ return client;
361
+ }
362
+ async connectWithRetry(acc, attempt = 0) {
363
+ try {
364
+ const opts = {
365
+ // biome-ignore lint/style/noNonNullAssertion: config validated at load
366
+ host: acc.imap_host,
367
+ port: acc.imap_port,
368
+ auth: {
369
+ user: acc.user,
370
+ pass: acc.pass
371
+ },
372
+ logger: false,
373
+ secure: acc.imap_tls !== "starttls" && acc.imap_tls !== "none"
374
+ };
375
+ if (acc.imap_tls === "none") {
376
+ opts.disableAutoIdle = true;
377
+ }
378
+ if (!acc.verify_tls) {
379
+ opts.tls = { rejectUnauthorized: false };
380
+ }
381
+ const client = new ImapFlow(opts);
382
+ await client.connect();
383
+ this.logger.info({ account: acc.name }, "IMAP connected");
384
+ return client;
385
+ } catch (err) {
386
+ const error = err;
387
+ if (error.message?.includes("TLS") || error.message?.includes("certificate")) {
388
+ throw new TlsError(`IMAP TLS error: ${error.message}`);
389
+ }
390
+ if (error.message?.includes("authentication") || error.message?.includes("login") || error.message?.includes("Auth")) {
391
+ throw new AuthError(`IMAP authentication failed: ${error.message}`);
392
+ }
393
+ if (attempt < MAX_RETRIES) {
394
+ const delay = Math.min(BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS);
395
+ this.logger.warn({ account: acc.name, attempt: attempt + 1, delay }, "IMAP reconnect");
396
+ await sleep(delay);
397
+ return this.connectWithRetry(acc, attempt + 1);
398
+ }
399
+ throw new ImapProtocolError(`Failed to connect to IMAP: ${error.message}`);
400
+ }
401
+ }
402
+ async pruneIdle() {
403
+ const now = Date.now();
404
+ for (const [account, entry] of this.connections) {
405
+ if (now - entry.lastUsed > IDLE_TIMEOUT_MS) {
406
+ this.logger.info({ account }, "Closing idle IMAP connection");
407
+ try {
408
+ await entry.client.logout();
409
+ } catch {
410
+ }
411
+ this.connections.delete(account);
412
+ }
413
+ }
414
+ }
415
+ async closeAll() {
416
+ for (const [account, entry] of this.connections) {
417
+ try {
418
+ if (entry.client.usable) await entry.client.logout();
419
+ } catch (err) {
420
+ this.logger.warn({ account, err }, "Error closing IMAP connection");
421
+ }
422
+ }
423
+ this.connections.clear();
424
+ }
425
+ };
426
+ function sleep(ms) {
427
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
428
+ }
429
+
430
+ // src/connections/smtp-pool.ts
431
+ import nodemailer from "nodemailer";
432
+ var RATE_LIMIT_WINDOW_MS = 6e4;
433
+ var DEFAULT_RATE_LIMIT = 10;
434
+ var SmtpPool = class {
435
+ constructor(config, logger) {
436
+ this.config = config;
437
+ this.logger = logger;
438
+ }
439
+ config;
440
+ logger;
441
+ transports = /* @__PURE__ */ new Map();
442
+ rateBuckets = /* @__PURE__ */ new Map();
443
+ // Liefert einen nodemailer-Transporter für den Account.
444
+ async acquire(account) {
445
+ this.checkRateLimit(account);
446
+ const existing = this.transports.get(account);
447
+ if (existing) return existing;
448
+ const accConfig = this.config.accounts.get(account);
449
+ if (!accConfig) {
450
+ throw new SmtpRelayError(`Account ${account} not configured`);
451
+ }
452
+ const transport = await this.createTransport(accConfig);
453
+ this.transports.set(account, transport);
454
+ return transport;
455
+ }
456
+ checkRateLimit(account) {
457
+ const limit = DEFAULT_RATE_LIMIT;
458
+ const bucket = this.rateBuckets.get(account) ?? { tokens: limit, lastRefill: Date.now() };
459
+ const now = Date.now();
460
+ const elapsed = now - bucket.lastRefill;
461
+ if (elapsed >= RATE_LIMIT_WINDOW_MS) {
462
+ bucket.tokens = limit;
463
+ bucket.lastRefill = now;
464
+ }
465
+ if (bucket.tokens <= 0) {
466
+ throw new RateLimitError(`SMTP rate limit (${limit}/minute) reached for account ${account}`);
467
+ }
468
+ bucket.tokens--;
469
+ this.rateBuckets.set(account, bucket);
470
+ }
471
+ async createTransport(acc) {
472
+ try {
473
+ const tlsOpts = {};
474
+ if (!acc.verify_tls) {
475
+ tlsOpts.rejectUnauthorized = false;
476
+ }
477
+ const transporter = nodemailer.createTransport({
478
+ // biome-ignore lint/style/noNonNullAssertion: config validated at load
479
+ host: acc.smtp_host,
480
+ port: acc.smtp_port,
481
+ secure: acc.smtp_tls === "implicit",
482
+ auth: {
483
+ user: acc.user,
484
+ pass: acc.pass
485
+ },
486
+ tls: tlsOpts,
487
+ // STARTTLS: wird von nodemailer automatisch bei secure=false und Port 587/25 verwendet
488
+ pool: true,
489
+ maxConnections: 1,
490
+ maxMessages: 100,
491
+ logger: false
492
+ });
493
+ this.logger.info({ account: acc.name }, "SMTP transport created");
494
+ return transporter;
495
+ } catch (err) {
496
+ const error = err;
497
+ if (error.message?.includes("auth") || error.message?.includes("login")) {
498
+ throw new AuthError(`SMTP authentication failed: ${error.message}`);
499
+ }
500
+ throw new SmtpRelayError(`Failed to create SMTP transport: ${error.message}`);
501
+ }
502
+ }
503
+ async closeAll() {
504
+ for (const [account, transport] of this.transports) {
505
+ try {
506
+ transport.close();
507
+ } catch (err) {
508
+ this.logger.warn({ account, err }, "Error closing SMTP transport");
509
+ }
510
+ }
511
+ this.transports.clear();
512
+ this.rateBuckets.clear();
513
+ }
514
+ };
515
+
516
+ // src/server/logging.ts
517
+ import { pino } from "pino";
518
+ var REDACT_KEYS = ["pass", "password", "token", "secret", "apikey", "apiKey"];
519
+ function createLogger(opts = {}) {
520
+ const level = opts.level ?? "info";
521
+ const format = opts.format ?? "json";
522
+ if (format === "pretty") {
523
+ return pino({
524
+ level,
525
+ redact: { paths: REDACT_KEYS.flatMap((k) => [k, `*.${k}`, `*.*.${k}`]), censor: "***" },
526
+ transport: { target: "pino-pretty", options: { destination: 2 } }
527
+ });
528
+ }
529
+ return pino({
530
+ level,
531
+ redact: { paths: REDACT_KEYS.flatMap((k) => [k, `*.${k}`, `*.*.${k}`]), censor: "***" }
532
+ });
533
+ }
534
+
535
+ // src/server/options.ts
536
+ function parseArgs(argv) {
537
+ const opts = {
538
+ safe: false,
539
+ readonly: false,
540
+ noImap: false,
541
+ noSmtp: false,
542
+ logLevel: "info",
543
+ logFormat: "json"
544
+ };
545
+ let subcommand;
546
+ let subcommandArg;
547
+ let help = false;
548
+ let version = false;
549
+ for (let i = 0; i < argv.length; i++) {
550
+ const arg = argv[i];
551
+ if (arg === "init" || arg === "test" || arg === "list-tools") {
552
+ subcommand = arg;
553
+ if (arg === "test" && argv[i + 1] && !argv[i + 1]?.startsWith("-")) {
554
+ subcommandArg = argv[++i];
555
+ }
556
+ } else if (arg === "--safe") opts.safe = true;
557
+ else if (arg === "--readonly") opts.readonly = true;
558
+ else if (arg === "--no-imap") opts.noImap = true;
559
+ else if (arg === "--no-smtp") opts.noSmtp = true;
560
+ else if (arg === "-h" || arg === "--help") help = true;
561
+ else if (arg === "-V" || arg === "--version") version = true;
562
+ else if (arg.startsWith("--allow-tools=")) opts.allowTools = csv(arg);
563
+ else if (arg.startsWith("--deny-tools=")) opts.denyTools = csv(arg);
564
+ else if (arg.startsWith("--account=")) opts.account = val(arg);
565
+ else if (arg.startsWith("--config=")) opts.configPath = val(arg);
566
+ else if (arg.startsWith("--log-level=")) opts.logLevel = val(arg);
567
+ else if (arg.startsWith("--log-format="))
568
+ opts.logFormat = val(arg) === "pretty" ? "pretty" : "json";
569
+ }
570
+ return { subcommand, subcommandArg, options: opts, help, version };
571
+ }
572
+ var val = (arg) => arg.slice(arg.indexOf("=") + 1);
573
+ var csv = (arg) => val(arg).split(",").map((s) => s.trim()).filter(Boolean);
574
+
575
+ // src/server/registry.ts
576
+ var READONLY_TOOLS = /* @__PURE__ */ new Set([
577
+ "imap_list_mailboxes",
578
+ "imap_status_mailbox",
579
+ "imap_list_messages",
580
+ "imap_get_message",
581
+ "imap_get_message_headers",
582
+ "imap_get_message_raw",
583
+ "imap_get_messages_bulk",
584
+ "imap_search",
585
+ "imap_download_attachment",
586
+ "imap_get_thread",
587
+ "imap_get_quota",
588
+ "imap_check_capabilities",
589
+ "smtp_verify_connection",
590
+ "account_list",
591
+ "meta_health",
592
+ "meta_server_info"
593
+ ]);
594
+ var DELETE_TOOLS = /* @__PURE__ */ new Set([
595
+ "imap_delete_message",
596
+ "imap_expunge",
597
+ "imap_delete_mailbox"
598
+ ]);
599
+ function matchesAny(name, patterns) {
600
+ return patterns.some((p) => p.endsWith("*") ? name.startsWith(p.slice(0, -1)) : name === p);
601
+ }
602
+ function passesFeatureFlags(t, o) {
603
+ if (o.noImap && t.category.startsWith("imap")) return false;
604
+ if (o.noSmtp && t.category === "smtp") return false;
605
+ if (o.readonly && !READONLY_TOOLS.has(t.name)) return false;
606
+ if (o.safe && DELETE_TOOLS.has(t.name)) return false;
607
+ return true;
608
+ }
609
+ function resolveActiveTools(all, o) {
610
+ const active = /* @__PURE__ */ new Set();
611
+ for (const t of all) if (passesFeatureFlags(t, o)) active.add(t.name);
612
+ if (o.allowTools?.length) {
613
+ for (const t of all) if (matchesAny(t.name, o.allowTools)) active.add(t.name);
614
+ }
615
+ if (o.denyTools?.length) {
616
+ for (const name of [...active]) if (matchesAny(name, o.denyTools)) active.delete(name);
617
+ }
618
+ return all.filter((t) => active.has(t.name));
619
+ }
620
+
621
+ // src/server/server.ts
622
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
623
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
624
+
625
+ // src/tools/imap-read/check-capabilities.ts
626
+ import { z as z2 } from "zod";
627
+
628
+ // src/tools/_types.ts
629
+ function defineTool(def) {
630
+ return def;
631
+ }
632
+
633
+ // src/tools/imap-read/check-capabilities.ts
634
+ var check_capabilities_default = defineTool({
635
+ name: "imap_check_capabilities",
636
+ description: "Server CAPABILITY-Liste",
637
+ category: "imap-read",
638
+ inputSchema: z2.object({
639
+ account: z2.string().optional().describe("Account name (default: default_account)")
640
+ }),
641
+ handler: async (input, ctx) => {
642
+ const accountName = ctx.resolveAccount(input.account);
643
+ const client = await ctx.imap.acquire(accountName);
644
+ const caps = [...client.capabilities.keys()];
645
+ return { capabilities: caps };
646
+ }
647
+ });
648
+
649
+ // src/tools/imap-read/download-attachment.ts
650
+ import { writeFileSync } from "fs";
651
+ import { z as z3 } from "zod";
652
+ var download_attachment_default = defineTool({
653
+ name: "imap_download_attachment",
654
+ description: "Gezielt eine MIME-Part extrahieren (Pfad oder Base64)",
655
+ category: "imap-read",
656
+ inputSchema: z3.object({
657
+ mailbox: z3.string().min(1).describe("Mailbox path"),
658
+ uid: z3.number().int().positive().describe("Message UID"),
659
+ partId: z3.string().min(1).describe("MIME part ID (e.g. '1', '2.1')"),
660
+ savePath: z3.string().optional().describe("Local path to save the attachment"),
661
+ account: z3.string().optional().describe("Account name (default: default_account)")
662
+ }),
663
+ handler: async (input, ctx) => {
664
+ const accountName = ctx.resolveAccount(input.account);
665
+ const client = await ctx.imap.acquire(accountName);
666
+ const mb = await client.mailboxOpen(input.mailbox);
667
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
668
+ const msg = await client.fetchOne(input.uid, {
669
+ uid: true,
670
+ bodyParts: [input.partId],
671
+ bodyStructure: true
672
+ });
673
+ if (!msg) throw new UidNotFoundError(input.uid, input.mailbox);
674
+ const part = msg.bodyParts?.get(input.partId);
675
+ if (!part) throw new AttachmentNotFoundError(`Part ${input.partId}`);
676
+ let contentType = "application/octet-stream";
677
+ let filename = `attachment-${input.partId}`;
678
+ if (msg.bodyStructure) {
679
+ contentType = msg.bodyStructure.type || contentType;
680
+ filename = msg.bodyStructure.parameters?.name || msg.bodyStructure.parameters?.filename || filename;
681
+ }
682
+ const buffer = Buffer.from(part);
683
+ const size = buffer.length;
684
+ const base64 = buffer.toString("base64");
685
+ if (input.savePath) {
686
+ writeFileSync(input.savePath, buffer);
687
+ }
688
+ return {
689
+ partId: input.partId,
690
+ ...filename ? { filename } : {},
691
+ contentType,
692
+ size,
693
+ ...input.savePath ? { savedPath: input.savePath } : { base64 }
694
+ };
695
+ }
696
+ });
697
+
698
+ // src/tools/imap-read/get-message-headers.ts
699
+ import { z as z4 } from "zod";
700
+ var get_message_headers_default = defineTool({
701
+ name: "imap_get_message_headers",
702
+ description: "Nur Header einer Mail",
703
+ category: "imap-read",
704
+ inputSchema: z4.object({
705
+ mailbox: z4.string().min(1).describe("Mailbox path"),
706
+ uid: z4.number().int().positive().describe("Message UID"),
707
+ account: z4.string().optional().describe("Account name (default: default_account)")
708
+ }),
709
+ handler: async (input, ctx) => {
710
+ const accountName = ctx.resolveAccount(input.account);
711
+ const client = await ctx.imap.acquire(accountName);
712
+ const mailbox = await client.mailboxOpen(input.mailbox);
713
+ if (!mailbox) throw new MailboxNotFoundError(input.mailbox);
714
+ const msg = await client.fetchOne(input.uid, {
715
+ uid: true,
716
+ headers: true
717
+ });
718
+ if (!msg) throw new UidNotFoundError(input.uid, input.mailbox);
719
+ const rawHeaders = msg.headers?.toString() ?? "";
720
+ const headers = {};
721
+ const lines = rawHeaders.split("\r\n");
722
+ let currentKey = null;
723
+ let currentValue = null;
724
+ for (const line of lines) {
725
+ if ((line.startsWith(" ") || line.startsWith(" ")) && currentKey) {
726
+ if (currentValue) {
727
+ currentValue += ` ${line.trim()}`;
728
+ }
729
+ continue;
730
+ }
731
+ if (currentKey && currentValue) {
732
+ const existing = headers[currentKey];
733
+ if (existing) {
734
+ if (Array.isArray(existing)) {
735
+ existing.push(currentValue);
736
+ } else {
737
+ headers[currentKey] = [existing, currentValue];
738
+ }
739
+ } else {
740
+ headers[currentKey] = currentValue;
741
+ }
742
+ }
743
+ const colonIdx = line.indexOf(":");
744
+ if (colonIdx > 0) {
745
+ currentKey = line.slice(0, colonIdx).trim();
746
+ currentValue = line.slice(colonIdx + 1).trim();
747
+ } else {
748
+ currentKey = null;
749
+ currentValue = null;
750
+ }
751
+ }
752
+ if (currentKey && currentValue) {
753
+ const existing = headers[currentKey];
754
+ if (existing) {
755
+ if (Array.isArray(existing)) {
756
+ existing.push(currentValue);
757
+ } else {
758
+ headers[currentKey] = [existing, currentValue];
759
+ }
760
+ } else {
761
+ headers[currentKey] = currentValue;
762
+ }
763
+ }
764
+ return {
765
+ uid: msg.uid,
766
+ headers
767
+ };
768
+ }
769
+ });
770
+
771
+ // src/tools/imap-read/get-message-raw.ts
772
+ import { z as z5 } from "zod";
773
+ var get_message_raw_default = defineTool({
774
+ name: "imap_get_message_raw",
775
+ description: "RFC-822 raw source einer Mail",
776
+ category: "imap-read",
777
+ inputSchema: z5.object({
778
+ mailbox: z5.string().min(1).describe("Mailbox path"),
779
+ uid: z5.number().int().positive().describe("Message UID"),
780
+ account: z5.string().optional().describe("Account name (default: default_account)")
781
+ }),
782
+ handler: async (input, ctx) => {
783
+ const accountName = ctx.resolveAccount(input.account);
784
+ const client = await ctx.imap.acquire(accountName);
785
+ const mailbox = await client.mailboxOpen(input.mailbox);
786
+ if (!mailbox) throw new MailboxNotFoundError(input.mailbox);
787
+ const msg = await client.fetchOne(input.uid, {
788
+ uid: true,
789
+ source: true
790
+ });
791
+ if (!msg) throw new UidNotFoundError(input.uid, input.mailbox);
792
+ return {
793
+ uid: msg.uid,
794
+ rfc822: msg.source?.toString() ?? ""
795
+ };
796
+ }
797
+ });
798
+
799
+ // src/tools/imap-read/get-message.ts
800
+ import { z as z6 } from "zod";
801
+
802
+ // src/lib/mime.ts
803
+ import { simpleParser } from "mailparser";
804
+ async function parseMime(raw) {
805
+ const parsed = await simpleParser(raw);
806
+ const body = {};
807
+ if (parsed.text) body.text = parsed.text;
808
+ if (parsed.html) body.html = parsed.html;
809
+ const attachments = (parsed.attachments || []).map(
810
+ (a, idx) => {
811
+ const fn = a.filename || extractFilenameFromHeaders(a.headers);
812
+ return {
813
+ partId: a.related ? `related.${idx + 1}` : String(idx + 1),
814
+ filename: fn ?? `attachment_${idx + 1}`,
815
+ contentType: a.contentType || "application/octet-stream",
816
+ size: a.size || a.content.length || 0,
817
+ ...a.contentId ? { contentId: a.contentId } : {},
818
+ ...a.contentDisposition === "inline" ? { disposition: "inline" } : a.contentDisposition === "attachment" ? { disposition: "attachment" } : {}
819
+ };
820
+ }
821
+ );
822
+ return { body, attachments };
823
+ }
824
+ function extractFilenameFromHeaders(headers) {
825
+ if (!headers) return void 0;
826
+ const cd = headers.get("content-disposition");
827
+ if (typeof cd === "string") {
828
+ const m = cd.match(/filename\*?=(?:[^']*'[^']*')?([^;\s]+)/i);
829
+ if (m) {
830
+ try {
831
+ const captured = m[1];
832
+ return captured ? decodeURIComponent(captured) : void 0;
833
+ } catch {
834
+ return m[1];
835
+ }
836
+ }
837
+ const m2 = cd.match(/filename="?([^";]*)"?/i);
838
+ if (m2) return m2[1];
839
+ }
840
+ return void 0;
841
+ }
842
+
843
+ // src/tools/imap-read/get-message.ts
844
+ function addressArray(addr) {
845
+ if (!addr) return [];
846
+ const list = Array.isArray(addr) ? addr : [addr];
847
+ const result = [];
848
+ for (const entry of list) {
849
+ if (entry && typeof entry === "object") {
850
+ const e = entry;
851
+ const addrStr = e.address ?? "";
852
+ if (addrStr) {
853
+ result.push({ ...e.name ? { name: e.name } : {}, address: addrStr });
854
+ }
855
+ }
856
+ }
857
+ return result;
858
+ }
859
+ var get_message_default = defineTool({
860
+ name: "imap_get_message",
861
+ description: "Vollst\xE4ndige Mail inkl. geparstem Body + Attachment-Metadaten",
862
+ category: "imap-read",
863
+ inputSchema: z6.object({
864
+ mailbox: z6.string().min(1).describe("Mailbox path"),
865
+ uid: z6.number().int().positive().describe("Message UID"),
866
+ account: z6.string().optional().describe("Account name (default: default_account)")
867
+ }),
868
+ handler: async (input, ctx) => {
869
+ const accountName = ctx.resolveAccount(input.account);
870
+ const client = await ctx.imap.acquire(accountName);
871
+ const mailbox = await client.mailboxOpen(input.mailbox);
872
+ if (!mailbox) throw new MailboxNotFoundError(input.mailbox);
873
+ const msg = await client.fetchOne(input.uid, {
874
+ uid: true,
875
+ envelope: true,
876
+ flags: true,
877
+ size: true,
878
+ source: true
879
+ });
880
+ if (!msg) throw new UidNotFoundError(input.uid, input.mailbox);
881
+ const raw = msg.source?.toString() ?? "";
882
+ let parsed;
883
+ try {
884
+ parsed = await parseMime(raw);
885
+ } catch {
886
+ parsed = { body: {}, attachments: [] };
887
+ }
888
+ const enc = msg.envelope;
889
+ return {
890
+ envelope: {
891
+ uid: msg.uid,
892
+ subject: enc?.subject ?? "",
893
+ from: addressArray(enc?.from),
894
+ to: addressArray(enc?.to),
895
+ ...enc?.cc?.length ? { cc: addressArray(enc.cc) } : {},
896
+ ...enc?.bcc?.length ? { bcc: addressArray(enc.bcc) } : {},
897
+ ...enc?.replyTo?.length ? { replyTo: addressArray(enc.replyTo) } : {},
898
+ date: (enc?.date ?? /* @__PURE__ */ new Date()).toISOString(),
899
+ ...enc?.messageId ? { messageId: enc.messageId } : {},
900
+ ...enc?.inReplyTo ? { inReplyTo: enc.inReplyTo } : {},
901
+ flags: [...msg.flags ?? []],
902
+ size: msg.size ?? 0,
903
+ hasAttachments: parsed.attachments.length > 0
904
+ },
905
+ ...parsed.body.text ? { text: parsed.body.text } : {},
906
+ ...parsed.body.html ? { html: parsed.body.html } : {},
907
+ attachments: parsed.attachments
908
+ };
909
+ }
910
+ });
911
+
912
+ // src/tools/imap-read/get-messages-bulk.ts
913
+ import { z as z7 } from "zod";
914
+ function addressArray2(addr) {
915
+ if (!addr) return [];
916
+ const list = Array.isArray(addr) ? addr : [addr];
917
+ const result = [];
918
+ for (const entry of list) {
919
+ if (entry && typeof entry === "object") {
920
+ const e = entry;
921
+ const addrStr = e.address ?? "";
922
+ if (addrStr) {
923
+ result.push({ ...e.name ? { name: e.name } : {}, address: addrStr });
924
+ }
925
+ }
926
+ }
927
+ return result;
928
+ }
929
+ var get_messages_bulk_default = defineTool({
930
+ name: "imap_get_messages_bulk",
931
+ description: "Bis N UIDs in einem Call holen",
932
+ category: "imap-read",
933
+ inputSchema: z7.object({
934
+ mailbox: z7.string().min(1).describe("Mailbox path"),
935
+ uids: z7.array(z7.number().int().positive()).min(1).max(500).describe("Message UIDs"),
936
+ account: z7.string().optional().describe("Account name (default: default_account)")
937
+ }),
938
+ handler: async (input, ctx) => {
939
+ const accountName = ctx.resolveAccount(input.account);
940
+ const client = await ctx.imap.acquire(accountName);
941
+ const mb = await client.mailboxOpen(input.mailbox);
942
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
943
+ const messages = [];
944
+ const notFound = [];
945
+ const uidSet = new Set(input.uids);
946
+ for await (const msg of client.fetch(input.uids, {
947
+ uid: true,
948
+ envelope: true,
949
+ flags: true,
950
+ size: true,
951
+ source: true
952
+ })) {
953
+ uidSet.delete(msg.uid);
954
+ const raw = msg.source?.toString() ?? "";
955
+ let parsed;
956
+ try {
957
+ parsed = await parseMime(raw);
958
+ } catch {
959
+ parsed = { body: {}, attachments: [] };
960
+ }
961
+ const enc = msg.envelope;
962
+ messages.push({
963
+ envelope: {
964
+ uid: msg.uid,
965
+ subject: enc?.subject ?? "",
966
+ from: addressArray2(enc?.from),
967
+ to: addressArray2(enc?.to),
968
+ ...enc?.cc?.length ? { cc: addressArray2(enc.cc) } : {},
969
+ ...enc?.bcc?.length ? { bcc: addressArray2(enc.bcc) } : {},
970
+ ...enc?.replyTo?.length ? { replyTo: addressArray2(enc.replyTo) } : {},
971
+ date: (enc?.date ?? /* @__PURE__ */ new Date()).toISOString(),
972
+ ...enc?.messageId ? { messageId: enc.messageId } : {},
973
+ ...enc?.inReplyTo ? { inReplyTo: enc.inReplyTo } : {},
974
+ flags: [...msg.flags ?? []],
975
+ size: msg.size ?? 0,
976
+ hasAttachments: parsed.attachments.length > 0
977
+ },
978
+ ...parsed.body.text ? { text: parsed.body.text } : {},
979
+ ...parsed.body.html ? { html: parsed.body.html } : {},
980
+ attachments: parsed.attachments
981
+ });
982
+ }
983
+ for (const uid of uidSet) notFound.push(uid);
984
+ return {
985
+ mailbox: input.mailbox,
986
+ messages,
987
+ notFound
988
+ };
989
+ }
990
+ });
991
+
992
+ // src/tools/imap-read/get-quota.ts
993
+ import { z as z8 } from "zod";
994
+ var get_quota_default = defineTool({
995
+ name: "imap_get_quota",
996
+ description: "Quota-Info abfragen (RFC 2087)",
997
+ category: "imap-read",
998
+ inputSchema: z8.object({
999
+ account: z8.string().optional().describe("Account name (default: default_account)")
1000
+ }),
1001
+ handler: async (input, ctx) => {
1002
+ const accountName = ctx.resolveAccount(input.account);
1003
+ const client = await ctx.imap.acquire(accountName);
1004
+ const caps = [...client.capabilities.keys()];
1005
+ if (!caps.some((c) => c.toUpperCase() === "QUOTA")) {
1006
+ return { root: "INBOX", usage: 0, limit: -1 };
1007
+ }
1008
+ try {
1009
+ const result = await client.getQuota();
1010
+ if (!result) {
1011
+ return { root: "INBOX", usage: 0, limit: -1 };
1012
+ }
1013
+ const usage = result.storage?.used ?? result.messages?.used ?? 0;
1014
+ const limit = result.storage?.limit ?? result.messages?.limit ?? -1;
1015
+ const resources = [];
1016
+ if (result.storage) {
1017
+ resources.push({
1018
+ name: "STORAGE",
1019
+ usage: result.storage.used,
1020
+ limit: result.storage.limit
1021
+ });
1022
+ }
1023
+ if (result.messages) {
1024
+ resources.push({
1025
+ name: "MESSAGES",
1026
+ usage: result.messages.used,
1027
+ limit: result.messages.limit
1028
+ });
1029
+ }
1030
+ return {
1031
+ root: result.path,
1032
+ usage,
1033
+ limit,
1034
+ ...resources.length > 1 ? { resources } : {}
1035
+ };
1036
+ } catch {
1037
+ return { root: "INBOX", usage: 0, limit: -1 };
1038
+ }
1039
+ }
1040
+ });
1041
+
1042
+ // src/tools/imap-read/get-thread.ts
1043
+ import { z as z9 } from "zod";
1044
+
1045
+ // src/lib/threading.ts
1046
+ function reconstructThread(messages, rootUid) {
1047
+ if (messages.length === 0) return [];
1048
+ const byUid = /* @__PURE__ */ new Map();
1049
+ const byMessageId = /* @__PURE__ */ new Map();
1050
+ for (const msg of messages) {
1051
+ byUid.set(msg.uid, msg);
1052
+ if (msg.messageId) {
1053
+ byMessageId.set(msg.messageId, msg);
1054
+ }
1055
+ }
1056
+ const root = byUid.get(rootUid);
1057
+ if (!root) return [];
1058
+ const allMessageIds = /* @__PURE__ */ new Set();
1059
+ for (const msg of messages) {
1060
+ if (msg.messageId) allMessageIds.add(msg.messageId);
1061
+ if (msg.references) for (const ref of msg.references) allMessageIds.add(ref);
1062
+ if (msg.inReplyTo) allMessageIds.add(msg.inReplyTo);
1063
+ }
1064
+ const threadSet = /* @__PURE__ */ new Set();
1065
+ threadSet.add(rootUid);
1066
+ const seen = /* @__PURE__ */ new Set();
1067
+ const queue = [rootUid];
1068
+ while (queue.length > 0) {
1069
+ const currentUid = queue.pop();
1070
+ if (seen.has(currentUid)) continue;
1071
+ seen.add(currentUid);
1072
+ const current = byUid.get(currentUid);
1073
+ if (!current) continue;
1074
+ const relevantIds = /* @__PURE__ */ new Set();
1075
+ if (current.messageId) relevantIds.add(current.messageId);
1076
+ if (current.inReplyTo) relevantIds.add(current.inReplyTo);
1077
+ if (current.references) for (const ref of current.references) relevantIds.add(ref);
1078
+ for (const other of messages) {
1079
+ if (threadSet.has(other.uid)) continue;
1080
+ if (!other.inReplyTo && !other.references) continue;
1081
+ if (other.inReplyTo && relevantIds.has(other.inReplyTo)) {
1082
+ threadSet.add(other.uid);
1083
+ queue.push(other.uid);
1084
+ continue;
1085
+ }
1086
+ if (other.references) {
1087
+ for (const ref of other.references) {
1088
+ if (relevantIds.has(ref)) {
1089
+ threadSet.add(other.uid);
1090
+ queue.push(other.uid);
1091
+ break;
1092
+ }
1093
+ }
1094
+ }
1095
+ if (current.messageId && other.references?.includes(current.messageId)) {
1096
+ threadSet.add(other.uid);
1097
+ queue.push(other.uid);
1098
+ }
1099
+ }
1100
+ if (current.inReplyTo) {
1101
+ const parent = byMessageId.get(current.inReplyTo);
1102
+ if (parent) {
1103
+ threadSet.add(parent.uid);
1104
+ queue.push(parent.uid);
1105
+ }
1106
+ }
1107
+ if (current.references) {
1108
+ for (const ref of current.references) {
1109
+ const refMsg = byMessageId.get(ref);
1110
+ if (refMsg) {
1111
+ threadSet.add(refMsg.uid);
1112
+ queue.push(refMsg.uid);
1113
+ }
1114
+ }
1115
+ }
1116
+ }
1117
+ const result = messages.filter((m) => threadSet.has(m.uid));
1118
+ result.sort((a, b) => a.date.getTime() - b.date.getTime());
1119
+ return result;
1120
+ }
1121
+
1122
+ // src/tools/imap-read/get-thread.ts
1123
+ function addressArray3(addr) {
1124
+ if (!addr) return [];
1125
+ const list = Array.isArray(addr) ? addr : [addr];
1126
+ const result = [];
1127
+ for (const entry of list) {
1128
+ if (entry && typeof entry === "object") {
1129
+ const e = entry;
1130
+ const addrStr = e.address ?? "";
1131
+ if (addrStr) {
1132
+ result.push({ ...e.name ? { name: e.name } : {}, address: addrStr });
1133
+ }
1134
+ }
1135
+ }
1136
+ return result;
1137
+ }
1138
+ var get_thread_default = defineTool({
1139
+ name: "imap_get_thread",
1140
+ description: "Konversation via In-Reply-To/References rekonstruieren",
1141
+ category: "imap-read",
1142
+ inputSchema: z9.object({
1143
+ mailbox: z9.string().min(1).describe("Mailbox path"),
1144
+ uid: z9.number().int().positive().describe("Root message UID"),
1145
+ account: z9.string().optional().describe("Account name (default: default_account)")
1146
+ }),
1147
+ handler: async (input, ctx) => {
1148
+ const accountName = ctx.resolveAccount(input.account);
1149
+ const client = await ctx.imap.acquire(accountName);
1150
+ const mb = await client.mailboxOpen(input.mailbox);
1151
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
1152
+ const allMessages = [];
1153
+ const extras = /* @__PURE__ */ new Map();
1154
+ for await (const msg of client.fetch("1:*", {
1155
+ uid: true,
1156
+ envelope: true,
1157
+ flags: true,
1158
+ size: true,
1159
+ headers: ["message-id", "in-reply-to", "references"]
1160
+ })) {
1161
+ const enc = msg.envelope;
1162
+ if (!enc) continue;
1163
+ const rawHeaders = msg.headers?.toString() ?? "";
1164
+ const refMatch = rawHeaders.match(/^references:\s*(.*)$/im);
1165
+ const refs = refMatch ? refMatch[1]?.trim().split(/\s+/).filter(Boolean) : void 0;
1166
+ allMessages.push({
1167
+ uid: msg.uid,
1168
+ messageId: enc.messageId ?? void 0,
1169
+ inReplyTo: enc.inReplyTo ?? void 0,
1170
+ references: refs,
1171
+ date: enc.date ?? /* @__PURE__ */ new Date()
1172
+ });
1173
+ extras.set(msg.uid, {
1174
+ subject: enc.subject ?? "",
1175
+ from: addressArray3(enc.from),
1176
+ to: addressArray3(enc.to),
1177
+ ...enc.cc?.length ? { cc: addressArray3(enc.cc) } : {},
1178
+ ...enc.bcc?.length ? { bcc: addressArray3(enc.bcc) } : {},
1179
+ ...enc.replyTo?.length ? { replyTo: addressArray3(enc.replyTo) } : {},
1180
+ date: (enc.date ?? /* @__PURE__ */ new Date()).toISOString(),
1181
+ ...enc.messageId ? { messageId: enc.messageId } : {},
1182
+ ...enc.inReplyTo ? { inReplyTo: enc.inReplyTo } : {},
1183
+ flags: [...msg.flags ?? []],
1184
+ size: msg.size ?? 0,
1185
+ hasAttachments: false
1186
+ });
1187
+ }
1188
+ const thread = reconstructThread(allMessages, input.uid);
1189
+ const root = allMessages.find((m) => m.uid === input.uid);
1190
+ if (!root) throw new UidNotFoundError(input.uid, input.mailbox);
1191
+ const messages = thread.map((m) => ({
1192
+ ...extras.get(m.uid) ?? {}
1193
+ }));
1194
+ return {
1195
+ rootUid: input.uid,
1196
+ mailbox: input.mailbox,
1197
+ messages
1198
+ };
1199
+ }
1200
+ });
1201
+
1202
+ // src/tools/imap-read/list-mailboxes.ts
1203
+ import { z as z10 } from "zod";
1204
+ var list_mailboxes_default = defineTool({
1205
+ name: "imap_list_mailboxes",
1206
+ description: "Folder enumerieren mit Special-Use-Flags (RFC 6154)",
1207
+ category: "imap-read",
1208
+ inputSchema: z10.object({
1209
+ account: z10.string().optional().describe("Account name (default: default_account)")
1210
+ }),
1211
+ handler: async (input, ctx) => {
1212
+ const accountName = ctx.resolveAccount(input.account);
1213
+ const client = await ctx.imap.acquire(accountName);
1214
+ const mailboxes = await client.list();
1215
+ const result = mailboxes.map((mb) => {
1216
+ const flags = [...mb.flags || []];
1217
+ const specialUse = flags.find(
1218
+ (f) => [
1219
+ "\\Inbox",
1220
+ "\\Sent",
1221
+ "\\Drafts",
1222
+ "\\Trash",
1223
+ "\\Junk",
1224
+ "\\Archive",
1225
+ "\\All",
1226
+ "\\Flagged"
1227
+ ].includes(f)
1228
+ );
1229
+ return {
1230
+ path: mb.path,
1231
+ delimiter: mb.delimiter,
1232
+ flags,
1233
+ ...specialUse ? { specialUse } : {},
1234
+ subscribed: mb.subscribed
1235
+ };
1236
+ });
1237
+ return { mailboxes: result };
1238
+ }
1239
+ });
1240
+
1241
+ // src/tools/imap-read/list-messages.ts
1242
+ import { z as z11 } from "zod";
1243
+ function addressArray4(addr) {
1244
+ if (!addr) return [];
1245
+ const list = Array.isArray(addr) ? addr : [addr];
1246
+ const result = [];
1247
+ for (const entry of list) {
1248
+ if (entry && typeof entry === "object") {
1249
+ const e = entry;
1250
+ const addrStr = e.address ?? "";
1251
+ if (addrStr) {
1252
+ result.push({ ...e.name ? { name: e.name } : {}, address: addrStr });
1253
+ }
1254
+ }
1255
+ }
1256
+ return result;
1257
+ }
1258
+ var list_messages_default = defineTool({
1259
+ name: "imap_list_messages",
1260
+ description: "Paginierte Envelope-Liste in einem Folder",
1261
+ category: "imap-read",
1262
+ inputSchema: z11.object({
1263
+ mailbox: z11.string().min(1).describe("Mailbox path"),
1264
+ page: z11.number().int().positive().default(1).describe("Page number (default: 1)"),
1265
+ pageSize: z11.number().int().positive().max(500).default(50).describe("Messages per page (default: 50, max: 500)"),
1266
+ sort: z11.enum(["date", "subject", "from", "size"]).optional().describe("Sort field"),
1267
+ sortOrder: z11.enum(["asc", "desc"]).default("desc").describe("Sort order (default: desc)"),
1268
+ account: z11.string().optional().describe("Account name (default: default_account)")
1269
+ }),
1270
+ handler: async (input, ctx) => {
1271
+ const accountName = ctx.resolveAccount(input.account);
1272
+ const client = await ctx.imap.acquire(accountName);
1273
+ const mailbox = await client.mailboxOpen(input.mailbox);
1274
+ if (!mailbox) {
1275
+ throw new MailboxNotFoundError(input.mailbox);
1276
+ }
1277
+ const total = mailbox.exists;
1278
+ const currentPage = input.page ?? 1;
1279
+ const pageSize = input.pageSize ?? 50;
1280
+ const startNum = (currentPage - 1) * pageSize + 1;
1281
+ const endNum = Math.min(startNum + pageSize - 1, total);
1282
+ if (startNum > total) {
1283
+ return {
1284
+ mailbox: input.mailbox,
1285
+ page: currentPage,
1286
+ pageSize,
1287
+ total,
1288
+ messages: []
1289
+ };
1290
+ }
1291
+ const messages = [];
1292
+ for await (const msg of client.fetch(`${startNum}:${endNum}`, {
1293
+ uid: true,
1294
+ envelope: true,
1295
+ flags: true,
1296
+ size: true
1297
+ })) {
1298
+ const enc = msg.envelope;
1299
+ if (!enc) continue;
1300
+ let hasAttachments = false;
1301
+ if (msg.bodyStructure?.type?.toLowerCase() === "multipart") {
1302
+ hasAttachments = true;
1303
+ }
1304
+ messages.push({
1305
+ uid: msg.uid,
1306
+ seq: msg.seq,
1307
+ subject: enc.subject ?? "",
1308
+ from: addressArray4(enc.from),
1309
+ to: addressArray4(enc.to),
1310
+ ...enc.cc?.length ? { cc: addressArray4(enc.cc) } : {},
1311
+ ...enc.bcc?.length ? { bcc: addressArray4(enc.bcc) } : {},
1312
+ ...enc.replyTo?.length ? { replyTo: addressArray4(enc.replyTo) } : {},
1313
+ date: (enc.date ?? /* @__PURE__ */ new Date()).toISOString(),
1314
+ ...enc.messageId ? { messageId: enc.messageId } : {},
1315
+ ...enc.inReplyTo ? { inReplyTo: enc.inReplyTo } : {},
1316
+ flags: [...msg.flags ?? []],
1317
+ size: msg.size ?? 0,
1318
+ hasAttachments
1319
+ });
1320
+ }
1321
+ return {
1322
+ mailbox: input.mailbox,
1323
+ page: currentPage,
1324
+ pageSize,
1325
+ total,
1326
+ messages
1327
+ };
1328
+ }
1329
+ });
1330
+
1331
+ // src/tools/imap-read/search.ts
1332
+ import { z as z12 } from "zod";
1333
+
1334
+ // src/lib/search-builder.ts
1335
+ function parseSearchDate(value) {
1336
+ const isoMatch = value.match(/^\d{4}-\d{2}-\d{2}/);
1337
+ if (isoMatch) return new Date(isoMatch[0]);
1338
+ const parsed = new Date(value);
1339
+ if (!Number.isNaN(parsed.getTime())) return parsed;
1340
+ const months = {
1341
+ jan: "01",
1342
+ feb: "02",
1343
+ mar: "03",
1344
+ apr: "04",
1345
+ may: "05",
1346
+ jun: "06",
1347
+ jul: "07",
1348
+ aug: "08",
1349
+ sep: "09",
1350
+ oct: "10",
1351
+ nov: "11",
1352
+ dec: "12"
1353
+ };
1354
+ const parts = value.split("-");
1355
+ if (parts.length === 3) {
1356
+ const day = parts[0].padStart(2, "0");
1357
+ const monKey = parts[1]?.toLowerCase().slice(0, 3);
1358
+ const mon = monKey ? months[monKey] : void 0;
1359
+ const yearRaw = parts[2];
1360
+ const year = yearRaw.length === 2 ? `20${yearRaw}` : yearRaw;
1361
+ if (mon && day) return /* @__PURE__ */ new Date(`${year}-${mon}-${day}`);
1362
+ }
1363
+ throw new Error(
1364
+ `Cannot parse date: ${value}. Use DD-Mon-YYYY (e.g., 21-May-2026) or YYYY-MM-DD.`
1365
+ );
1366
+ }
1367
+ function buildSearchQuery(criteria) {
1368
+ const query = {};
1369
+ if (criteria.from !== void 0) query.from = criteria.from;
1370
+ if (criteria.to !== void 0) query.to = criteria.to;
1371
+ if (criteria.cc !== void 0) query.cc = criteria.cc;
1372
+ if (criteria.bcc !== void 0) query.bcc = criteria.bcc;
1373
+ if (criteria.subject !== void 0) query.subject = criteria.subject;
1374
+ if (criteria.body !== void 0) query.body = criteria.body;
1375
+ if (criteria.text !== void 0) query.text = criteria.text;
1376
+ if (criteria.since !== void 0) query.since = parseSearchDate(criteria.since);
1377
+ if (criteria.before !== void 0) query.before = parseSearchDate(criteria.before);
1378
+ if (criteria.on !== void 0) query.on = parseSearchDate(criteria.on);
1379
+ if (criteria.sentSince !== void 0) query.sentSince = parseSearchDate(criteria.sentSince);
1380
+ if (criteria.sentBefore !== void 0) query.sentBefore = parseSearchDate(criteria.sentBefore);
1381
+ if (criteria.sentOn !== void 0) query.sentOn = parseSearchDate(criteria.sentOn);
1382
+ if (criteria.larger !== void 0) query.larger = criteria.larger;
1383
+ if (criteria.smaller !== void 0) query.smaller = criteria.smaller;
1384
+ if (criteria.unseen !== void 0) query.unseen = criteria.unseen;
1385
+ if (criteria.seen !== void 0) query.seen = criteria.seen;
1386
+ if (criteria.flagged !== void 0) query.flagged = criteria.flagged;
1387
+ if (criteria.unflagged !== void 0) query.unflagged = criteria.unflagged;
1388
+ if (criteria.answered !== void 0) query.answered = criteria.answered;
1389
+ if (criteria.unanswered !== void 0) query.unanswered = criteria.unanswered;
1390
+ if (criteria.deleted !== void 0) query.deleted = criteria.deleted;
1391
+ if (criteria.undeleted !== void 0) query.undeleted = criteria.undeleted;
1392
+ if (criteria.new !== void 0) query.new = criteria.new;
1393
+ if (criteria.old !== void 0) query.old = criteria.old;
1394
+ if (criteria.recent !== void 0) query.recent = criteria.recent;
1395
+ if (criteria.keyword !== void 0) query.keyword = criteria.keyword;
1396
+ if (criteria.unkeyword !== void 0) query.unkeyword = criteria.unkeyword;
1397
+ return query;
1398
+ }
1399
+
1400
+ // src/tools/imap-read/search.ts
1401
+ var search_default = defineTool({
1402
+ name: "imap_search",
1403
+ description: "Vollst\xE4ndiger RFC-3501-SEARCH-Builder",
1404
+ category: "imap-read",
1405
+ inputSchema: z12.object({
1406
+ mailbox: z12.string().min(1).describe("Mailbox path"),
1407
+ from: z12.string().optional().describe("Sender address"),
1408
+ to: z12.string().optional().describe("Recipient address"),
1409
+ cc: z12.string().optional().describe("CC recipient address"),
1410
+ bcc: z12.string().optional().describe("BCC recipient address"),
1411
+ subject: z12.string().optional().describe("Subject line"),
1412
+ body: z12.string().optional().describe("Body text"),
1413
+ text: z12.string().optional().describe("Full text (headers + body)"),
1414
+ since: z12.string().optional().describe("Messages on or after this date (DD-Mon-YYYY or YYYY-MM-DD)"),
1415
+ before: z12.string().optional().describe("Messages before this date"),
1416
+ on: z12.string().optional().describe("Messages on this exact date"),
1417
+ sentSince: z12.string().optional().describe("Sent on or after this date"),
1418
+ sentBefore: z12.string().optional().describe("Sent before this date"),
1419
+ sentOn: z12.string().optional().describe("Sent on this exact date"),
1420
+ larger: z12.number().int().positive().optional().describe("Larger than N bytes"),
1421
+ smaller: z12.number().int().positive().optional().describe("Smaller than N bytes"),
1422
+ unseen: z12.boolean().optional().describe("Not read"),
1423
+ seen: z12.boolean().optional().describe("Read"),
1424
+ flagged: z12.boolean().optional().describe("Flagged"),
1425
+ unflagged: z12.boolean().optional().describe("Not flagged"),
1426
+ answered: z12.boolean().optional().describe("Answered"),
1427
+ unanswered: z12.boolean().optional().describe("Not answered"),
1428
+ deleted: z12.boolean().optional().describe("Deleted"),
1429
+ undeleted: z12.boolean().optional().describe("Not deleted"),
1430
+ keyword: z12.string().optional().describe("Has this keyword"),
1431
+ unkeyword: z12.string().optional().describe("Does not have this keyword"),
1432
+ new: z12.boolean().optional().describe("New (unseen + recent)"),
1433
+ old: z12.boolean().optional().describe("Old (not recent)"),
1434
+ recent: z12.boolean().optional().describe("Recent"),
1435
+ account: z12.string().optional().describe("Account name (default: default_account)")
1436
+ }),
1437
+ handler: async (input, ctx) => {
1438
+ const accountName = ctx.resolveAccount(input.account);
1439
+ const client = await ctx.imap.acquire(accountName);
1440
+ const mb = await client.mailboxOpen(input.mailbox);
1441
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
1442
+ const { mailbox: _mb, account: _acct, ...searchCriteria } = input;
1443
+ const query = buildSearchQuery(
1444
+ searchCriteria
1445
+ );
1446
+ const result = await client.search(query);
1447
+ const uids = result ? [...result] : [];
1448
+ return {
1449
+ mailbox: input.mailbox,
1450
+ uids,
1451
+ count: uids.length
1452
+ };
1453
+ }
1454
+ });
1455
+
1456
+ // src/tools/imap-read/status-mailbox.ts
1457
+ import { z as z13 } from "zod";
1458
+ var status_mailbox_default = defineTool({
1459
+ name: "imap_status_mailbox",
1460
+ description: "Counts (unread/total/recent) ohne SELECT (STATUS)",
1461
+ category: "imap-read",
1462
+ inputSchema: z13.object({
1463
+ mailbox: z13.string().min(1).describe("Mailbox path (e.g. INBOX)"),
1464
+ account: z13.string().optional().describe("Account name (default: default_account)")
1465
+ }),
1466
+ handler: async (input, ctx) => {
1467
+ const accountName = ctx.resolveAccount(input.account);
1468
+ const client = await ctx.imap.acquire(accountName);
1469
+ try {
1470
+ const status = await client.status(input.mailbox, {
1471
+ messages: true,
1472
+ unseen: true,
1473
+ recent: true,
1474
+ uidNext: true,
1475
+ uidValidity: true
1476
+ });
1477
+ return {
1478
+ path: input.mailbox,
1479
+ messages: status.messages ?? 0,
1480
+ unseen: status.unseen ?? 0,
1481
+ recent: status.recent ?? 0,
1482
+ ...status.uidNext !== void 0 ? { uidNext: status.uidNext } : {},
1483
+ ...status.uidValidity !== void 0 ? { uidValidity: status.uidValidity } : {}
1484
+ };
1485
+ } catch (err) {
1486
+ throw new MailboxNotFoundError(input.mailbox);
1487
+ }
1488
+ }
1489
+ });
1490
+
1491
+ // src/tools/imap-write/append-message.ts
1492
+ import { z as z14 } from "zod";
1493
+ var append_message_default = defineTool({
1494
+ name: "imap_append_message",
1495
+ description: "Mail in Folder schreiben (APPEND, Drafts/Import)",
1496
+ category: "imap-write",
1497
+ inputSchema: z14.object({
1498
+ mailbox: z14.string().min(1).describe("Target mailbox path"),
1499
+ raw: z14.string().min(1).describe("RFC-822 raw message content"),
1500
+ flags: z14.array(z14.string()).optional().describe("Optional flags to set (e.g. ['\\Seen', '\\Drafts'])"),
1501
+ date: z14.string().optional().describe("Optional internal date (ISO 8601 or imap date-time)"),
1502
+ account: z14.string().optional().describe("Account name (default: default_account)")
1503
+ }),
1504
+ handler: async (input, ctx) => {
1505
+ const accountName = ctx.resolveAccount(input.account);
1506
+ const client = await ctx.imap.acquire(accountName);
1507
+ const normalizedFlags = input.flags?.map((f) => f.startsWith("\\") ? f : `\\${f}`);
1508
+ const internalDate = input.date ? new Date(input.date) : void 0;
1509
+ try {
1510
+ const result = await client.append(input.mailbox, input.raw, normalizedFlags, internalDate);
1511
+ const uid = result && result.uid !== void 0 ? Number(result.uid) : void 0;
1512
+ return {
1513
+ mailbox: input.mailbox,
1514
+ ...uid !== void 0 ? { uid } : {}
1515
+ };
1516
+ } catch (err) {
1517
+ throw new ImapProtocolError(`Failed to append message: ${err}`);
1518
+ }
1519
+ }
1520
+ });
1521
+
1522
+ // src/tools/imap-write/bulk-mark.ts
1523
+ import { z as z15 } from "zod";
1524
+ var bulk_mark_default = defineTool({
1525
+ name: "imap_bulk_mark",
1526
+ description: "Bulk-STORE \xFCber mehrere UIDs",
1527
+ category: "imap-write",
1528
+ inputSchema: z15.object({
1529
+ mailbox: z15.string().min(1).describe("Mailbox path"),
1530
+ uids: z15.array(z15.number().int().positive()).min(1).max(500).describe("Message UIDs"),
1531
+ flags: z15.array(z15.string()).min(1).describe("Flags to set/add/remove"),
1532
+ mode: z15.enum(["set", "add", "remove"]).default("set").describe("STORE mode"),
1533
+ account: z15.string().optional().describe("Account name (default: default_account)")
1534
+ }),
1535
+ handler: async (input, ctx) => {
1536
+ const accountName = ctx.resolveAccount(input.account);
1537
+ const client = await ctx.imap.acquire(accountName);
1538
+ const mb = await client.mailboxOpen(input.mailbox);
1539
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
1540
+ const normalizedFlags = input.flags.map(
1541
+ (f) => f.startsWith("\\") ? f : `\\${f}`
1542
+ );
1543
+ const notFound = [];
1544
+ let modified = 0;
1545
+ for (const uid of input.uids) {
1546
+ try {
1547
+ switch (input.mode) {
1548
+ case "set":
1549
+ await client.messageFlagsSet(uid, normalizedFlags);
1550
+ break;
1551
+ case "add":
1552
+ await client.messageFlagsAdd(uid, normalizedFlags);
1553
+ break;
1554
+ case "remove":
1555
+ await client.messageFlagsRemove(uid, normalizedFlags);
1556
+ break;
1557
+ }
1558
+ modified++;
1559
+ } catch {
1560
+ notFound.push(uid);
1561
+ }
1562
+ }
1563
+ return {
1564
+ modified,
1565
+ uids: input.uids.filter((u) => !notFound.includes(u)),
1566
+ notFound
1567
+ };
1568
+ }
1569
+ });
1570
+
1571
+ // src/tools/imap-write/bulk-move.ts
1572
+ import { z as z16 } from "zod";
1573
+ var bulk_move_default = defineTool({
1574
+ name: "imap_bulk_move",
1575
+ description: "Bulk-MOVE \xFCber mehrere UIDs",
1576
+ category: "imap-write",
1577
+ inputSchema: z16.object({
1578
+ fromMailbox: z16.string().min(1).describe("Source mailbox path"),
1579
+ toMailbox: z16.string().min(1).describe("Target mailbox path"),
1580
+ uids: z16.array(z16.number().int().positive()).min(1).max(500).describe("Message UIDs to move"),
1581
+ account: z16.string().optional().describe("Account name (default: default_account)")
1582
+ }),
1583
+ handler: async (input, ctx) => {
1584
+ const accountName = ctx.resolveAccount(input.account);
1585
+ const client = await ctx.imap.acquire(accountName);
1586
+ const mb = await client.mailboxOpen(input.fromMailbox);
1587
+ if (!mb) throw new MailboxNotFoundError(input.fromMailbox);
1588
+ const moved = [];
1589
+ const notFound = [];
1590
+ try {
1591
+ await client.messageMove(input.uids, input.toMailbox);
1592
+ moved.push(...input.uids);
1593
+ } catch {
1594
+ for (const uid of input.uids) {
1595
+ try {
1596
+ await client.messageMove(uid, input.toMailbox);
1597
+ moved.push(uid);
1598
+ } catch {
1599
+ try {
1600
+ await client.messageCopy(uid, input.toMailbox);
1601
+ await client.messageDelete(uid);
1602
+ moved.push(uid);
1603
+ } catch {
1604
+ notFound.push(uid);
1605
+ }
1606
+ }
1607
+ }
1608
+ }
1609
+ return {
1610
+ fromMailbox: input.fromMailbox,
1611
+ toMailbox: input.toMailbox,
1612
+ moved: moved.length,
1613
+ uids: moved,
1614
+ notFound
1615
+ };
1616
+ }
1617
+ });
1618
+
1619
+ // src/tools/imap-write/copy-message.ts
1620
+ import { z as z17 } from "zod";
1621
+ var copy_message_default = defineTool({
1622
+ name: "imap_copy_message",
1623
+ description: "Mail kopieren (COPY)",
1624
+ category: "imap-write",
1625
+ inputSchema: z17.object({
1626
+ fromMailbox: z17.string().min(1).describe("Source mailbox path"),
1627
+ toMailbox: z17.string().min(1).describe("Target mailbox path"),
1628
+ uid: z17.number().int().positive().describe("Message UID"),
1629
+ account: z17.string().optional().describe("Account name (default: default_account)")
1630
+ }),
1631
+ handler: async (input, ctx) => {
1632
+ const accountName = ctx.resolveAccount(input.account);
1633
+ const client = await ctx.imap.acquire(accountName);
1634
+ const mb = await client.mailboxOpen(input.fromMailbox);
1635
+ if (!mb) throw new MailboxNotFoundError(input.fromMailbox);
1636
+ let targetUid;
1637
+ try {
1638
+ const result = await client.messageCopy(input.uid, input.toMailbox);
1639
+ if (result && result.uidMap) {
1640
+ targetUid = result.uidMap.get(input.uid);
1641
+ }
1642
+ } catch (err) {
1643
+ throw new ImapProtocolError(`Failed to copy message: ${err}`);
1644
+ }
1645
+ return {
1646
+ fromMailbox: input.fromMailbox,
1647
+ toMailbox: input.toMailbox,
1648
+ sourceUid: input.uid,
1649
+ ...targetUid !== void 0 ? { targetUid } : {}
1650
+ };
1651
+ }
1652
+ });
1653
+
1654
+ // src/tools/imap-write/delete-message.ts
1655
+ import { z as z18 } from "zod";
1656
+ var delete_message_default = defineTool({
1657
+ name: "imap_delete_message",
1658
+ description: "STORE \\Deleted + optional EXPUNGE",
1659
+ category: "imap-write",
1660
+ inputSchema: z18.object({
1661
+ mailbox: z18.string().min(1).describe("Mailbox path"),
1662
+ uid: z18.number().int().positive().describe("Message UID"),
1663
+ expunge: z18.boolean().default(false).describe("Also EXPUNGE after marking (default: false)"),
1664
+ account: z18.string().optional().describe("Account name (default: default_account)")
1665
+ }),
1666
+ handler: async (input, ctx) => {
1667
+ const accountName = ctx.resolveAccount(input.account);
1668
+ const client = await ctx.imap.acquire(accountName);
1669
+ const mb = await client.mailboxOpen(input.mailbox);
1670
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
1671
+ try {
1672
+ await client.messageDelete(input.uid);
1673
+ } catch (err) {
1674
+ throw new UidNotFoundError(input.uid, input.mailbox);
1675
+ }
1676
+ let expunged = false;
1677
+ if (input.expunge) {
1678
+ try {
1679
+ await client.mailboxClose();
1680
+ expunged = true;
1681
+ } catch (err) {
1682
+ throw new ImapProtocolError(`Failed to expunge after delete: ${err}`);
1683
+ }
1684
+ }
1685
+ return {
1686
+ uid: input.uid,
1687
+ mailbox: input.mailbox,
1688
+ expunged
1689
+ };
1690
+ }
1691
+ });
1692
+
1693
+ // src/tools/imap-write/expunge.ts
1694
+ import { z as z19 } from "zod";
1695
+ var expunge_default = defineTool({
1696
+ name: "imap_expunge",
1697
+ description: "EXPUNGE eines Folders",
1698
+ category: "imap-write",
1699
+ inputSchema: z19.object({
1700
+ mailbox: z19.string().min(1).describe("Mailbox path to expunge"),
1701
+ account: z19.string().optional().describe("Account name (default: default_account)")
1702
+ }),
1703
+ handler: async (input, ctx) => {
1704
+ const accountName = ctx.resolveAccount(input.account);
1705
+ const client = await ctx.imap.acquire(accountName);
1706
+ const mb = await client.mailboxOpen(input.mailbox);
1707
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
1708
+ const beforeCount = mb.exists;
1709
+ try {
1710
+ await client.mailboxClose();
1711
+ const after = await client.mailboxOpen(input.mailbox);
1712
+ const afterCount = after?.exists ?? beforeCount;
1713
+ return {
1714
+ mailbox: input.mailbox,
1715
+ expunged: Math.max(0, beforeCount - afterCount)
1716
+ };
1717
+ } catch (err) {
1718
+ throw new ImapProtocolError(`Failed to expunge mailbox: ${err}`);
1719
+ }
1720
+ }
1721
+ });
1722
+
1723
+ // src/tools/imap-write/mark-message.ts
1724
+ import { z as z20 } from "zod";
1725
+ var mark_message_default = defineTool({
1726
+ name: "imap_mark_message",
1727
+ description: "Flags setzen/entfernen (STORE)",
1728
+ category: "imap-write",
1729
+ inputSchema: z20.object({
1730
+ mailbox: z20.string().min(1).describe("Mailbox path"),
1731
+ uid: z20.number().int().positive().describe("Message UID"),
1732
+ flags: z20.array(z20.string()).min(1).describe("Flags to set/add/remove (e.g. ['\\Seen', '\\Flagged'])"),
1733
+ mode: z20.enum(["set", "add", "remove"]).default("set").describe("STORE mode: set (replace), add (+FLAGS), remove (-FLAGS)"),
1734
+ account: z20.string().optional().describe("Account name (default: default_account)")
1735
+ }),
1736
+ handler: async (input, ctx) => {
1737
+ const accountName = ctx.resolveAccount(input.account);
1738
+ const client = await ctx.imap.acquire(accountName);
1739
+ const mb = await client.mailboxOpen(input.mailbox);
1740
+ if (!mb) throw new MailboxNotFoundError(input.mailbox);
1741
+ const normalizedFlags = input.flags.map(
1742
+ (f) => f.startsWith("\\") ? f : `\\${f}`
1743
+ );
1744
+ try {
1745
+ switch (input.mode) {
1746
+ case "set":
1747
+ await client.messageFlagsSet(input.uid, normalizedFlags);
1748
+ break;
1749
+ case "add":
1750
+ await client.messageFlagsAdd(input.uid, normalizedFlags);
1751
+ break;
1752
+ case "remove":
1753
+ await client.messageFlagsRemove(input.uid, normalizedFlags);
1754
+ break;
1755
+ }
1756
+ } catch (err) {
1757
+ throw new ImapProtocolError(`Failed to set flags: ${err}`);
1758
+ }
1759
+ return {
1760
+ uid: input.uid,
1761
+ flags: input.flags
1762
+ };
1763
+ }
1764
+ });
1765
+
1766
+ // src/tools/imap-write/move-message.ts
1767
+ import { z as z21 } from "zod";
1768
+ var move_message_default = defineTool({
1769
+ name: "imap_move_message",
1770
+ description: "Mail verschieben (MOVE, Fallback COPY+EXPUNGE)",
1771
+ category: "imap-write",
1772
+ inputSchema: z21.object({
1773
+ fromMailbox: z21.string().min(1).describe("Source mailbox path"),
1774
+ toMailbox: z21.string().min(1).describe("Target mailbox path"),
1775
+ uid: z21.number().int().positive().describe("Message UID"),
1776
+ account: z21.string().optional().describe("Account name (default: default_account)")
1777
+ }),
1778
+ handler: async (input, ctx) => {
1779
+ const accountName = ctx.resolveAccount(input.account);
1780
+ const client = await ctx.imap.acquire(accountName);
1781
+ const mb = await client.mailboxOpen(input.fromMailbox);
1782
+ if (!mb) throw new MailboxNotFoundError(input.fromMailbox);
1783
+ let targetUid;
1784
+ try {
1785
+ const result = await client.messageMove(input.uid, input.toMailbox);
1786
+ if (result && result.uidMap) {
1787
+ targetUid = result.uidMap.get(input.uid);
1788
+ }
1789
+ } catch (err) {
1790
+ try {
1791
+ const copyResult = await client.messageCopy(input.uid, input.toMailbox);
1792
+ if (copyResult && copyResult.uidMap) {
1793
+ targetUid = copyResult.uidMap.get(input.uid);
1794
+ }
1795
+ await client.messageDelete(input.uid);
1796
+ } catch (fallbackErr) {
1797
+ throw new ImapProtocolError(`Failed to move message: ${fallbackErr}`);
1798
+ }
1799
+ }
1800
+ return {
1801
+ fromMailbox: input.fromMailbox,
1802
+ toMailbox: input.toMailbox,
1803
+ sourceUid: input.uid,
1804
+ ...targetUid !== void 0 ? { targetUid } : {}
1805
+ };
1806
+ }
1807
+ });
1808
+
1809
+ // src/tools/imap-mailbox/create-mailbox.ts
1810
+ import { z as z22 } from "zod";
1811
+ var create_mailbox_default = defineTool({
1812
+ name: "imap_create_mailbox",
1813
+ description: "Neuen Folder anlegen (CREATE)",
1814
+ category: "imap-mailbox",
1815
+ inputSchema: z22.object({
1816
+ path: z22.string().min(1).describe("Mailbox path to create"),
1817
+ account: z22.string().optional().describe("Account name (default: default_account)")
1818
+ }),
1819
+ handler: async (input, ctx) => {
1820
+ const accountName = ctx.resolveAccount(input.account);
1821
+ const client = await ctx.imap.acquire(accountName);
1822
+ try {
1823
+ await client.mailboxCreate(input.path);
1824
+ } catch (err) {
1825
+ throw new ImapProtocolError(`Failed to create mailbox: ${err}`);
1826
+ }
1827
+ return { path: input.path, created: true };
1828
+ }
1829
+ });
1830
+
1831
+ // src/tools/imap-mailbox/delete-mailbox.ts
1832
+ import { z as z23 } from "zod";
1833
+ var delete_mailbox_default = defineTool({
1834
+ name: "imap_delete_mailbox",
1835
+ description: "Folder l\xF6schen (DELETE)",
1836
+ category: "imap-mailbox",
1837
+ inputSchema: z23.object({
1838
+ path: z23.string().min(1).describe("Mailbox path to delete"),
1839
+ account: z23.string().optional().describe("Account name (default: default_account)")
1840
+ }),
1841
+ handler: async (input, ctx) => {
1842
+ const accountName = ctx.resolveAccount(input.account);
1843
+ const client = await ctx.imap.acquire(accountName);
1844
+ try {
1845
+ await client.mailboxDelete(input.path);
1846
+ } catch (err) {
1847
+ throw new ImapProtocolError(`Failed to delete mailbox: ${err}`);
1848
+ }
1849
+ return { path: input.path, deleted: true };
1850
+ }
1851
+ });
1852
+
1853
+ // src/tools/imap-mailbox/rename-mailbox.ts
1854
+ import { z as z24 } from "zod";
1855
+ var rename_mailbox_default = defineTool({
1856
+ name: "imap_rename_mailbox",
1857
+ description: "Folder umbenennen (RENAME)",
1858
+ category: "imap-mailbox",
1859
+ inputSchema: z24.object({
1860
+ from: z24.string().min(1).describe("Current mailbox path"),
1861
+ to: z24.string().min(1).describe("New mailbox path"),
1862
+ account: z24.string().optional().describe("Account name (default: default_account)")
1863
+ }),
1864
+ handler: async (input, ctx) => {
1865
+ const accountName = ctx.resolveAccount(input.account);
1866
+ const client = await ctx.imap.acquire(accountName);
1867
+ try {
1868
+ await client.mailboxRename(input.from, input.to);
1869
+ } catch (err) {
1870
+ throw new ImapProtocolError(`Failed to rename mailbox: ${err}`);
1871
+ }
1872
+ return { from: input.from, to: input.to, renamed: true };
1873
+ }
1874
+ });
1875
+
1876
+ // src/tools/imap-mailbox/subscribe-mailbox.ts
1877
+ import { z as z25 } from "zod";
1878
+ var subscribe_mailbox_default = defineTool({
1879
+ name: "imap_subscribe_mailbox",
1880
+ description: "Folder abonnieren (SUBSCRIBE)",
1881
+ category: "imap-mailbox",
1882
+ inputSchema: z25.object({
1883
+ path: z25.string().min(1).describe("Mailbox path to subscribe"),
1884
+ account: z25.string().optional().describe("Account name (default: default_account)")
1885
+ }),
1886
+ handler: async (input, ctx) => {
1887
+ const accountName = ctx.resolveAccount(input.account);
1888
+ const client = await ctx.imap.acquire(accountName);
1889
+ try {
1890
+ await client.mailboxSubscribe(input.path);
1891
+ } catch (err) {
1892
+ throw new ImapProtocolError(`Failed to subscribe to mailbox: ${err}`);
1893
+ }
1894
+ return { path: input.path, subscribed: true };
1895
+ }
1896
+ });
1897
+
1898
+ // src/tools/imap-mailbox/unsubscribe-mailbox.ts
1899
+ import { z as z26 } from "zod";
1900
+ var unsubscribe_mailbox_default = defineTool({
1901
+ name: "imap_unsubscribe_mailbox",
1902
+ description: "Folder-Abo entfernen (UNSUBSCRIBE)",
1903
+ category: "imap-mailbox",
1904
+ inputSchema: z26.object({
1905
+ path: z26.string().min(1).describe("Mailbox path to unsubscribe"),
1906
+ account: z26.string().optional().describe("Account name (default: default_account)")
1907
+ }),
1908
+ handler: async (input, ctx) => {
1909
+ const accountName = ctx.resolveAccount(input.account);
1910
+ const client = await ctx.imap.acquire(accountName);
1911
+ try {
1912
+ await client.mailboxUnsubscribe(input.path);
1913
+ } catch (err) {
1914
+ throw new ImapProtocolError(`Failed to unsubscribe from mailbox: ${err}`);
1915
+ }
1916
+ return { path: input.path, subscribed: false };
1917
+ }
1918
+ });
1919
+
1920
+ // src/tools/smtp/forward.ts
1921
+ import { z as z27 } from "zod";
1922
+
1923
+ // src/lib/sent-folder.ts
1924
+ var SENT_FALLBACKS = [
1925
+ "Sent",
1926
+ "Sent Items",
1927
+ "[Gmail]/Sent Mail",
1928
+ "[Gmail]/Gesendet",
1929
+ "INBOX.Sent",
1930
+ "Gesendet",
1931
+ "Gesendete Elemente"
1932
+ ];
1933
+ async function resolveSentFolder(client) {
1934
+ const mailboxes = await client.list();
1935
+ for (const mb of mailboxes) {
1936
+ if (mb.specialUse === "\\Sent" || mb.specialUse === "Sent") {
1937
+ return mb.path;
1938
+ }
1939
+ if (mb.flags.has("\\Sent")) {
1940
+ return mb.path;
1941
+ }
1942
+ }
1943
+ for (const name of SENT_FALLBACKS) {
1944
+ try {
1945
+ const status = await client.status(name, { messages: true });
1946
+ if (status) return name;
1947
+ } catch {
1948
+ }
1949
+ }
1950
+ return void 0;
1951
+ }
1952
+
1953
+ // src/tools/smtp/forward.ts
1954
+ var forward_default = defineTool({
1955
+ name: "smtp_forward",
1956
+ description: "Weiterleiten (Original quoted oder als Attachment) + Sent-Ablage",
1957
+ category: "smtp",
1958
+ inputSchema: z27.object({
1959
+ to: z27.union([z27.string(), z27.array(z27.object({ address: z27.string(), name: z27.string().optional() }))]).describe("Forward recipient(s)"),
1960
+ cc: z27.union([z27.string(), z27.array(z27.object({ address: z27.string(), name: z27.string().optional() }))]).optional().describe("CC recipient(s)"),
1961
+ bcc: z27.union([z27.string(), z27.array(z27.object({ address: z27.string(), name: z27.string().optional() }))]).optional().describe("BCC recipient(s)"),
1962
+ subject: z27.string().optional().describe("Forward subject (default: Fwd: <original subject>)"),
1963
+ text: z27.string().optional().describe("Additional plain text body"),
1964
+ html: z27.string().optional().describe("Additional HTML body"),
1965
+ originalMailbox: z27.string().min(1).describe("Mailbox containing the original message"),
1966
+ originalUid: z27.number().int().positive().describe("UID of the original message"),
1967
+ forwardAsAttachment: z27.boolean().default(false).describe("Forward as attachment (RFC-822) (default: false)"),
1968
+ account: z27.string().optional().describe("Account name (default: default_account)"),
1969
+ saveToSent: z27.boolean().default(true).describe("Save copy to Sent folder (default: true)")
1970
+ }),
1971
+ handler: async (input, ctx) => {
1972
+ const accountName = ctx.resolveAccount(input.account);
1973
+ const transport = await ctx.smtp.acquire(accountName);
1974
+ const accConfig = ctx.config.accounts.get(accountName);
1975
+ const imapClient = await ctx.imap.acquire(accountName);
1976
+ const mb = await imapClient.mailboxOpen(input.originalMailbox);
1977
+ if (!mb) throw new MailboxNotFoundError(input.originalMailbox);
1978
+ const original = await imapClient.fetchOne(input.originalUid, {
1979
+ uid: true,
1980
+ envelope: true,
1981
+ source: true
1982
+ });
1983
+ if (!original) throw new UidNotFoundError(input.originalUid, input.originalMailbox);
1984
+ const enc = original.envelope;
1985
+ if (!enc) throw new Error("Original message has no envelope");
1986
+ const subject = input.subject ?? (enc.subject?.startsWith("Fwd:") ? enc.subject : `Fwd: ${enc.subject ?? ""}`);
1987
+ const mailOptions = {
1988
+ from: accConfig.from_name ? `"${accConfig.from_name}" <${accConfig.user}>` : accConfig.user,
1989
+ to: typeof input.to === "string" ? input.to : input.to.map((a) => a.name ? `"${a.name}" <${a.address}>` : a.address).join(", "),
1990
+ cc: input.cc ? typeof input.cc === "string" ? input.cc : input.cc.map((a) => a.name ? `"${a.name}" <${a.address}>` : a.address).join(", ") : void 0,
1991
+ bcc: input.bcc ? typeof input.bcc === "string" ? input.bcc : input.bcc.map((a) => a.name ? `"${a.name}" <${a.address}>` : a.address).join(", ") : void 0,
1992
+ subject
1993
+ };
1994
+ if (input.forwardAsAttachment) {
1995
+ const rawSource = original.source?.toString() ?? "";
1996
+ mailOptions.attachments = [
1997
+ {
1998
+ filename: `forwarded-${input.originalUid}.eml`,
1999
+ content: rawSource,
2000
+ contentType: "message/rfc822"
2001
+ }
2002
+ ];
2003
+ mailOptions.text = input.text ?? "";
2004
+ mailOptions.html = input.html ?? "";
2005
+ } else {
2006
+ const fromStr = enc.from ? Array.isArray(enc.from) ? enc.from.map((f) => f.address ?? "").join(", ") : "" : "";
2007
+ const toStr = enc.to ? Array.isArray(enc.to) ? enc.to.map((t) => t.address ?? "").join(", ") : "" : "";
2008
+ const dateStr = enc.date ? enc.date.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
2009
+ const quotedBody = original.source?.toString() ?? "";
2010
+ const quotedText = input.text ? `${input.text}
2011
+
2012
+ -------- Forwarded Message --------
2013
+ Subject: ${enc.subject ?? ""}
2014
+ Date: ${dateStr}
2015
+ From: ${fromStr}
2016
+ To: ${toStr}
2017
+
2018
+ ${quotedBody}` : `-------- Forwarded Message --------
2019
+ Subject: ${enc.subject ?? ""}
2020
+ Date: ${dateStr}
2021
+ From: ${fromStr}
2022
+ To: ${toStr}
2023
+
2024
+ ${quotedBody}`;
2025
+ mailOptions.text = quotedText;
2026
+ }
2027
+ let info;
2028
+ try {
2029
+ info = await transport.sendMail(mailOptions);
2030
+ } catch (err) {
2031
+ throw new SmtpRelayError(`Failed to forward email: ${err}`);
2032
+ }
2033
+ let savedToSent = false;
2034
+ let sentMailbox;
2035
+ let sentSaveError;
2036
+ if (input.saveToSent) {
2037
+ try {
2038
+ const sentFolder = await resolveSentFolder(imapClient);
2039
+ if (sentFolder) {
2040
+ sentMailbox = sentFolder;
2041
+ const rawLines = [];
2042
+ rawLines.push(`From: ${mailOptions.from}`);
2043
+ rawLines.push(`To: ${mailOptions.to}`);
2044
+ if (mailOptions.cc) rawLines.push(`Cc: ${mailOptions.cc}`);
2045
+ rawLines.push(`Subject: ${subject}`);
2046
+ rawLines.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
2047
+ rawLines.push("MIME-Version: 1.0");
2048
+ rawLines.push("Content-Type: text/plain; charset=UTF-8");
2049
+ rawLines.push("");
2050
+ rawLines.push(mailOptions.text ?? "");
2051
+ await imapClient.append(sentFolder, rawLines.join("\r\n"));
2052
+ savedToSent = true;
2053
+ } else {
2054
+ sentSaveError = "No Sent folder found";
2055
+ }
2056
+ } catch (err) {
2057
+ sentSaveError = `Failed to save to Sent: ${err}`;
2058
+ }
2059
+ }
2060
+ return {
2061
+ messageId: info.messageId ?? "",
2062
+ accepted: info.accepted ?? [],
2063
+ rejected: info.rejected ?? [],
2064
+ response: info.response ?? "",
2065
+ savedToSent,
2066
+ ...sentMailbox ? { sentMailbox } : {},
2067
+ ...sentSaveError ? { sentSaveError } : {}
2068
+ };
2069
+ }
2070
+ });
2071
+
2072
+ // src/tools/smtp/reply.ts
2073
+ import { z as z28 } from "zod";
2074
+ var reply_default = defineTool({
2075
+ name: "smtp_reply",
2076
+ description: "Antwort mit korrekter In-Reply-To/References-Kette + Sent-Ablage",
2077
+ category: "smtp",
2078
+ inputSchema: z28.object({
2079
+ to: z28.union([z28.string(), z28.array(z28.object({ address: z28.string(), name: z28.string().optional() }))]).optional().describe("Reply recipient(s) (default: from of original)"),
2080
+ cc: z28.union([z28.string(), z28.array(z28.object({ address: z28.string(), name: z28.string().optional() }))]).optional().describe("CC recipient(s)"),
2081
+ subject: z28.string().optional().describe("Reply subject (default: Re: <original subject>)"),
2082
+ text: z28.string().optional().describe("Plain text body"),
2083
+ html: z28.string().optional().describe("HTML body"),
2084
+ originalMailbox: z28.string().min(1).describe("Mailbox containing the original message"),
2085
+ originalUid: z28.number().int().positive().describe("UID of the original message"),
2086
+ replyAll: z28.boolean().default(false).describe("Reply to all recipients (default: false)"),
2087
+ account: z28.string().optional().describe("Account name (default: default_account)"),
2088
+ saveToSent: z28.boolean().default(true).describe("Save copy to Sent folder (default: true)")
2089
+ }),
2090
+ handler: async (input, ctx) => {
2091
+ const accountName = ctx.resolveAccount(input.account);
2092
+ const transport = await ctx.smtp.acquire(accountName);
2093
+ const accConfig = ctx.config.accounts.get(accountName);
2094
+ const imapClient = await ctx.imap.acquire(accountName);
2095
+ const mb = await imapClient.mailboxOpen(input.originalMailbox);
2096
+ if (!mb) throw new MailboxNotFoundError(input.originalMailbox);
2097
+ const original = await imapClient.fetchOne(input.originalUid, {
2098
+ uid: true,
2099
+ envelope: true,
2100
+ headers: true
2101
+ });
2102
+ if (!original) throw new UidNotFoundError(input.originalUid, input.originalMailbox);
2103
+ const enc = original.envelope;
2104
+ if (!enc) throw new Error("Original message has no envelope");
2105
+ const messageId = enc.messageId ?? "";
2106
+ const rawHeaders = original.headers?.toString() ?? "";
2107
+ const refMatch = rawHeaders.match(/^references:\s*(.*)$/im);
2108
+ const existingRefs = refMatch ? refMatch[1]?.trim().split(/\s+/) ?? [] : [];
2109
+ const references = [...existingRefs, messageId].filter(Boolean);
2110
+ const inReplyTo = messageId;
2111
+ let to = input.to;
2112
+ const cc = input.cc;
2113
+ if (!to) {
2114
+ const fromAddr = enc.from?.[0];
2115
+ to = fromAddr?.address ?? "";
2116
+ }
2117
+ if (input.replyAll && enc.to) {
2118
+ }
2119
+ const subject = input.subject ?? (enc.subject?.startsWith("Re:") ? enc.subject : `Re: ${enc.subject ?? ""}`);
2120
+ const mailOptions = {
2121
+ from: accConfig.from_name ? `"${accConfig.from_name}" <${accConfig.user}>` : accConfig.user,
2122
+ to: typeof to === "string" ? to : Array.isArray(to) ? to.map((a) => a.name ? `"${a.name}" <${a.address}>` : a.address).join(", ") : void 0,
2123
+ cc: typeof cc === "string" ? cc : Array.isArray(cc) ? cc.map((a) => a.name ? `"${a.name}" <${a.address}>` : a.address).join(", ") : void 0,
2124
+ subject,
2125
+ text: input.text,
2126
+ html: input.html,
2127
+ inReplyTo,
2128
+ references: references.join(" ")
2129
+ };
2130
+ let info;
2131
+ try {
2132
+ info = await transport.sendMail(mailOptions);
2133
+ } catch (err) {
2134
+ throw new SmtpRelayError(`Failed to send reply: ${err}`);
2135
+ }
2136
+ let savedToSent = false;
2137
+ let sentMailbox;
2138
+ let sentSaveError;
2139
+ if (input.saveToSent) {
2140
+ try {
2141
+ const sentFolder = await resolveSentFolder(imapClient);
2142
+ if (sentFolder) {
2143
+ sentMailbox = sentFolder;
2144
+ const rawLines = [];
2145
+ rawLines.push(`From: ${mailOptions.from}`);
2146
+ rawLines.push(`To: ${mailOptions.to}`);
2147
+ if (mailOptions.cc) rawLines.push(`Cc: ${mailOptions.cc}`);
2148
+ rawLines.push(`Subject: ${subject}`);
2149
+ rawLines.push(`In-Reply-To: ${inReplyTo}`);
2150
+ rawLines.push(`References: ${references.join(" ")}`);
2151
+ rawLines.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
2152
+ rawLines.push("MIME-Version: 1.0");
2153
+ rawLines.push("Content-Type: text/plain; charset=UTF-8");
2154
+ rawLines.push("");
2155
+ rawLines.push(input.text ?? input.html ?? "");
2156
+ await imapClient.append(sentFolder, rawLines.join("\r\n"));
2157
+ savedToSent = true;
2158
+ } else {
2159
+ sentSaveError = "No Sent folder found";
2160
+ }
2161
+ } catch (err) {
2162
+ sentSaveError = `Failed to save to Sent: ${err}`;
2163
+ }
2164
+ }
2165
+ return {
2166
+ messageId: info.messageId ?? "",
2167
+ inReplyTo,
2168
+ accepted: info.accepted ?? [],
2169
+ rejected: info.rejected ?? [],
2170
+ response: info.response ?? "",
2171
+ savedToSent,
2172
+ ...sentMailbox ? { sentMailbox } : {},
2173
+ ...sentSaveError ? { sentSaveError } : {}
2174
+ };
2175
+ }
2176
+ });
2177
+
2178
+ // src/tools/smtp/send-raw.ts
2179
+ import { z as z29 } from "zod";
2180
+ var send_raw_default = defineTool({
2181
+ name: "smtp_send_raw",
2182
+ description: "Vor-formatierte RFC-822 senden (Power-User) + Sent-Ablage",
2183
+ category: "smtp",
2184
+ inputSchema: z29.object({
2185
+ raw: z29.string().min(1).describe("Raw RFC-822 message content to send"),
2186
+ account: z29.string().optional().describe("Account name (default: default_account)"),
2187
+ saveToSent: z29.boolean().default(true).describe("Save copy to Sent folder (default: true)")
2188
+ }),
2189
+ handler: async (input, ctx) => {
2190
+ const accountName = ctx.resolveAccount(input.account);
2191
+ const transport = await ctx.smtp.acquire(accountName);
2192
+ const mailOptions = {
2193
+ raw: input.raw
2194
+ };
2195
+ let info;
2196
+ try {
2197
+ info = await transport.sendMail(mailOptions);
2198
+ } catch (err) {
2199
+ throw new SmtpRelayError(`Failed to send raw email: ${err}`);
2200
+ }
2201
+ let savedToSent = false;
2202
+ let sentMailbox;
2203
+ let sentSaveError;
2204
+ if (input.saveToSent) {
2205
+ try {
2206
+ const imapClient = await ctx.imap.acquire(accountName);
2207
+ const sentFolder = await resolveSentFolder(imapClient);
2208
+ if (sentFolder) {
2209
+ sentMailbox = sentFolder;
2210
+ await imapClient.append(sentFolder, input.raw);
2211
+ savedToSent = true;
2212
+ } else {
2213
+ sentSaveError = "No Sent folder found";
2214
+ }
2215
+ } catch (err) {
2216
+ sentSaveError = `Failed to save to Sent: ${err}`;
2217
+ }
2218
+ }
2219
+ return {
2220
+ messageId: info.messageId ?? "",
2221
+ accepted: info.accepted ?? [],
2222
+ rejected: info.rejected ?? [],
2223
+ response: info.response ?? "",
2224
+ savedToSent,
2225
+ ...sentMailbox ? { sentMailbox } : {},
2226
+ ...sentSaveError ? { sentSaveError } : {}
2227
+ };
2228
+ }
2229
+ });
2230
+
2231
+ // src/tools/smtp/send.ts
2232
+ import { z as z30 } from "zod";
2233
+ function buildAddresses(list) {
2234
+ if (!list) return void 0;
2235
+ if (typeof list === "string") return list;
2236
+ return list.map((a) => a.name ? `"${a.name}" <${a.address}>` : a.address);
2237
+ }
2238
+ var send_default = defineTool({
2239
+ name: "smtp_send",
2240
+ description: "Mail senden + optionale Sent-Ablage",
2241
+ category: "smtp",
2242
+ inputSchema: z30.object({
2243
+ to: z30.union([z30.string(), z30.array(z30.object({ address: z30.string(), name: z30.string().optional() }))]).describe("Recipient(s)"),
2244
+ cc: z30.union([z30.string(), z30.array(z30.object({ address: z30.string(), name: z30.string().optional() }))]).optional().describe("CC recipient(s)"),
2245
+ bcc: z30.union([z30.string(), z30.array(z30.object({ address: z30.string(), name: z30.string().optional() }))]).optional().describe("BCC recipient(s)"),
2246
+ subject: z30.string().min(1).describe("Email subject"),
2247
+ text: z30.string().optional().describe("Plain text body"),
2248
+ html: z30.string().optional().describe("HTML body"),
2249
+ attachments: z30.array(
2250
+ z30.object({
2251
+ filename: z30.string().optional(),
2252
+ content: z30.string().optional(),
2253
+ path: z30.string().optional(),
2254
+ contentType: z30.string().optional(),
2255
+ cid: z30.string().optional()
2256
+ })
2257
+ ).optional().describe("Attachments"),
2258
+ account: z30.string().optional().describe("Account name (default: default_account)"),
2259
+ saveToSent: z30.boolean().default(true).describe("Save copy to Sent folder (default: true)")
2260
+ }),
2261
+ handler: async (input, ctx) => {
2262
+ const accountName = ctx.resolveAccount(input.account);
2263
+ const transport = await ctx.smtp.acquire(accountName);
2264
+ const accConfig = ctx.config.accounts.get(accountName);
2265
+ const mailOptions = {
2266
+ from: accConfig.from_name ? `"${accConfig.from_name}" <${accConfig.user}>` : accConfig.user,
2267
+ to: buildAddresses(input.to),
2268
+ cc: buildAddresses(
2269
+ input.cc
2270
+ ),
2271
+ bcc: buildAddresses(
2272
+ input.bcc
2273
+ ),
2274
+ subject: input.subject,
2275
+ text: input.text,
2276
+ html: input.html,
2277
+ attachments: input.attachments
2278
+ };
2279
+ let info;
2280
+ try {
2281
+ info = await transport.sendMail(mailOptions);
2282
+ } catch (err) {
2283
+ throw new SmtpRelayError(`Failed to send email: ${err}`);
2284
+ }
2285
+ let savedToSent = false;
2286
+ let sentMailbox;
2287
+ let sentSaveError;
2288
+ if (input.saveToSent) {
2289
+ try {
2290
+ const imapClient = await ctx.imap.acquire(accountName);
2291
+ const sentFolder = await resolveSentFolder(imapClient);
2292
+ if (sentFolder) {
2293
+ sentMailbox = sentFolder;
2294
+ const raw = await buildRfc822(
2295
+ {
2296
+ user: accConfig.user,
2297
+ ...accConfig.from_name ? { from_name: accConfig.from_name } : {}
2298
+ },
2299
+ mailOptions
2300
+ );
2301
+ await imapClient.append(sentFolder, raw);
2302
+ savedToSent = true;
2303
+ } else {
2304
+ sentSaveError = "No Sent folder found";
2305
+ }
2306
+ } catch (err) {
2307
+ sentSaveError = `Failed to save to Sent: ${err}`;
2308
+ }
2309
+ }
2310
+ return {
2311
+ messageId: info.messageId ?? "",
2312
+ accepted: info.accepted ?? [],
2313
+ rejected: info.rejected ?? [],
2314
+ response: info.response ?? "",
2315
+ savedToSent,
2316
+ ...sentMailbox ? { sentMailbox } : {},
2317
+ ...sentSaveError ? { sentSaveError } : {}
2318
+ };
2319
+ }
2320
+ });
2321
+ async function buildRfc822(acc, opts) {
2322
+ const lines = [];
2323
+ const from = typeof opts.from === "string" ? opts.from : acc.from_name ? `"${acc.from_name}" <${acc.user}>` : acc.user;
2324
+ const to = typeof opts.to === "string" ? opts.to : Array.isArray(opts.to) ? opts.to.join(", ") : "";
2325
+ const date = (/* @__PURE__ */ new Date()).toUTCString();
2326
+ lines.push(`From: ${from}`);
2327
+ lines.push(`To: ${to}`);
2328
+ if (opts.cc) {
2329
+ const ccStr = typeof opts.cc === "string" ? opts.cc : opts.cc.map((a) => a.address).join(", ");
2330
+ lines.push(`Cc: ${ccStr}`);
2331
+ }
2332
+ lines.push(`Subject: ${opts.subject ?? ""}`);
2333
+ lines.push(`Date: ${date}`);
2334
+ lines.push("MIME-Version: 1.0");
2335
+ lines.push("Content-Type: text/plain; charset=UTF-8");
2336
+ lines.push("Content-Transfer-Encoding: 7bit");
2337
+ lines.push("");
2338
+ lines.push(opts.text ? opts.text : opts.html ? opts.html : "");
2339
+ return lines.join("\r\n");
2340
+ }
2341
+
2342
+ // src/tools/smtp/verify-connection.ts
2343
+ import { z as z31 } from "zod";
2344
+ var verify_connection_default = defineTool({
2345
+ name: "smtp_verify_connection",
2346
+ description: "SMTP-Connection-Health-Check (EHLO, AUTH)",
2347
+ category: "smtp",
2348
+ inputSchema: z31.object({
2349
+ account: z31.string().optional().describe("Account name (default: default_account)")
2350
+ }),
2351
+ handler: async (input, ctx) => {
2352
+ const accountName = ctx.resolveAccount(input.account);
2353
+ const transport = await ctx.smtp.acquire(accountName);
2354
+ const accConfig = ctx.config.accounts.get(accountName);
2355
+ const start = Date.now();
2356
+ try {
2357
+ const ok = await transport.verify();
2358
+ const latencyMs = Date.now() - start;
2359
+ return {
2360
+ ok,
2361
+ host: accConfig.smtp_host ?? "unknown",
2362
+ port: accConfig.smtp_port,
2363
+ tls: accConfig.smtp_tls === "implicit" ? "implicit" : accConfig.smtp_tls === "starttls" ? "starttls" : "none",
2364
+ latencyMs
2365
+ };
2366
+ } catch (err) {
2367
+ const error = err;
2368
+ if (error.message?.includes("auth") || error.message?.includes("login")) {
2369
+ throw new AuthError(`SMTP authentication failed: ${error.message}`);
2370
+ }
2371
+ throw new SmtpRelayError(`SMTP connection failed: ${error.message}`);
2372
+ }
2373
+ }
2374
+ });
2375
+
2376
+ // src/tools/account/add.ts
2377
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2378
+ import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
2379
+ import { z as z32 } from "zod";
2380
+ var add_default = defineTool({
2381
+ name: "account_add",
2382
+ description: "Neuen Account zur Config hinzuf\xFCgen",
2383
+ category: "account",
2384
+ inputSchema: z32.object({
2385
+ name: z32.string().min(1).describe("Account name"),
2386
+ user: z32.string().min(1).describe("Email address / IMAP login"),
2387
+ pass: z32.string().min(1).describe("Password or app password"),
2388
+ imap_host: z32.string().optional().describe("IMAP host (auto-detect if omitted)"),
2389
+ imap_port: z32.number().int().positive().optional().describe("IMAP port (default: 993)"),
2390
+ imap_tls: z32.enum(["implicit", "starttls", "none"]).optional().describe("IMAP TLS mode"),
2391
+ smtp_host: z32.string().optional().describe("SMTP host (auto-detect if omitted)"),
2392
+ smtp_port: z32.number().int().positive().optional().describe("SMTP port (default: 465)"),
2393
+ smtp_tls: z32.enum(["implicit", "starttls", "none"]).optional().describe("SMTP TLS mode"),
2394
+ from_name: z32.string().optional().describe("Display name for sent emails"),
2395
+ verify_tls: z32.boolean().optional().describe("Verify TLS certificate (default: true)")
2396
+ }),
2397
+ handler: async (input, ctx) => {
2398
+ if (ctx.config.mode !== "config-file" || !ctx.config.configPath) {
2399
+ throw new ConfigError(
2400
+ "Account mutations require a config file (multi-account mode). Use --config=<path> or set up a config file."
2401
+ );
2402
+ }
2403
+ const configPath = ctx.config.configPath;
2404
+ let raw;
2405
+ try {
2406
+ raw = readFileSync2(configPath, "utf8");
2407
+ } catch {
2408
+ throw new ConfigError(`Cannot read config file: ${configPath}`);
2409
+ }
2410
+ const parsed = parseToml2(raw);
2411
+ const accounts = parsed.accounts ?? [];
2412
+ if (accounts.some((a) => a.name === input.name)) {
2413
+ throw new ConfigError(`Account "${input.name}" already exists`);
2414
+ }
2415
+ const newAccount = {
2416
+ name: input.name,
2417
+ user: input.user,
2418
+ pass: input.pass
2419
+ };
2420
+ if (input.imap_host) newAccount.imap_host = input.imap_host;
2421
+ if (input.imap_port) newAccount.imap_port = input.imap_port;
2422
+ if (input.imap_tls) newAccount.imap_tls = input.imap_tls;
2423
+ if (input.smtp_host) newAccount.smtp_host = input.smtp_host;
2424
+ if (input.smtp_port) newAccount.smtp_port = input.smtp_port;
2425
+ if (input.smtp_tls) newAccount.smtp_tls = input.smtp_tls;
2426
+ if (input.from_name) newAccount.from_name = input.from_name;
2427
+ if (input.verify_tls !== void 0) newAccount.verify_tls = input.verify_tls;
2428
+ accounts.push(newAccount);
2429
+ parsed.accounts = accounts;
2430
+ const tomlStr = stringifyToml(parsed);
2431
+ writeFileSync2(configPath, tomlStr, "utf8");
2432
+ ctx.logger.info({ account: input.name }, "Account added to config file");
2433
+ return { name: input.name, created: true, configPath };
2434
+ }
2435
+ });
2436
+
2437
+ // src/tools/account/delete.ts
2438
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2439
+ import { parse as parseToml3, stringify as stringifyToml2 } from "smol-toml";
2440
+ import { z as z33 } from "zod";
2441
+ var delete_default = defineTool({
2442
+ name: "account_delete",
2443
+ description: "Account aus Config entfernen",
2444
+ category: "account",
2445
+ inputSchema: z33.object({
2446
+ name: z33.string().min(1).describe("Account name to delete")
2447
+ }),
2448
+ handler: async (input, ctx) => {
2449
+ if (ctx.config.mode !== "config-file" || !ctx.config.configPath) {
2450
+ throw new ConfigError("Account mutations require a config file (multi-account mode).");
2451
+ }
2452
+ const configPath = ctx.config.configPath;
2453
+ let raw;
2454
+ try {
2455
+ raw = readFileSync3(configPath, "utf8");
2456
+ } catch {
2457
+ throw new ConfigError(`Cannot read config file: ${configPath}`);
2458
+ }
2459
+ const parsed = parseToml3(raw);
2460
+ const accounts = parsed.accounts ?? [];
2461
+ const idx = accounts.findIndex((a) => a.name === input.name);
2462
+ if (idx === -1) {
2463
+ throw new AccountNotFoundError(input.name);
2464
+ }
2465
+ accounts.splice(idx, 1);
2466
+ parsed.accounts = accounts;
2467
+ if (parsed.default_account === input.name) {
2468
+ if (accounts.length > 0) {
2469
+ parsed.default_account = accounts[0]?.name;
2470
+ } else {
2471
+ parsed.default_account = void 0;
2472
+ }
2473
+ }
2474
+ const tomlStr = stringifyToml2(parsed);
2475
+ writeFileSync3(configPath, tomlStr, "utf8");
2476
+ ctx.logger.info({ account: input.name }, "Account deleted");
2477
+ return { name: input.name, deleted: true };
2478
+ }
2479
+ });
2480
+
2481
+ // src/tools/account/list.ts
2482
+ import { z as z34 } from "zod";
2483
+ var list_default = defineTool({
2484
+ name: "account_list",
2485
+ description: "Konfigurierte Accounts auflisten (Credentials masked)",
2486
+ category: "account",
2487
+ inputSchema: z34.object({}),
2488
+ handler: async (_input, ctx) => {
2489
+ const accounts = [...ctx.config.accounts.entries()].map(([name, acc]) => ({
2490
+ name,
2491
+ user: acc.user,
2492
+ imapHost: acc.imap_host ?? "auto-detect",
2493
+ smtpHost: acc.smtp_host ?? "auto-detect"
2494
+ }));
2495
+ return {
2496
+ defaultAccount: ctx.config.defaultAccount,
2497
+ accounts,
2498
+ mode: ctx.config.mode
2499
+ };
2500
+ }
2501
+ });
2502
+
2503
+ // src/tools/account/update.ts
2504
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
2505
+ import { parse as parseToml4, stringify as stringifyToml3 } from "smol-toml";
2506
+ import { z as z35 } from "zod";
2507
+ var update_default = defineTool({
2508
+ name: "account_update",
2509
+ description: "Bestehenden Account modifizieren",
2510
+ category: "account",
2511
+ inputSchema: z35.object({
2512
+ name: z35.string().min(1).describe("Account name to modify"),
2513
+ user: z35.string().optional().describe("New email address / IMAP login"),
2514
+ pass: z35.string().optional().describe("New password or app password"),
2515
+ imap_host: z35.string().optional().describe("New IMAP host"),
2516
+ imap_port: z35.number().int().positive().optional().describe("New IMAP port"),
2517
+ imap_tls: z35.enum(["implicit", "starttls", "none"]).optional().describe("New IMAP TLS mode"),
2518
+ smtp_host: z35.string().optional().describe("New SMTP host"),
2519
+ smtp_port: z35.number().int().positive().optional().describe("New SMTP port"),
2520
+ smtp_tls: z35.enum(["implicit", "starttls", "none"]).optional().describe("New SMTP TLS mode"),
2521
+ from_name: z35.string().optional().describe("New display name"),
2522
+ verify_tls: z35.boolean().optional().describe("New TLS verification setting")
2523
+ }),
2524
+ handler: async (input, ctx) => {
2525
+ if (ctx.config.mode !== "config-file" || !ctx.config.configPath) {
2526
+ throw new ConfigError("Account mutations require a config file (multi-account mode).");
2527
+ }
2528
+ const configPath = ctx.config.configPath;
2529
+ let raw;
2530
+ try {
2531
+ raw = readFileSync4(configPath, "utf8");
2532
+ } catch {
2533
+ throw new ConfigError(`Cannot read config file: ${configPath}`);
2534
+ }
2535
+ const parsed = parseToml4(raw);
2536
+ const accounts = parsed.accounts ?? [];
2537
+ const account = accounts.find((a) => a.name === input.name);
2538
+ if (!account) {
2539
+ throw new AccountNotFoundError(input.name);
2540
+ }
2541
+ const changedFields = [];
2542
+ const updatable = [
2543
+ ["user", input.user],
2544
+ ["pass", input.pass],
2545
+ ["imap_host", input.imap_host],
2546
+ ["imap_port", input.imap_port],
2547
+ ["imap_tls", input.imap_tls],
2548
+ ["smtp_host", input.smtp_host],
2549
+ ["smtp_port", input.smtp_port],
2550
+ ["smtp_tls", input.smtp_tls],
2551
+ ["from_name", input.from_name]
2552
+ ];
2553
+ for (const [key, value] of updatable) {
2554
+ if (value !== void 0) {
2555
+ account[key] = value;
2556
+ changedFields.push(key);
2557
+ }
2558
+ }
2559
+ if (input.verify_tls !== void 0) {
2560
+ account.verify_tls = input.verify_tls;
2561
+ changedFields.push("verify_tls");
2562
+ }
2563
+ if (changedFields.length === 0) {
2564
+ throw new ConfigError("No fields to update");
2565
+ }
2566
+ parsed.accounts = accounts;
2567
+ const tomlStr = stringifyToml3(parsed);
2568
+ writeFileSync4(configPath, tomlStr, "utf8");
2569
+ ctx.logger.info({ account: input.name, changedFields }, "Account updated");
2570
+ return { name: input.name, updated: true, changedFields };
2571
+ }
2572
+ });
2573
+
2574
+ // src/tools/meta/health.ts
2575
+ import { z as z36 } from "zod";
2576
+ var health_default = defineTool({
2577
+ name: "meta_health",
2578
+ description: "IMAP + SMTP Erreichbarkeit, Latenz, Capabilities",
2579
+ category: "meta",
2580
+ inputSchema: z36.object({
2581
+ account: z36.string().optional().describe("Account name (default: default_account)")
2582
+ }),
2583
+ handler: async (input, ctx) => {
2584
+ const accountName = ctx.resolveAccount(input.account);
2585
+ const accConfig = ctx.config.accounts.get(accountName);
2586
+ let imapOk = false;
2587
+ let imapLatencyMs;
2588
+ let imapCaps;
2589
+ let imapError;
2590
+ try {
2591
+ const imapStart = Date.now();
2592
+ const client = await ctx.imap.acquire(accountName);
2593
+ const caps = [...client.capabilities.keys()];
2594
+ imapLatencyMs = Date.now() - imapStart;
2595
+ imapOk = true;
2596
+ imapCaps = caps;
2597
+ } catch (err) {
2598
+ imapError = String(err);
2599
+ }
2600
+ let smtpOk = false;
2601
+ let smtpLatencyMs;
2602
+ let smtpError;
2603
+ try {
2604
+ const smtpStart = Date.now();
2605
+ const transport = await ctx.smtp.acquire(accountName);
2606
+ const verify = await transport.verify();
2607
+ smtpLatencyMs = Date.now() - smtpStart;
2608
+ smtpOk = verify;
2609
+ } catch (err) {
2610
+ smtpError = String(err);
2611
+ }
2612
+ return {
2613
+ account: accountName,
2614
+ imap: {
2615
+ ok: imapOk,
2616
+ ...imapLatencyMs !== void 0 ? { latencyMs: imapLatencyMs } : {},
2617
+ ...imapCaps ? { capabilities: imapCaps } : {},
2618
+ ...imapError ? { error: imapError } : {}
2619
+ },
2620
+ smtp: {
2621
+ ok: smtpOk,
2622
+ ...smtpLatencyMs !== void 0 ? { latencyMs: smtpLatencyMs } : {},
2623
+ ...smtpError ? { error: smtpError } : {}
2624
+ }
2625
+ };
2626
+ }
2627
+ });
2628
+
2629
+ // src/tools/meta/server-info.ts
2630
+ import { z as z37 } from "zod";
2631
+ var server_info_default = defineTool({
2632
+ name: "meta_server_info",
2633
+ description: "Aktive Tools, aktiver Modus, Version",
2634
+ category: "meta",
2635
+ inputSchema: z37.object({}),
2636
+ handler: async (_input, ctx) => {
2637
+ const flags = {
2638
+ safe: ctx.flags.safe,
2639
+ readonly: ctx.flags.readonly,
2640
+ noImap: ctx.flags.noImap,
2641
+ noSmtp: ctx.flags.noSmtp
2642
+ };
2643
+ if (ctx.flags.allowTools?.length) flags.allowTools = ctx.flags.allowTools;
2644
+ if (ctx.flags.denyTools?.length) flags.denyTools = ctx.flags.denyTools;
2645
+ return {
2646
+ name: SERVER_NAME,
2647
+ version: SERVER_VERSION,
2648
+ activeTools: ctx.activeTools,
2649
+ flags
2650
+ };
2651
+ }
2652
+ });
2653
+
2654
+ // src/tools/index.ts
2655
+ var ALL_TOOLS = [
2656
+ list_mailboxes_default,
2657
+ status_mailbox_default,
2658
+ list_messages_default,
2659
+ get_message_default,
2660
+ get_message_headers_default,
2661
+ get_message_raw_default,
2662
+ get_messages_bulk_default,
2663
+ search_default,
2664
+ download_attachment_default,
2665
+ get_thread_default,
2666
+ get_quota_default,
2667
+ check_capabilities_default,
2668
+ mark_message_default,
2669
+ bulk_mark_default,
2670
+ move_message_default,
2671
+ copy_message_default,
2672
+ bulk_move_default,
2673
+ append_message_default,
2674
+ expunge_default,
2675
+ delete_message_default,
2676
+ create_mailbox_default,
2677
+ delete_mailbox_default,
2678
+ rename_mailbox_default,
2679
+ subscribe_mailbox_default,
2680
+ unsubscribe_mailbox_default,
2681
+ send_default,
2682
+ reply_default,
2683
+ forward_default,
2684
+ verify_connection_default,
2685
+ send_raw_default,
2686
+ list_default,
2687
+ add_default,
2688
+ update_default,
2689
+ delete_default,
2690
+ health_default,
2691
+ server_info_default
2692
+ ];
2693
+
2694
+ // src/server/server.ts
2695
+ var SERVER_NAME = "classic-imap-smtp-mcp";
2696
+ var SERVER_VERSION = "0.3.0";
2697
+ function buildServer(opts, ctx, logger) {
2698
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
2699
+ const active = resolveActiveTools(ALL_TOOLS, opts);
2700
+ logger.info({ count: active.length, tools: active.map((t) => t.name) }, "Registering tools");
2701
+ for (const tool of active) {
2702
+ server.registerTool(
2703
+ tool.name,
2704
+ { description: tool.description, inputSchema: tool.inputSchema },
2705
+ async (input) => {
2706
+ try {
2707
+ const result = await tool.handler(input, ctx);
2708
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2709
+ } catch (err) {
2710
+ const payload = err instanceof McpMailError ? err.toResult() : { code: "INTERNAL_ERROR", message: String(err) };
2711
+ logger.error({ tool: tool.name, err: payload }, "Tool call failed");
2712
+ return {
2713
+ isError: true,
2714
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
2715
+ };
2716
+ }
2717
+ }
2718
+ );
2719
+ }
2720
+ return server;
2721
+ }
2722
+ async function runStdio(server) {
2723
+ const transport = new StdioServerTransport();
2724
+ await server.connect(transport);
2725
+ }
2726
+
2727
+ // src/bin/main.ts
2728
+ var HELP = `classic-imap-smtp-mcp \u2014 classic IMAP/SMTP MCP server (stdio)
2729
+
2730
+ Usage: classic-imap-smtp-mcp [options]
2731
+
2732
+ Options:
2733
+ --safe Disable delete tools (delete/expunge/delete-mailbox)
2734
+ --readonly Read-only: no writes, no SMTP send
2735
+ --no-imap Disable all IMAP tools
2736
+ --no-smtp Disable all SMTP tools
2737
+ --allow-tools=<csv> Explicitly enable tools (overrides feature flags, prefix wildcards)
2738
+ --deny-tools=<csv> Explicitly remove tools (wins over everything, prefix wildcards)
2739
+ --account=<name> Default account override
2740
+ --config=<path> Alternative config path
2741
+ --log-level=<level> trace|debug|info|warn|error (default: info)
2742
+ --log-format=<fmt> json|pretty (default: json)
2743
+ -h, --help Show help
2744
+ -V, --version Show version
2745
+
2746
+ Subcommands:
2747
+ init Write a template config to the XDG path
2748
+ test [account] Test IMAP+SMTP connection
2749
+ list-tools Dry-run: which tools would register with the current flags
2750
+ `;
2751
+ var TEMPLATE_CONFIG = `# classic-imap-smtp-mcp \u2014 Multi-Account Config
2752
+ # Generated by \`classic-imap-smtp-mcp init\`
2753
+ # Pfad: see defaultConfigPath()
2754
+ # Datei sollte Permission 0600 haben \u2014 der Server warnt sonst.
2755
+
2756
+ default_account = "personal"
2757
+
2758
+ # Optional: globale Rate-Limits
2759
+ [limits]
2760
+ smtp_per_minute = 10
2761
+ imap_ops_per_second = 100
2762
+
2763
+ # --- F\xFCge hier deine Accounts ein ---
2764
+ # Beispiel mit Provider-Auto-Detect (nur user/pass n\xF6tig):
2765
+ [[accounts]]
2766
+ name = "personal"
2767
+ user = "you@gmail.com"
2768
+ pass = "your-app-password"
2769
+ from_name = "Your Name"
2770
+
2771
+ # Beispiel mit explizitem Host/Port:
2772
+ # [[accounts]]
2773
+ # name = "work"
2774
+ # user = "you@company.com"
2775
+ # pass = "another-app-password"
2776
+ # imap_host = "imap.company.com"
2777
+ # imap_port = 993
2778
+ # imap_tls = "implicit"
2779
+ # smtp_host = "smtp.company.com"
2780
+ # smtp_port = 587
2781
+ # smtp_tls = "starttls"
2782
+ # from_name = "You at Work"
2783
+ `;
2784
+ async function main() {
2785
+ const parsed = parseArgs(process.argv.slice(2));
2786
+ if (parsed.help) {
2787
+ process.stdout.write(HELP);
2788
+ return;
2789
+ }
2790
+ if (parsed.version) {
2791
+ process.stdout.write(`${SERVER_VERSION}
2792
+ `);
2793
+ return;
2794
+ }
2795
+ const logger = createLogger({ level: parsed.options.logLevel, format: parsed.options.logFormat });
2796
+ if (parsed.options.noImap && parsed.options.noSmtp) {
2797
+ logger.error("Both --no-imap and --no-smtp set: server would expose no mail tools. Aborting.");
2798
+ process.exit(1);
2799
+ }
2800
+ if (parsed.subcommand === "init") {
2801
+ await handleInit(logger);
2802
+ return;
2803
+ }
2804
+ if (parsed.subcommand === "list-tools") {
2805
+ await handleListTools(parsed.options, logger);
2806
+ return;
2807
+ }
2808
+ if (parsed.subcommand === "test") {
2809
+ await handleTest(parsed.options, parsed.subcommandArg, logger);
2810
+ return;
2811
+ }
2812
+ const config = loadConfig(parsed.options.configPath);
2813
+ const imap = new ImapPool(config, logger);
2814
+ const smtp = new SmtpPool(config, logger);
2815
+ const activeTools = resolveActiveTools(ALL_TOOLS, parsed.options).map((t) => t.name);
2816
+ const ctx = {
2817
+ config,
2818
+ imap,
2819
+ smtp,
2820
+ logger,
2821
+ activeTools,
2822
+ flags: parsed.options,
2823
+ resolveAccount(name) {
2824
+ const target = name ?? parsed.options.account ?? config.defaultAccount;
2825
+ if (!config.accounts.has(target)) throw new AccountNotFoundError(target);
2826
+ return target;
2827
+ }
2828
+ };
2829
+ const server = buildServer(parsed.options, ctx, logger);
2830
+ const shutdown = async () => {
2831
+ await Promise.allSettled([imap.closeAll(), smtp.closeAll()]);
2832
+ process.exit(0);
2833
+ };
2834
+ process.on("SIGINT", shutdown);
2835
+ process.on("SIGTERM", shutdown);
2836
+ await runStdio(server);
2837
+ logger.info("classic-imap-smtp-mcp running on stdio");
2838
+ }
2839
+ async function handleInit(logger) {
2840
+ const configPath = defaultConfigPath();
2841
+ const dir = dirname(configPath);
2842
+ if (!existsSync(configPath)) {
2843
+ mkdirSync(dir, { recursive: true });
2844
+ writeFileSync5(configPath, TEMPLATE_CONFIG, "utf8");
2845
+ try {
2846
+ if (process.platform !== "win32") {
2847
+ const { chmodSync } = await import("fs");
2848
+ chmodSync(configPath, 384);
2849
+ }
2850
+ } catch {
2851
+ }
2852
+ process.stdout.write(`Template config written to: ${configPath}
2853
+ `);
2854
+ process.stdout.write("Edit the file with your account details, then run the server.\n");
2855
+ } else {
2856
+ process.stdout.write(`Config file already exists: ${configPath}
2857
+ `);
2858
+ }
2859
+ }
2860
+ async function handleListTools(options, logger) {
2861
+ const active = resolveActiveTools(ALL_TOOLS, options);
2862
+ const lines = [];
2863
+ lines.push(`Active tools: ${active.length}/${ALL_TOOLS.length}`);
2864
+ for (const t of active) {
2865
+ lines.push(` ${t.name.padEnd(32)} ${t.category}`);
2866
+ }
2867
+ const denied = ALL_TOOLS.length - active.length;
2868
+ if (denied > 0) {
2869
+ lines.push(`
2870
+ Blocked by flags: ${denied} tool(s)`);
2871
+ const blocked = ALL_TOOLS.filter((t) => !active.find((a) => a.name === t.name));
2872
+ for (const t of blocked) {
2873
+ lines.push(` ${t.name.padEnd(32)} ${t.category}`);
2874
+ }
2875
+ }
2876
+ process.stdout.write(`${lines.join("\n")}
2877
+ `);
2878
+ }
2879
+ async function handleTest(options, accountArg, logger) {
2880
+ const config = loadConfig(options.configPath);
2881
+ const imap = new ImapPool(config, logger);
2882
+ const smtp = new SmtpPool(config, logger);
2883
+ const accountName = accountArg ?? config.defaultAccount;
2884
+ if (!config.accounts.has(accountName)) {
2885
+ process.stderr.write(`Account not found: ${accountName}
2886
+ `);
2887
+ process.exit(1);
2888
+ }
2889
+ process.stdout.write(`Testing account: ${accountName}
2890
+ `);
2891
+ process.stdout.write(" IMAP... ");
2892
+ try {
2893
+ const start = Date.now();
2894
+ const client = await imap.acquire(accountName);
2895
+ const caps = [...client.capabilities.keys()];
2896
+ const latency = Date.now() - start;
2897
+ process.stdout.write(`OK (${latency}ms, ${caps.length} capabilities)
2898
+ `);
2899
+ } catch (err) {
2900
+ process.stdout.write(`FAIL: ${err}
2901
+ `);
2902
+ }
2903
+ process.stdout.write(" SMTP... ");
2904
+ try {
2905
+ const start = Date.now();
2906
+ const transport = await smtp.acquire(accountName);
2907
+ await transport.verify();
2908
+ const latency = Date.now() - start;
2909
+ process.stdout.write(`OK (${latency}ms)
2910
+ `);
2911
+ } catch (err) {
2912
+ process.stdout.write(`FAIL: ${err}
2913
+ `);
2914
+ }
2915
+ await imap.closeAll();
2916
+ await smtp.closeAll();
2917
+ }
2918
+ main().catch((err) => {
2919
+ const msg = err instanceof McpMailError ? `${err.code}: ${err.message}` : String(err);
2920
+ process.stderr.write(`Fatal: ${msg}
2921
+ `);
2922
+ process.exit(1);
2923
+ });
2924
+ //# sourceMappingURL=main.js.map