@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
package/dist/imap.js ADDED
@@ -0,0 +1,300 @@
1
+ import { ImapFlow } from 'imapflow';
2
+ import { missingConfigKeys, missingConfigMessage, } from './config.js';
3
+ import { MailError, ToolInputError } from './errors.js';
4
+ const defaultFactory = (config) => new ImapFlow({
5
+ host: config.host ?? '',
6
+ port: config.port,
7
+ secure: config.tls === 'implicit',
8
+ // Left unset, imapflow upgrades opportunistically — which means a
9
+ // downgrade attack succeeds silently. Both non-implicit modes say
10
+ // explicitly which one they meant.
11
+ doSTARTTLS: config.tls === 'starttls',
12
+ auth: { user: config.user ?? '', pass: config.password ?? '' },
13
+ // The library logs full IMAP traffic — message bodies included — to stdout
14
+ // by default. stdout is the MCP transport, so this is not optional.
15
+ logger: false,
16
+ // Scoped to this connection: NODE_TLS_REJECT_UNAUTHORIZED would disable
17
+ // certificate checking for the whole process, SMTP included.
18
+ tls: config.insecureTls ? { rejectUnauthorized: false } : {},
19
+ });
20
+ /** How long a single IMAP command may take before the call is abandoned. */
21
+ const COMMAND_TIMEOUT_MS = 30_000;
22
+ /**
23
+ * Connection manager around a single IMAP account.
24
+ *
25
+ * Reads take the mailbox lock read-only and use BODY.PEEK throughout, so
26
+ * fetching a message never changes what the human sees as unread. The only
27
+ * exception is deliberate and explicit: {@link tagSeen}, which writes the
28
+ * server's own bookkeeping keyword.
29
+ */
30
+ export class ImapClient {
31
+ config;
32
+ factory;
33
+ connection;
34
+ connecting;
35
+ constructor(config, factory = defaultFactory) {
36
+ this.config = config;
37
+ this.factory = factory;
38
+ }
39
+ /** Credentials are checked per call, not at startup. */
40
+ assertConfigured() {
41
+ const missing = missingConfigKeys(this.config);
42
+ if (missing.length > 0) {
43
+ throw new Error(missingConfigMessage(missing));
44
+ }
45
+ }
46
+ async connection_() {
47
+ this.assertConfigured();
48
+ if (this.connection !== undefined)
49
+ return this.connection;
50
+ if (this.connecting !== undefined)
51
+ return this.connecting;
52
+ this.connecting = (async () => {
53
+ const client = this.factory(this.config.imap);
54
+ try {
55
+ await client.connect();
56
+ }
57
+ catch (error) {
58
+ this.connecting = undefined;
59
+ throw asMailError(error);
60
+ }
61
+ this.connection = client;
62
+ this.connecting = undefined;
63
+ return client;
64
+ })();
65
+ return this.connecting;
66
+ }
67
+ /**
68
+ * Runs `fn` with the mailbox open and the lock held.
69
+ *
70
+ * A dropped connection is retried exactly once: IMAP sessions are long-lived
71
+ * and idle ones get reaped by servers and NAT gateways alike, so the first
72
+ * call after a pause routinely fails for reasons that have nothing to do with
73
+ * the request.
74
+ */
75
+ async withMailbox(mailbox, readOnly, fn) {
76
+ const path = mailbox ?? this.config.imap.mailbox;
77
+ try {
78
+ return await this.run(path, readOnly, fn);
79
+ }
80
+ catch (error) {
81
+ if (!isConnectionError(error))
82
+ throw error;
83
+ this.forget();
84
+ return await this.run(path, readOnly, fn);
85
+ }
86
+ }
87
+ async run(path, readOnly, fn) {
88
+ const client = await this.connection_();
89
+ let lock;
90
+ try {
91
+ lock = await client.getMailboxLock(path, { readOnly });
92
+ // A NOOP before SEARCH: without it the server is free to keep reporting
93
+ // the mailbox as it looked when the connection was opened, and mail that
94
+ // arrived since then stays invisible.
95
+ await withTimeout(client.noop(), 'NOOP');
96
+ return await fn(client, path);
97
+ }
98
+ catch (error) {
99
+ throw asMailError(error);
100
+ }
101
+ finally {
102
+ lock?.release();
103
+ }
104
+ }
105
+ /** Runs `fn` without selecting a mailbox, for LIST/STATUS/CREATE style calls. */
106
+ async withConnection(fn) {
107
+ try {
108
+ return await fn(await this.connection_());
109
+ }
110
+ catch (error) {
111
+ if (!isConnectionError(error))
112
+ throw asMailError(error);
113
+ this.forget();
114
+ try {
115
+ return await fn(await this.connection_());
116
+ }
117
+ catch (retryError) {
118
+ throw asMailError(retryError);
119
+ }
120
+ }
121
+ }
122
+ forget() {
123
+ try {
124
+ this.connection?.close();
125
+ }
126
+ catch {
127
+ // Already gone; nothing to clean up.
128
+ }
129
+ this.connection = undefined;
130
+ }
131
+ async listMailboxes() {
132
+ return this.withConnection(async (client) => {
133
+ // statusQuery folds the per-folder counters into the same round trip, so
134
+ // list_mailboxes can answer "which folder, and how much is in it" at once.
135
+ const entries = await withTimeout(client.list({
136
+ statusQuery: { messages: true, unseen: true, uidNext: true },
137
+ }), 'LIST');
138
+ return entries.map((entry) => ({
139
+ path: entry.path,
140
+ name: entry.name,
141
+ delimiter: entry.delimiter,
142
+ specialUse: entry.specialUse,
143
+ subscribed: entry.subscribed,
144
+ selectable: !entry.flags.has('\\Noselect'),
145
+ messages: entry.status?.messages,
146
+ unseen: entry.status?.unseen,
147
+ uidNext: entry.status?.uidNext,
148
+ }));
149
+ });
150
+ }
151
+ /**
152
+ * Whether the account can store the bookkeeping keyword.
153
+ *
154
+ * `\*` in PERMANENTFLAGS means the server accepts arbitrary keywords. Some
155
+ * providers accept none at all, and there the new-mail tracking cannot work —
156
+ * better to say so than to tag silently into the void.
157
+ */
158
+ keywordSupported(permanentFlags) {
159
+ const keyword = this.config.imap.seenKeyword;
160
+ if (keyword === '')
161
+ return false;
162
+ return permanentFlags.has('\\*') || permanentFlags.has(keyword);
163
+ }
164
+ get seenKeyword() {
165
+ return this.config.imap.seenKeyword;
166
+ }
167
+ get defaultMailbox() {
168
+ return this.config.imap.mailbox;
169
+ }
170
+ get maxMessages() {
171
+ return this.config.imap.maxMessages;
172
+ }
173
+ get user() {
174
+ return this.config.imap.user;
175
+ }
176
+ /**
177
+ * Reads the threading headers of one message, for linking a draft into an
178
+ * existing conversation.
179
+ *
180
+ * Only the Message-ID and the References chain are taken; nothing the sender
181
+ * wrote as prose comes back from here.
182
+ */
183
+ async threadHeaders(mailbox, uid) {
184
+ return this.withMailbox(mailbox, true, async (connection) => {
185
+ for await (const message of connection.fetch([uid], { uid: true, envelope: true, headers: ['references', 'in-reply-to'] }, { uid: true })) {
186
+ const raw = message.headers?.toString('utf-8') ?? '';
187
+ const ids = raw.match(/<[^\s<>]{1,255}>/g) ?? [];
188
+ const messageId = message.envelope?.messageId;
189
+ const chain = [...ids, ...(messageId === undefined ? [] : [messageId])];
190
+ return {
191
+ messageId,
192
+ // Bounded: a long-running thread accumulates dozens of ids and each
193
+ // one lengthens the header we are about to write.
194
+ references: [...new Set(chain)].slice(-20),
195
+ };
196
+ }
197
+ throw new ToolInputError(`imap-mcp: no message with UID ${uid} in this mailbox.`);
198
+ });
199
+ }
200
+ async search(client, query) {
201
+ const found = await withTimeout(client.search(query, { uid: true }), 'SEARCH');
202
+ return found === false ? [] : found;
203
+ }
204
+ /** Envelope-level fetch, newest UID first. */
205
+ async fetchSummaries(client, uids) {
206
+ if (uids.length === 0)
207
+ return [];
208
+ const messages = [];
209
+ for await (const message of client.fetch(uids, {
210
+ uid: true,
211
+ envelope: true,
212
+ flags: true,
213
+ size: true,
214
+ internalDate: true,
215
+ }, { uid: true })) {
216
+ messages.push(message);
217
+ }
218
+ return messages.sort((a, b) => b.uid - a.uid);
219
+ }
220
+ /**
221
+ * Adds the bookkeeping keyword to the given UIDs.
222
+ *
223
+ * This is the one write the server performs on its own initiative, and it
224
+ * stays available under the default IMAP_READ_ONLY: without it `list_new_messages`
225
+ * would return the same mail forever. It touches no flag a human interacts
226
+ * with — `\Seen` in particular is left alone.
227
+ */
228
+ async tagSeen(client, uids) {
229
+ if (uids.length === 0 || this.config.imap.seenKeyword === '')
230
+ return;
231
+ await withTimeout(client.messageFlagsAdd(uids, [this.config.imap.seenKeyword], {
232
+ uid: true,
233
+ }), 'STORE');
234
+ }
235
+ async close() {
236
+ if (this.connection === undefined)
237
+ return;
238
+ try {
239
+ await this.connection.logout();
240
+ }
241
+ catch {
242
+ this.connection.close();
243
+ }
244
+ this.connection = undefined;
245
+ }
246
+ }
247
+ /** Rejects a promise that outlives the command timeout. */
248
+ export async function withTimeout(promise, command, ms = COMMAND_TIMEOUT_MS) {
249
+ let timer;
250
+ try {
251
+ return await Promise.race([
252
+ promise,
253
+ new Promise((_resolve, reject) => {
254
+ timer = setTimeout(() => reject(new MailError(`IMAP ${command} timed out after ${ms / 1000} seconds`, 'ETIMEDOUT')), ms);
255
+ }),
256
+ ]);
257
+ }
258
+ finally {
259
+ if (timer !== undefined)
260
+ clearTimeout(timer);
261
+ }
262
+ }
263
+ const CONNECTION_ERROR_CODES = new Set([
264
+ 'ECONNRESET',
265
+ 'EPIPE',
266
+ 'ETIMEDOUT',
267
+ 'ECONNREFUSED',
268
+ 'EHOSTUNREACH',
269
+ 'ENOTCONN',
270
+ 'NoConnection',
271
+ ]);
272
+ function isConnectionError(error) {
273
+ if (error instanceof MailError) {
274
+ return error.code !== undefined && CONNECTION_ERROR_CODES.has(error.code);
275
+ }
276
+ const code = error?.code;
277
+ return typeof code === 'string' && CONNECTION_ERROR_CODES.has(code);
278
+ }
279
+ /**
280
+ * Normalises whatever imapflow threw into a {@link MailError}.
281
+ *
282
+ * Only the response text is carried over, never the whole error object: it
283
+ * holds the command that was sent, and for a LOGIN that means the password.
284
+ */
285
+ export function asMailError(error) {
286
+ if (error instanceof MailError)
287
+ return error;
288
+ if (error instanceof ToolInputError)
289
+ throw error;
290
+ const source = error;
291
+ const message = typeof source?.message === 'string' ? source.message : String(error);
292
+ const code = typeof source?.code === 'string' ? source.code : undefined;
293
+ const responseText = typeof source?.responseText === 'string'
294
+ ? source.responseText
295
+ : typeof source?.response === 'string'
296
+ ? source.response
297
+ : '';
298
+ return new MailError(`IMAP error: ${message}`, code, responseText);
299
+ }
300
+ //# sourceMappingURL=imap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imap.js","sourceRoot":"","sources":["../src/imap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { loadConfig } from './config.js';
4
+ import { createServer } from './server.js';
5
+ import { ToolFilterError } from './tool-filter.js';
6
+ async function main() {
7
+ const config = loadConfig();
8
+ if (config.imap.insecureTls) {
9
+ console.error('imap-mcp: IMAP_INSECURE_TLS=true — certificate validation is disabled for the mail connections');
10
+ }
11
+ if (config.readOnly) {
12
+ console.error('imap-mcp: IMAP_READ_ONLY is not "false" — the mailbox write tools are not registered');
13
+ }
14
+ let server;
15
+ try {
16
+ server = createServer(config);
17
+ }
18
+ catch (error) {
19
+ // A bad tool list is operator feedback, not a crash.
20
+ if (error instanceof ToolFilterError) {
21
+ console.error(`imap-mcp: ${error.message}`);
22
+ process.exit(1);
23
+ }
24
+ throw error;
25
+ }
26
+ // stdout belongs to the protocol; everything human-readable goes to stderr.
27
+ await server.connect(new StdioServerTransport());
28
+ console.error(config.imap.host === undefined
29
+ ? 'imap-mcp: connected without configuration — tools are listed but every call will fail'
30
+ : `imap-mcp: connected, mailbox "${config.imap.mailbox}" on ${config.imap.host}`);
31
+ }
32
+ main().catch((error) => {
33
+ console.error('imap-mcp: fatal error:', error);
34
+ process.exit(1);
35
+ });
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CACX,gGAAgG,CACjG,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CACX,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,qDAAqD;QACrD,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;YACrC,OAAO,CAAC,KAAK,CAAC,aAAa,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,4EAA4E;IAC5E,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CACX,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;QAC5B,CAAC,CAAC,uFAAuF;QACzF,CAAC,CAAC,iCAAiC,MAAM,CAAC,IAAI,CAAC,OAAO,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CACnF,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { FetchMessageObject } from 'imapflow';
2
+ import { type SecurityAssessment } from './analyze.js';
3
+ import { type AttachmentCandidate } from './attachments.js';
4
+ export interface MessageSummary {
5
+ uid: number;
6
+ subject: string;
7
+ from: string;
8
+ to: string;
9
+ date: string | undefined;
10
+ size: number | undefined;
11
+ flags: string[];
12
+ seen: boolean;
13
+ flagged: boolean;
14
+ answered: boolean;
15
+ hasAttachments: boolean;
16
+ }
17
+ /** Projection of an envelope fetch. Every string here came from the sender. */
18
+ export declare function summarize(message: FetchMessageObject): MessageSummary;
19
+ export interface RenderedMessage {
20
+ /** Server-side facts and verdicts. Safe to present as this server's voice. */
21
+ metadata: {
22
+ uid: number;
23
+ date: string | undefined;
24
+ messageId: string | undefined;
25
+ /** The References/In-Reply-To chain, for reconstructing the conversation. */
26
+ references: string[];
27
+ security: SecurityAssessment;
28
+ attachments: Array<Pick<AttachmentCandidate, 'partId' | 'filename' | 'contentType' | 'size'>>;
29
+ };
30
+ /** Headers and body, written by the sender. */
31
+ content: string;
32
+ }
33
+ /**
34
+ * Parses a raw message and splits it into what this server knows and what the
35
+ * sender wrote.
36
+ *
37
+ * The split is the whole point: the metadata block can be trusted because the
38
+ * server produced it, and everything in `content` gets fenced by the caller so
39
+ * the model can tell where the server stops speaking.
40
+ */
41
+ export declare function renderMessage(uid: number, source: Buffer, trustedAuthservId?: string): Promise<RenderedMessage>;
42
+ /**
43
+ * Message-IDs from the References/In-Reply-To chain, for thread reconstruction.
44
+ * Bounded: a long-running thread accumulates hundreds of them, and each one
45
+ * becomes a search term.
46
+ */
47
+ export declare function threadIdsOf(parsed: {
48
+ messageId?: string | undefined;
49
+ references?: string | string[] | undefined;
50
+ inReplyTo?: string | undefined;
51
+ }): string[];
@@ -0,0 +1,155 @@
1
+ import { simpleParser } from 'mailparser';
2
+ import { assess, htmlToText, sanitizeText, } from './analyze.js';
3
+ import { collectAttachments } from './attachments.js';
4
+ /**
5
+ * Per-field caps. One oversized header must not be able to eat the whole result
6
+ * budget on its own — a 2 MB Subject is legal MIME.
7
+ */
8
+ const SUBJECT_MAX = 2000;
9
+ const ADDRESS_MAX = 4000;
10
+ /** RFC 5322 allows a long Message-ID; nothing needs more than this to be useful. */
11
+ const MESSAGE_ID_MAX = 256;
12
+ /** Projection of an envelope fetch. Every string here came from the sender. */
13
+ export function summarize(message) {
14
+ const envelope = message.envelope;
15
+ const flags = [...(message.flags ?? [])];
16
+ return {
17
+ uid: message.uid,
18
+ subject: sanitizeText(envelope?.subject ?? '(no subject)', SUBJECT_MAX),
19
+ from: sanitizeText(formatEnvelopeAddresses(envelope?.from), ADDRESS_MAX),
20
+ to: sanitizeText(formatEnvelopeAddresses(envelope?.to), ADDRESS_MAX),
21
+ date: isoDate(envelope?.date ?? message.internalDate),
22
+ size: message.size,
23
+ flags,
24
+ seen: flags.includes('\\Seen'),
25
+ flagged: flags.includes('\\Flagged'),
26
+ answered: flags.includes('\\Answered'),
27
+ hasAttachments: collectAttachments(message.bodyStructure).length > 0,
28
+ };
29
+ }
30
+ /** imapflow hands back a Date, but a malformed header can leave a raw string. */
31
+ function isoDate(value) {
32
+ if (value === undefined)
33
+ return undefined;
34
+ const date = value instanceof Date ? value : new Date(value);
35
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
36
+ }
37
+ function formatEnvelopeAddresses(addresses) {
38
+ if (addresses === undefined || addresses.length === 0)
39
+ return '(none)';
40
+ return addresses
41
+ .map((entry) => {
42
+ const address = entry.address ?? '(no address)';
43
+ return entry.name === undefined || entry.name === ''
44
+ ? address
45
+ : `${entry.name} <${address}>`;
46
+ })
47
+ .join(', ');
48
+ }
49
+ function formatParsedAddresses(value) {
50
+ if (value === undefined)
51
+ return '(none)';
52
+ const list = Array.isArray(value) ? value : [value];
53
+ const text = list.map((entry) => entry.text).join(', ');
54
+ return text === '' ? '(none)' : text;
55
+ }
56
+ /**
57
+ * Parses a raw message and splits it into what this server knows and what the
58
+ * sender wrote.
59
+ *
60
+ * The split is the whole point: the metadata block can be trusted because the
61
+ * server produced it, and everything in `content` gets fenced by the caller so
62
+ * the model can tell where the server stops speaking.
63
+ */
64
+ export async function renderMessage(uid, source, trustedAuthservId) {
65
+ const parsed = await simpleParser(source, {
66
+ // Attachments are fetched deliberately, one at a time, through the policy
67
+ // in attachments.ts. Parsing them here would pull every byte into memory
68
+ // for a call that only wants the text.
69
+ skipImageLinks: true,
70
+ });
71
+ const body = bodyTextOf(parsed);
72
+ const text = sanitizeText(body);
73
+ const security = assess(`${parsed.subject ?? ''}\n${text}`, headerValue(parsed, 'authentication-results'), trustedAuthservId);
74
+ const content = [
75
+ `From: ${sanitizeText(formatParsedAddresses(parsed.from), ADDRESS_MAX)}`,
76
+ `To: ${sanitizeText(formatParsedAddresses(parsed.to), ADDRESS_MAX)}`,
77
+ parsed.cc === undefined
78
+ ? undefined
79
+ : `Cc: ${sanitizeText(formatParsedAddresses(parsed.cc), ADDRESS_MAX)}`,
80
+ `Subject: ${sanitizeText(parsed.subject ?? '(no subject)', SUBJECT_MAX)}`,
81
+ '',
82
+ text,
83
+ ]
84
+ .filter((line) => line !== undefined)
85
+ .join('\n');
86
+ return {
87
+ metadata: {
88
+ uid,
89
+ date: parsed.date?.toISOString(),
90
+ // Sender-chosen, unbounded in length, and it lands in the metadata block
91
+ // *outside* the fence — the one part of the result the model is told is
92
+ // ours. Every other sender string on this path is sanitised; this one
93
+ // was going through raw.
94
+ messageId: parsed.messageId === undefined
95
+ ? undefined
96
+ : sanitizeText(parsed.messageId, MESSAGE_ID_MAX),
97
+ references: threadIdsOf({
98
+ ...(parsed.references === undefined
99
+ ? {}
100
+ : { references: parsed.references }),
101
+ ...(parsed.inReplyTo === undefined
102
+ ? {}
103
+ : { inReplyTo: parsed.inReplyTo }),
104
+ }),
105
+ security,
106
+ attachments: [],
107
+ },
108
+ content,
109
+ };
110
+ }
111
+ /**
112
+ * Prefers the plain-text part, falls back to converting the HTML one.
113
+ *
114
+ * mailparser's own `text` fallback is deliberately not used: it keeps content
115
+ * the recipient never sees, which is precisely where an instruction meant only
116
+ * for the model would be parked.
117
+ */
118
+ function bodyTextOf(parsed) {
119
+ if (typeof parsed.text === 'string' && parsed.text.trim() !== '') {
120
+ return parsed.text;
121
+ }
122
+ if (typeof parsed.html === 'string' && parsed.html !== '') {
123
+ return htmlToText(parsed.html);
124
+ }
125
+ return '(no text content)';
126
+ }
127
+ function headerValue(parsed, name) {
128
+ const value = parsed.headers.get(name);
129
+ if (typeof value === 'string')
130
+ return value;
131
+ if (Array.isArray(value))
132
+ return value.filter((v) => typeof v === 'string').join('\n');
133
+ return undefined;
134
+ }
135
+ /**
136
+ * Message-IDs from the References/In-Reply-To chain, for thread reconstruction.
137
+ * Bounded: a long-running thread accumulates hundreds of them, and each one
138
+ * becomes a search term.
139
+ */
140
+ export function threadIdsOf(parsed) {
141
+ const references = parsed.references === undefined
142
+ ? []
143
+ : Array.isArray(parsed.references)
144
+ ? parsed.references
145
+ : parsed.references.split(/\s+/);
146
+ const all = [
147
+ ...references,
148
+ ...(parsed.inReplyTo === undefined ? [] : [parsed.inReplyTo]),
149
+ ...(parsed.messageId === undefined ? [] : [parsed.messageId]),
150
+ ]
151
+ .map((id) => id.trim())
152
+ .filter((id) => /^<[^\s<>]{1,255}>$/.test(id));
153
+ return [...new Set(all)].slice(0, 50);
154
+ }
155
+ //# sourceMappingURL=message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../src/message.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Config } from './config.js';
3
+ import { ImapClient } from './imap.js';
4
+ /**
5
+ * Exposes attachments as MCP resources.
6
+ *
7
+ * This is the second half of "downloadable": where `IMAP_DOWNLOAD_DIR` is unset
8
+ * — a container, a remote deployment, anywhere the server has no useful
9
+ * filesystem — the client can still fetch the bytes itself over the protocol
10
+ * instead of having them base64-encoded into the conversation.
11
+ *
12
+ * The read callback re-runs the *entire* attachment policy. It has to: a
13
+ * resource read does not go through `get_attachments`, so anything enforced
14
+ * only there would simply be a second, unguarded door to the same bytes.
15
+ */
16
+ export declare function registerAttachmentResources(server: McpServer, client: ImapClient, config: Config): void;