@ni-c/imap-mcp 0.3.0 → 0.4.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/README.md +20 -4
- package/dist/analyze.d.ts +32 -3
- package/dist/analyze.js +97 -8
- package/dist/analyze.js.map +1 -1
- package/dist/attachments.d.ts +11 -0
- package/dist/attachments.js +1 -1
- package/dist/attachments.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +21 -1
- package/dist/config.js.map +1 -1
- package/dist/extract/child.d.ts +1 -0
- package/dist/extract/child.js +83 -0
- package/dist/extract/child.js.map +1 -0
- package/dist/extract/index.d.ts +41 -0
- package/dist/extract/index.js +183 -0
- package/dist/extract/index.js.map +1 -0
- package/dist/extract/ooxml.d.ts +35 -0
- package/dist/extract/ooxml.js +627 -0
- package/dist/extract/ooxml.js.map +1 -0
- package/dist/extract/pdf.d.ts +62 -0
- package/dist/extract/pdf.js +539 -0
- package/dist/extract/pdf.js.map +1 -0
- package/dist/extract/types.d.ts +56 -0
- package/dist/extract/types.js +13 -0
- package/dist/extract/types.js.map +1 -0
- package/dist/output-schema.d.ts +1 -0
- package/dist/output-schema.js +6 -0
- package/dist/output-schema.js.map +1 -1
- package/dist/resources.js +4 -1
- package/dist/resources.js.map +1 -1
- package/dist/result.d.ts +7 -1
- package/dist/result.js +14 -5
- package/dist/result.js.map +1 -1
- package/dist/tools/read.js +406 -45
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.js +10 -2
- package/dist/tools/write.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtractKind, ExtractRequest, ExtractResponse } from './types.js';
|
|
2
|
+
export type { ExtractKind, ExtractReason, ExtractRequest, ExtractResponse, } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* How long a document may be parsed before the process doing it is killed.
|
|
5
|
+
*
|
|
6
|
+
* Generous next to the five seconds a regular expression gets in the sibling
|
|
7
|
+
* servers, because a hundred-page PDF is honest work — and finite, because a
|
|
8
|
+
* document that has not finished by now is not going to.
|
|
9
|
+
*/
|
|
10
|
+
export declare const EXTRACT_TIMEOUT_MS = 20000;
|
|
11
|
+
/**
|
|
12
|
+
* Characters the child may return, before any of the paging below.
|
|
13
|
+
*
|
|
14
|
+
* Not a context budget — that is `max_chars` at the tool, and it is much
|
|
15
|
+
* smaller. This is the ceiling on what is held in memory and paged through,
|
|
16
|
+
* and the child enforces it for every format: nothing larger is ever built
|
|
17
|
+
* there, let alone sent back.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MAX_EXTRACT_CHARS = 1000000;
|
|
20
|
+
/** The same set as content types, for `get_server_info`. */
|
|
21
|
+
export declare const EXTRACTABLE_TYPES: string[];
|
|
22
|
+
/** Prose for the refusals, so every one of them names the same set. */
|
|
23
|
+
export declare const EXTRACTABLE_TYPE_NAMES: string;
|
|
24
|
+
export declare function extractKindOf(contentType: string): ExtractKind | undefined;
|
|
25
|
+
export declare function isExtractable(contentType: string): boolean;
|
|
26
|
+
/** The magic-byte verdict an honest container of this kind produces. */
|
|
27
|
+
export declare function expectedSignature(kind: ExtractKind): string;
|
|
28
|
+
export declare function extractDocumentText(request: ExtractRequest, limits?: ChildLimits): Promise<ExtractResponse>;
|
|
29
|
+
/**
|
|
30
|
+
* Narrowed by the tests, never by the server.
|
|
31
|
+
*
|
|
32
|
+
* The timeout and the memory ceiling are the two guards whose whole purpose is
|
|
33
|
+
* what happens when they fire, and neither can be reached in a test at its real
|
|
34
|
+
* value without a document engineered to spend twenty seconds or a quarter of a
|
|
35
|
+
* gigabyte. Making them arguments is what lets the failure paths be exercised
|
|
36
|
+
* in milliseconds; nothing in `src/` passes them.
|
|
37
|
+
*/
|
|
38
|
+
export interface ChildLimits {
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
memoryMb?: number;
|
|
41
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
/**
|
|
4
|
+
* How long a document may be parsed before the process doing it is killed.
|
|
5
|
+
*
|
|
6
|
+
* Generous next to the five seconds a regular expression gets in the sibling
|
|
7
|
+
* servers, because a hundred-page PDF is honest work — and finite, because a
|
|
8
|
+
* document that has not finished by now is not going to.
|
|
9
|
+
*/
|
|
10
|
+
export const EXTRACT_TIMEOUT_MS = 20_000;
|
|
11
|
+
/**
|
|
12
|
+
* Characters the child may return, before any of the paging below.
|
|
13
|
+
*
|
|
14
|
+
* Not a context budget — that is `max_chars` at the tool, and it is much
|
|
15
|
+
* smaller. This is the ceiling on what is held in memory and paged through,
|
|
16
|
+
* and the child enforces it for every format: nothing larger is ever built
|
|
17
|
+
* there, let alone sent back.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_EXTRACT_CHARS = 1_000_000;
|
|
20
|
+
/**
|
|
21
|
+
* V8 heap the parsing process may use.
|
|
22
|
+
*
|
|
23
|
+
* Best effort, and named as such. It bounds a pathological object graph, and
|
|
24
|
+
* when it fires the child aborts — on its own, which is the whole reason the
|
|
25
|
+
* parse is in a process. It does not bound typed arrays, which are external
|
|
26
|
+
* memory; the deflate pre-scan in `pdf.ts` and the entry caps in `ooxml.ts`
|
|
27
|
+
* are what cover those.
|
|
28
|
+
*/
|
|
29
|
+
const CHILD_MEMORY_MB = 256;
|
|
30
|
+
/**
|
|
31
|
+
* Requests admitted at once, running and waiting together.
|
|
32
|
+
*
|
|
33
|
+
* Extractions run one at a time (see {@link queue}), so a request that arrives
|
|
34
|
+
* behind seven others would wait up to seven timeouts for its turn, holding
|
|
35
|
+
* its mailbox lock throughout. Past this many it is refused outright, which is
|
|
36
|
+
* an answer the caller can act on.
|
|
37
|
+
*/
|
|
38
|
+
const MAX_IN_FLIGHT = 8;
|
|
39
|
+
/** Content types this server can read text out of, and what each one is. */
|
|
40
|
+
const EXTRACTABLE = new Map([
|
|
41
|
+
['application/pdf', 'pdf'],
|
|
42
|
+
[
|
|
43
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
44
|
+
'docx',
|
|
45
|
+
],
|
|
46
|
+
['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx'],
|
|
47
|
+
[
|
|
48
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
49
|
+
'pptx',
|
|
50
|
+
],
|
|
51
|
+
['application/vnd.oasis.opendocument.text', 'odt'],
|
|
52
|
+
['application/vnd.oasis.opendocument.spreadsheet', 'ods'],
|
|
53
|
+
]);
|
|
54
|
+
/** The same set as content types, for `get_server_info`. */
|
|
55
|
+
export const EXTRACTABLE_TYPES = [...EXTRACTABLE.keys()];
|
|
56
|
+
/** Prose for the refusals, so every one of them names the same set. */
|
|
57
|
+
export const EXTRACTABLE_TYPE_NAMES = 'PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx) and OpenDocument text ' +
|
|
58
|
+
'and spreadsheets (.odt, .ods)';
|
|
59
|
+
export function extractKindOf(contentType) {
|
|
60
|
+
return EXTRACTABLE.get(contentType.toLowerCase());
|
|
61
|
+
}
|
|
62
|
+
export function isExtractable(contentType) {
|
|
63
|
+
return EXTRACTABLE.has(contentType.toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
/** The magic-byte verdict an honest container of this kind produces. */
|
|
66
|
+
export function expectedSignature(kind) {
|
|
67
|
+
// Every OOXML and OpenDocument file is a zip, which is why `sniffContent`
|
|
68
|
+
// reports one for all five of them.
|
|
69
|
+
return kind === 'pdf' ? 'application/pdf' : 'application/zip';
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Serialises extractions.
|
|
73
|
+
*
|
|
74
|
+
* One process per call and no limit would mean N concurrent tool calls holding
|
|
75
|
+
* N processes of {@link CHILD_MEMORY_MB} each, which is a memory limit that
|
|
76
|
+
* multiplies by a number the caller chooses. The queue is the whole mechanism:
|
|
77
|
+
* the next extraction starts when the previous process is gone.
|
|
78
|
+
*/
|
|
79
|
+
let queue = Promise.resolve();
|
|
80
|
+
let inFlight = 0;
|
|
81
|
+
export async function extractDocumentText(request, limits = {}) {
|
|
82
|
+
if (inFlight >= MAX_IN_FLIGHT)
|
|
83
|
+
return { ok: false, reason: 'busy' };
|
|
84
|
+
inFlight += 1;
|
|
85
|
+
try {
|
|
86
|
+
const run = queue.then(() => runInChild(request, limits), () => runInChild(request, limits));
|
|
87
|
+
queue = run.catch(() => undefined);
|
|
88
|
+
return await run;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
inFlight -= 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** A code for the log, never a message. */
|
|
95
|
+
function codeOf(error) {
|
|
96
|
+
const value = error;
|
|
97
|
+
if (typeof value?.code === 'string')
|
|
98
|
+
return value.code;
|
|
99
|
+
if (typeof value?.name === 'string')
|
|
100
|
+
return value.name;
|
|
101
|
+
return 'unknown';
|
|
102
|
+
}
|
|
103
|
+
async function runInChild(request, limits = {}) {
|
|
104
|
+
const timeoutMs = limits.timeoutMs ?? EXTRACT_TIMEOUT_MS;
|
|
105
|
+
const memoryMb = limits.memoryMb ?? CHILD_MEMORY_MB;
|
|
106
|
+
const child = fork(new URL(import.meta.url.endsWith('.ts') ? './child.ts' : './child.js', import.meta.url), [], {
|
|
107
|
+
// Stated rather than inherited. The parent's own flags may be ones a
|
|
108
|
+
// child cannot take — `--input-type` is one — and an inherited flag that
|
|
109
|
+
// fails to parse would turn every extraction into a silent `internal`.
|
|
110
|
+
execArgv: [`--max-old-space-size=${memoryMb}`],
|
|
111
|
+
// Not tidiness — correctness. The parent's stdout is this server's
|
|
112
|
+
// JSON-RPC transport, and pdf.js logs. One line from inside the parser
|
|
113
|
+
// would corrupt the framing and hang the session. Discarded; stderr is
|
|
114
|
+
// shared, because that is where every other diagnostic in this server
|
|
115
|
+
// already goes.
|
|
116
|
+
stdio: ['ignore', 'ignore', 'inherit', 'ipc'],
|
|
117
|
+
// Structured clone rather than JSON: the request carries the document as
|
|
118
|
+
// a typed array, and JSON would turn it into an array of numbers ten
|
|
119
|
+
// times its size.
|
|
120
|
+
serialization: 'advanced',
|
|
121
|
+
});
|
|
122
|
+
let timer;
|
|
123
|
+
try {
|
|
124
|
+
return await new Promise((resolve) => {
|
|
125
|
+
// The first answer wins. Everything after it — the exit of a child that
|
|
126
|
+
// was killed because it had already answered, most of all — is silence,
|
|
127
|
+
// not a second verdict.
|
|
128
|
+
let settled = false;
|
|
129
|
+
const settle = (value) => {
|
|
130
|
+
if (settled)
|
|
131
|
+
return;
|
|
132
|
+
settled = true;
|
|
133
|
+
resolve(value);
|
|
134
|
+
};
|
|
135
|
+
timer = setTimeout(() => {
|
|
136
|
+
settle({ ok: false, reason: 'timeout' });
|
|
137
|
+
}, timeoutMs);
|
|
138
|
+
child.once('message', (value) => {
|
|
139
|
+
settle(value);
|
|
140
|
+
});
|
|
141
|
+
child.once('error', (error) => {
|
|
142
|
+
if (settled)
|
|
143
|
+
return;
|
|
144
|
+
console.error(`imap-mcp: extraction process failed: ${codeOf(error)}`);
|
|
145
|
+
settle({ ok: false, reason: 'internal' });
|
|
146
|
+
});
|
|
147
|
+
child.once('exit', (code, signal) => {
|
|
148
|
+
// Only reached when the child left without answering. An abort is what
|
|
149
|
+
// V8 does when the heap limit is hit — and what the process does
|
|
150
|
+
// *instead of* taking the server with it, which is the property being
|
|
151
|
+
// bought here.
|
|
152
|
+
if (settled)
|
|
153
|
+
return;
|
|
154
|
+
if (signal === 'SIGABRT' || code === 134) {
|
|
155
|
+
settle({ ok: false, reason: 'out-of-memory' });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
console.error(`imap-mcp: extraction process exited early: ${signal ?? `code ${code}`}`);
|
|
159
|
+
settle({ ok: false, reason: 'internal' });
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
child.send(request);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error(`imap-mcp: extraction request failed: ${codeOf(error)}`);
|
|
166
|
+
settle({ ok: false, reason: 'internal' });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
if (timer)
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
// Unconditional: on the timeout path the process is still inside the parse
|
|
174
|
+
// and will never exit on its own. SIGKILL, because a parser stuck in native
|
|
175
|
+
// code does not check for anything gentler — and because a process, unlike
|
|
176
|
+
// a thread, can be killed from outside whatever it is doing.
|
|
177
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
178
|
+
child.kill('SIGKILL');
|
|
179
|
+
await once(child, 'exit');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extract/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ExtractKind, ExtractResponse } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Turns markup into readable text. Injected rather than imported.
|
|
4
|
+
*
|
|
5
|
+
* `htmlToText` lives in `../analyze.ts`, and this module is reached from the
|
|
6
|
+
* extraction child, which Node loads directly — where a `../analyze.js`
|
|
7
|
+
* specifier does not resolve to the `.ts` beside it. Passing the function in
|
|
8
|
+
* keeps this file free of relative value imports, which is the property that
|
|
9
|
+
* lets it run in the child at all. It also makes the seam explicit: the tests
|
|
10
|
+
* hand it the same `htmlToText` the child does.
|
|
11
|
+
*/
|
|
12
|
+
export type MarkupToText = (markup: string, maxChars: number) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Reads the text of an OOXML or OpenDocument container.
|
|
15
|
+
*
|
|
16
|
+
* The whole defence lives in the `filter` callback below, and it is worth
|
|
17
|
+
* saying why that specific place. `unzipSync` inflates an entry into a buffer
|
|
18
|
+
* sized by the *declared* uncompressed size out of the central directory — a
|
|
19
|
+
* number the sender chose, checked against nothing. The filter is the last
|
|
20
|
+
* point before that allocation, and returning `false` there means the entry is
|
|
21
|
+
* never inflated and never sized.
|
|
22
|
+
*
|
|
23
|
+
* Measured on fflate 0.8.3: a declared size far past the real one does not blow
|
|
24
|
+
* up resident memory on Linux, because the allocation is virtual and untouched
|
|
25
|
+
* pages cost nothing; and a declared size *below* the real one truncates the
|
|
26
|
+
* output to what was declared, because fflate does not grow a caller-sized
|
|
27
|
+
* buffer. The guard stays regardless — it is free, it is the only thing
|
|
28
|
+
* standing between an *honest* high-ratio entry and its real expansion, and a
|
|
29
|
+
* host that does not overcommit would pay the full price.
|
|
30
|
+
*
|
|
31
|
+
* This never recurses. An entry that is itself an archive is not in the name
|
|
32
|
+
* allowlist, so a nested bomb is not descended into; that is a property to keep
|
|
33
|
+
* rather than an omission to fix.
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractZipDocument(kind: Exclude<ExtractKind, 'pdf'>, bytes: Uint8Array, maxChars: number, toText: MarkupToText): Promise<ExtractResponse>;
|