fluxmail 0.13.0 → 0.14.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/accounts/registry.d.ts +1 -0
- package/dist/accounts/registry.d.ts.map +1 -1
- package/dist/accounts/registry.js +3 -2
- package/dist/accounts/registry.js.map +1 -1
- package/dist/accounts/telemetry.d.ts +6 -1
- package/dist/accounts/telemetry.d.ts.map +1 -1
- package/dist/accounts/telemetry.js +17 -0
- package/dist/accounts/telemetry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +122 -25
- package/dist/cli.js.map +1 -1
- package/dist/cliInstances.d.ts +6 -4
- package/dist/cliInstances.d.ts.map +1 -1
- package/dist/cliInstances.js +29 -9
- package/dist/cliInstances.js.map +1 -1
- package/dist/http/app.d.ts.map +1 -1
- package/dist/http/app.js +16 -1
- package/dist/http/app.js.map +1 -1
- package/dist/http/identity.d.ts.map +1 -1
- package/dist/http/identity.js +65 -10
- package/dist/http/identity.js.map +1 -1
- package/dist/http/rest.d.ts.map +1 -1
- package/dist/http/rest.js +30 -8
- package/dist/http/rest.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/attachmentContent.d.ts +32 -0
- package/dist/mcp/attachmentContent.d.ts.map +1 -0
- package/dist/mcp/attachmentContent.js +155 -0
- package/dist/mcp/attachmentContent.js.map +1 -0
- package/dist/mcp/buildServer.d.ts +3 -1
- package/dist/mcp/buildServer.d.ts.map +1 -1
- package/dist/mcp/buildServer.js +130 -49
- package/dist/mcp/buildServer.js.map +1 -1
- package/dist/mcp/pdfTextWorker.d.ts +2 -0
- package/dist/mcp/pdfTextWorker.d.ts.map +1 -0
- package/dist/mcp/pdfTextWorker.js +39 -0
- package/dist/mcp/pdfTextWorker.js.map +1 -0
- package/dist/permissions.d.ts +19 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +42 -2
- package/dist/permissions.js.map +1 -1
- package/dist/service/emailService.d.ts +1 -0
- package/dist/service/emailService.d.ts.map +1 -1
- package/dist/service/emailService.js +4 -1
- package/dist/service/emailService.js.map +1 -1
- package/dist/service/publicErrors.d.ts +4 -0
- package/dist/service/publicErrors.d.ts.map +1 -1
- package/dist/service/publicErrors.js +10 -1
- package/dist/service/publicErrors.js.map +1 -1
- package/dist/storage/apiKeys.d.ts.map +1 -1
- package/dist/storage/apiKeys.js +2 -2
- package/dist/storage/apiKeys.js.map +1 -1
- package/package.json +7 -5
- package/pdf-cmaps/78-EUC-H.bcmap +0 -0
- package/pdf-cmaps/78-EUC-V.bcmap +0 -0
- package/pdf-cmaps/78-H.bcmap +0 -0
- package/pdf-cmaps/78-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/78-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/78-V.bcmap +0 -0
- package/pdf-cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Add-H.bcmap +0 -0
- package/pdf-cmaps/Add-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/Add-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Add-V.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-3.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-4.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-5.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/pdf-cmaps/B5-H.bcmap +0 -0
- package/pdf-cmaps/B5-V.bcmap +0 -0
- package/pdf-cmaps/B5pc-H.bcmap +0 -0
- package/pdf-cmaps/B5pc-V.bcmap +0 -0
- package/pdf-cmaps/CNS-EUC-H.bcmap +0 -0
- package/pdf-cmaps/CNS-EUC-V.bcmap +0 -0
- package/pdf-cmaps/CNS1-H.bcmap +0 -0
- package/pdf-cmaps/CNS1-V.bcmap +0 -0
- package/pdf-cmaps/CNS2-H.bcmap +0 -0
- package/pdf-cmaps/CNS2-V.bcmap +3 -0
- package/pdf-cmaps/ETHK-B5-H.bcmap +0 -0
- package/pdf-cmaps/ETHK-B5-V.bcmap +0 -0
- package/pdf-cmaps/ETen-B5-H.bcmap +0 -0
- package/pdf-cmaps/ETen-B5-V.bcmap +0 -0
- package/pdf-cmaps/ETenms-B5-H.bcmap +3 -0
- package/pdf-cmaps/ETenms-B5-V.bcmap +0 -0
- package/pdf-cmaps/EUC-H.bcmap +0 -0
- package/pdf-cmaps/EUC-V.bcmap +0 -0
- package/pdf-cmaps/Ext-H.bcmap +0 -0
- package/pdf-cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Ext-V.bcmap +0 -0
- package/pdf-cmaps/GB-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GB-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GB-H.bcmap +4 -0
- package/pdf-cmaps/GB-V.bcmap +0 -0
- package/pdf-cmaps/GBK-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBK-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBK2K-H.bcmap +0 -0
- package/pdf-cmaps/GBK2K-V.bcmap +0 -0
- package/pdf-cmaps/GBKp-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBKp-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBT-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBT-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBT-H.bcmap +0 -0
- package/pdf-cmaps/GBT-V.bcmap +0 -0
- package/pdf-cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBpc-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBpc-EUC-V.bcmap +0 -0
- package/pdf-cmaps/H.bcmap +0 -0
- package/pdf-cmaps/HKdla-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKdla-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKdlb-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKdlb-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKgccs-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKgccs-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKm314-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKm314-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKm471-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKm471-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKscs-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKscs-B5-V.bcmap +0 -0
- package/pdf-cmaps/Hankaku.bcmap +0 -0
- package/pdf-cmaps/Hiragana.bcmap +0 -0
- package/pdf-cmaps/KSC-EUC-H.bcmap +0 -0
- package/pdf-cmaps/KSC-EUC-V.bcmap +0 -0
- package/pdf-cmaps/KSC-H.bcmap +0 -0
- package/pdf-cmaps/KSC-Johab-H.bcmap +0 -0
- package/pdf-cmaps/KSC-Johab-V.bcmap +0 -0
- package/pdf-cmaps/KSC-V.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-H.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-V.bcmap +0 -0
- package/pdf-cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/pdf-cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/pdf-cmaps/Katakana.bcmap +0 -0
- package/pdf-cmaps/LICENSE +36 -0
- package/pdf-cmaps/NWP-H.bcmap +0 -0
- package/pdf-cmaps/NWP-V.bcmap +0 -0
- package/pdf-cmaps/RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Roman.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/pdf-cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/V.bcmap +0 -0
- package/pdf-cmaps/WP-Symbol.bcmap +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { Worker } from 'node:worker_threads';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const TEXT_TYPES = /^(text\/[\w.+-]+|application\/(json|xml|csv|yaml|x-yaml|javascript|[\w.-]+\+(json|xml)))$/;
|
|
5
|
+
const IMAGE_TYPES = /^image\/(png|jpeg|gif|webp)$/;
|
|
6
|
+
// At most `workers` extractions run at once in this process, so concurrent requests cannot
|
|
7
|
+
// multiply the heap limit. Waiting for a worker shares the same timeout as extraction.
|
|
8
|
+
const PDF_TEXT_LIMITS = { maxPages: 200, maxChars: 80_000, heapMb: 128, timeoutMs: 15_000, workers: 2 };
|
|
9
|
+
export function mediaType(mimeType) {
|
|
10
|
+
return mimeType.split(';')[0].trim().toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
function decodeText(content) {
|
|
13
|
+
try {
|
|
14
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(content);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Some clients, including claude.ai, only pass a blob to the model through
|
|
22
|
+
* file storage, so UTF-8 text attachments go out as text.
|
|
23
|
+
*/
|
|
24
|
+
export function attachmentContents(uri, mimeType, content) {
|
|
25
|
+
const text = TEXT_TYPES.test(mediaType(mimeType)) ? decodeText(content) : undefined;
|
|
26
|
+
return text === undefined ? { uri, mimeType, blob: content.toString('base64') } : { uri, mimeType, text };
|
|
27
|
+
}
|
|
28
|
+
export function inlineContent(uri, mimeType, content) {
|
|
29
|
+
if (IMAGE_TYPES.test(mediaType(mimeType)))
|
|
30
|
+
return { type: 'image', data: content.toString('base64'), mimeType };
|
|
31
|
+
return { type: 'resource', resource: attachmentContents(uri, mimeType, content) };
|
|
32
|
+
}
|
|
33
|
+
function parsePages(pages) {
|
|
34
|
+
const [first, last = first] = pages.split('-').map(Number);
|
|
35
|
+
return { first, last };
|
|
36
|
+
}
|
|
37
|
+
export const pageRange = z
|
|
38
|
+
.string()
|
|
39
|
+
.regex(/^[1-9]\d{0,5}(-[1-9]\d{0,5})?$/)
|
|
40
|
+
.refine((value) => {
|
|
41
|
+
const { first, last } = parsePages(value);
|
|
42
|
+
return last >= first;
|
|
43
|
+
}, { message: 'Use a page or an ascending range such as 201-400.' })
|
|
44
|
+
.describe('PDF page or page range to read as text, such as 201-400');
|
|
45
|
+
// The build emits the worker next to this module; tests load the source.
|
|
46
|
+
const PDF_WORKER_URL = new URL(import.meta.url.endsWith('.ts') ? './pdfTextWorker.ts' : './pdfTextWorker.js', import.meta.url);
|
|
47
|
+
// Character maps for PDFs that use predefined CJK encodings, copied from pdfjs-dist at the
|
|
48
|
+
// pdf.js version unpdf bundles. Refresh them when upgrading unpdf. Same path from src and dist.
|
|
49
|
+
export const PDF_CMAP_PDFJS_VERSION = '5.6.205';
|
|
50
|
+
const PDF_CMAP_DIRECTORY = fileURLToPath(new URL('../../pdf-cmaps/', import.meta.url));
|
|
51
|
+
let runningWorkers = 0;
|
|
52
|
+
const workerQueue = [];
|
|
53
|
+
/** Resolves true once a worker slot is held, or false when the signal aborts first. */
|
|
54
|
+
function acquireWorkerSlot(signal) {
|
|
55
|
+
if (signal.aborted)
|
|
56
|
+
return Promise.resolve(false);
|
|
57
|
+
if (runningWorkers < PDF_TEXT_LIMITS.workers) {
|
|
58
|
+
runningWorkers++;
|
|
59
|
+
return Promise.resolve(true);
|
|
60
|
+
}
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
const grant = () => {
|
|
63
|
+
signal.removeEventListener('abort', cancel);
|
|
64
|
+
resolve(true);
|
|
65
|
+
};
|
|
66
|
+
const cancel = () => {
|
|
67
|
+
workerQueue.splice(workerQueue.indexOf(grant), 1);
|
|
68
|
+
resolve(false);
|
|
69
|
+
};
|
|
70
|
+
workerQueue.push(grant);
|
|
71
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Hand the slot to the next waiting extraction, or free it. */
|
|
75
|
+
function releaseWorkerSlot() {
|
|
76
|
+
const next = workerQueue.shift();
|
|
77
|
+
if (next)
|
|
78
|
+
next();
|
|
79
|
+
else
|
|
80
|
+
runningWorkers--;
|
|
81
|
+
}
|
|
82
|
+
/** Extract PDF text in a memory-limited worker. Resolves undefined when extraction fails or is aborted. */
|
|
83
|
+
export async function pdfText(content, signal, pages = '1-999999', onError) {
|
|
84
|
+
const waitTimeout = AbortSignal.timeout(PDF_TEXT_LIMITS.timeoutMs);
|
|
85
|
+
if (!(await acquireWorkerSlot(AbortSignal.any([signal, waitTimeout])))) {
|
|
86
|
+
if (waitTimeout.aborted && !signal.aborted)
|
|
87
|
+
onError?.(new Error('PDF text extraction timed out waiting for a worker.'));
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return await runPdfWorker(content, signal, pages, onError);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
releaseWorkerSlot();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function runPdfWorker(content, signal, pages, onError) {
|
|
98
|
+
const { first: firstPage, last: lastPage } = parsePages(pages);
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
if (signal.aborted)
|
|
101
|
+
return resolve(undefined);
|
|
102
|
+
const bytes = new Uint8Array(content);
|
|
103
|
+
const worker = new Worker(PDF_WORKER_URL, {
|
|
104
|
+
workerData: {
|
|
105
|
+
bytes,
|
|
106
|
+
cMapDirectory: PDF_CMAP_DIRECTORY,
|
|
107
|
+
firstPage,
|
|
108
|
+
lastPage,
|
|
109
|
+
maxPages: PDF_TEXT_LIMITS.maxPages,
|
|
110
|
+
maxChars: PDF_TEXT_LIMITS.maxChars,
|
|
111
|
+
},
|
|
112
|
+
transferList: [bytes.buffer],
|
|
113
|
+
resourceLimits: { maxOldGenerationSizeMb: PDF_TEXT_LIMITS.heapMb },
|
|
114
|
+
});
|
|
115
|
+
let settled = false;
|
|
116
|
+
const finish = (result, error) => {
|
|
117
|
+
if (settled)
|
|
118
|
+
return;
|
|
119
|
+
settled = true;
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
signal.removeEventListener('abort', abort);
|
|
122
|
+
if (error)
|
|
123
|
+
onError?.(error);
|
|
124
|
+
// Resolve after the thread stops so a released slot no longer holds its heap.
|
|
125
|
+
void worker.terminate().finally(() => resolve(result));
|
|
126
|
+
};
|
|
127
|
+
const abort = () => finish();
|
|
128
|
+
const timer = setTimeout(() => finish(undefined, new Error('PDF text extraction timed out.')), PDF_TEXT_LIMITS.timeoutMs);
|
|
129
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
130
|
+
worker.once('message', (result) => finish(result));
|
|
131
|
+
worker.once('error', (error) => finish(undefined, error));
|
|
132
|
+
worker.once('exit', (code) => finish(undefined, new Error(`PDF text worker exited with code ${code}.`)));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
export function pdfTextContent(filename, pdf) {
|
|
136
|
+
if (!pdf)
|
|
137
|
+
return [];
|
|
138
|
+
const { firstPage, lastPage, totalPages } = pdf;
|
|
139
|
+
if (lastPage < firstPage) {
|
|
140
|
+
return [{ type: 'text', text: `${filename} has ${totalPages} ${totalPages === 1 ? 'page' : 'pages'}.` }];
|
|
141
|
+
}
|
|
142
|
+
const whole = firstPage === 1 && lastPage === totalPages && !pdf.cut;
|
|
143
|
+
const pages = firstPage === lastPage ? `page ${firstPage}` : `pages ${firstPage}-${lastPage}`;
|
|
144
|
+
const cut = pdf.cut ? `, cut at ${PDF_TEXT_LIMITS.maxChars.toLocaleString('en-US')} characters` : '';
|
|
145
|
+
const scope = whole ? '' : ` (${pages} of ${totalPages}${cut})`;
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
type: 'text',
|
|
149
|
+
text: pdf.text
|
|
150
|
+
? `Text extracted from ${filename}${scope}:\n\n${pdf.text}`
|
|
151
|
+
: `No text found in ${filename}${scope}.`,
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=attachmentContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachmentContent.js","sourceRoot":"","sources":["../../src/mcp/attachmentContent.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { EmailService, SendInput } from '../service/emailService.js';
|
|
3
|
-
import { type PermissionPolicy } from '../permissions.js';
|
|
3
|
+
import { type PermissionPolicy, type PermissionSource } from '../permissions.js';
|
|
4
4
|
import { type Telemetry } from '../telemetry.js';
|
|
5
5
|
import { type Logger } from '../logging.js';
|
|
6
6
|
type DraftArgs = {
|
|
@@ -28,6 +28,8 @@ export declare function toSendRequest(args: DraftArgs & {
|
|
|
28
28
|
export type McpTransport = 'http' | 'stdio' | 'unknown';
|
|
29
29
|
export interface BuildMcpServerOptions {
|
|
30
30
|
permissions?: PermissionPolicy;
|
|
31
|
+
/** How the connection's permissions can be changed. Defaults to the stdio options for stdio transport. */
|
|
32
|
+
permissionSource?: PermissionSource;
|
|
31
33
|
maxAttachmentBytes?: number;
|
|
32
34
|
telemetry?: Telemetry;
|
|
33
35
|
transport?: McpTransport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildServer.d.ts","sourceRoot":"","sources":["../../src/mcp/buildServer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"buildServer.d.ts","sourceRoot":"","sources":["../../src/mcp/buildServer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AA2BtF,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAG1E,OAAO,EAQL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAoB,KAAK,SAAS,EAA4B,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAsIxD,KAAK,SAAS,GAAG;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AA0BF,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAuBrG;AAoDD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,0GAA0G;IAC1G,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAuKrD,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,qBAA0B,GAAG,SAAS,CAg2BpG"}
|
package/dist/mcp/buildServer.js
CHANGED
|
@@ -3,15 +3,20 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
5
5
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
-
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { isInitializeRequest, LATEST_PROTOCOL_VERSION, ListToolsRequestSchema, SUPPORTED_PROTOCOL_VERSIONS, } from '@modelcontextprotocol/sdk/types.js';
|
|
7
7
|
import { EmailError, isEmailError, mergeEmailQueries, parseEmailSearch, parseSingleAddress, } from '@fluxmail/core';
|
|
8
8
|
import { DEFAULT_MAX_ATTACHMENT_BYTES } from '../config.js';
|
|
9
|
-
import {
|
|
9
|
+
import { DEFAULT_PERMISSION_POLICY, MCP_CAPABILITIES, MCP_CAPABILITY_DESCRIPTIONS, enableCapabilitiesInstructions, hasCapability, normalizePermissionPolicy, } from '../permissions.js';
|
|
10
10
|
import { captureOperation } from '../telemetry.js';
|
|
11
11
|
import { logFailure } from '../logging.js';
|
|
12
|
-
import { ClientInputError, publicError } from '../service/publicErrors.js';
|
|
12
|
+
import { ClientInputError, MissingCapabilityError, publicError } from '../service/publicErrors.js';
|
|
13
13
|
import { outputSchemas } from './outputSchemas.js';
|
|
14
|
+
import { attachmentContents, inlineContent, mediaType, pageRange, pdfText, pdfTextContent, } from './attachmentContent.js';
|
|
14
15
|
const MAX_BODY_CHARS = 50_000;
|
|
16
|
+
const BYTES_PER_MEGABYTE = 1024 * 1024;
|
|
17
|
+
function sizeLabel(bytes) {
|
|
18
|
+
return bytes % BYTES_PER_MEGABYTE === 0 ? `${bytes / BYTES_PER_MEGABYTE} MB` : `${bytes} bytes`;
|
|
19
|
+
}
|
|
15
20
|
const TELEMETRY_ERROR = Symbol('telemetryError');
|
|
16
21
|
const EMPTY_INPUT_SCHEMA = {
|
|
17
22
|
type: 'object',
|
|
@@ -219,7 +224,9 @@ function toolFeatureProperties(tool, args) {
|
|
|
219
224
|
case 'modify_emails':
|
|
220
225
|
return typeof input.action === 'string' ? { action: input.action } : {};
|
|
221
226
|
case 'download_attachment':
|
|
222
|
-
return {
|
|
227
|
+
return {
|
|
228
|
+
destination: input.inline === true ? 'inline' : typeof input.pages === 'string' ? 'pdf_pages' : 'resource',
|
|
229
|
+
};
|
|
223
230
|
default:
|
|
224
231
|
return {};
|
|
225
232
|
}
|
|
@@ -316,8 +323,22 @@ const MODIFY_CAPABILITIES = {
|
|
|
316
323
|
removeLabels: 'mail.organize',
|
|
317
324
|
};
|
|
318
325
|
const PROTECTED_MOVE_DESTINATIONS = new Set(['archive', 'trash']);
|
|
326
|
+
function disabledCapabilitiesInstructions(permissions, source) {
|
|
327
|
+
const disabled = MCP_CAPABILITIES.filter((capability) => !hasCapability(permissions, capability));
|
|
328
|
+
if (!disabled.length)
|
|
329
|
+
return '';
|
|
330
|
+
const actions = disabled.map((capability) => `- ${capability} (${MCP_CAPABILITY_DESCRIPTIONS[capability].replace(/\.$/, '')}). ` +
|
|
331
|
+
enableCapabilitiesInstructions(permissions, [capability], source));
|
|
332
|
+
return ('\n\nThis connection has limited permissions. Tools for the disabled actions below are not available. ' +
|
|
333
|
+
'When the user asks for a disabled action, such as sending, replying to, or forwarding mail, tell them ' +
|
|
334
|
+
'it is disabled for this Fluxmail connection and how to enable it. Before changing permissions, explain ' +
|
|
335
|
+
"the access the change grants and get the user's explicit approval for that permission change. A request " +
|
|
336
|
+
'to perform an email action does not authorize changing permissions.\n' +
|
|
337
|
+
actions.join('\n'));
|
|
338
|
+
}
|
|
319
339
|
export function buildMcpServer(service, options = {}) {
|
|
320
|
-
const permissions = normalizePermissionPolicy(options.permissions ??
|
|
340
|
+
const permissions = normalizePermissionPolicy(options.permissions ?? DEFAULT_PERMISSION_POLICY);
|
|
341
|
+
const permissionSource = options.permissionSource ?? (options.transport === 'stdio' ? { kind: 'stdio' } : undefined);
|
|
321
342
|
const maxAttachmentBytes = options.maxAttachmentBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES;
|
|
322
343
|
const can = (capability) => hasCapability(permissions, capability);
|
|
323
344
|
const canAll = (capabilities) => capabilities.every(can);
|
|
@@ -325,9 +346,20 @@ export function buildMcpServer(service, options = {}) {
|
|
|
325
346
|
const requireCapabilities = (capabilities) => {
|
|
326
347
|
const missing = capabilities.filter((capability) => !can(capability));
|
|
327
348
|
if (missing.length) {
|
|
328
|
-
throw new
|
|
349
|
+
throw new MissingCapabilityError(`This MCP connection does not allow: ${missing.join(', ')}. ` +
|
|
350
|
+
enableCapabilitiesInstructions(permissions, missing, permissionSource), missing);
|
|
329
351
|
}
|
|
330
352
|
};
|
|
353
|
+
const downloadAttachment = async (accountId, messageId, attachmentId) => {
|
|
354
|
+
const attachment = await service.getAttachment(accountId, messageId, attachmentId, maxAttachmentBytes);
|
|
355
|
+
if (attachment.content.length > maxAttachmentBytes) {
|
|
356
|
+
throw new ClientInputError('invalid_request', 'Attachment is too large to return through MCP.', {
|
|
357
|
+
sizeBytes: attachment.content.length,
|
|
358
|
+
maxBytes: maxAttachmentBytes,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return attachment;
|
|
362
|
+
};
|
|
331
363
|
// Every tool except get_status (the diagnostic way out) enforces the plan quota.
|
|
332
364
|
const gated = (tool, capability, fn) => handle(tool, async (...args) => {
|
|
333
365
|
requireCapabilities([capability]);
|
|
@@ -343,13 +375,41 @@ export function buildMcpServer(service, options = {}) {
|
|
|
343
375
|
}, () => service.enforceQuota(), options);
|
|
344
376
|
const server = new McpServer({ name: 'fluxmail', title: 'Fluxmail Email', version: VERSION }, {
|
|
345
377
|
instructions: "Fluxmail is the user's email integration, already authenticated against their real mailboxes. " +
|
|
346
|
-
'Use the available Fluxmail tools for email tasks instead of browser automation or another email connector. ' +
|
|
347
378
|
'Tool results are structured for you, not for display. Message ids, thread ids, draft ids, and ' +
|
|
348
379
|
'account ids are internal references: keep them for chaining calls (replying, forwarding, archiving), ' +
|
|
349
380
|
'but do not show them to the user unless asked. Report outcomes in plain language with details people ' +
|
|
350
381
|
"care about, e.g. 'Sent \"Quarterly report\" to ann@example.com' or 'Archived the thread', rather " +
|
|
351
|
-
'than echoing raw payloads, ids, or field names.'
|
|
382
|
+
'than echoing raw payloads, ids, or field names.' +
|
|
383
|
+
disabledCapabilitiesInstructions(permissions, permissionSource),
|
|
352
384
|
});
|
|
385
|
+
// resource_link arrived in MCP 2025-06-18. Streamable HTTP sends the version on each request;
|
|
386
|
+
// stdio negotiates it once, so remember it. The SDK has no public accessor for the negotiated
|
|
387
|
+
// version, so observe the initialize request. The SDK installs onmessage before starting the
|
|
388
|
+
// transport, and this wrapper runs before the SDK handles each message.
|
|
389
|
+
let negotiatedProtocolVersion;
|
|
390
|
+
const connect = server.connect.bind(server);
|
|
391
|
+
server.connect = (transport) => {
|
|
392
|
+
const start = transport.start.bind(transport);
|
|
393
|
+
transport.start = () => {
|
|
394
|
+
const onmessage = transport.onmessage;
|
|
395
|
+
transport.onmessage = (message, extra) => {
|
|
396
|
+
if (isInitializeRequest(message)) {
|
|
397
|
+
const requested = message.params.protocolVersion;
|
|
398
|
+
negotiatedProtocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requested)
|
|
399
|
+
? requested
|
|
400
|
+
: LATEST_PROTOCOL_VERSION;
|
|
401
|
+
}
|
|
402
|
+
onmessage?.(message, extra);
|
|
403
|
+
};
|
|
404
|
+
return start();
|
|
405
|
+
};
|
|
406
|
+
return connect(transport);
|
|
407
|
+
};
|
|
408
|
+
const supportsResourceLinks = (extra) => {
|
|
409
|
+
const header = extra.requestInfo?.headers['mcp-protocol-version'];
|
|
410
|
+
const version = (typeof header === 'string' ? header : undefined) ?? negotiatedProtocolVersion ?? '2025-03-26';
|
|
411
|
+
return version >= '2025-06-18';
|
|
412
|
+
};
|
|
353
413
|
const listedTools = [];
|
|
354
414
|
const registerTool = ((name, config, callback) => {
|
|
355
415
|
const outputSchema = outputSchemas[name];
|
|
@@ -370,46 +430,52 @@ export function buildMcpServer(service, options = {}) {
|
|
|
370
430
|
});
|
|
371
431
|
if (can('mail.read'))
|
|
372
432
|
registerTool('list_accounts', {
|
|
433
|
+
title: 'List accounts',
|
|
373
434
|
description: 'List connected email accounts (id, provider, email, status, capabilities).',
|
|
374
435
|
inputSchema: {},
|
|
375
436
|
annotations: { readOnlyHint: true },
|
|
376
437
|
}, gated('list_accounts', 'mail.read', async () => service.listAccounts()));
|
|
377
438
|
if (can('mail.read'))
|
|
378
439
|
registerTool('get_status', {
|
|
379
|
-
|
|
380
|
-
|
|
440
|
+
title: 'Get status',
|
|
441
|
+
description: 'Account connection and scheduled-send status, including accounts that need re-authentication. ' +
|
|
442
|
+
'Administrators also see plan details.',
|
|
381
443
|
inputSchema: {},
|
|
382
444
|
annotations: { readOnlyHint: true },
|
|
383
445
|
}, allowed('get_status', 'mail.read', async () => service.status()));
|
|
384
446
|
if (can('mail.read'))
|
|
385
447
|
registerTool('list_folders', {
|
|
448
|
+
title: 'List folders',
|
|
386
449
|
description: 'List navigable folders for an account, with roles (inbox, sent, drafts, trash, spam, starred).',
|
|
387
450
|
inputSchema: { accountId: accountIdParam },
|
|
388
451
|
annotations: { readOnlyHint: true },
|
|
389
452
|
}, gated('list_folders', 'mail.read', async (args) => service.listFolders(args.accountId)));
|
|
390
453
|
if (can('mail.read'))
|
|
391
454
|
registerTool('list_labels', {
|
|
455
|
+
title: 'List labels',
|
|
392
456
|
description: 'List Gmail user labels or Outlook categories for an account.',
|
|
393
457
|
inputSchema: { accountId: accountIdParam },
|
|
394
458
|
annotations: { readOnlyHint: true },
|
|
395
459
|
}, gated('list_labels', 'mail.read', async (args) => service.listLabels(args.accountId)));
|
|
396
460
|
if (can('mail.read'))
|
|
397
461
|
registerTool('list_send_as', {
|
|
462
|
+
title: 'List sender addresses',
|
|
398
463
|
description: 'List sender addresses available for an account.',
|
|
399
464
|
inputSchema: { accountId: accountIdParam },
|
|
400
465
|
annotations: { readOnlyHint: true },
|
|
401
466
|
}, gated('list_send_as', 'mail.read', async (args) => service.listSendAs(args.accountId)));
|
|
402
467
|
if (can('mail.read'))
|
|
403
468
|
registerTool('list_emails', {
|
|
469
|
+
title: 'List emails',
|
|
404
470
|
description: "List emails from the user's connected mailbox with metadata and optional previews. Filter by folder, " +
|
|
405
|
-
'sender, unread, dates, etc. Paginate with pageToken. Use get_email for full bodies.
|
|
406
|
-
"This is the way to check the user's email; no browser or other email integration is needed.",
|
|
471
|
+
'sender, unread, dates, etc. Paginate with pageToken. Use get_email for full bodies.',
|
|
407
472
|
inputSchema: { accountId: accountIdParam, ...queryShape },
|
|
408
473
|
annotations: { readOnlyHint: true },
|
|
409
474
|
}, gated('list_emails', 'mail.read', async (args) => service.listMessages(args.accountId, emailQuery(args), pageOpts(args))));
|
|
410
475
|
const { text: _text, ...searchFilterShape } = queryShape;
|
|
411
476
|
if (can('mail.read'))
|
|
412
477
|
registerTool('search_emails', {
|
|
478
|
+
title: 'Search emails',
|
|
413
479
|
description: 'Search one account with typed portable syntax. The query supports text, from:, to:, subject:, in:, ' +
|
|
414
480
|
'read and starred states, attachments, and date filters.',
|
|
415
481
|
inputSchema: {
|
|
@@ -442,6 +508,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
442
508
|
const { pageToken: _batchPageToken, rawProviderQuery: _batchRawProviderQuery, ...batchSearchFilterShape } = searchFilterShape;
|
|
443
509
|
if (can('mail.read'))
|
|
444
510
|
registerTool('search_emails_batch', {
|
|
511
|
+
title: 'Search multiple accounts',
|
|
445
512
|
description: 'Search up to 20 accounts with one portable query and return one result group per account.',
|
|
446
513
|
inputSchema: {
|
|
447
514
|
accounts: z
|
|
@@ -496,6 +563,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
496
563
|
}));
|
|
497
564
|
if (can('mail.read'))
|
|
498
565
|
registerTool('get_email', {
|
|
566
|
+
title: 'Get email',
|
|
499
567
|
description: 'Fetch one email in full: body (text and/or HTML), recipients, attachment metadata.',
|
|
500
568
|
inputSchema: {
|
|
501
569
|
accountId: accountIdParam,
|
|
@@ -506,6 +574,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
506
574
|
}, gated('get_email', 'mail.read', async (args) => selectBody(await service.getMessage(args.accountId, args.messageId), args.bodyFormat ?? 'both', MAX_BODY_CHARS)));
|
|
507
575
|
if (can('mail.read'))
|
|
508
576
|
registerTool('get_email_body', {
|
|
577
|
+
title: 'Read email body',
|
|
509
578
|
description: 'Read a bounded portion of one email body. Use nextOffset to continue.',
|
|
510
579
|
inputSchema: {
|
|
511
580
|
accountId: accountIdParam,
|
|
@@ -530,6 +599,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
530
599
|
}));
|
|
531
600
|
if (can('mail.read'))
|
|
532
601
|
registerTool('get_thread', {
|
|
602
|
+
title: 'Get thread',
|
|
533
603
|
description: 'Fetch a page of conversation messages with bounded body content.',
|
|
534
604
|
inputSchema: {
|
|
535
605
|
accountId: accountIdParam,
|
|
@@ -574,14 +644,17 @@ export function buildMcpServer(service, options = {}) {
|
|
|
574
644
|
}));
|
|
575
645
|
if (can('mail.drafts'))
|
|
576
646
|
registerTool('get_draft', {
|
|
647
|
+
title: 'Get draft',
|
|
577
648
|
description: 'Read an existing draft by its draft ID.',
|
|
578
649
|
inputSchema: { accountId: accountIdParam, draftId: idParam },
|
|
579
650
|
annotations: { readOnlyHint: true },
|
|
580
651
|
}, gated('get_draft', 'mail.drafts', async (args) => service.getDraft(args.accountId, args.draftId)));
|
|
581
652
|
if (can('mail.drafts'))
|
|
582
653
|
registerTool('create_draft', {
|
|
654
|
+
title: 'Create draft',
|
|
583
655
|
description: 'Create a draft. For a reply draft, pass replyToMessageId (recipients/subject are derived; replyAll for reply-all).',
|
|
584
656
|
inputSchema: draftShape,
|
|
657
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
585
658
|
}, gated('create_draft', 'mail.drafts', async (args) => {
|
|
586
659
|
if (args.replyToMessageId)
|
|
587
660
|
requireCapabilities(['mail.read']);
|
|
@@ -589,8 +662,10 @@ export function buildMcpServer(service, options = {}) {
|
|
|
589
662
|
}));
|
|
590
663
|
if (can('mail.drafts'))
|
|
591
664
|
registerTool('update_draft', {
|
|
665
|
+
title: 'Update draft',
|
|
592
666
|
description: 'Replace the content of an existing draft (full replacement, not a patch).',
|
|
593
667
|
inputSchema: { draftId: idParam, ...draftShape },
|
|
668
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
|
|
594
669
|
}, gated('update_draft', 'mail.drafts', async (args) => {
|
|
595
670
|
if (args.replyToMessageId)
|
|
596
671
|
requireCapabilities(['mail.read']);
|
|
@@ -598,15 +673,17 @@ export function buildMcpServer(service, options = {}) {
|
|
|
598
673
|
}));
|
|
599
674
|
if (can('mail.drafts'))
|
|
600
675
|
registerTool('delete_draft', {
|
|
676
|
+
title: 'Delete draft',
|
|
601
677
|
description: 'Delete a draft.',
|
|
602
678
|
inputSchema: { accountId: accountIdParam, draftId: idParam },
|
|
603
|
-
annotations: { destructiveHint: true },
|
|
679
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
|
|
604
680
|
}, gated('delete_draft', 'mail.drafts', async (args) => {
|
|
605
681
|
await service.deleteDraft(args.accountId, args.draftId);
|
|
606
682
|
return { deleted: args.draftId };
|
|
607
683
|
}));
|
|
608
684
|
if (can('mail.send'))
|
|
609
685
|
registerTool('preview_send', {
|
|
686
|
+
title: 'Preview send',
|
|
610
687
|
description: 'Show the resolved sender, recipients, subject, and attachments without sending.',
|
|
611
688
|
inputSchema: { draftId: idParam.optional(), ...draftShape },
|
|
612
689
|
annotations: { readOnlyHint: true },
|
|
@@ -619,9 +696,9 @@ export function buildMcpServer(service, options = {}) {
|
|
|
619
696
|
}));
|
|
620
697
|
if (can('mail.send'))
|
|
621
698
|
registerTool('send_email', {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
'
|
|
699
|
+
title: 'Send email',
|
|
700
|
+
description: "Send an email from the user's connected account. Three modes: direct (to + subject + body), sending " +
|
|
701
|
+
'an existing draft (draftId), or replying (replyToMessageId, optionally replyAll) ' +
|
|
625
702
|
'where recipients, subject, and threading are derived from the original. Confirm with the user when ' +
|
|
626
703
|
'intent is ambiguous. Add sendAt to any mode to schedule instead of sending now.',
|
|
627
704
|
inputSchema: {
|
|
@@ -640,7 +717,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
640
717
|
'for delivery by fluxmail serve or fluxmail scheduled run using the same store. Stdio does not ' +
|
|
641
718
|
'deliver schedules. Overdue mail may send when a worker starts. Returns a scheduleId for list/cancel.'),
|
|
642
719
|
},
|
|
643
|
-
annotations: { destructiveHint: true },
|
|
720
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
644
721
|
}, handleResult('send_email', async (args) => {
|
|
645
722
|
requireCapabilities(['mail.send']);
|
|
646
723
|
if (args.replyToMessageId !== undefined)
|
|
@@ -652,6 +729,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
652
729
|
}, () => service.enforceQuota(), options));
|
|
653
730
|
if (can('mail.read'))
|
|
654
731
|
registerTool('list_scheduled_emails', {
|
|
732
|
+
title: 'List scheduled emails',
|
|
655
733
|
description: 'List scheduled sends: pending ones first (with sendAt), then past ones (sent, failed, canceled). ' +
|
|
656
734
|
'For failed entries, lastError says what went wrong. Pending sends only fire while the ' +
|
|
657
735
|
'Fluxmail server is running.',
|
|
@@ -660,18 +738,22 @@ export function buildMcpServer(service, options = {}) {
|
|
|
660
738
|
}, gated('list_scheduled_emails', 'mail.read', async (args) => service.listScheduled(args.accountId)));
|
|
661
739
|
if (can('mail.send'))
|
|
662
740
|
registerTool('get_delivery_operation', {
|
|
741
|
+
title: 'Get delivery status',
|
|
663
742
|
description: 'Check whether a send or forward succeeded, failed, or has an uncertain outcome.',
|
|
664
743
|
inputSchema: { accountId: idParam, operationId: idParam },
|
|
665
744
|
annotations: { readOnlyHint: true },
|
|
666
745
|
}, allowed('get_delivery_operation', 'mail.send', async (args) => service.getDelivery(args.accountId, args.operationId)));
|
|
667
746
|
if (can('mail.drafts'))
|
|
668
747
|
registerTool('cancel_scheduled_email', {
|
|
748
|
+
title: 'Cancel scheduled email',
|
|
669
749
|
description: 'Cancel a pending scheduled send by scheduleId (from send_email with sendAt, or list_scheduled_emails). ' +
|
|
670
750
|
'The draft stays in the Drafts folder, so the content is not lost.',
|
|
671
751
|
inputSchema: { scheduleId: idParam },
|
|
752
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
672
753
|
}, gated('cancel_scheduled_email', 'mail.drafts', async (args) => service.cancelScheduled(args.scheduleId)));
|
|
673
754
|
if (canAll(['mail.send', 'mail.read']))
|
|
674
755
|
registerTool('forward_email', {
|
|
756
|
+
title: 'Forward email',
|
|
675
757
|
description: 'Forward an email to new recipients: quoted original body, "Fwd:" subject, original attachments included ' +
|
|
676
758
|
'unless includeAttachments=false. Optional comment appears above the forwarded content.',
|
|
677
759
|
inputSchema: {
|
|
@@ -690,7 +772,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
690
772
|
.describe('Comment above the forwarded message. Keep prose paragraphs on one continuous line and separate paragraphs with blank lines.'),
|
|
691
773
|
includeAttachments: z.boolean().optional().describe('Default true'),
|
|
692
774
|
},
|
|
693
|
-
annotations: { destructiveHint: true },
|
|
775
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
694
776
|
}, handleResult('forward_email', async (args) => {
|
|
695
777
|
requireCapabilities(['mail.send', 'mail.read']);
|
|
696
778
|
const to = parseAddresses(args.to) ?? [];
|
|
@@ -708,6 +790,7 @@ export function buildMcpServer(service, options = {}) {
|
|
|
708
790
|
if (modifyActions.length) {
|
|
709
791
|
const allowedModifyActions = new Set(modifyActions);
|
|
710
792
|
registerTool('modify_emails', {
|
|
793
|
+
title: 'Modify emails',
|
|
711
794
|
description: 'Batch-modify emails using the actions allowed for this connection. Moving requires folder; labels require labels.',
|
|
712
795
|
inputSchema: {
|
|
713
796
|
accountId: accountIdParam,
|
|
@@ -720,7 +803,9 @@ export function buildMcpServer(service, options = {}) {
|
|
|
720
803
|
labels: z.array(z.string().min(1)).max(100).optional().describe('Labels for addLabels/removeLabels'),
|
|
721
804
|
},
|
|
722
805
|
annotations: {
|
|
806
|
+
readOnlyHint: false,
|
|
723
807
|
destructiveHint: canAny(['mail.trash', 'mail.delete']),
|
|
808
|
+
idempotentHint: true,
|
|
724
809
|
},
|
|
725
810
|
}, handleResult('modify_emails', async (args) => {
|
|
726
811
|
if (!allowedModifyActions.has(args.action)) {
|
|
@@ -756,46 +841,45 @@ export function buildMcpServer(service, options = {}) {
|
|
|
756
841
|
}
|
|
757
842
|
if (can('mail.read'))
|
|
758
843
|
registerTool('download_attachment', {
|
|
759
|
-
|
|
844
|
+
title: 'Download attachment',
|
|
845
|
+
description: `Get metadata and a resource link for one attachment, up to ${sizeLabel(maxAttachmentBytes)}. ` +
|
|
846
|
+
'With inline, the result also includes the content. For a PDF, inline adds extracted text, and pages ' +
|
|
847
|
+
'returns the text of a page range such as 201-400.',
|
|
760
848
|
inputSchema: {
|
|
761
|
-
accountId:
|
|
849
|
+
accountId: accountIdParam,
|
|
762
850
|
messageId: idParam,
|
|
763
851
|
attachmentId: attachmentIdParam,
|
|
764
852
|
inline: z.boolean().optional(),
|
|
853
|
+
pages: pageRange.optional(),
|
|
765
854
|
},
|
|
766
855
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
767
|
-
}, gatedResult('download_attachment', 'mail.read', async (args) => {
|
|
768
|
-
const { meta, content } = await
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
});
|
|
856
|
+
}, gatedResult('download_attachment', 'mail.read', async (args, extra) => {
|
|
857
|
+
const { meta, content, accountId } = await downloadAttachment(args.accountId, args.messageId, args.attachmentId);
|
|
858
|
+
const mimeType = meta.mimeType || 'application/octet-stream';
|
|
859
|
+
const pdf = mediaType(mimeType) === 'application/pdf';
|
|
860
|
+
if (args.pages && !pdf) {
|
|
861
|
+
throw new ClientInputError('invalid_request', 'Pages apply only to PDF attachments.');
|
|
774
862
|
}
|
|
863
|
+
const extracted = pdf && (args.inline || args.pages)
|
|
864
|
+
? await pdfText(content, extra.signal, args.pages, (error) => options.logger?.warn('mcp.pdf_text_failed', 'PDF text extraction failed.', error, {
|
|
865
|
+
productSurface: 'mcp',
|
|
866
|
+
operation: 'download_attachment',
|
|
867
|
+
}))
|
|
868
|
+
: undefined;
|
|
775
869
|
const uri = [
|
|
776
870
|
'fluxmail://attachment',
|
|
777
|
-
|
|
778
|
-
encodeURIComponent(args.messageId),
|
|
779
|
-
encodeURIComponent(args.attachmentId),
|
|
780
|
-
encodeURIComponent(meta.filename),
|
|
871
|
+
...[accountId, args.messageId, args.attachmentId, meta.filename].map(encodeURIComponent),
|
|
781
872
|
].join('/');
|
|
873
|
+
const resourceLinks = supportsResourceLinks(extra);
|
|
782
874
|
return {
|
|
783
875
|
structuredContent: { data: meta },
|
|
784
876
|
content: [
|
|
785
|
-
{ type: 'text', text: JSON.stringify({ data: meta }, null, 2) },
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
? [
|
|
789
|
-
{
|
|
790
|
-
type: 'resource',
|
|
791
|
-
resource: {
|
|
792
|
-
uri,
|
|
793
|
-
mimeType: meta.mimeType || 'application/octet-stream',
|
|
794
|
-
blob: content.toString('base64'),
|
|
795
|
-
},
|
|
796
|
-
},
|
|
797
|
-
]
|
|
877
|
+
{ type: 'text', text: JSON.stringify(resourceLinks ? { data: meta } : { data: meta, uri }, null, 2) },
|
|
878
|
+
...(resourceLinks
|
|
879
|
+
? [{ type: 'resource_link', name: meta.filename, uri, mimeType: meta.mimeType }]
|
|
798
880
|
: []),
|
|
881
|
+
...(args.inline ? [inlineContent(uri, mimeType, content)] : []),
|
|
882
|
+
...pdfTextContent(meta.filename, extracted),
|
|
799
883
|
],
|
|
800
884
|
};
|
|
801
885
|
}));
|
|
@@ -805,11 +889,8 @@ export function buildMcpServer(service, options = {}) {
|
|
|
805
889
|
}), { description: 'Read one attachment from an authorized mailbox.' }, async (uri, variables) => {
|
|
806
890
|
requireCapabilities(['mail.read']);
|
|
807
891
|
service.enforceQuota();
|
|
808
|
-
const { meta, content } = await
|
|
809
|
-
|
|
810
|
-
throw new ClientInputError('invalid_request', 'Attachment is too large to return through MCP.');
|
|
811
|
-
}
|
|
812
|
-
return { contents: [{ uri: uri.href, mimeType: meta.mimeType, blob: content.toString('base64') }] };
|
|
892
|
+
const { meta, content } = await downloadAttachment(decodeURIComponent(String(variables.accountId)), decodeURIComponent(String(variables.messageId)), decodeURIComponent(String(variables.attachmentId)));
|
|
893
|
+
return { contents: [attachmentContents(uri.href, meta.mimeType || 'application/octet-stream', content)] };
|
|
813
894
|
});
|
|
814
895
|
if (listedTools.length) {
|
|
815
896
|
// The SDK's list handler declares draft-07 for output schemas, which strict clients reject.
|