@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,96 @@
1
+ /**
2
+ * What the client said it can do.
3
+ *
4
+ * One module, because two places ask the same question and one of them decides
5
+ * whether a message may be sent. When they read different sources they give
6
+ * different answers, and the answer that matters is the one that refuses.
7
+ *
8
+ * ## Why this exists at all
9
+ *
10
+ * The first version of both checks read the capabilities out of the request's
11
+ * `_meta` envelope, under `io.modelcontextprotocol/clientCapabilities`. That key
12
+ * belongs to protocol revision 2026-07-28. The SDK in use here does not speak
13
+ * that revision at all: its newest is 2025-11-25, and on such a connection the
14
+ * envelope is not merely empty but absent.
15
+ *
16
+ * So the check answered "this client cannot show a form" for every client that
17
+ * ever connected, and every send was refused. Fail-closed, so nothing went out
18
+ * without a confirmation. It also meant nothing went out at all.
19
+ *
20
+ * Measured on 31.07.2026 against `serveStdio` with SDK 2.0.0:
21
+ *
22
+ * | Source | On a 2025-11-25 connection |
23
+ * | :--- | :--- |
24
+ * | `ctx.mcpReq.envelope` | absent |
25
+ * | `server.server.getClientCapabilities()` | the declaration, complete |
26
+ *
27
+ * And the round trip itself works: a returned `inputRequired` reaches the client
28
+ * as an `elicitation/create`, the answer comes back, and the handler runs a
29
+ * second time with it. Only the question of whether to try was broken.
30
+ *
31
+ * ## Both sources, not one
32
+ *
33
+ * The envelope is asked first and the handshake second, rather than replacing
34
+ * one with the other. The envelope is per request and is what a 2026-era
35
+ * connection carries; the handshake is per connection and is what this era has.
36
+ * A server that has to guess which one exists is a server that breaks on the
37
+ * next SDK release, in whichever direction it moves.
38
+ */
39
+ /** Reserved `_meta` key carrying the capabilities on a 2026-era request. */
40
+ const ENVELOPE_KEY = 'io.modelcontextprotocol/clientCapabilities';
41
+ /**
42
+ * The declaration made at handshake time.
43
+ *
44
+ * A function, and registered rather than imported: it belongs to the server
45
+ * instance, which is built inside the connection factory and does not exist when
46
+ * this module is loaded.
47
+ */
48
+ let handshake;
49
+ export function setHandshakeCapabilities(source) {
50
+ handshake = source;
51
+ }
52
+ /** For tests, which must not inherit a source from another test. */
53
+ export function _clearHandshakeCapabilities() {
54
+ handshake = undefined;
55
+ }
56
+ /**
57
+ * What this client declared, from whichever source has it.
58
+ *
59
+ * Typed as unknown on purpose. The client sent this, so its shape is a claim
60
+ * rather than a guarantee, and every reader narrows it by hand.
61
+ */
62
+ export function declaredCapabilities(ctx) {
63
+ const fromEnvelope = ctx?.mcpReq?.envelope?.[ENVELOPE_KEY];
64
+ if (fromEnvelope !== undefined && fromEnvelope !== null)
65
+ return fromEnvelope;
66
+ try {
67
+ return handshake?.();
68
+ }
69
+ catch {
70
+ // A source that throws is a source that says nothing, and saying nothing
71
+ // means the caller refuses. That is the safe direction for every caller
72
+ // this module has.
73
+ return undefined;
74
+ }
75
+ }
76
+ /** Whether a declaration names a member, treating an empty object as present. */
77
+ function declares(capabilities, group, member) {
78
+ const entry = capabilities?.[group];
79
+ if (entry === undefined || entry === null)
80
+ return false;
81
+ const value = entry[member];
82
+ if (value !== undefined && value !== null && value !== false)
83
+ return true;
84
+ // A declaration with no modes at all is the pre-mode way of saying "form",
85
+ // which is the SDK's own reading. It says nothing about url.
86
+ return member === 'form' && Object.keys(entry).length === 0;
87
+ }
88
+ /** Whether the client can show a form and give an answer back. */
89
+ export function canElicitForm(ctx) {
90
+ return declares(declaredCapabilities(ctx), 'elicitation', 'form');
91
+ }
92
+ /** Whether the client can put a URL in front of the user. */
93
+ export function canElicitUrl(ctx) {
94
+ return declares(declaredCapabilities(ctx), 'elicitation', 'url');
95
+ }
96
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/tools/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,4EAA4E;AAC5E,MAAM,YAAY,GAAG,4CAA4C,CAAA;AAEjE;;;;;;GAMG;AACH,IAAI,SAAsC,CAAA;AAE1C,MAAM,UAAU,wBAAwB,CAAC,MAAqB;IAC5D,SAAS,GAAG,MAAM,CAAA;AACpB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,2BAA2B;IACzC,SAAS,GAAG,SAAS,CAAA;AACvB,CAAC;AAMD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,MAAM,YAAY,GAAI,GAAyB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAA;IACjF,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,YAAY,CAAA;IAC5E,IAAI,CAAC;QACH,OAAO,SAAS,EAAE,EAAE,CAAA;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,wEAAwE;QACxE,mBAAmB;QACnB,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,SAAS,QAAQ,CAAC,YAAqB,EAAE,KAAa,EAAE,MAAsB;IAC5E,MAAM,KAAK,GAAI,YAAoD,EAAE,CAAC,KAAK,CAAC,CAAA;IAC5E,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IACvD,MAAM,KAAK,GAAI,KAAiC,CAAC,MAAM,CAAC,CAAA;IACxD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,IAAI,CAAA;IACzE,2EAA2E;IAC3E,6DAA6D;IAC7D,OAAO,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAA;AACxF,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,OAAO,QAAQ,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAA;AACnE,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,OAAO,QAAQ,CAAC,oBAAoB,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,CAAA;AAClE,CAAC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The confirmation that stands between a model and the outside world.
3
+ *
4
+ * The project's central rule is that a boundary living in a system prompt is
5
+ * not a boundary. Mail is text written by strangers, handed to a model that can
6
+ * call tools, so a prepared message can ask for itself to be forwarded. The
7
+ * answer is a round trip through the protocol that the server, not the prompt,
8
+ * insists on.
9
+ *
10
+ * Three properties make it worth more than a formality:
11
+ *
12
+ * 1. **It fails closed.** A client that cannot show a form does not get to
13
+ * send. There is no fallback that goes ahead without a yes, and no setting
14
+ * that turns the question off. That is a deliberate difference from the
15
+ * sign-in prompt, where a client that cannot elicit gets a text answer: the
16
+ * worst outcome there is an inconvenience, and here it is a message that
17
+ * cannot be recalled.
18
+ * 2. **The yes is bound to one specific message.** A digest of the recipients,
19
+ * the subject and the body is sealed into `requestState`, which travels
20
+ * through the client and comes back. On the second round the digest is
21
+ * recomputed from what the client now asks for and compared. If it differs,
22
+ * nothing is sent: what was confirmed is not what was requested. The seal is
23
+ * an HMAC, so the client cannot mint or alter one, and it is bound to the
24
+ * tool so a confirmation for one cannot be replayed at another.
25
+ * 3. **The question shows what actually goes out.** Recipients separated into
26
+ * To, Cc and Bcc, the subject, and the first lines of the body, all derived
27
+ * from the same object the digest covers and the sender builds from.
28
+ *
29
+ * ## Why this is a returned value rather than a call
30
+ *
31
+ * The same reason as the sign-in prompt, and it is written out in
32
+ * sign-in-required.ts: protocol revision 2026-07-28 has no server-to-client
33
+ * request channel, so `elicitInput` throws on every request this server will
34
+ * serve. The handler returns `inputRequired(...)` instead and the client calls
35
+ * the tool again.
36
+ */
37
+ import type { CallToolResult, InputRequiredResult } from '@modelcontextprotocol/server';
38
+ import { type Draft } from '../mail/compose.js';
39
+ /** The key our embedded elicitation is filed under. */
40
+ export declare const CONFIRM_KEY = "protonMcpSendConfirmation";
41
+ /** What travels through the client and comes back, sealed. */
42
+ export interface PendingSend {
43
+ /** Which tool asked. A confirmation for one must not work for another. */
44
+ tool: string;
45
+ /** Covers exactly what the user was shown. */
46
+ digest: string;
47
+ }
48
+ /**
49
+ * A fingerprint of everything that would leave the machine.
50
+ *
51
+ * Every field a recipient would see, and nothing else: two calls that differ
52
+ * only in which mailbox a draft was looked up in describe the same message and
53
+ * must not be told apart, while a changed address must be.
54
+ */
55
+ export declare function digestOf(draft: Draft): string;
56
+ /**
57
+ * Whether the client said it can show a form.
58
+ *
59
+ * Asked rather than attempted, because an embedded request the client never
60
+ * declared is refused by the SDK after the handler has already returned. The
61
+ * caller would then get that refusal instead of a question, and the tool would
62
+ * have to decide what to do about a confirmation that never appeared. Better to
63
+ * know beforehand and refuse plainly.
64
+ *
65
+ * Where the declaration is read from is capabilities.ts, and it is worth
66
+ * following that link once: reading it from the wrong place is what made every
67
+ * send refuse in v0.4.0.
68
+ */
69
+ export declare function clientCanConfirm(ctx: unknown): boolean;
70
+ /** What the message would look like to the person being asked. */
71
+ export declare function describeForConfirmation(draft: Draft, what: string): string;
72
+ /** The question, returned from the handler rather than pushed to the client. */
73
+ export declare function confirmationRequest(draft: Draft, what: string, requestState: string): InputRequiredResult;
74
+ /** What the client sent back, if anything. */
75
+ export declare function confirmationAnswer(ctx: unknown): 'missing' | 'yes' | 'no';
76
+ /** The sealed state from a previous round, already verified by the SDK. */
77
+ export declare function pendingSend(ctx: unknown): PendingSend | undefined;
78
+ /** Refusals, in one place so they read the same wherever they come from. */
79
+ export declare const refuse: {
80
+ noElicitation: () => CallToolResult;
81
+ declined: () => CallToolResult;
82
+ changed: () => CallToolResult;
83
+ wrongTool: () => CallToolResult;
84
+ };
@@ -0,0 +1,212 @@
1
+ /**
2
+ * The confirmation that stands between a model and the outside world.
3
+ *
4
+ * The project's central rule is that a boundary living in a system prompt is
5
+ * not a boundary. Mail is text written by strangers, handed to a model that can
6
+ * call tools, so a prepared message can ask for itself to be forwarded. The
7
+ * answer is a round trip through the protocol that the server, not the prompt,
8
+ * insists on.
9
+ *
10
+ * Three properties make it worth more than a formality:
11
+ *
12
+ * 1. **It fails closed.** A client that cannot show a form does not get to
13
+ * send. There is no fallback that goes ahead without a yes, and no setting
14
+ * that turns the question off. That is a deliberate difference from the
15
+ * sign-in prompt, where a client that cannot elicit gets a text answer: the
16
+ * worst outcome there is an inconvenience, and here it is a message that
17
+ * cannot be recalled.
18
+ * 2. **The yes is bound to one specific message.** A digest of the recipients,
19
+ * the subject and the body is sealed into `requestState`, which travels
20
+ * through the client and comes back. On the second round the digest is
21
+ * recomputed from what the client now asks for and compared. If it differs,
22
+ * nothing is sent: what was confirmed is not what was requested. The seal is
23
+ * an HMAC, so the client cannot mint or alter one, and it is bound to the
24
+ * tool so a confirmation for one cannot be replayed at another.
25
+ * 3. **The question shows what actually goes out.** Recipients separated into
26
+ * To, Cc and Bcc, the subject, and the first lines of the body, all derived
27
+ * from the same object the digest covers and the sender builds from.
28
+ *
29
+ * ## Why this is a returned value rather than a call
30
+ *
31
+ * The same reason as the sign-in prompt, and it is written out in
32
+ * sign-in-required.ts: protocol revision 2026-07-28 has no server-to-client
33
+ * request channel, so `elicitInput` throws on every request this server will
34
+ * serve. The handler returns `inputRequired(...)` instead and the client calls
35
+ * the tool again.
36
+ */
37
+ import { createHash } from 'node:crypto';
38
+ import { inputRequired, acceptedContent } from '@modelcontextprotocol/server';
39
+ import { describeAttachments, describeRecipients, describeUrls, fingerprintPart, firstLines, hiddenTextOf, composedBody, showRecipient, summariseQuote, } from '../mail/compose.js';
40
+ import { canElicitForm } from './capabilities.js';
41
+ /** The key our embedded elicitation is filed under. */
42
+ export const CONFIRM_KEY = 'protonMcpSendConfirmation';
43
+ /**
44
+ * A fingerprint of everything that would leave the machine.
45
+ *
46
+ * Every field a recipient would see, and nothing else: two calls that differ
47
+ * only in which mailbox a draft was looked up in describe the same message and
48
+ * must not be told apart, while a changed address must be.
49
+ */
50
+ export function digestOf(draft) {
51
+ const canonical = JSON.stringify({
52
+ from: draft.from.address,
53
+ to: draft.to.map((r) => r.address).sort(),
54
+ cc: draft.cc.map((r) => r.address).sort(),
55
+ bcc: draft.bcc.map((r) => r.address).sort(),
56
+ subject: draft.subject,
57
+ text: draft.text,
58
+ // The markup source, not the readable rendering of it. Two messages can
59
+ // read identically and differ in every link target, which is the whole
60
+ // reason formatted mail needs a confirmation at all.
61
+ html: draft.html ?? null,
62
+ // The quote is part of what goes out, so it is part of what was agreed to.
63
+ quoted: draft.quotedHtml ?? null,
64
+ inReplyTo: draft.inReplyTo ?? null,
65
+ attached: draft.attachedMessage?.filename ?? null,
66
+ // The contents of every carried file, not just its name. A confirmation
67
+ // that covered the name alone would let the picture be swapped after it
68
+ // was agreed to.
69
+ parts: (draft.inlineParts ?? [])
70
+ .map((part) => `${part.contentId}:${part.contentType}:${fingerprintPart(part.content)}`)
71
+ .sort(),
72
+ });
73
+ return createHash('sha256').update(canonical).digest('hex');
74
+ }
75
+ /**
76
+ * Whether the client said it can show a form.
77
+ *
78
+ * Asked rather than attempted, because an embedded request the client never
79
+ * declared is refused by the SDK after the handler has already returned. The
80
+ * caller would then get that refusal instead of a question, and the tool would
81
+ * have to decide what to do about a confirmation that never appeared. Better to
82
+ * know beforehand and refuse plainly.
83
+ *
84
+ * Where the declaration is read from is capabilities.ts, and it is worth
85
+ * following that link once: reading it from the wrong place is what made every
86
+ * send refuse in v0.4.0.
87
+ */
88
+ export function clientCanConfirm(ctx) {
89
+ return canElicitForm(ctx);
90
+ }
91
+ /** What the message would look like to the person being asked. */
92
+ export function describeForConfirmation(draft, what) {
93
+ const lines = [
94
+ `${what} would be sent from ${showRecipient(draft.from)}.`,
95
+ '',
96
+ describeRecipients(draft),
97
+ `Subject: ${draft.subject || '(no subject)'}`,
98
+ '',
99
+ 'The message begins:',
100
+ firstLines(composedBody(draft)),
101
+ ];
102
+ // A quoted message is named rather than listed. Measured on a real mailbox:
103
+ // four images per message on average and up to thirty links. Listing those
104
+ // would bury the two addresses the sender is answering for under thirty that
105
+ // arrived in their mailbox anyway, and a confirmation nobody reads to the end
106
+ // protects nobody.
107
+ const quoted = summariseQuote(draft);
108
+ if (quoted) {
109
+ lines.push('', `Below that, the message being answered is quoted as it was written, with ` +
110
+ `${quoted.links} link(s) and ${quoted.images} image(s) of its own. Those are not listed ` +
111
+ 'here: they arrived in your mailbox already, and the addresses shown below are the ones ' +
112
+ 'this message adds.');
113
+ }
114
+ // Everything below is shown in full and never shortened. The excerpt above
115
+ // stops after a few lines, and an address on line thirty is exactly where one
116
+ // would be put in order not to be read.
117
+ const urls = describeUrls(draft);
118
+ if (urls.length) {
119
+ lines.push('', `Every address in this message (${urls.length}):`);
120
+ for (const url of urls) {
121
+ const shown = url.kind === 'link' ? `shown as "${url.label || '(no text)'}"` : `image${url.label ? `, described as "${url.label}"` : ''}`;
122
+ lines.push(` ${url.url}`, ` ${shown}`);
123
+ }
124
+ lines.push(' A link\'s text and its address are two different things. Read the addresses.');
125
+ }
126
+ // Measured: an image's alt text is what a recipient reads, because clients
127
+ // block remote images by default, and it never appears in the body above.
128
+ const hidden = hiddenTextOf(draft);
129
+ if (hidden.length) {
130
+ lines.push('', 'Text a recipient can read that is not in the body above:');
131
+ for (const entry of hidden)
132
+ lines.push(` ${entry}`);
133
+ }
134
+ if (quoted?.hiddenText.length) {
135
+ lines.push('', `The quoted message also carries ${quoted.hiddenText.length} alt text(s) or titles.`);
136
+ }
137
+ const files = describeAttachments(draft);
138
+ if (files.length) {
139
+ lines.push('', `Carried with it (${files.length}):`);
140
+ for (const file of files)
141
+ lines.push(` ${file}`);
142
+ }
143
+ lines.push('', 'Sending cannot be undone. Confirm only if these recipients are the ones you meant.');
144
+ return lines.join('\n');
145
+ }
146
+ /** The question, returned from the handler rather than pushed to the client. */
147
+ export function confirmationRequest(draft, what, requestState) {
148
+ return inputRequired({
149
+ requestState,
150
+ inputRequests: {
151
+ [CONFIRM_KEY]: inputRequired.elicit({
152
+ message: describeForConfirmation(draft, what),
153
+ requestedSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ confirm: {
157
+ type: 'boolean',
158
+ title: 'Send this message',
159
+ description: 'Tick only if the recipients above are the ones you meant.',
160
+ },
161
+ },
162
+ required: ['confirm'],
163
+ },
164
+ }),
165
+ },
166
+ });
167
+ }
168
+ /** What the client sent back, if anything. */
169
+ export function confirmationAnswer(ctx) {
170
+ const responses = ctx?.mcpReq?.inputResponses;
171
+ if (!responses)
172
+ return 'missing';
173
+ const content = acceptedContent(responses, CONFIRM_KEY);
174
+ // Absent means declined, cancelled, or a response of another kind. Anything
175
+ // that is not an explicit true is a no, including a missing field: this is
176
+ // the one place where being generous would mean sending mail nobody asked for.
177
+ if (content === undefined)
178
+ return 'no';
179
+ return content.confirm === true ? 'yes' : 'no';
180
+ }
181
+ /** The sealed state from a previous round, already verified by the SDK. */
182
+ export function pendingSend(ctx) {
183
+ const accessor = ctx?.mcpReq?.requestState;
184
+ if (typeof accessor !== 'function')
185
+ return undefined;
186
+ const state = accessor();
187
+ if (!state || typeof state !== 'object')
188
+ return undefined;
189
+ return typeof state.tool === 'string' && typeof state.digest === 'string' ? state : undefined;
190
+ }
191
+ const failure = (text) => ({
192
+ content: [{ type: 'text', text }],
193
+ isError: true,
194
+ });
195
+ /** Refusals, in one place so they read the same wherever they come from. */
196
+ export const refuse = {
197
+ noElicitation: () => failure('This client cannot show a confirmation, so nothing was sent.\n\n' +
198
+ 'Every message this server sends has to be confirmed by a person through the protocol\'s ' +
199
+ 'elicitation mechanism, and a client that does not support it cannot be asked. There is no ' +
200
+ 'setting that turns the confirmation off: mail cannot be recalled, and a model reading a ' +
201
+ 'message written by a stranger is exactly the situation this guards against.\n\n' +
202
+ 'Write the message as a draft instead, with create_draft or reply_draft, and send it from ' +
203
+ 'Proton itself.'),
204
+ declined: () => failure('The user did not confirm, so nothing was sent. The message was not changed.'),
205
+ changed: () => failure('Nothing was sent: the message is not the one that was confirmed.\n\n' +
206
+ 'The confirmation is bound to the exact recipients, subject and text that were shown. ' +
207
+ 'Something differs now, so the yes does not apply. Ask again with the message as it should ' +
208
+ 'go out, and the user will see what they are agreeing to.'),
209
+ wrongTool: () => failure('Nothing was sent: the confirmation belongs to a different operation and cannot be reused ' +
210
+ 'for this one.'),
211
+ };
212
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/tools/confirm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Tools: create_draft, update_draft, reply_draft, forward_draft, list_drafts.
3
+ *
4
+ * A draft is where a prepared message belongs. Nothing here sends, and nothing
5
+ * here can: none of these functions knows the SMTP port exists. Sending is a
6
+ * separate tool behind a confirmation the server enforces.
7
+ *
8
+ * That separation is the feature, not an implementation detail. A model that
9
+ * has read a message written by a stranger can be talked into preparing a
10
+ * reply; it must not be able to be talked into delivering one.
11
+ */
12
+ import type { McpServer } from '@modelcontextprotocol/server';
13
+ import type { Connection } from '../bridge/connection.js';
14
+ /**
15
+ * Where the message comes from.
16
+ *
17
+ * A function, because nobody may be signed in yet when the tools are registered
18
+ * and the address can change while the server runs.
19
+ */
20
+ export type FromAddress = () => string | undefined;
21
+ export declare function registerDraftTools(server: McpServer, connection: Connection, readOnly: () => boolean, from: FromAddress): void;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Tools: create_draft, update_draft, reply_draft, forward_draft, list_drafts.
3
+ *
4
+ * A draft is where a prepared message belongs. Nothing here sends, and nothing
5
+ * here can: none of these functions knows the SMTP port exists. Sending is a
6
+ * separate tool behind a confirmation the server enforces.
7
+ *
8
+ * That separation is the feature, not an implementation detail. A model that
9
+ * has read a message written by a stranger can be talked into preparing a
10
+ * reply; it must not be able to be talked into delivering one.
11
+ */
12
+ import * as z from 'zod';
13
+ import { createDraft, updateDraft, replyDraft, forwardDraft, listDrafts, DRAFTS, } from '../mail/drafts.js';
14
+ import { describeRecipients, MARKUP_NOTE, PLAIN_TEXT_NOTE, UTF8_NOTE } from '../mail/compose.js';
15
+ import { BridgeError } from '../bridge/errors.js';
16
+ import { formatList } from './format.js';
17
+ import { describeFailure, withSignIn } from './failures.js';
18
+ import { track } from '../in-flight.js';
19
+ const ok = (text) => ({ content: [{ type: 'text', text }] });
20
+ /** Written into every answer that created or changed a draft. */
21
+ function describeDraft(draft, what) {
22
+ const lines = [
23
+ `${what} in "${DRAFTS}".`,
24
+ '',
25
+ `Subject: ${draft.subject || '(no subject)'}`,
26
+ describeRecipients(draft),
27
+ `Id: ${draft.messageId}`,
28
+ ];
29
+ if (draft.carriedAttachments?.length) {
30
+ lines.push('', `The original carried ${draft.carriedAttachments.length} attachment(s): ` +
31
+ `${draft.carriedAttachments.join(', ')}. The whole original message is attached to the ` +
32
+ 'draft, so nothing of it is lost.');
33
+ }
34
+ lines.push('', 'Nothing has been sent. Use send_draft when the message is ready; that asks the user to ' +
35
+ 'confirm before anything leaves the machine.');
36
+ return lines.join('\n');
37
+ }
38
+ const addressList = (what) => z
39
+ .array(z.string())
40
+ .optional()
41
+ .describe(`${what} Each entry is an address, optionally as "Display Name <name@example.com>".`);
42
+ const messageArgument = z
43
+ .string()
44
+ .describe('The message id from a listing or a search, with or without angle brackets.');
45
+ function requireFrom(from) {
46
+ const address = from();
47
+ if (!address) {
48
+ throw new BridgeError('Nobody is signed in, so there is no address to write from. Sign in through the ' +
49
+ 'configuration page first.');
50
+ }
51
+ return address;
52
+ }
53
+ export function registerDraftTools(server, connection, readOnly, from) {
54
+ server.registerTool('create_draft', {
55
+ title: 'Create a draft',
56
+ description: 'Writes a new draft into the Drafts mailbox. Nothing is sent. The draft is plain text: ' +
57
+ 'Proton drops the plain text part of a message that also carries HTML, so composing HTML ' +
58
+ 'would mean the recipient sees something other than what was written here. ' +
59
+ 'Attachments from files on this machine are not supported on purpose. ' +
60
+ PLAIN_TEXT_NOTE + ' ' + MARKUP_NOTE + ' ' + UTF8_NOTE,
61
+ inputSchema: z.object({
62
+ to: addressList('The main recipients.'),
63
+ cc: addressList('Recipients in copy, visible to everyone.'),
64
+ bcc: addressList('Recipients in blind copy, hidden from the others but they do receive it.'),
65
+ subject: z.string().optional().describe('The subject line.'),
66
+ text: z.string().optional().describe('The body, as plain text.'),
67
+ html: z
68
+ .string()
69
+ .optional()
70
+ .describe('The body as markup instead of plain text. Give this or text, not both. Permitted ' +
71
+ 'are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, ' +
72
+ 'links and images, with colour, font, alignment, spacing and borders. Images may ' +
73
+ 'point at a full address; anything outside the permitted set is refused with a ' +
74
+ 'reason rather than removed.'),
75
+ }),
76
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
77
+ }, async (input, ctx) => track(() => withSignIn(ctx, async () => {
78
+ try {
79
+ const draft = await createDraft(connection, readOnly(), requireFrom(from), input);
80
+ return ok(describeDraft(draft, 'Draft written'));
81
+ }
82
+ catch (error) {
83
+ return describeFailure(error, 'creating the draft');
84
+ }
85
+ }), 'create_draft'));
86
+ server.registerTool('update_draft', {
87
+ title: 'Change a draft',
88
+ description: 'Replaces a draft with a changed version, keeping the same id. Fields that are not given ' +
89
+ 'are carried over, including the blind copies, which drafts written here keep. Only ' +
90
+ 'messages in the Drafts mailbox that carry the draft flag can be changed this way. ' +
91
+ PLAIN_TEXT_NOTE + ' ' + MARKUP_NOTE + ' ' + UTF8_NOTE,
92
+ inputSchema: z.object({
93
+ messageId: messageArgument,
94
+ to: addressList('Replaces the main recipients.'),
95
+ cc: addressList('Replaces the copies.'),
96
+ bcc: addressList('Replaces the blind copies.'),
97
+ subject: z.string().optional().describe('Replaces the subject.'),
98
+ text: z.string().optional().describe('Replaces the body.'),
99
+ html: z
100
+ .string()
101
+ .optional()
102
+ .describe('Replaces the body with markup. Give this or text, not both. Permitted ' +
103
+ 'are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, ' +
104
+ 'links and images, with colour, font, alignment, spacing and borders. Images may ' +
105
+ 'point at a full address; anything outside the permitted set is refused with a ' +
106
+ 'reason rather than removed.'),
107
+ }),
108
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
109
+ }, async ({ messageId, ...changes }, ctx) => track(() => withSignIn(ctx, async () => {
110
+ try {
111
+ const draft = await updateDraft(connection, readOnly(), requireFrom(from), messageId, changes);
112
+ return ok(describeDraft(draft, 'Draft replaced'));
113
+ }
114
+ catch (error) {
115
+ return describeFailure(error, 'changing the draft');
116
+ }
117
+ }), 'update_draft'));
118
+ server.registerTool('reply_draft', {
119
+ title: 'Prepare a reply as a draft',
120
+ description: 'Writes a reply to a message as a draft. The recipient is the message\'s Reply-To if it ' +
121
+ 'set one, otherwise its sender. The original is quoted below the new text. Nothing is sent. ' +
122
+ 'Note on threading: the reply is built with In-Reply-To and References, but Proton replaces ' +
123
+ 'those with its own internal thread id when it stores a draft, so a draft that is stored ' +
124
+ 'and sent later carries Proton\'s idea of the conversation rather than ours. ' +
125
+ PLAIN_TEXT_NOTE + ' ' + MARKUP_NOTE + ' ' + UTF8_NOTE,
126
+ inputSchema: z.object({
127
+ messageId: messageArgument,
128
+ text: z.string().describe('The reply itself. The original is quoted below it.'),
129
+ replyAll: z
130
+ .boolean()
131
+ .default(false)
132
+ .describe('When true, the other recipients of the original are put in copy.'),
133
+ html: z
134
+ .string()
135
+ .optional()
136
+ .describe('Your part of the message as markup instead of plain text. The original is quoted ' +
137
+ 'below it either way. The quote is always built from the original\'s text, never ' +
138
+ 'from its own markup, so a message full of markup this server would not send can ' +
139
+ 'still be replied to or forwarded.'),
140
+ mailbox: z.string().optional().describe('The mailbox the original is in, if known.'),
141
+ }),
142
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
143
+ }, async ({ messageId, text, replyAll, mailbox, html }, ctx) => track(() => withSignIn(ctx, async () => {
144
+ try {
145
+ const draft = await replyDraft(connection, readOnly(), requireFrom(from), messageId, text, { all: replyAll, ...(mailbox ? { mailbox } : {}), ...(html !== undefined ? { html } : {}) });
146
+ return ok(describeDraft(draft, 'Reply written'));
147
+ }
148
+ catch (error) {
149
+ return describeFailure(error, 'preparing the reply');
150
+ }
151
+ }), 'reply_draft'));
152
+ server.registerTool('forward_draft', {
153
+ title: 'Prepare a forward as a draft',
154
+ description: 'Writes a forward of a message as a draft. The original is quoted below the new text, and ' +
155
+ 'when it carries attachments the whole original message is attached as well, so nothing of ' +
156
+ 'it is lost. Nothing is sent. ' +
157
+ PLAIN_TEXT_NOTE + ' ' + MARKUP_NOTE + ' ' + UTF8_NOTE,
158
+ inputSchema: z.object({
159
+ messageId: messageArgument,
160
+ to: z.array(z.string()).min(1).describe('Who to forward it to.'),
161
+ text: z.string().default('').describe('Anything to say above the forwarded message.'),
162
+ html: z
163
+ .string()
164
+ .optional()
165
+ .describe('Your part of the message as markup instead of plain text. The original is quoted ' +
166
+ 'below it either way. The quote is always built from the original\'s text, never ' +
167
+ 'from its own markup, so a message full of markup this server would not send can ' +
168
+ 'still be replied to or forwarded.'),
169
+ mailbox: z.string().optional().describe('The mailbox the original is in, if known.'),
170
+ }),
171
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
172
+ }, async ({ messageId, to, text, mailbox, html }, ctx) => track(() => withSignIn(ctx, async () => {
173
+ try {
174
+ const draft = await forwardDraft(connection, readOnly(), requireFrom(from), messageId, to, text, { ...(mailbox ? { mailbox } : {}), ...(html !== undefined ? { html } : {}) });
175
+ return ok(describeDraft(draft, 'Forward written'));
176
+ }
177
+ catch (error) {
178
+ return describeFailure(error, 'preparing the forward');
179
+ }
180
+ }), 'forward_draft'));
181
+ server.registerTool('list_drafts', {
182
+ title: 'List drafts',
183
+ description: 'Lists the drafts, newest first. Headers only, like every listing here. Use get_message ' +
184
+ 'with an id to read one.',
185
+ inputSchema: z.object({
186
+ limit: z.number().int().min(1).max(100).default(25).describe('How many to return.'),
187
+ offset: z.number().int().min(0).default(0).describe('How many to skip, for paging.'),
188
+ }),
189
+ annotations: { readOnlyHint: true, openWorldHint: false },
190
+ }, async ({ limit, offset }, ctx) => track(() => withSignIn(ctx, async () => {
191
+ try {
192
+ return ok(formatList(await listDrafts(connection, { limit, offset })));
193
+ }
194
+ catch (error) {
195
+ return describeFailure(error, 'listing the drafts');
196
+ }
197
+ }), 'list_drafts'));
198
+ }
199
+ //# sourceMappingURL=drafts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/tools/drafts.ts"],"names":[],"mappings":"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"}