@ni-c/imap-mcp 0.2.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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/analyze.d.ts +129 -0
  4. package/dist/analyze.js +313 -0
  5. package/dist/analyze.js.map +1 -0
  6. package/dist/approval.d.ts +45 -0
  7. package/dist/approval.js +69 -0
  8. package/dist/approval.js.map +1 -0
  9. package/dist/attachments.d.ts +55 -0
  10. package/dist/attachments.js +270 -0
  11. package/dist/attachments.js.map +1 -0
  12. package/dist/audit.d.ts +17 -0
  13. package/dist/audit.js +33 -0
  14. package/dist/audit.js.map +1 -0
  15. package/dist/config.d.ts +75 -0
  16. package/dist/config.js +202 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/confirm.d.ts +59 -0
  19. package/dist/confirm.js +92 -0
  20. package/dist/confirm.js.map +1 -0
  21. package/dist/download.d.ts +23 -0
  22. package/dist/download.js +65 -0
  23. package/dist/download.js.map +1 -0
  24. package/dist/draft.d.ts +34 -0
  25. package/dist/draft.js +119 -0
  26. package/dist/draft.js.map +1 -0
  27. package/dist/errors.d.ts +15 -0
  28. package/dist/errors.js +24 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/imap.d.ts +161 -0
  31. package/dist/imap.js +300 -0
  32. package/dist/imap.js.map +1 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +36 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/message.d.ts +51 -0
  37. package/dist/message.js +155 -0
  38. package/dist/message.js.map +1 -0
  39. package/dist/resources.d.ts +16 -0
  40. package/dist/resources.js +89 -0
  41. package/dist/resources.js.map +1 -0
  42. package/dist/result.d.ts +57 -0
  43. package/dist/result.js +193 -0
  44. package/dist/result.js.map +1 -0
  45. package/dist/schema.d.ts +42 -0
  46. package/dist/schema.js +99 -0
  47. package/dist/schema.js.map +1 -0
  48. package/dist/server.d.ts +8 -0
  49. package/dist/server.js +64 -0
  50. package/dist/server.js.map +1 -0
  51. package/dist/stream.d.ts +9 -0
  52. package/dist/stream.js +25 -0
  53. package/dist/stream.js.map +1 -0
  54. package/dist/tool-filter.d.ts +45 -0
  55. package/dist/tool-filter.js +171 -0
  56. package/dist/tool-filter.js.map +1 -0
  57. package/dist/tools/catalogue.d.ts +46 -0
  58. package/dist/tools/catalogue.js +67 -0
  59. package/dist/tools/catalogue.js.map +1 -0
  60. package/dist/tools/read.d.ts +4 -0
  61. package/dist/tools/read.js +576 -0
  62. package/dist/tools/read.js.map +1 -0
  63. package/dist/tools/write.d.ts +5 -0
  64. package/dist/tools/write.js +291 -0
  65. package/dist/tools/write.js.map +1 -0
  66. package/package.json +70 -0
@@ -0,0 +1,270 @@
1
+ import { defuseAutoFetch, stripInvisible } from './analyze.js';
2
+ /** How deep the MIME tree is walked. Forwarded mail nests; a loop does not. */
3
+ const MAX_DEPTH = 5;
4
+ /** Upper bound on how many parts one listing may describe. */
5
+ const MAX_ATTACHMENTS = 50;
6
+ const MAX_FILENAME_LENGTH = 120;
7
+ /**
8
+ * Extensions that are executable somewhere. The list is long on purpose: this
9
+ * is a refusal list applied on top of the content-type allowlist, and the cost
10
+ * of an extra entry is one unusual attachment that has to be fetched by other
11
+ * means.
12
+ */
13
+ const EXECUTABLE_EXTENSIONS = new Set([
14
+ 'ade',
15
+ 'adp',
16
+ 'app',
17
+ 'appimage',
18
+ 'application',
19
+ 'appref-ms',
20
+ 'asp',
21
+ 'aspx',
22
+ 'bas',
23
+ 'bat',
24
+ 'cer',
25
+ 'chm',
26
+ 'cmd',
27
+ 'com',
28
+ 'cpl',
29
+ 'crt',
30
+ 'csh',
31
+ 'deb',
32
+ 'dll',
33
+ 'dmg',
34
+ 'exe',
35
+ 'fxp',
36
+ 'gadget',
37
+ 'hlp',
38
+ 'hta',
39
+ 'inf',
40
+ 'ins',
41
+ 'iso',
42
+ 'isp',
43
+ 'its',
44
+ 'jar',
45
+ 'js',
46
+ 'jse',
47
+ 'ksh',
48
+ 'lnk',
49
+ 'mad',
50
+ 'maf',
51
+ 'mag',
52
+ 'mam',
53
+ 'maq',
54
+ 'mar',
55
+ 'mas',
56
+ 'mat',
57
+ 'mau',
58
+ 'mav',
59
+ 'maw',
60
+ 'mda',
61
+ 'mdb',
62
+ 'mde',
63
+ 'mdt',
64
+ 'mdw',
65
+ 'mdz',
66
+ 'msc',
67
+ 'msh',
68
+ 'msh1',
69
+ 'msh2',
70
+ 'mshxml',
71
+ 'msi',
72
+ 'msp',
73
+ 'mst',
74
+ 'ops',
75
+ 'pcd',
76
+ 'pif',
77
+ 'pkg',
78
+ 'pl',
79
+ 'plg',
80
+ 'prf',
81
+ 'prg',
82
+ 'ps1',
83
+ 'ps2',
84
+ 'psc1',
85
+ 'psc2',
86
+ 'py',
87
+ 'pyc',
88
+ 'pyo',
89
+ 'rb',
90
+ 'reg',
91
+ 'rpm',
92
+ 'scf',
93
+ 'scr',
94
+ 'sct',
95
+ 'sh',
96
+ 'shb',
97
+ 'shs',
98
+ 'url',
99
+ 'vb',
100
+ 'vbe',
101
+ 'vbs',
102
+ 'vsmacros',
103
+ 'vsw',
104
+ 'ws',
105
+ 'wsc',
106
+ 'wsf',
107
+ 'wsh',
108
+ 'xll',
109
+ ]);
110
+ /**
111
+ * A filename ending in something that looks like a document extension followed
112
+ * by an executable one — `invoice.pdf.exe`. Mail clients that hide known
113
+ * extensions render it as `invoice.pdf`.
114
+ */
115
+ const DOUBLE_EXTENSION_BAIT = /\.(pdf|docx?|xlsx?|pptx?|txt|csv|jpe?g|png|gif|zip|rtf|odt|ods)\.[a-z0-9]{1,5}$/i;
116
+ /**
117
+ * Strips a filename down to something safe to print and to reason about.
118
+ *
119
+ * The name is never used to open a file — nothing here writes to disk — but it
120
+ * does reach the model, and a name carrying directional overrides or path
121
+ * separators is trying to be read as something it is not.
122
+ */
123
+ export function sanitizeFilename(raw) {
124
+ if (raw === undefined || raw.trim() === '')
125
+ return '(unnamed)';
126
+ const cleaned = defuseAutoFetch(stripInvisible(raw.normalize('NFKC')))
127
+ .replace(/[/\\]/g, '_')
128
+ .replace(/^\.+/, '')
129
+ .trim();
130
+ if (cleaned === '')
131
+ return '(unnamed)';
132
+ return cleaned.length > MAX_FILENAME_LENGTH
133
+ ? `${cleaned.slice(0, MAX_FILENAME_LENGTH)}…`
134
+ : cleaned;
135
+ }
136
+ export function extensionOf(filename) {
137
+ const match = /\.([A-Za-z0-9]{1,10})$/.exec(filename);
138
+ return match?.[1]?.toLowerCase() ?? '';
139
+ }
140
+ /**
141
+ * Walks the MIME tree and returns every part that is an attachment.
142
+ *
143
+ * Parts inside a forwarded `message/rfc822` are included: an attachment that
144
+ * arrives one level deeper is exactly as dangerous, and a listing that stops at
145
+ * the outer envelope would report "no attachments" for the most common way of
146
+ * passing a file along.
147
+ */
148
+ export function collectAttachments(structure) {
149
+ const found = [];
150
+ walk(structure, 0, found);
151
+ return found.slice(0, MAX_ATTACHMENTS);
152
+ }
153
+ function walk(node, depth, found) {
154
+ if (node === undefined ||
155
+ depth > MAX_DEPTH ||
156
+ found.length >= MAX_ATTACHMENTS)
157
+ return;
158
+ if (node.childNodes !== undefined && node.childNodes.length > 0) {
159
+ for (const child of node.childNodes)
160
+ walk(child, depth + 1, found);
161
+ return;
162
+ }
163
+ const type = (node.type ?? 'application/octet-stream').toLowerCase();
164
+ const disposition = node.disposition?.toLowerCase();
165
+ const declaredName = node.dispositionParameters?.filename ?? node.parameters?.name;
166
+ // A part is an attachment when it says so, or when it carries a filename, or
167
+ // when it is simply not one of the two body types. Inline images count: they
168
+ // are still bytes from a stranger.
169
+ const isBody = (type === 'text/plain' || type === 'text/html') &&
170
+ disposition !== 'attachment' &&
171
+ declaredName === undefined;
172
+ if (isBody || node.part === undefined)
173
+ return;
174
+ const filename = sanitizeFilename(declaredName);
175
+ const notes = [];
176
+ if (declaredName !== undefined && DOUBLE_EXTENSION_BAIT.test(declaredName)) {
177
+ notes.push('filename has a double extension — it renders as a document but is not one');
178
+ }
179
+ found.push({
180
+ partId: node.part,
181
+ filename,
182
+ contentType: type,
183
+ size: node.size,
184
+ disposition,
185
+ allowed: true,
186
+ notes,
187
+ });
188
+ }
189
+ /**
190
+ * Applies the declaration-level policy.
191
+ *
192
+ * Everything checked here comes from the sender, so a pass means only "nothing
193
+ * in what it claims about itself disqualifies it". The bytes are checked
194
+ * separately in {@link sniffContent} when they are actually fetched.
195
+ */
196
+ export function checkPolicy(candidate, policy) {
197
+ const notes = [...candidate.notes];
198
+ let allowed = true;
199
+ const extension = extensionOf(candidate.filename);
200
+ if (extension !== '' && EXECUTABLE_EXTENSIONS.has(extension)) {
201
+ allowed = false;
202
+ notes.push(`refused: .${extension} is an executable file type`);
203
+ }
204
+ if (!policy.allowedTypes.includes(candidate.contentType)) {
205
+ allowed = false;
206
+ notes.push(`refused: content type ${candidate.contentType} is not in the allowlist (IMAP_ATTACHMENT_TYPES)`);
207
+ }
208
+ if (candidate.size !== undefined && candidate.size > policy.maxBytes) {
209
+ allowed = false;
210
+ notes.push(`refused: declared size ${candidate.size} exceeds IMAP_MAX_ATTACHMENT_BYTES (${policy.maxBytes})`);
211
+ }
212
+ return { ...candidate, allowed, notes };
213
+ }
214
+ /**
215
+ * Identifies content by its leading bytes.
216
+ *
217
+ * The declared content type is a claim by the sender; this is the only check
218
+ * that looks at what was actually sent. An executable renamed to `.txt` and
219
+ * declared as `text/plain` passes every other gate and fails here.
220
+ */
221
+ export function sniffContent(buffer) {
222
+ const byte = (i) => buffer[i] ?? -1;
223
+ if (byte(0) === 0x4d && byte(1) === 0x5a) {
224
+ return { executable: true, detectedType: 'application/x-msdownload' };
225
+ }
226
+ if (byte(0) === 0x7f &&
227
+ byte(1) === 0x45 &&
228
+ byte(2) === 0x4c &&
229
+ byte(3) === 0x46) {
230
+ return { executable: true, detectedType: 'application/x-elf' };
231
+ }
232
+ // `>>> 0` is load-bearing: JS bitwise operators produce a *signed* 32-bit
233
+ // result, so without it 0xfeedface arrives as a negative number and none of
234
+ // the Mach-O comparisons below can ever match.
235
+ const magic32 = ((byte(0) << 24) | (byte(1) << 16) | (byte(2) << 8) | byte(3)) >>> 0;
236
+ if (magic32 === 0xfeedface ||
237
+ magic32 === 0xfeedfacf ||
238
+ magic32 === 0xcafebabe) {
239
+ return { executable: true, detectedType: 'application/x-mach-binary' };
240
+ }
241
+ if (byte(0) === 0x23 && byte(1) === 0x21) {
242
+ return { executable: true, detectedType: 'text/x-shellscript' };
243
+ }
244
+ const starts = (signature) => signature.every((value, index) => byte(index) === value);
245
+ if (starts([0x25, 0x50, 0x44, 0x46])) {
246
+ return { executable: false, detectedType: 'application/pdf' };
247
+ }
248
+ if (starts([0x89, 0x50, 0x4e, 0x47])) {
249
+ return { executable: false, detectedType: 'image/png' };
250
+ }
251
+ if (starts([0xff, 0xd8, 0xff])) {
252
+ return { executable: false, detectedType: 'image/jpeg' };
253
+ }
254
+ if (starts([0x47, 0x49, 0x46, 0x38])) {
255
+ return { executable: false, detectedType: 'image/gif' };
256
+ }
257
+ if (starts([0x52, 0x49, 0x46, 0x46]) &&
258
+ byte(8) === 0x57 &&
259
+ byte(9) === 0x45 &&
260
+ byte(10) === 0x42 &&
261
+ byte(11) === 0x50) {
262
+ return { executable: false, detectedType: 'image/webp' };
263
+ }
264
+ if (starts([0x50, 0x4b, 0x03, 0x04])) {
265
+ // Also every modern Office and OpenDocument file.
266
+ return { executable: false, detectedType: 'application/zip' };
267
+ }
268
+ return { executable: false, detectedType: undefined };
269
+ }
270
+ //# sourceMappingURL=attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Records every change this server makes to the mailbox.
3
+ *
4
+ * The line goes to stderr, which is the one channel the model never reads: the
5
+ * MCP transport owns stdout, and tool results go back to the model. So this is
6
+ * the only place a human can afterwards reconstruct what a hijacked session
7
+ * actually did — the model's own account of events is not evidence.
8
+ *
9
+ * Deliberately no subjects, senders or bodies. An audit line that quotes the
10
+ * mail would put attacker-chosen text into the operator's log viewer, and the
11
+ * facts needed to undo something are the UIDs and the folder, not the prose.
12
+ * The one exception is the path of a saved attachment: its filename was chosen
13
+ * by the sender, but it has been through sanitizeFilename — no control or
14
+ * invisible characters, no separators — and without it the line could not say
15
+ * which file to go and delete.
16
+ */
17
+ export declare function audit(operation: string, details: Record<string, unknown>): void;
package/dist/audit.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Records every change this server makes to the mailbox.
3
+ *
4
+ * The line goes to stderr, which is the one channel the model never reads: the
5
+ * MCP transport owns stdout, and tool results go back to the model. So this is
6
+ * the only place a human can afterwards reconstruct what a hijacked session
7
+ * actually did — the model's own account of events is not evidence.
8
+ *
9
+ * Deliberately no subjects, senders or bodies. An audit line that quotes the
10
+ * mail would put attacker-chosen text into the operator's log viewer, and the
11
+ * facts needed to undo something are the UIDs and the folder, not the prose.
12
+ * The one exception is the path of a saved attachment: its filename was chosen
13
+ * by the sender, but it has been through sanitizeFilename — no control or
14
+ * invisible characters, no separators — and without it the line could not say
15
+ * which file to go and delete.
16
+ */
17
+ export function audit(operation, details) {
18
+ const fields = Object.entries(details)
19
+ .filter(([, value]) => value !== undefined)
20
+ .map(([key, value]) => `${key}=${format(value)}`)
21
+ .join(' ');
22
+ console.error(`imap-mcp audit ${new Date().toISOString()} ${operation} ${fields}`);
23
+ }
24
+ /** Long UID lists are abbreviated so one bulk call cannot flood the log. */
25
+ function format(value) {
26
+ if (Array.isArray(value)) {
27
+ return value.length > 20
28
+ ? `[${value.slice(0, 20).join(',')},…+${value.length - 20}]`
29
+ : `[${value.join(',')}]`;
30
+ }
31
+ return String(value);
32
+ }
33
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,KAAK,CACnB,SAAiB,EACjB,OAAgC;IAEhC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;SACnC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;SAChD,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,CAAC,KAAK,CACX,kBAAkB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,SAAS,IAAI,MAAM,EAAE,CACpE,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,MAAM,GAAG,EAAE;YACtB,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,MAAM,GAAG,EAAE,GAAG;YAC5D,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7B,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,75 @@
1
+ /** How the IMAP connection is encrypted. */
2
+ export type TlsMode = 'implicit' | 'starttls' | 'none';
3
+ export interface ImapConfig {
4
+ host: string | undefined;
5
+ port: number;
6
+ user: string | undefined;
7
+ password: string | undefined;
8
+ tls: TlsMode;
9
+ insecureTls: boolean;
10
+ /** Mailbox the message tools default to. */
11
+ mailbox: string;
12
+ /**
13
+ * Custom IMAP keyword marking messages already handed to the model. Empty
14
+ * means the feature is off and `list_new_messages` is not registered.
15
+ */
16
+ seenKeyword: string;
17
+ /** Overrides the folder auto-detected from the \Drafts special-use flag. */
18
+ draftsMailbox: string | undefined;
19
+ /**
20
+ * The authserv-id this account's own provider stamps into
21
+ * Authentication-Results. Only a header carrying it is treated as
22
+ * non-forgeable; unset, every SPF/DKIM/DMARC verdict is reported as forgeable,
23
+ * because a sender can write that header too and nothing in the message
24
+ * distinguishes theirs from the provider's.
25
+ */
26
+ trustedAuthservId: string | undefined;
27
+ maxMessages: number;
28
+ maxAttachmentBytes: number;
29
+ allowedAttachmentTypes: string[];
30
+ /**
31
+ * Where attachments may be written. Unset means this server never touches the
32
+ * filesystem — setting it is the opt-in, and it is the only source of the
33
+ * target directory. A caller cannot choose where bytes from a stranger land.
34
+ */
35
+ downloadDir: string | undefined;
36
+ maxDownloadBytes: number;
37
+ }
38
+ export interface Config {
39
+ imap: ImapConfig;
40
+ /**
41
+ * When true — the default — the mailbox write tools are not registered.
42
+ *
43
+ * Note the default, which is the opposite of every other server in this
44
+ * family. This variable replaced `IMAP_ALLOW_WRITE`, and that one was opt-in:
45
+ * setting nothing meant no write access to a mailbox. Renaming it without
46
+ * keeping that default would have handed write access to every installation
47
+ * that upgraded without reading the changelog. The name is now shared with
48
+ * the rest of the family; the default deliberately is not.
49
+ */
50
+ readOnly: boolean;
51
+ /**
52
+ * Raw value of `IMAP_ALLOW_TOOLS` — comma-separated tool names, `list_*`
53
+ * prefixes, or `essential`. Kept unparsed on purpose: this file is a mirror
54
+ * of the environment, and the names can only be checked against the tool
55
+ * catalogue, which `buildToolFilter` does.
56
+ */
57
+ allowTools: string | undefined;
58
+ /** Raw value of `IMAP_DENY_TOOLS`, same shape, subtracted from the above. */
59
+ denyTools: string | undefined;
60
+ }
61
+ export declare const DEFAULT_ATTACHMENT_TYPES: string[];
62
+ /** Shown when the configuration is incomplete — at startup and on every call. */
63
+ export declare function missingConfigMessage(missing: string[]): string;
64
+ /** Names of the required environment variables that are unset in `config`. */
65
+ export declare function missingConfigKeys(config: Config): string[];
66
+ /**
67
+ * Reads the configuration from environment variables.
68
+ *
69
+ * Missing credentials are only a warning, not a fatal error: the server must be
70
+ * able to complete the MCP handshake and answer `tools/list` without them, so
71
+ * registries and sandbox inspectors can introspect it. Malformed values still
72
+ * exit — a host with a newline in it could smuggle a second command into the
73
+ * IMAP session, and a bad port would connect somewhere unintended.
74
+ */
75
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): Config;
package/dist/config.js ADDED
@@ -0,0 +1,202 @@
1
+ export const DEFAULT_ATTACHMENT_TYPES = [
2
+ 'application/pdf',
3
+ 'application/json',
4
+ 'application/xml',
5
+ 'application/zip',
6
+ 'application/rtf',
7
+ 'application/vnd.oasis.opendocument.text',
8
+ 'application/vnd.oasis.opendocument.spreadsheet',
9
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
10
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
11
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
12
+ 'image/png',
13
+ 'image/jpeg',
14
+ 'image/gif',
15
+ 'image/webp',
16
+ 'text/plain',
17
+ 'text/csv',
18
+ 'text/html',
19
+ 'text/calendar',
20
+ ];
21
+ const DEFAULT_MAX_MESSAGES = 100;
22
+ /** Inline cap: this one protects the model's context window. */
23
+ const DEFAULT_MAX_ATTACHMENT_BYTES = 1024 * 1024;
24
+ /** Disk cap: this one protects the filesystem, which is a different concern. */
25
+ const DEFAULT_MAX_DOWNLOAD_BYTES = 25 * 1024 * 1024;
26
+ const DEFAULT_SEEN_KEYWORD = 'AiSeen';
27
+ /** Shown when the configuration is incomplete — at startup and on every call. */
28
+ export function missingConfigMessage(missing) {
29
+ return (`missing required environment variable(s): ${missing.join(', ')}\n` +
30
+ 'Required: IMAP_HOST (e.g. imap.example.net), IMAP_USER, IMAP_PASSWORD\n' +
31
+ 'Optional: IMAP_PORT, IMAP_TLS (implicit|starttls|none), IMAP_MAILBOX, ' +
32
+ 'IMAP_SEEN_KEYWORD, IMAP_READ_ONLY=false to expose the mailbox write ' +
33
+ 'tools (it defaults to true), IMAP_ALLOW_TOOLS / IMAP_DENY_TOOLS to narrow ' +
34
+ 'the tool list, IMAP_DOWNLOAD_DIR to allow saving attachments to disk, ' +
35
+ 'IMAP_INSECURE_TLS=true to accept self-signed certificates');
36
+ }
37
+ /** Names of the required environment variables that are unset in `config`. */
38
+ export function missingConfigKeys(config) {
39
+ return [
40
+ !config.imap.host && 'IMAP_HOST',
41
+ !config.imap.user && 'IMAP_USER',
42
+ !config.imap.password && 'IMAP_PASSWORD',
43
+ ].filter((v) => Boolean(v));
44
+ }
45
+ /**
46
+ * Reads the configuration from environment variables.
47
+ *
48
+ * Missing credentials are only a warning, not a fatal error: the server must be
49
+ * able to complete the MCP handshake and answer `tools/list` without them, so
50
+ * registries and sandbox inspectors can introspect it. Malformed values still
51
+ * exit — a host with a newline in it could smuggle a second command into the
52
+ * IMAP session, and a bad port would connect somewhere unintended.
53
+ */
54
+ export function loadConfig(env = process.env) {
55
+ const host = env.IMAP_HOST;
56
+ const user = env.IMAP_USER;
57
+ const password = env.IMAP_PASSWORD;
58
+ // Removed here, before any branch below can return or exit: the password must
59
+ // not stay in the environment for the process lifetime, where it is visible
60
+ // to child processes and in /proc/<pid>/environ.
61
+ delete env.IMAP_PASSWORD;
62
+ const tls = parseTlsMode(env.IMAP_TLS);
63
+ if (host !== undefined)
64
+ assertSafeHost(host, 'IMAP_HOST');
65
+ const draftsMailbox = env.IMAP_DRAFTS_MAILBOX;
66
+ if (draftsMailbox !== undefined) {
67
+ assertSingleLine(draftsMailbox, 'IMAP_DRAFTS_MAILBOX');
68
+ }
69
+ const trustedAuthservId = env.IMAP_TRUSTED_AUTHSERV_ID?.trim() || undefined;
70
+ if (trustedAuthservId !== undefined) {
71
+ assertSingleLine(trustedAuthservId, 'IMAP_TRUSTED_AUTHSERV_ID');
72
+ }
73
+ const config = {
74
+ imap: {
75
+ host,
76
+ port: parsePort(env.IMAP_PORT, tls === 'implicit' ? 993 : 143, 'IMAP_PORT'),
77
+ user,
78
+ password,
79
+ tls,
80
+ insecureTls: env.IMAP_INSECURE_TLS === 'true',
81
+ mailbox: env.IMAP_MAILBOX || 'INBOX',
82
+ seenKeyword: parseKeyword(env.IMAP_SEEN_KEYWORD),
83
+ draftsMailbox,
84
+ trustedAuthservId,
85
+ maxMessages: parseCount(env.IMAP_MAX_MESSAGES, DEFAULT_MAX_MESSAGES, 'IMAP_MAX_MESSAGES'),
86
+ maxAttachmentBytes: parseCount(env.IMAP_MAX_ATTACHMENT_BYTES, DEFAULT_MAX_ATTACHMENT_BYTES, 'IMAP_MAX_ATTACHMENT_BYTES'),
87
+ allowedAttachmentTypes: parseTypes(env.IMAP_ATTACHMENT_TYPES),
88
+ downloadDir: env.IMAP_DOWNLOAD_DIR,
89
+ maxDownloadBytes: parseCount(env.IMAP_MAX_DOWNLOAD_BYTES, DEFAULT_MAX_DOWNLOAD_BYTES, 'IMAP_MAX_DOWNLOAD_BYTES'),
90
+ },
91
+ // Defaults to true, unlike the rest of the family — see the field comment.
92
+ readOnly: env.IMAP_READ_ONLY !== 'false',
93
+ allowTools: env.IMAP_ALLOW_TOOLS,
94
+ denyTools: env.IMAP_DENY_TOOLS,
95
+ };
96
+ // Silently ignoring a removed security variable is the worst of the options:
97
+ // whoever set it once believes it is still in force. IMAP_ALLOW_WRITE=true
98
+ // used to be the only way to reach the write tools, so an installation that
99
+ // still sets it is one that wants them — and would otherwise get a read-only
100
+ // server without being told why.
101
+ if (env.IMAP_ALLOW_WRITE !== undefined) {
102
+ console.error('imap-mcp: IMAP_ALLOW_WRITE has been replaced by IMAP_READ_ONLY. Set ' +
103
+ 'IMAP_READ_ONLY=false for the write tools, or unset IMAP_ALLOW_WRITE ' +
104
+ 'to keep the read-only default.');
105
+ process.exit(1);
106
+ }
107
+ const missing = missingConfigKeys(config);
108
+ if (missing.length > 0) {
109
+ console.error(`imap-mcp: ${missingConfigMessage(missing)}`);
110
+ }
111
+ if (config.imap.tls === 'none' && !isLoopbackHost(host)) {
112
+ console.error('imap-mcp: WARNING: IMAP_TLS=none against a non-local host — the password ' +
113
+ 'and every message will cross the network unencrypted.');
114
+ }
115
+ return config;
116
+ }
117
+ function parseTlsMode(raw) {
118
+ if (raw === undefined || raw === '')
119
+ return 'implicit';
120
+ if (raw === 'implicit' || raw === 'starttls' || raw === 'none')
121
+ return raw;
122
+ console.error('imap-mcp: IMAP_TLS must be one of implicit, starttls or none');
123
+ process.exit(1);
124
+ }
125
+ function parsePort(raw, fallback, name) {
126
+ if (raw === undefined || raw === '')
127
+ return fallback;
128
+ const value = Number(raw);
129
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
130
+ // The value itself is not echoed: config errors end up in logs.
131
+ console.error(`imap-mcp: ${name} must be an integer between 1 and 65535`);
132
+ process.exit(1);
133
+ }
134
+ return value;
135
+ }
136
+ function parseCount(raw, fallback, name) {
137
+ if (raw === undefined || raw === '')
138
+ return fallback;
139
+ const value = Number(raw);
140
+ if (!Number.isInteger(value) || value < 1) {
141
+ console.error(`imap-mcp: ${name} must be a positive integer`);
142
+ process.exit(1);
143
+ }
144
+ return value;
145
+ }
146
+ /**
147
+ * An IMAP keyword is an atom: no spaces and none of the characters that would
148
+ * end it early or open a literal. Empty turns the new-mail tracking off.
149
+ */
150
+ function parseKeyword(raw) {
151
+ if (raw === undefined)
152
+ return DEFAULT_SEEN_KEYWORD;
153
+ if (raw === '')
154
+ return '';
155
+ if (!/^[A-Za-z0-9$_.-]+$/.test(raw)) {
156
+ console.error('imap-mcp: IMAP_SEEN_KEYWORD must consist of letters, digits, $, _, . or -');
157
+ process.exit(1);
158
+ }
159
+ return raw;
160
+ }
161
+ function parseTypes(raw) {
162
+ if (raw === undefined || raw.trim() === '')
163
+ return DEFAULT_ATTACHMENT_TYPES;
164
+ return raw
165
+ .split(',')
166
+ .map((t) => t.trim().toLowerCase())
167
+ .filter((t) => t !== '');
168
+ }
169
+ /**
170
+ * Rejects anything that could break out of the line it is written on. IMAP is a
171
+ * line protocol; a CR or LF in a hostname is a command-smuggling primitive, not
172
+ * a typo.
173
+ */
174
+ function assertSafeHost(value, name) {
175
+ // A hostname or IPv4 address — or an IPv6 address, which is the only place
176
+ // a colon is legal. Allowing ":" everywhere would silently accept
177
+ // "imap.example.net:993", which the error message promises to reject.
178
+ const hostname = /^[A-Za-z0-9._-]+$/.test(value);
179
+ const ipv6 = /^\[?[0-9A-Fa-f:.]*:[0-9A-Fa-f:.]*\]?$/.test(value);
180
+ if (!hostname && !ipv6) {
181
+ console.error(`imap-mcp: ${name} must be a plain hostname or IP address without ` +
182
+ 'scheme, port, credentials or whitespace');
183
+ process.exit(1);
184
+ }
185
+ }
186
+ /** Spaces are fine in a mailbox name; a line break would end the command. */
187
+ function assertSingleLine(value, name) {
188
+ if (/[\r\n]/.test(value)) {
189
+ console.error(`imap-mcp: ${name} must not contain line breaks`);
190
+ process.exit(1);
191
+ }
192
+ }
193
+ function isLoopbackHost(hostname) {
194
+ if (hostname === undefined)
195
+ return false;
196
+ return (hostname === 'localhost' ||
197
+ hostname.endsWith('.localhost') ||
198
+ hostname.startsWith('127.') ||
199
+ hostname === '::1' ||
200
+ hostname === '[::1]');
201
+ }
202
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}