@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,45 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * A tool list that names something this server does not have.
4
+ *
5
+ * Thrown rather than `process.exit(1)`: `createServer` is called in-process by
6
+ * the tests, and an exiting constructor cannot be tested. `src/index.ts` turns
7
+ * it back into an exit code.
8
+ */
9
+ export declare class ToolFilterError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export interface ToolFilter {
13
+ /** False when neither variable was set — then nothing is wrapped at all. */
14
+ readonly active: boolean;
15
+ /** The tools that survive. Only meaningful while `active`. */
16
+ readonly selected: ReadonlySet<string>;
17
+ }
18
+ /**
19
+ * Reads `<PREFIX>_ALLOW_TOOLS` / `<PREFIX>_DENY_TOOLS` and works out which
20
+ * tools survive.
21
+ *
22
+ * Every entry has to match at least one tool in the catalogue. An entry that
23
+ * matches nothing is fatal rather than ignored, because the failure it produces
24
+ * otherwise — a tool quietly missing from `tools/list` — is invisible: nobody
25
+ * looks for the cause of an absence in an environment variable.
26
+ */
27
+ export declare function buildToolFilter(config: {
28
+ allowTools: string | undefined;
29
+ denyTools: string | undefined;
30
+ readOnly: boolean;
31
+ }): ToolFilter;
32
+ /**
33
+ * Makes `server` register only the tools the filter selected.
34
+ *
35
+ * The tool is registered and then removed again rather than skipped. Skipping
36
+ * looks cheaper and breaks one case: the SDK installs its `tools/list` handler
37
+ * from inside the registration path, so a server whose every tool was skipped
38
+ * would answer `tools/list` with "method not found" instead of an empty list.
39
+ * `remove()` deletes the entry from the SDK's tool map outright, which makes a
40
+ * filtered tool answer `Tool X not found` — exactly what a tool the read-only
41
+ * mode never registered already does. `disable()` would be wrong: it hides the
42
+ * tool from `tools/list` but still answers a call with "disabled", which is the
43
+ * advertising-a-refusal this server avoids everywhere else.
44
+ */
45
+ export declare function installToolFilter(server: McpServer, filter: ToolFilter): void;
@@ -0,0 +1,171 @@
1
+ import { ALL_TOOLS, ESSENTIAL_TOOLS, READ_TOOLS } from './tools/catalogue.js';
2
+ /**
3
+ * A tool list that names something this server does not have.
4
+ *
5
+ * Thrown rather than `process.exit(1)`: `createServer` is called in-process by
6
+ * the tests, and an exiting constructor cannot be tested. `src/index.ts` turns
7
+ * it back into an exit code.
8
+ */
9
+ export class ToolFilterError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'ToolFilterError';
13
+ }
14
+ }
15
+ /** The `essential` preset is spelled out here so it cannot collide with a tool name. */
16
+ const PRESET = 'essential';
17
+ /**
18
+ * Splits a comma-separated value into entries.
19
+ *
20
+ * Empty entries are dropped, so `a,,b` and a trailing comma are both fine, and
21
+ * a value that is empty or only whitespace counts as *unset* — `X_ALLOW_TOOLS=`
22
+ * in a compose file must not mean "allow nothing". Entries are lowercased: the
23
+ * catalogue is entirely lowercase, so this is lossless, and a shell that
24
+ * upper-cased a name should not take the server down.
25
+ */
26
+ function entriesOf(raw) {
27
+ if (raw === undefined)
28
+ return undefined;
29
+ const entries = raw
30
+ .split(',')
31
+ .map((entry) => entry.trim().toLowerCase())
32
+ .filter((entry) => entry.length > 0);
33
+ return entries.length > 0 ? entries : undefined;
34
+ }
35
+ /**
36
+ * Expands one entry to the catalogue tools it names.
37
+ *
38
+ * A pattern is a literal prefix plus exactly one trailing `*`. Anything else is
39
+ * rejected outright: `*_zone` and `list_*_x` look plausible, match nothing, and
40
+ * would otherwise be silent forever.
41
+ */
42
+ function expand(entry, variable) {
43
+ const star = entry.indexOf('*');
44
+ if (star !== -1) {
45
+ if (star !== entry.length - 1) {
46
+ throw new ToolFilterError(`${variable}: "${entry}" is not a valid entry — a pattern is a prefix ` +
47
+ 'followed by a single trailing "*", for example "list_*". Everything ' +
48
+ 'else is an exact tool name.');
49
+ }
50
+ const prefix = entry.slice(0, -1);
51
+ return ALL_TOOLS.filter((tool) => tool.startsWith(prefix));
52
+ }
53
+ return ALL_TOOLS.filter((tool) => tool === entry);
54
+ }
55
+ /** The full catalogue, for the "these are the names that exist" half of an error. */
56
+ function catalogueList() {
57
+ return [...ALL_TOOLS].sort().join(', ');
58
+ }
59
+ /**
60
+ * Reads `<PREFIX>_ALLOW_TOOLS` / `<PREFIX>_DENY_TOOLS` and works out which
61
+ * tools survive.
62
+ *
63
+ * Every entry has to match at least one tool in the catalogue. An entry that
64
+ * matches nothing is fatal rather than ignored, because the failure it produces
65
+ * otherwise — a tool quietly missing from `tools/list` — is invisible: nobody
66
+ * looks for the cause of an absence in an environment variable.
67
+ */
68
+ export function buildToolFilter(config) {
69
+ const allow = entriesOf(config.allowTools);
70
+ const deny = entriesOf(config.denyTools);
71
+ if (allow === undefined && deny === undefined) {
72
+ return { active: false, selected: new Set() };
73
+ }
74
+ // What would be registered without any filter. In read-only mode the write
75
+ // tools never reach `registerTool`, but they stay in the catalogue so that a
76
+ // name from that half is answered with "read-only suppresses it", never with
77
+ // "no such tool".
78
+ const registered = new Set(config.readOnly ? READ_TOOLS : ALL_TOOLS);
79
+ // Set when an allow entry named real tools and read-only suppressed all of
80
+ // them, so that "nothing is left" can name the reason rather than shrugging.
81
+ let suppressedByReadOnly = false;
82
+ let selected;
83
+ if (allow === undefined) {
84
+ selected = new Set(registered);
85
+ }
86
+ else {
87
+ selected = new Set();
88
+ for (const entry of allow) {
89
+ if (entry === PRESET) {
90
+ // Preset members are not names the operator typed, so a member that
91
+ // read-only suppresses is dropped silently rather than being an error.
92
+ for (const tool of ESSENTIAL_TOOLS) {
93
+ if (registered.has(tool))
94
+ selected.add(tool);
95
+ }
96
+ continue;
97
+ }
98
+ const matches = expand(entry, 'IMAP_ALLOW_TOOLS');
99
+ if (matches.length === 0) {
100
+ throw new ToolFilterError(`IMAP_ALLOW_TOOLS: no tool matches "${entry}". ` +
101
+ `Valid tools: ${catalogueList()}. "${PRESET}" selects the curated preset.`);
102
+ }
103
+ const survivors = matches.filter((tool) => registered.has(tool));
104
+ if (survivors.length === 0) {
105
+ if (entry.endsWith('*')) {
106
+ // A pattern is a template, not a claim about one tool: warn, continue.
107
+ console.error(`imap-mcp: IMAP_ALLOW_TOOLS: "${entry}" matches only write ` +
108
+ 'tools, which IMAP_READ_ONLY suppresses — it contributes nothing.');
109
+ suppressedByReadOnly = true;
110
+ continue;
111
+ }
112
+ // An exact name, though, was typed by someone who believes it is exposed.
113
+ throw new ToolFilterError(`IMAP_ALLOW_TOOLS: "${entry}" is a write tool, but IMAP_READ_ONLY ` +
114
+ 'is set — it is never registered. Remove it from IMAP_ALLOW_TOOLS, ' +
115
+ `or unset IMAP_READ_ONLY. Available in read-only mode: ${[...READ_TOOLS].sort().join(', ')}.`);
116
+ }
117
+ for (const tool of survivors)
118
+ selected.add(tool);
119
+ }
120
+ }
121
+ for (const entry of deny ?? []) {
122
+ // Deny lists are written defensively — "never expose delete_*, whatever
123
+ // else is on" — so matching nothing that survives is fine. Matching nothing
124
+ // in the catalogue is still a typo.
125
+ const matches = expand(entry, 'IMAP_DENY_TOOLS');
126
+ if (matches.length === 0) {
127
+ throw new ToolFilterError(`IMAP_DENY_TOOLS: no tool matches "${entry}". Valid tools: ${catalogueList()}.`);
128
+ }
129
+ for (const tool of matches)
130
+ selected.delete(tool);
131
+ }
132
+ if (selected.size === 0) {
133
+ throw new ToolFilterError(suppressedByReadOnly
134
+ ? 'IMAP_ALLOW_TOOLS selects only write tools, but IMAP_READ_ONLY is ' +
135
+ 'set — the server would start with an empty tool list.'
136
+ : 'IMAP_ALLOW_TOOLS/IMAP_DENY_TOOLS leave no tools registered — the ' +
137
+ 'server would start with an empty tool list.');
138
+ }
139
+ return { active: true, selected };
140
+ }
141
+ /**
142
+ * Makes `server` register only the tools the filter selected.
143
+ *
144
+ * The tool is registered and then removed again rather than skipped. Skipping
145
+ * looks cheaper and breaks one case: the SDK installs its `tools/list` handler
146
+ * from inside the registration path, so a server whose every tool was skipped
147
+ * would answer `tools/list` with "method not found" instead of an empty list.
148
+ * `remove()` deletes the entry from the SDK's tool map outright, which makes a
149
+ * filtered tool answer `Tool X not found` — exactly what a tool the read-only
150
+ * mode never registered already does. `disable()` would be wrong: it hides the
151
+ * tool from `tools/list` but still answers a call with "disabled", which is the
152
+ * advertising-a-refusal this server avoids everywhere else.
153
+ */
154
+ export function installToolFilter(server, filter) {
155
+ if (!filter.active)
156
+ return;
157
+ const register = server.registerTool.bind(server);
158
+ // An object literal rather than a plain function so the method picks up the
159
+ // SDK's generic signature by contextual typing — the call sites keep their
160
+ // typed handler arguments and nothing needs a cast.
161
+ const wrapper = {
162
+ registerTool(name, config, cb) {
163
+ const tool = register(name, config, cb);
164
+ if (!filter.selected.has(name))
165
+ tool.remove();
166
+ return tool;
167
+ },
168
+ };
169
+ server.registerTool = wrapper.registerTool;
170
+ }
171
+ //# sourceMappingURL=tool-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-filter.js","sourceRoot":"","sources":["../src/tool-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The tools this server can register, declared rather than discovered.
3
+ *
4
+ * Declared, because the tool filter has to answer "is this a name you have?"
5
+ * *before* anything is registered — and in read-only mode the write tools are
6
+ * never registered at all. Deriving the catalogue from what actually reached
7
+ * `registerTool` would make `IMAP_ALLOW_TOOLS=delete_messages` report "unknown
8
+ * tool" under `IMAP_READ_ONLY=true`, which is the one answer that is wrong.
9
+ *
10
+ * This is also the full tool surface, hard-coded on purpose: a tool that appears
11
+ * or disappears by accident is a change to the server's contract and has to be a
12
+ * deliberate edit here. `test/tool-filter.test.ts` asserts that these lists and
13
+ * the tools the server really registers are the same set.
14
+ *
15
+ * Note this covers *tools* only. This server also registers attachment
16
+ * resources (`src/resources.ts`), and the filter does not touch those.
17
+ */
18
+ /**
19
+ * Registered always — but "read" here means "registered under the read-only
20
+ * default", not "touches nothing".
21
+ *
22
+ * Two of them do write, and an operator who reads `IMAP_READ_ONLY=true` as
23
+ * "never changes anything" would be wrong about both. `list_new_messages`
24
+ * issues a STORE to set the seen keyword, which is how it can answer "anything
25
+ * new?" at all — that is the deliberate trade, and `dry_run` previews it
26
+ * without marking. `get_attachments` creates files when `IMAP_DOWNLOAD_DIR` is
27
+ * set. Neither carries `readOnlyHint: true`; the other four do.
28
+ */
29
+ export declare const READ_TOOLS: readonly ["get_attachments", "get_message", "get_server_info", "list_mailboxes", "list_messages", "list_new_messages"];
30
+ /** Registered only when `IMAP_READ_ONLY` is turned off. */
31
+ export declare const WRITE_TOOLS: readonly ["delete_messages", "manage_mailbox", "move_messages", "save_draft", "set_message_flags"];
32
+ /** Every tool, read-only mode aside. */
33
+ export declare const ALL_TOOLS: readonly string[];
34
+ /**
35
+ * What `IMAP_ALLOW_TOOLS=essential` selects: find the mail, read it, file it.
36
+ *
37
+ * Six of eleven. Left out on purpose: `delete_messages` (irreversible),
38
+ * `save_draft` (composing is a different job from triage), `get_attachments`
39
+ * (large payloads, and the attachment resources cover the same ground), and
40
+ * `get_server_info` and `manage_mailbox`, which are administrative.
41
+ *
42
+ * Four of the six are read tools, so `IMAP_ALLOW_TOOLS=essential` remains a
43
+ * working combination under the default `IMAP_READ_ONLY=true` — it then yields
44
+ * exactly those four.
45
+ */
46
+ export declare const ESSENTIAL_TOOLS: readonly string[];
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The tools this server can register, declared rather than discovered.
3
+ *
4
+ * Declared, because the tool filter has to answer "is this a name you have?"
5
+ * *before* anything is registered — and in read-only mode the write tools are
6
+ * never registered at all. Deriving the catalogue from what actually reached
7
+ * `registerTool` would make `IMAP_ALLOW_TOOLS=delete_messages` report "unknown
8
+ * tool" under `IMAP_READ_ONLY=true`, which is the one answer that is wrong.
9
+ *
10
+ * This is also the full tool surface, hard-coded on purpose: a tool that appears
11
+ * or disappears by accident is a change to the server's contract and has to be a
12
+ * deliberate edit here. `test/tool-filter.test.ts` asserts that these lists and
13
+ * the tools the server really registers are the same set.
14
+ *
15
+ * Note this covers *tools* only. This server also registers attachment
16
+ * resources (`src/resources.ts`), and the filter does not touch those.
17
+ */
18
+ /**
19
+ * Registered always — but "read" here means "registered under the read-only
20
+ * default", not "touches nothing".
21
+ *
22
+ * Two of them do write, and an operator who reads `IMAP_READ_ONLY=true` as
23
+ * "never changes anything" would be wrong about both. `list_new_messages`
24
+ * issues a STORE to set the seen keyword, which is how it can answer "anything
25
+ * new?" at all — that is the deliberate trade, and `dry_run` previews it
26
+ * without marking. `get_attachments` creates files when `IMAP_DOWNLOAD_DIR` is
27
+ * set. Neither carries `readOnlyHint: true`; the other four do.
28
+ */
29
+ export const READ_TOOLS = [
30
+ 'get_attachments',
31
+ 'get_message',
32
+ 'get_server_info',
33
+ 'list_mailboxes',
34
+ 'list_messages',
35
+ 'list_new_messages',
36
+ ];
37
+ /** Registered only when `IMAP_READ_ONLY` is turned off. */
38
+ export const WRITE_TOOLS = [
39
+ 'delete_messages',
40
+ 'manage_mailbox',
41
+ 'move_messages',
42
+ 'save_draft',
43
+ 'set_message_flags',
44
+ ];
45
+ /** Every tool, read-only mode aside. */
46
+ export const ALL_TOOLS = [...READ_TOOLS, ...WRITE_TOOLS];
47
+ /**
48
+ * What `IMAP_ALLOW_TOOLS=essential` selects: find the mail, read it, file it.
49
+ *
50
+ * Six of eleven. Left out on purpose: `delete_messages` (irreversible),
51
+ * `save_draft` (composing is a different job from triage), `get_attachments`
52
+ * (large payloads, and the attachment resources cover the same ground), and
53
+ * `get_server_info` and `manage_mailbox`, which are administrative.
54
+ *
55
+ * Four of the six are read tools, so `IMAP_ALLOW_TOOLS=essential` remains a
56
+ * working combination under the default `IMAP_READ_ONLY=true` — it then yields
57
+ * exactly those four.
58
+ */
59
+ export const ESSENTIAL_TOOLS = [
60
+ 'list_mailboxes',
61
+ 'list_new_messages',
62
+ 'list_messages',
63
+ 'get_message',
64
+ 'set_message_flags',
65
+ 'move_messages',
66
+ ];
67
+ //# sourceMappingURL=catalogue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue.js","sourceRoot":"","sources":["../../src/tools/catalogue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,iBAAiB;IACjB,aAAa;IACb,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,mBAAmB;CACX,CAAC;AAEX,2DAA2D;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,YAAY;IACZ,mBAAmB;CACX,CAAC;AAEX,wCAAwC;AACxC,MAAM,CAAC,MAAM,SAAS,GAAsB,CAAC,GAAG,UAAU,EAAE,GAAG,WAAW,CAAC,CAAC;AAE5E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsB;IAChD,gBAAgB;IAChB,mBAAmB;IACnB,eAAe;IACf,aAAa;IACb,mBAAmB;IACnB,eAAe;CAChB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Config } from '../config.js';
3
+ import { ImapClient } from '../imap.js';
4
+ export declare function registerReadTools(server: McpServer, client: ImapClient, config: Config): void;