@rndmjoker/proton-mcp 0.4.4

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 (141) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +405 -0
  3. package/dist/activity.d.ts +49 -0
  4. package/dist/activity.js +72 -0
  5. package/dist/activity.js.map +1 -0
  6. package/dist/bridge/certificate.d.ts +75 -0
  7. package/dist/bridge/certificate.js +176 -0
  8. package/dist/bridge/certificate.js.map +1 -0
  9. package/dist/bridge/connection.d.ts +99 -0
  10. package/dist/bridge/connection.js +244 -0
  11. package/dist/bridge/connection.js.map +1 -0
  12. package/dist/bridge/errors.d.ts +35 -0
  13. package/dist/bridge/errors.js +56 -0
  14. package/dist/bridge/errors.js.map +1 -0
  15. package/dist/config.d.ts +92 -0
  16. package/dist/config.js +142 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credentials/availability.d.ts +62 -0
  19. package/dist/credentials/availability.js +107 -0
  20. package/dist/credentials/availability.js.map +1 -0
  21. package/dist/credentials/encrypted-file.d.ts +50 -0
  22. package/dist/credentials/encrypted-file.js +156 -0
  23. package/dist/credentials/encrypted-file.js.map +1 -0
  24. package/dist/credentials/keyring.d.ts +48 -0
  25. package/dist/credentials/keyring.js +97 -0
  26. package/dist/credentials/keyring.js.map +1 -0
  27. package/dist/credentials/plain-file.d.ts +24 -0
  28. package/dist/credentials/plain-file.js +78 -0
  29. package/dist/credentials/plain-file.js.map +1 -0
  30. package/dist/credentials/probe-keyring.d.ts +11 -0
  31. package/dist/credentials/probe-keyring.js +57 -0
  32. package/dist/credentials/probe-keyring.js.map +1 -0
  33. package/dist/credentials/session.d.ts +15 -0
  34. package/dist/credentials/session.js +21 -0
  35. package/dist/credentials/session.js.map +1 -0
  36. package/dist/credentials/store.d.ts +80 -0
  37. package/dist/credentials/store.js +94 -0
  38. package/dist/credentials/store.js.map +1 -0
  39. package/dist/in-flight.d.ts +33 -0
  40. package/dist/in-flight.js +78 -0
  41. package/dist/in-flight.js.map +1 -0
  42. package/dist/mail/actions.d.ts +116 -0
  43. package/dist/mail/actions.js +265 -0
  44. package/dist/mail/actions.js.map +1 -0
  45. package/dist/mail/attachments.d.ts +39 -0
  46. package/dist/mail/attachments.js +86 -0
  47. package/dist/mail/attachments.js.map +1 -0
  48. package/dist/mail/compose.d.ts +264 -0
  49. package/dist/mail/compose.js +395 -0
  50. package/dist/mail/compose.js.map +1 -0
  51. package/dist/mail/drafts.d.ts +138 -0
  52. package/dist/mail/drafts.js +449 -0
  53. package/dist/mail/drafts.js.map +1 -0
  54. package/dist/mail/ids.d.ts +78 -0
  55. package/dist/mail/ids.js +135 -0
  56. package/dist/mail/ids.js.map +1 -0
  57. package/dist/mail/labels.d.ts +61 -0
  58. package/dist/mail/labels.js +139 -0
  59. package/dist/mail/labels.js.map +1 -0
  60. package/dist/mail/markup.d.ts +91 -0
  61. package/dist/mail/markup.js +265 -0
  62. package/dist/mail/markup.js.map +1 -0
  63. package/dist/mail/messages.d.ts +147 -0
  64. package/dist/mail/messages.js +219 -0
  65. package/dist/mail/messages.js.map +1 -0
  66. package/dist/mail/quote.d.ts +65 -0
  67. package/dist/mail/quote.js +157 -0
  68. package/dist/mail/quote.js.map +1 -0
  69. package/dist/mail/search.d.ts +66 -0
  70. package/dist/mail/search.js +107 -0
  71. package/dist/mail/search.js.map +1 -0
  72. package/dist/mail/send.d.ts +61 -0
  73. package/dist/mail/send.js +132 -0
  74. package/dist/mail/send.js.map +1 -0
  75. package/dist/mime/parse.d.ts +110 -0
  76. package/dist/mime/parse.js +194 -0
  77. package/dist/mime/parse.js.map +1 -0
  78. package/dist/server.d.ts +13 -0
  79. package/dist/server.js +387 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/session.d.ts +86 -0
  82. package/dist/session.js +220 -0
  83. package/dist/session.js.map +1 -0
  84. package/dist/settings.d.ts +52 -0
  85. package/dist/settings.js +80 -0
  86. package/dist/settings.js.map +1 -0
  87. package/dist/tools/actions.d.ts +21 -0
  88. package/dist/tools/actions.js +133 -0
  89. package/dist/tools/actions.js.map +1 -0
  90. package/dist/tools/capabilities.d.ts +52 -0
  91. package/dist/tools/capabilities.js +96 -0
  92. package/dist/tools/capabilities.js.map +1 -0
  93. package/dist/tools/confirm.d.ts +84 -0
  94. package/dist/tools/confirm.js +212 -0
  95. package/dist/tools/confirm.js.map +1 -0
  96. package/dist/tools/drafts.d.ts +21 -0
  97. package/dist/tools/drafts.js +199 -0
  98. package/dist/tools/drafts.js.map +1 -0
  99. package/dist/tools/failures.d.ts +46 -0
  100. package/dist/tools/failures.js +157 -0
  101. package/dist/tools/failures.js.map +1 -0
  102. package/dist/tools/format.d.ts +55 -0
  103. package/dist/tools/format.js +220 -0
  104. package/dist/tools/format.js.map +1 -0
  105. package/dist/tools/guidance.d.ts +38 -0
  106. package/dist/tools/guidance.js +226 -0
  107. package/dist/tools/guidance.js.map +1 -0
  108. package/dist/tools/interface.d.ts +28 -0
  109. package/dist/tools/interface.js +63 -0
  110. package/dist/tools/interface.js.map +1 -0
  111. package/dist/tools/labels.d.ts +21 -0
  112. package/dist/tools/labels.js +92 -0
  113. package/dist/tools/labels.js.map +1 -0
  114. package/dist/tools/list-folders.d.ts +14 -0
  115. package/dist/tools/list-folders.js +70 -0
  116. package/dist/tools/list-folders.js.map +1 -0
  117. package/dist/tools/messages.d.ts +9 -0
  118. package/dist/tools/messages.js +221 -0
  119. package/dist/tools/messages.js.map +1 -0
  120. package/dist/tools/send.d.ts +29 -0
  121. package/dist/tools/send.js +236 -0
  122. package/dist/tools/send.js.map +1 -0
  123. package/dist/tools/sign-in-required.d.ts +94 -0
  124. package/dist/tools/sign-in-required.js +168 -0
  125. package/dist/tools/sign-in-required.js.map +1 -0
  126. package/dist/web/layout.d.ts +75 -0
  127. package/dist/web/layout.js +590 -0
  128. package/dist/web/layout.js.map +1 -0
  129. package/dist/web/pages.d.ts +79 -0
  130. package/dist/web/pages.js +284 -0
  131. package/dist/web/pages.js.map +1 -0
  132. package/dist/web/sections.d.ts +105 -0
  133. package/dist/web/sections.js +207 -0
  134. package/dist/web/sections.js.map +1 -0
  135. package/dist/web/security.d.ts +122 -0
  136. package/dist/web/security.js +180 -0
  137. package/dist/web/security.js.map +1 -0
  138. package/dist/web/server.d.ts +153 -0
  139. package/dist/web/server.js +622 -0
  140. package/dist/web/server.js.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * What this server tells a model about itself.
3
+ *
4
+ * Two channels, and the difference between them is the whole design here:
5
+ *
6
+ * - **`instructions`** travels with every connection whether anyone wants it or
7
+ * not, so it costs context on every single session. It says only what a model
8
+ * would otherwise get wrong before it had a reason to look anything up.
9
+ * - **Resources** are read on demand. They can afford to be thorough, because
10
+ * nobody pays for the ones that are not read.
11
+ *
12
+ * The split is not tidiness. A tool description that grows until nobody reads
13
+ * it protects nobody, and the same is true of instructions. So the short text
14
+ * names the traps and points here; the long text explains why they are traps.
15
+ *
16
+ * What goes in them is not invented. Everything below was measured against a
17
+ * running Bridge and a real mailbox, and the numbers are kept in because a model
18
+ * deciding whether to wait three seconds or twenty is better served by the
19
+ * measurement than by the word "briefly".
20
+ */
21
+ /**
22
+ * Sent to every client at connection time.
23
+ *
24
+ * Deliberately short. Each line is here because a model got it wrong in
25
+ * practice or would have: writing HTML into a plain text field, transliterating
26
+ * umlauts away, checking a write immediately and reporting the intermediate
27
+ * state as the outcome.
28
+ */
29
+ export const INSTRUCTIONS = [
30
+ 'This server reads and writes a Proton Mail account through a local Proton Mail Bridge.',
31
+ '',
32
+ 'Four things are worth knowing before the first call, because they are the ones that go',
33
+ 'wrong quietly:',
34
+ '',
35
+ '1. Nothing is sent without a person agreeing to it. Every sending tool asks first, through',
36
+ ' the client, showing the final recipients. The first call never sends, and there is no',
37
+ ' setting that turns the question off.',
38
+ '2. A message body is either plain text or markup, never both. Markup written into the text',
39
+ ' field arrives as visible tags. All text is UTF-8: write words the way the language',
40
+ ' spells them rather than transliterating characters away.',
41
+ '3. Writes need time to settle, and how much depends on what was written. Flags are',
42
+ ' immediate; a move takes about fifteen seconds; a new message needs about twenty before',
43
+ ' it appears in "All Mail". Checking straight after a write reports an intermediate state',
44
+ ' as though it were the result.',
45
+ '4. Nothing here deletes for good. "Delete" means moving to the trash.',
46
+ '',
47
+ 'Two guides can be read when they are needed:',
48
+ ' proton-mcp://guide/writing composing messages, formatting, quoting, what the',
49
+ ' confirmation shows',
50
+ ' proton-mcp://guide/bridge how the Bridge behaves, with the measurements behind the',
51
+ ' numbers above',
52
+ ].join('\n');
53
+ const WRITING_GUIDE = `# Writing messages with this server
54
+
55
+ ## Text or markup, never both
56
+
57
+ Every tool that composes a message takes either \`text\` or \`html\`. Giving both is
58
+ refused rather than resolved by a preference, because Proton keeps the markup and
59
+ drops the plain text half of a message that carries both. Whichever one this
60
+ server chose to prefer, the other would silently never arrive.
61
+
62
+ Markup placed in the \`text\` field is not interpreted. It arrives as visible
63
+ characters and the recipient reads the tags.
64
+
65
+ ## What markup you may write
66
+
67
+ Headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links
68
+ and images. Styling by colour, background, font, size, weight, alignment,
69
+ spacing and borders. Images may point at a full address or at a file carried
70
+ inside the message.
71
+
72
+ Anything else is **refused, not quietly removed**, and the answer names what and
73
+ why. A message that was silently altered is no longer the message anyone agreed
74
+ to, so there is nothing to guess at: if a send is refused, the reason says which
75
+ element or property and what to use instead.
76
+
77
+ Two refusals are worth understanding rather than working around:
78
+
79
+ - **Properties that hide content** (\`display\`, \`visibility\`, \`opacity\`,
80
+ \`position\`, a size of zero) are refused. They let a message be delivered
81
+ looking different from how it was written.
82
+ - **Addresses** are limited to \`http\`, \`https\`, \`mailto\` and \`cid\`. A \`data:\`
83
+ address would carry a whole file inside the markup, where nothing lists it as
84
+ an attachment.
85
+
86
+ ## What the person confirming is shown
87
+
88
+ Not only the body. Understanding this explains most of the rules above.
89
+
90
+ - The beginning of what you wrote, as it will read.
91
+ - **Every address in it, in full**, links and images alike, each with the text it
92
+ is shown as. A link's visible text and its target are the same string in plain
93
+ text and two different strings in markup, and that difference is the shape of
94
+ every phishing message ever written.
95
+ - **Every piece of text a recipient can read that the body preview does not
96
+ show.** An image's alt text is the clearest case: it never appears in the
97
+ converted body, and mail clients block remote images by default, so it is
98
+ frequently what the recipient actually reads.
99
+ - Every file carried with the message, with its size.
100
+
101
+ The agreement is bound to that exact message by a fingerprint covering the
102
+ sender, every recipient, the subject, the markup and the contents of each
103
+ carried file. Changing anything between the question and the answer means
104
+ nothing is sent. So compose the message you mean, then ask; do not ask and then
105
+ adjust.
106
+
107
+ ## Replying and forwarding
108
+
109
+ The original is quoted below your part, with its formatting intact. You write
110
+ your own part and nothing else; the quote is assembled by this server from the
111
+ message being answered.
112
+
113
+ - Your part goes through the rules above.
114
+ - **The quote does not**, and could not: real mail is full of elements this
115
+ server would never write itself, and holding a quote to that list would make
116
+ replying to most messages impossible. It is kept as it was written, minus the
117
+ elements that would reach out of the quote and restyle your text above it.
118
+ - The confirmation counts the quote rather than listing it. A quoted newsletter
119
+ carries dozens of addresses that arrived in the recipient's mailbox already,
120
+ and listing those would bury the ones you are answering for.
121
+
122
+ Replying as a **draft** and replying as a **send** differ in more than whether
123
+ anything leaves. Proton rewrites the thread headers of anything it stores, so a
124
+ reply written to a draft and sent later carries Proton's idea of the
125
+ conversation. A reply handed straight to SMTP keeps its \`In-Reply-To\` and stays
126
+ in the thread. If threading matters, use \`send_reply\` rather than
127
+ \`reply_draft\` followed by \`send_draft\`.
128
+
129
+ ## Attachments
130
+
131
+ A message cannot be given a file from this machine. That is a decision that has
132
+ not been made rather than a feature that is missing: which paths a model may
133
+ reach is its own question. Forwarding loses nothing regardless, because the
134
+ original travels along whole when it carries attachments.
135
+ `;
136
+ const BRIDGE_GUIDE = `# How the Bridge behaves
137
+
138
+ Everything here was measured against a running Bridge rather than read in a
139
+ specification, and several of these contradict what the specification would
140
+ suggest.
141
+
142
+ ## Waiting times differ by an order of magnitude
143
+
144
+ | Operation | Before it is visible |
145
+ | :--- | :--- |
146
+ | Setting a flag, read state or star | immediately, and unchanged twenty seconds later |
147
+ | A message appended to a mailbox | immediately in that mailbox |
148
+ | The same message in "All Mail" | about twenty seconds |
149
+ | A move between folders, settled | about fifteen seconds |
150
+ | SMTP accepting a message | about six and a half seconds |
151
+ | A sent message visible over IMAP | about three seconds after that |
152
+
153
+ During the settling time a move shows the message in **both** mailboxes. A check
154
+ run straight after a write therefore reports something that is not the outcome.
155
+ Wait, or simply report what was asked for.
156
+
157
+ ## Folders and labels are different things
158
+
159
+ A message lives in exactly one folder and carries any number of labels.
160
+
161
+ - Applying a label leaves the message where it is. Removing one leaves the
162
+ folder and the other labels untouched, and does not move anything to trash.
163
+ - **Moving a message into a label is not a move.** The label is applied and the
164
+ message stays in its folder. \`move_messages\` refuses a label for that reason
165
+ and points at \`add_label\`.
166
+ - Labels survive a move between folders.
167
+ - "All Mail" holds every message exactly once, including the ones in the trash.
168
+ A listing of it does not say which folder a message is really in.
169
+
170
+ ## Two commands that report success without doing anything
171
+
172
+ - **A copy into a mailbox that does not exist reports success and changes
173
+ nothing.** No error, and the destination is not created either. Whether a
174
+ label exists has to be established before the call, not after it.
175
+ - **A move out of "All Mail" does nothing**, and reports a refusal as a falsy
176
+ return value rather than as an error. Moving needs the folder a message really
177
+ sits in.
178
+
179
+ ## Other measured behaviour
180
+
181
+ - **Proton fills the inbox through delivery, not through IMAP.** A message moved
182
+ to "INBOX" was measured to end up elsewhere shortly afterwards.
183
+ - **A message with both a text and an HTML part arrives with only the HTML.**
184
+ - **Proton attaches its own public key to every message sent through the
185
+ Bridge.** It is filtered out of every attachment list here, so a client showing
186
+ three attachments where this server reports two is showing that key.
187
+ - **Proton rewrites the thread headers of anything it stores.** A draft appended
188
+ with \`In-Reply-To\` and \`References\` comes back without the first and with the
189
+ second replaced. A message handed to SMTP keeps them.
190
+ - **Searching is fast and local.** It runs against the Bridge's own database.
191
+ Ordering a listing by date is the expensive part, because there is no
192
+ server-side sort: it reads one date per message in the mailbox before a page
193
+ can be cut, and the answer says so when it took a while.
194
+
195
+ ## The trap behind all of these
196
+
197
+ Every one of the surprises above looked settled after a single measurement and
198
+ turned out differently on the second. A move looked like a copy because the
199
+ message was briefly in both places. A missing label looked like a successful
200
+ copy because the command said so. If something here is ever re-measured, it is
201
+ worth doing twice with a pause in between.
202
+ `;
203
+ /**
204
+ * Makes the guides readable.
205
+ *
206
+ * Registered as resources rather than folded into tool descriptions, because a
207
+ * description is paid for on every listing whether it is needed or not, and
208
+ * these are long by design.
209
+ */
210
+ export function registerGuidance(server) {
211
+ server.registerResource('writing-guide', 'proton-mcp://guide/writing', {
212
+ title: 'Writing messages with this server',
213
+ description: 'Composing messages: text against markup, what markup is permitted and what is refused, ' +
214
+ 'what the person confirming a send is shown, how replies and forwards quote the original, ' +
215
+ 'and why replying as a draft and replying as a send are not the same thing.',
216
+ mimeType: 'text/markdown',
217
+ }, async (uri) => ({ contents: [{ uri: uri.href, text: WRITING_GUIDE, mimeType: 'text/markdown' }] }));
218
+ server.registerResource('bridge-guide', 'proton-mcp://guide/bridge', {
219
+ title: 'How the Proton Mail Bridge behaves',
220
+ description: 'Measured behaviour of the Bridge: how long each kind of write takes to settle, how ' +
221
+ 'folders and labels differ, and the two commands that report success without having done ' +
222
+ 'anything.',
223
+ mimeType: 'text/markdown',
224
+ }, async (uri) => ({ contents: [{ uri: uri.href, text: BRIDGE_GUIDE, mimeType: 'text/markdown' }] }));
225
+ }
226
+ //# sourceMappingURL=guidance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.js","sourceRoot":"","sources":["../../src/tools/guidance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,wFAAwF;IACxF,EAAE;IACF,wFAAwF;IACxF,gBAAgB;IAChB,EAAE;IACF,4FAA4F;IAC5F,0FAA0F;IAC1F,yCAAyC;IACzC,4FAA4F;IAC5F,uFAAuF;IACvF,6DAA6D;IAC7D,oFAAoF;IACpF,2FAA2F;IAC3F,4FAA4F;IAC5F,kCAAkC;IAClC,uEAAuE;IACvE,EAAE;IACF,8CAA8C;IAC9C,kFAAkF;IAClF,mDAAmD;IACnD,yFAAyF;IACzF,8CAA8C;CAC/C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEZ,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFrB,CAAA;AAED,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEpB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,CAAC,gBAAgB,CACrB,eAAe,EACf,4BAA4B,EAC5B;QACE,KAAK,EAAE,mCAAmC;QAC1C,WAAW,EACT,yFAAyF;YACzF,2FAA2F;YAC3F,4EAA4E;QAC9E,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CACnG,CAAA;IAED,MAAM,CAAC,gBAAgB,CACrB,cAAc,EACd,2BAA2B,EAC3B;QACE,KAAK,EAAE,oCAAoC;QAC3C,WAAW,EACT,qFAAqF;YACrF,0FAA0F;YAC1F,WAAW;QACb,QAAQ,EAAE,eAAe;KAC1B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAClG,CAAA;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Tool: open_configuration
3
+ *
4
+ * Hands out the address of the local web interface, including the access token.
5
+ *
6
+ * Why a tool rather than only a line on stderr: most clients hide a server's
7
+ * stderr, so a user who wants to reach the interface would have to go digging
8
+ * through logs. Asking the assistant is the obvious move, and this makes it
9
+ * work.
10
+ *
11
+ * The token does end up in the conversation. That is a deliberate trade: it
12
+ * grants access to a page on loopback that shows configuration and state, never
13
+ * message content, and it is worthless from any other machine. It changes with
14
+ * every restart. The alternative, making the user hunt for a log line, would
15
+ * mostly result in nobody using the interface at all.
16
+ */
17
+ import type { McpServer } from '@modelcontextprotocol/server';
18
+ import type { StoreKind } from '../credentials/store.js';
19
+ export interface InterfaceInfo {
20
+ url: () => string;
21
+ running: () => boolean;
22
+ signedIn: () => boolean;
23
+ address: () => string | undefined;
24
+ storeKind: () => StoreKind | undefined;
25
+ /** Whether credentials are stored but still need a master password. */
26
+ locked?: () => Promise<boolean>;
27
+ }
28
+ export declare function registerInterfaceTool(server: McpServer, info: InterfaceInfo): void;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Tool: open_configuration
3
+ *
4
+ * Hands out the address of the local web interface, including the access token.
5
+ *
6
+ * Why a tool rather than only a line on stderr: most clients hide a server's
7
+ * stderr, so a user who wants to reach the interface would have to go digging
8
+ * through logs. Asking the assistant is the obvious move, and this makes it
9
+ * work.
10
+ *
11
+ * The token does end up in the conversation. That is a deliberate trade: it
12
+ * grants access to a page on loopback that shows configuration and state, never
13
+ * message content, and it is worthless from any other machine. It changes with
14
+ * every restart. The alternative, making the user hunt for a log line, would
15
+ * mostly result in nobody using the interface at all.
16
+ */
17
+ import * as z from 'zod';
18
+ import { STORE_DESCRIPTIONS } from '../credentials/store.js';
19
+ import { track } from '../in-flight.js';
20
+ export function registerInterfaceTool(server, info) {
21
+ server.registerTool('open_configuration', {
22
+ title: 'Open the configuration interface',
23
+ description: 'Returns the address of the local configuration interface, which runs for as long as this ' +
24
+ 'server does. Use it when the user asks how to sign in, where to change settings, or when ' +
25
+ 'another tool reports that no credentials are available. The address contains an access ' +
26
+ 'token and changes every time the server restarts.',
27
+ inputSchema: z.object({}),
28
+ annotations: { readOnlyHint: true, openWorldHint: false },
29
+ }, async () => track(async () => {
30
+ if (!info.running()) {
31
+ const failure = {
32
+ content: [
33
+ {
34
+ type: 'text',
35
+ text: 'The configuration interface is not running. It failed to start, most likely ' +
36
+ 'because its port was in use. Set PROTON_MCP_WEB_PORT to a free port and restart ' +
37
+ 'the server. Credentials can still be provided through the BRIDGE_USER and ' +
38
+ 'BRIDGE_PASS environment variables.',
39
+ },
40
+ ],
41
+ isError: true,
42
+ };
43
+ return failure;
44
+ }
45
+ const lines = [`Configuration interface: ${info.url()}`, ''];
46
+ if (info.signedIn()) {
47
+ const kind = info.storeKind();
48
+ lines.push(`Signed in as ${info.address()}.`, kind
49
+ ? `Credentials are kept in: ${STORE_DESCRIPTIONS[kind].title}.`
50
+ : 'Credentials are held for this session only.', '', 'The page shows the connection state and lets the user sign out.');
51
+ }
52
+ else if ((await info.locked?.()) === true) {
53
+ lines.push('Credentials are stored in an encrypted file and only need to be unlocked. Opening the', 'address above leads to a page asking for the master password, and nothing else: the', 'Bridge password is already on disk.', '', 'Tell the user to open it themselves. Do not ask them for the master password here.');
54
+ }
55
+ else {
56
+ lines.push('Not signed in yet. Opening the address above leads to a form asking for the Proton', 'address and the Bridge password, which is the password the Bridge generates and not', 'the Proton account password.', '', 'Tell the user to open it themselves. Do not ask them for the password here: it has no', 'business in a conversation, and the page exists so that it never has to be.');
57
+ }
58
+ lines.push('', 'The address is only reachable from this machine and the token changes on every restart.');
59
+ const answer = { content: [{ type: 'text', text: lines.join('\n') }] };
60
+ return answer;
61
+ }, 'open_configuration'));
62
+ }
63
+ //# sourceMappingURL=interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/tools/interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAGxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAYvC,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,IAAmB;IAC1E,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,kCAAkC;QACzC,WAAW,EACT,2FAA2F;YAC3F,2FAA2F;YAC3F,yFAAyF;YACzF,mDAAmD;QACrD,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE;KAC1D,EACD,KAAK,IAAI,EAAE,CACT,KAAK,CAAC,KAAK,IAAI,EAAE;QACf,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACpB,MAAM,OAAO,GAAmB;gBAC9B,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EACF,8EAA8E;4BAC9E,kFAAkF;4BAClF,4EAA4E;4BAC5E,oCAAoC;qBACvC;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAA;YACD,OAAO,OAAO,CAAA;QAChB,CAAC;QAED,MAAM,KAAK,GAAa,CAAC,4BAA4B,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QAEtE,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;YAC7B,KAAK,CAAC,IAAI,CACR,gBAAgB,IAAI,CAAC,OAAO,EAAE,GAAG,EACjC,IAAI;gBACF,CAAC,CAAC,4BAA4B,kBAAkB,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG;gBAC/D,CAAC,CAAC,6CAA6C,EACjD,EAAE,EACF,iEAAiE,CAClE,CAAA;QACH,CAAC;aAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5C,KAAK,CAAC,IAAI,CACR,uFAAuF,EACvF,qFAAqF,EACrF,qCAAqC,EACrC,EAAE,EACF,oFAAoF,CACrF,CAAA;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CACR,oFAAoF,EACpF,qFAAqF,EACrF,8BAA8B,EAC9B,EAAE,EACF,uFAAuF,EACvF,6EAA6E,CAC9E,CAAA;QACH,CAAC;QAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,yFAAyF,CAC1F,CAAA;QAED,MAAM,MAAM,GAAmB,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAA;QACtF,OAAO,MAAM,CAAA;IACf,CAAC,EAAE,oBAAoB,CAAC,CAC3B,CAAA;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Tools: add_label, remove_label
3
+ *
4
+ * The first tools that change anything. Two properties matter more than the
5
+ * feature itself:
6
+ *
7
+ * - They honour the read-only mode. Until now `PROTON_MCP_READ_ONLY` was a
8
+ * setting the web interface displayed and nothing enforced, because there was
9
+ * nothing to prevent. From here on the display has to be true.
10
+ * - They never report success they have not established. A copy into a label
11
+ * that does not exist is accepted by the Bridge and changes nothing, so the
12
+ * label is checked beforehand, and the answer says what was asked for rather
13
+ * than claiming a verified outcome.
14
+ *
15
+ * Labelling is not put behind a confirmation. It changes no content, sends
16
+ * nothing outside, and is undone by the opposite call. The confirmation
17
+ * requirement belongs to sending, where a wrong decision cannot be taken back.
18
+ */
19
+ import type { McpServer } from '@modelcontextprotocol/server';
20
+ import type { Connection } from '../bridge/connection.js';
21
+ export declare function registerLabelTools(server: McpServer, connection: Connection, readOnly: () => boolean): void;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Tools: add_label, remove_label
3
+ *
4
+ * The first tools that change anything. Two properties matter more than the
5
+ * feature itself:
6
+ *
7
+ * - They honour the read-only mode. Until now `PROTON_MCP_READ_ONLY` was a
8
+ * setting the web interface displayed and nothing enforced, because there was
9
+ * nothing to prevent. From here on the display has to be true.
10
+ * - They never report success they have not established. A copy into a label
11
+ * that does not exist is accepted by the Bridge and changes nothing, so the
12
+ * label is checked beforehand, and the answer says what was asked for rather
13
+ * than claiming a verified outcome.
14
+ *
15
+ * Labelling is not put behind a confirmation. It changes no content, sends
16
+ * nothing outside, and is undone by the opposite call. The confirmation
17
+ * requirement belongs to sending, where a wrong decision cannot be taken back.
18
+ */
19
+ import * as z from 'zod';
20
+ import { addLabel, removeLabel, LABEL_PREFIX } from '../mail/labels.js';
21
+ import { describeFailure, withSignIn } from './failures.js';
22
+ import { track } from '../in-flight.js';
23
+ const ok = (text) => ({ content: [{ type: 'text', text }] });
24
+ /**
25
+ * Written into both answers.
26
+ *
27
+ * Measured: right after a write the Bridge reports an intermediate state. A
28
+ * model that checks its own result immediately will describe something that is
29
+ * not the outcome, so it is told not to.
30
+ */
31
+ const DELAY_NOTE = 'The Bridge needs a moment to reconcile with Proton. Listing the label straight away ' +
32
+ 'may not show the change yet, and an immediate check would be misleading rather than informative.';
33
+ const labelArgument = z
34
+ .string()
35
+ .describe(`The label name, for example "Work". The "${LABEL_PREFIX}" prefix may be included and is optional. ` +
36
+ 'Use list_folders to see which labels exist. Labels are created in Proton itself, not here.');
37
+ const messageArgument = z
38
+ .string()
39
+ .describe('The message id from a listing or a search, with or without angle brackets.');
40
+ const mailboxHint = z
41
+ .string()
42
+ .optional()
43
+ .describe('The mailbox the message is in, if known. Only a shortcut for the lookup.');
44
+ export function registerLabelTools(server, connection, readOnly) {
45
+ server.registerTool('add_label', {
46
+ title: 'Apply a label',
47
+ description: 'Applies an existing label to a message. In Proton a message lives in exactly one folder ' +
48
+ 'but can carry any number of labels, so this adds to the message rather than moving it: ' +
49
+ 'it stays where it is and keeps the labels it already has. ' +
50
+ 'The label has to exist already; this server does not create labels.',
51
+ inputSchema: z.object({
52
+ messageId: messageArgument,
53
+ label: labelArgument,
54
+ mailbox: mailboxHint,
55
+ }),
56
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
57
+ }, async ({ messageId, label, mailbox }, ctx) => track(() => withSignIn(ctx, async () => {
58
+ try {
59
+ const result = await addLabel(connection, readOnly(), messageId, label, mailbox);
60
+ return ok(`Applied "${result.label}" to ${result.messageId}, which is in "${result.path}". ` +
61
+ `It has not been moved. ${DELAY_NOTE}`);
62
+ }
63
+ catch (error) {
64
+ return describeFailure(error, `applying the label "${label}"`);
65
+ }
66
+ }), 'add_label'));
67
+ server.registerTool('remove_label', {
68
+ title: 'Remove a label',
69
+ description: 'Removes a label from a message. The message keeps its folder and its other labels, and ' +
70
+ 'does not go to trash. Only labels can be removed this way, never folders: a message ' +
71
+ 'always lives in some folder, and moving it is a different operation.',
72
+ inputSchema: z.object({
73
+ messageId: messageArgument,
74
+ label: labelArgument,
75
+ }),
76
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
77
+ }, async ({ messageId, label }, ctx) => track(() => withSignIn(ctx, async () => {
78
+ try {
79
+ const result = await removeLabel(connection, readOnly(), messageId, label);
80
+ if (!result.wasApplied) {
81
+ return ok(`${result.messageId} does not carry "${result.label}", so there was nothing to remove. ` +
82
+ 'Nothing was changed.');
83
+ }
84
+ return ok(`Removed "${result.label}" from ${result.messageId}. The message keeps its folder and ` +
85
+ `its other labels. ${DELAY_NOTE}`);
86
+ }
87
+ catch (error) {
88
+ return describeFailure(error, `removing the label "${label}"`);
89
+ }
90
+ }), 'remove_label'));
91
+ }
92
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sourceRoot":"","sources":["../../src/tools/labels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tool: list_folders
3
+ *
4
+ * Lists the mailboxes. Deliberately its own tool and never a side dish of
5
+ * another answer: mailbox names are personal data on their own. Folders and
6
+ * labels belong to the Proton account rather than to a single address, so this
7
+ * exposes the whole account structure. A name like Folders/Banking/Revolut
8
+ * gives away a bank even when not a single message inside it is visible.
9
+ */
10
+ import type { McpServer } from '@modelcontextprotocol/server';
11
+ import type { Connection, Mailbox } from '../bridge/connection.js';
12
+ /** Builds the text output. Kept compact, because mailbox lists get long. */
13
+ export declare function format(mailboxes: Mailbox[]): string;
14
+ export declare function registerListFolders(server: McpServer, connection: Connection): void;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Tool: list_folders
3
+ *
4
+ * Lists the mailboxes. Deliberately its own tool and never a side dish of
5
+ * another answer: mailbox names are personal data on their own. Folders and
6
+ * labels belong to the Proton account rather than to a single address, so this
7
+ * exposes the whole account structure. A name like Folders/Banking/Revolut
8
+ * gives away a bank even when not a single message inside it is visible.
9
+ */
10
+ import * as z from 'zod';
11
+ import { describeFailure, withSignIn } from './failures.js';
12
+ import { track } from '../in-flight.js';
13
+ const inputSchema = z.object({
14
+ kind: z
15
+ .enum(['all', 'system', 'folder', 'label'])
16
+ .default('all')
17
+ .describe('Which mailboxes to list. "system" covers Inbox, Sent, Trash and so on, ' +
18
+ '"folder" the user-created folders, "label" the labels.'),
19
+ onlySelectable: z
20
+ .boolean()
21
+ .default(false)
22
+ .describe('When true, only mailboxes that can hold messages are listed.'),
23
+ });
24
+ /** Builds the text output. Kept compact, because mailbox lists get long. */
25
+ export function format(mailboxes) {
26
+ if (mailboxes.length === 0)
27
+ return 'No mailboxes found.';
28
+ const groups = [
29
+ ['system', 'System mailboxes'],
30
+ ['folder', 'Folders'],
31
+ ['label', 'Labels'],
32
+ ];
33
+ const lines = [];
34
+ for (const [kind, heading] of groups) {
35
+ const part = mailboxes.filter((m) => m.kind === kind && m.selectable);
36
+ if (part.length === 0)
37
+ continue;
38
+ lines.push(`## ${heading} (${part.length})`);
39
+ for (const m of part) {
40
+ const role = m.specialUse ? ` [${m.specialUse}]` : '';
41
+ lines.push(`- ${m.path}${role}`);
42
+ }
43
+ lines.push('');
44
+ }
45
+ return lines.join('\n').trimEnd();
46
+ }
47
+ export function registerListFolders(server, connection) {
48
+ server.registerTool('list_folders', {
49
+ title: 'List mailboxes',
50
+ description: 'Lists the folders and labels of the Proton account. ' +
51
+ 'Proton distinguishes folders (a message lives in exactly one) from labels ' +
52
+ '(a message can carry any number). Over IMAP both appear as directories.',
53
+ inputSchema,
54
+ annotations: { readOnlyHint: true, openWorldHint: false },
55
+ }, async ({ kind, onlySelectable }, ctx) => track(() => withSignIn(ctx, async () => {
56
+ try {
57
+ let mailboxes = await connection.listMailboxes();
58
+ if (kind !== 'all')
59
+ mailboxes = mailboxes.filter((m) => m.kind === kind);
60
+ if (onlySelectable)
61
+ mailboxes = mailboxes.filter((m) => m.selectable);
62
+ const answer = { content: [{ type: 'text', text: format(mailboxes) }] };
63
+ return answer;
64
+ }
65
+ catch (error) {
66
+ return describeFailure(error, 'listing mailboxes');
67
+ }
68
+ }), 'list_folders'));
69
+ }
70
+ //# sourceMappingURL=list-folders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-folders.js","sourceRoot":"","sources":["../../src/tools/list-folders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The reading tools: list_messages, get_message, get_attachment.
3
+ *
4
+ * All three are read-only. Every one of them goes through track() so that a
5
+ * shutdown waits for the answer instead of cutting it off.
6
+ */
7
+ import type { McpServer } from '@modelcontextprotocol/server';
8
+ import type { Connection } from '../bridge/connection.js';
9
+ export declare function registerMessageTools(server: McpServer, connection: Connection): void;