@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.
- package/LICENSE +20 -0
- package/README.md +405 -0
- package/dist/activity.d.ts +49 -0
- package/dist/activity.js +72 -0
- package/dist/activity.js.map +1 -0
- package/dist/bridge/certificate.d.ts +75 -0
- package/dist/bridge/certificate.js +176 -0
- package/dist/bridge/certificate.js.map +1 -0
- package/dist/bridge/connection.d.ts +99 -0
- package/dist/bridge/connection.js +244 -0
- package/dist/bridge/connection.js.map +1 -0
- package/dist/bridge/errors.d.ts +35 -0
- package/dist/bridge/errors.js +56 -0
- package/dist/bridge/errors.js.map +1 -0
- package/dist/config.d.ts +92 -0
- package/dist/config.js +142 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials/availability.d.ts +62 -0
- package/dist/credentials/availability.js +107 -0
- package/dist/credentials/availability.js.map +1 -0
- package/dist/credentials/encrypted-file.d.ts +50 -0
- package/dist/credentials/encrypted-file.js +156 -0
- package/dist/credentials/encrypted-file.js.map +1 -0
- package/dist/credentials/keyring.d.ts +48 -0
- package/dist/credentials/keyring.js +97 -0
- package/dist/credentials/keyring.js.map +1 -0
- package/dist/credentials/plain-file.d.ts +24 -0
- package/dist/credentials/plain-file.js +78 -0
- package/dist/credentials/plain-file.js.map +1 -0
- package/dist/credentials/probe-keyring.d.ts +11 -0
- package/dist/credentials/probe-keyring.js +57 -0
- package/dist/credentials/probe-keyring.js.map +1 -0
- package/dist/credentials/session.d.ts +15 -0
- package/dist/credentials/session.js +21 -0
- package/dist/credentials/session.js.map +1 -0
- package/dist/credentials/store.d.ts +80 -0
- package/dist/credentials/store.js +94 -0
- package/dist/credentials/store.js.map +1 -0
- package/dist/in-flight.d.ts +33 -0
- package/dist/in-flight.js +78 -0
- package/dist/in-flight.js.map +1 -0
- package/dist/mail/actions.d.ts +116 -0
- package/dist/mail/actions.js +265 -0
- package/dist/mail/actions.js.map +1 -0
- package/dist/mail/attachments.d.ts +39 -0
- package/dist/mail/attachments.js +86 -0
- package/dist/mail/attachments.js.map +1 -0
- package/dist/mail/compose.d.ts +264 -0
- package/dist/mail/compose.js +395 -0
- package/dist/mail/compose.js.map +1 -0
- package/dist/mail/drafts.d.ts +138 -0
- package/dist/mail/drafts.js +449 -0
- package/dist/mail/drafts.js.map +1 -0
- package/dist/mail/ids.d.ts +78 -0
- package/dist/mail/ids.js +135 -0
- package/dist/mail/ids.js.map +1 -0
- package/dist/mail/labels.d.ts +61 -0
- package/dist/mail/labels.js +139 -0
- package/dist/mail/labels.js.map +1 -0
- package/dist/mail/markup.d.ts +91 -0
- package/dist/mail/markup.js +265 -0
- package/dist/mail/markup.js.map +1 -0
- package/dist/mail/messages.d.ts +147 -0
- package/dist/mail/messages.js +219 -0
- package/dist/mail/messages.js.map +1 -0
- package/dist/mail/quote.d.ts +65 -0
- package/dist/mail/quote.js +157 -0
- package/dist/mail/quote.js.map +1 -0
- package/dist/mail/search.d.ts +66 -0
- package/dist/mail/search.js +107 -0
- package/dist/mail/search.js.map +1 -0
- package/dist/mail/send.d.ts +61 -0
- package/dist/mail/send.js +132 -0
- package/dist/mail/send.js.map +1 -0
- package/dist/mime/parse.d.ts +110 -0
- package/dist/mime/parse.js +194 -0
- package/dist/mime/parse.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +387 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +86 -0
- package/dist/session.js +220 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +52 -0
- package/dist/settings.js +80 -0
- package/dist/settings.js.map +1 -0
- package/dist/tools/actions.d.ts +21 -0
- package/dist/tools/actions.js +133 -0
- package/dist/tools/actions.js.map +1 -0
- package/dist/tools/capabilities.d.ts +52 -0
- package/dist/tools/capabilities.js +96 -0
- package/dist/tools/capabilities.js.map +1 -0
- package/dist/tools/confirm.d.ts +84 -0
- package/dist/tools/confirm.js +212 -0
- package/dist/tools/confirm.js.map +1 -0
- package/dist/tools/drafts.d.ts +21 -0
- package/dist/tools/drafts.js +199 -0
- package/dist/tools/drafts.js.map +1 -0
- package/dist/tools/failures.d.ts +46 -0
- package/dist/tools/failures.js +157 -0
- package/dist/tools/failures.js.map +1 -0
- package/dist/tools/format.d.ts +55 -0
- package/dist/tools/format.js +220 -0
- package/dist/tools/format.js.map +1 -0
- package/dist/tools/guidance.d.ts +38 -0
- package/dist/tools/guidance.js +226 -0
- package/dist/tools/guidance.js.map +1 -0
- package/dist/tools/interface.d.ts +28 -0
- package/dist/tools/interface.js +63 -0
- package/dist/tools/interface.js.map +1 -0
- package/dist/tools/labels.d.ts +21 -0
- package/dist/tools/labels.js +92 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/list-folders.d.ts +14 -0
- package/dist/tools/list-folders.js +70 -0
- package/dist/tools/list-folders.js.map +1 -0
- package/dist/tools/messages.d.ts +9 -0
- package/dist/tools/messages.js +221 -0
- package/dist/tools/messages.js.map +1 -0
- package/dist/tools/send.d.ts +29 -0
- package/dist/tools/send.js +236 -0
- package/dist/tools/send.js.map +1 -0
- package/dist/tools/sign-in-required.d.ts +94 -0
- package/dist/tools/sign-in-required.js +168 -0
- package/dist/tools/sign-in-required.js.map +1 -0
- package/dist/web/layout.d.ts +75 -0
- package/dist/web/layout.js +590 -0
- package/dist/web/layout.js.map +1 -0
- package/dist/web/pages.d.ts +79 -0
- package/dist/web/pages.js +284 -0
- package/dist/web/pages.js.map +1 -0
- package/dist/web/sections.d.ts +105 -0
- package/dist/web/sections.js +207 -0
- package/dist/web/sections.js.map +1 -0
- package/dist/web/security.d.ts +122 -0
- package/dist/web/security.js +180 -0
- package/dist/web/security.js.map +1 -0
- package/dist/web/server.d.ts +153 -0
- package/dist/web/server.js +622 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,221 @@
|
|
|
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 * as z from 'zod';
|
|
8
|
+
import { describeFailure, withSignIn } from './failures.js';
|
|
9
|
+
import { track } from '../in-flight.js';
|
|
10
|
+
import { listMessages, getMessage, MAX_LIST_LIMIT } from '../mail/messages.js';
|
|
11
|
+
import { getAttachment } from '../mail/attachments.js';
|
|
12
|
+
import { searchMessages } from '../mail/search.js';
|
|
13
|
+
import { ALL_MAIL } from '../mail/ids.js';
|
|
14
|
+
import { formatList, formatMessage, formatSearch, formatSize, wrapUntrusted } from './format.js';
|
|
15
|
+
const asError = describeFailure;
|
|
16
|
+
const ok = (text) => ({ content: [{ type: 'text', text }] });
|
|
17
|
+
export function registerMessageTools(server, connection) {
|
|
18
|
+
server.registerTool('list_messages', {
|
|
19
|
+
title: 'List messages',
|
|
20
|
+
description: 'Lists the messages of a mailbox, newest first by the date the sender wrote, which also holds ' +
|
|
21
|
+
'across pages. Returns headers only: sender, subject, date, size and the message id. It never ' +
|
|
22
|
+
'returns message bodies, because a single message averages around 16000 tokens raw. Use ' +
|
|
23
|
+
'get_message with an id to read one. Ordering reads one date per message in the mailbox, so a ' +
|
|
24
|
+
'very large mailbox takes a moment; the answer says so when it does.',
|
|
25
|
+
inputSchema: z.object({
|
|
26
|
+
mailbox: z
|
|
27
|
+
.string()
|
|
28
|
+
.default('INBOX')
|
|
29
|
+
.describe('The mailbox path, for example "INBOX", "Archive" or "Folders/Work". Use list_folders to see them.'),
|
|
30
|
+
limit: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(MAX_LIST_LIMIT)
|
|
35
|
+
.default(25)
|
|
36
|
+
.describe(`How many messages to return, at most ${MAX_LIST_LIMIT}.`),
|
|
37
|
+
offset: z
|
|
38
|
+
.number()
|
|
39
|
+
.int()
|
|
40
|
+
.min(0)
|
|
41
|
+
.default(0)
|
|
42
|
+
.describe('How many of the newest messages to skip. Used for paging.'),
|
|
43
|
+
unreadOnly: z.boolean().default(false).describe('When true, only unread messages are listed.'),
|
|
44
|
+
}),
|
|
45
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
46
|
+
}, async ({ mailbox, limit, offset, unreadOnly }, ctx) => track(() => withSignIn(ctx, async () => {
|
|
47
|
+
try {
|
|
48
|
+
const result = await listMessages(connection, mailbox, { limit, offset, unreadOnly });
|
|
49
|
+
return ok(formatList(result));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return asError(error, `listing "${mailbox}"`);
|
|
53
|
+
}
|
|
54
|
+
}), 'list_messages'));
|
|
55
|
+
server.registerTool('search_messages', {
|
|
56
|
+
title: 'Search messages',
|
|
57
|
+
description: 'Searches a mailbox by text, subject, sender, recipient, date range, read state, star or size. ' +
|
|
58
|
+
'At least one criterion is required. Returns headers only, newest first across pages, with paging. ' +
|
|
59
|
+
'A full-text search walks the local database of the Bridge and takes a few seconds on a large ' +
|
|
60
|
+
'mailbox, while criteria such as unread or date are fast. Searching "All Mail" covers every ' +
|
|
61
|
+
'mailbox including trash.',
|
|
62
|
+
inputSchema: z.object({
|
|
63
|
+
mailbox: z
|
|
64
|
+
.string()
|
|
65
|
+
.default(ALL_MAIL)
|
|
66
|
+
.describe(`Which mailbox to search. Defaults to "${ALL_MAIL}", which holds every message.`),
|
|
67
|
+
text: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Free text, matched against body and headers. This is the slow criterion.'),
|
|
71
|
+
subject: z.string().optional().describe('Substring of the subject.'),
|
|
72
|
+
from: z.string().optional().describe('Substring of the sender address or display name.'),
|
|
73
|
+
to: z.string().optional().describe('Substring of a recipient address.'),
|
|
74
|
+
since: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Only messages on or after this date, as YYYY-MM-DD.'),
|
|
78
|
+
before: z.string().optional().describe('Only messages before this date, as YYYY-MM-DD.'),
|
|
79
|
+
unreadOnly: z.boolean().optional().describe('When true, only unread messages.'),
|
|
80
|
+
starredOnly: z.boolean().optional().describe('When true, only starred messages.'),
|
|
81
|
+
largerThan: z
|
|
82
|
+
.number()
|
|
83
|
+
.int()
|
|
84
|
+
.min(1)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Minimum size in bytes. Useful for finding messages with large attachments.'),
|
|
87
|
+
limit: z.number().int().min(1).max(MAX_LIST_LIMIT).default(25).describe('How many results to return.'),
|
|
88
|
+
offset: z.number().int().min(0).default(0).describe('How many results to skip, for paging.'),
|
|
89
|
+
}),
|
|
90
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
91
|
+
}, async (args, ctx) => track(() => withSignIn(ctx, async () => {
|
|
92
|
+
try {
|
|
93
|
+
const criteria = { limit: args.limit, offset: args.offset };
|
|
94
|
+
if (args.text)
|
|
95
|
+
criteria.text = args.text;
|
|
96
|
+
if (args.subject)
|
|
97
|
+
criteria.subject = args.subject;
|
|
98
|
+
if (args.from)
|
|
99
|
+
criteria.from = args.from;
|
|
100
|
+
if (args.to)
|
|
101
|
+
criteria.to = args.to;
|
|
102
|
+
if (args.unreadOnly !== undefined)
|
|
103
|
+
criteria.seen = !args.unreadOnly;
|
|
104
|
+
if (args.starredOnly !== undefined)
|
|
105
|
+
criteria.flagged = args.starredOnly;
|
|
106
|
+
if (args.largerThan !== undefined)
|
|
107
|
+
criteria.largerThan = args.largerThan;
|
|
108
|
+
for (const [name, value] of [
|
|
109
|
+
['since', args.since],
|
|
110
|
+
['before', args.before],
|
|
111
|
+
]) {
|
|
112
|
+
if (!value)
|
|
113
|
+
continue;
|
|
114
|
+
const parsed = new Date(value);
|
|
115
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `"${value}" is not a usable date for ${name}. Expected YYYY-MM-DD.`,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
criteria[name] = parsed;
|
|
127
|
+
}
|
|
128
|
+
const result = await searchMessages(connection, args.mailbox, criteria);
|
|
129
|
+
return ok(formatSearch(result));
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return asError(error, `searching "${args.mailbox}"`);
|
|
133
|
+
}
|
|
134
|
+
}), 'search_messages'));
|
|
135
|
+
server.registerTool('get_message', {
|
|
136
|
+
title: 'Read a message',
|
|
137
|
+
description: 'Reads one message by its id and returns it as readable text. HTML is converted to text, ' +
|
|
138
|
+
'images and styling are dropped and no external content is fetched. Long bodies are shortened ' +
|
|
139
|
+
'with a visible note. The body is marked as untrusted third-party content.',
|
|
140
|
+
inputSchema: z.object({
|
|
141
|
+
messageId: z
|
|
142
|
+
.string()
|
|
143
|
+
.describe('The message id from list_messages or search, passed on unchanged.'),
|
|
144
|
+
mailbox: z
|
|
145
|
+
.string()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe('Optional: the mailbox to look in first. Saves a lookup. Without it the search goes through "All Mail".'),
|
|
148
|
+
maxChars: z
|
|
149
|
+
.number()
|
|
150
|
+
.int()
|
|
151
|
+
.min(500)
|
|
152
|
+
.max(200_000)
|
|
153
|
+
.optional()
|
|
154
|
+
.describe('Optional character budget for the body. Higher values cost context.'),
|
|
155
|
+
textOffset: z
|
|
156
|
+
.number()
|
|
157
|
+
.int()
|
|
158
|
+
.min(0)
|
|
159
|
+
.optional()
|
|
160
|
+
.describe('Where to continue reading a long message. The answer names the next offset when ' +
|
|
161
|
+
'something was left out, so a long message can be read in parts instead of raising ' +
|
|
162
|
+
'the budget until it fits.'),
|
|
163
|
+
}),
|
|
164
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
165
|
+
}, async ({ messageId, mailbox, maxChars, textOffset }, ctx) => track(() => withSignIn(ctx, async () => {
|
|
166
|
+
try {
|
|
167
|
+
const options = {};
|
|
168
|
+
if (mailbox)
|
|
169
|
+
options.hint = mailbox;
|
|
170
|
+
if (maxChars)
|
|
171
|
+
options.maxTextChars = maxChars;
|
|
172
|
+
if (textOffset)
|
|
173
|
+
options.textOffset = textOffset;
|
|
174
|
+
const message = await getMessage(connection, messageId, options);
|
|
175
|
+
return ok(formatMessage(message));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return asError(error, 'reading the message');
|
|
179
|
+
}
|
|
180
|
+
}), 'get_message'));
|
|
181
|
+
server.registerTool('get_attachment', {
|
|
182
|
+
title: 'Read an attachment',
|
|
183
|
+
description: 'Reads one attachment of a message by index. The index comes from the attachment list in ' +
|
|
184
|
+
'get_message. Textual attachments are returned as text, binary ones are refused with a reason ' +
|
|
185
|
+
'rather than dumped into the context.',
|
|
186
|
+
inputSchema: z.object({
|
|
187
|
+
messageId: z.string().describe('The message id the attachment belongs to.'),
|
|
188
|
+
index: z
|
|
189
|
+
.number()
|
|
190
|
+
.int()
|
|
191
|
+
.min(0)
|
|
192
|
+
.describe('Index of the attachment, as shown by get_message. Starts at 0.'),
|
|
193
|
+
mailbox: z.string().optional().describe('Optional: the mailbox to look in first.'),
|
|
194
|
+
offset: z
|
|
195
|
+
.number()
|
|
196
|
+
.int()
|
|
197
|
+
.min(0)
|
|
198
|
+
.optional()
|
|
199
|
+
.describe('Where to continue reading a long attachment. The answer names the next offset.'),
|
|
200
|
+
}),
|
|
201
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
202
|
+
}, async ({ messageId, index, mailbox, offset }, ctx) => track(() => withSignIn(ctx, async () => {
|
|
203
|
+
try {
|
|
204
|
+
const options = {};
|
|
205
|
+
if (mailbox)
|
|
206
|
+
options.hint = mailbox;
|
|
207
|
+
if (offset)
|
|
208
|
+
options.offset = offset;
|
|
209
|
+
const a = await getAttachment(connection, messageId, index, options);
|
|
210
|
+
const head = `Attachment "${a.filename}" (${a.contentType}, ${formatSize(a.size)})`;
|
|
211
|
+
if (a.unsupportedReason) {
|
|
212
|
+
return ok(`${head}\n\n${a.unsupportedReason}`);
|
|
213
|
+
}
|
|
214
|
+
return ok(wrapUntrusted(a.text ?? '', head));
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return asError(error, 'reading the attachment');
|
|
218
|
+
}
|
|
219
|
+
}), 'get_attachment'));
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/tools/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools: send_message, send_reply, send_forward, send_draft.
|
|
3
|
+
*
|
|
4
|
+
* All four go through one function, `confirmed`, and that is the point. Four
|
|
5
|
+
* copies of a confirmation is four chances for one of them to drift, and the
|
|
6
|
+
* one that drifts is the one that sends without asking.
|
|
7
|
+
*
|
|
8
|
+
* Why replying and forwarding are sending tools of their own, rather than
|
|
9
|
+
* "make a draft, then send the draft": measured, Proton rewrites the thread
|
|
10
|
+
* headers of anything it stores. A reply written to a draft and sent later
|
|
11
|
+
* carries Proton's internal thread id and not the In-Reply-To and References
|
|
12
|
+
* that were built. Building the reply and handing it to SMTP without storing it
|
|
13
|
+
* in between is the only way the headers we wrote are the headers that go out.
|
|
14
|
+
*/
|
|
15
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
16
|
+
import type { RequestStateCodec } from '@modelcontextprotocol/server';
|
|
17
|
+
import type { Connection } from '../bridge/connection.js';
|
|
18
|
+
import type { Config, BridgeCredentials } from '../config.js';
|
|
19
|
+
import { type PendingSend } from './confirm.js';
|
|
20
|
+
export interface SendDependencies {
|
|
21
|
+
config: Config;
|
|
22
|
+
connection: Connection;
|
|
23
|
+
getCredentials: () => Promise<BridgeCredentials | undefined>;
|
|
24
|
+
readOnly: () => boolean;
|
|
25
|
+
from: () => string | undefined;
|
|
26
|
+
/** Seals and opens the state that carries a confirmation across the round trip. */
|
|
27
|
+
codec: RequestStateCodec<PendingSend>;
|
|
28
|
+
}
|
|
29
|
+
export declare function registerSendTools(server: McpServer, deps: SendDependencies): void;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools: send_message, send_reply, send_forward, send_draft.
|
|
3
|
+
*
|
|
4
|
+
* All four go through one function, `confirmed`, and that is the point. Four
|
|
5
|
+
* copies of a confirmation is four chances for one of them to drift, and the
|
|
6
|
+
* one that drifts is the one that sends without asking.
|
|
7
|
+
*
|
|
8
|
+
* Why replying and forwarding are sending tools of their own, rather than
|
|
9
|
+
* "make a draft, then send the draft": measured, Proton rewrites the thread
|
|
10
|
+
* headers of anything it stores. A reply written to a draft and sent later
|
|
11
|
+
* carries Proton's internal thread id and not the In-Reply-To and References
|
|
12
|
+
* that were built. Building the reply and handing it to SMTP without storing it
|
|
13
|
+
* in between is the only way the headers we wrote are the headers that go out.
|
|
14
|
+
*/
|
|
15
|
+
import * as z from 'zod';
|
|
16
|
+
import { BridgeError } from '../bridge/errors.js';
|
|
17
|
+
import { htmlToText } from '../mime/parse.js';
|
|
18
|
+
import { sendMessage } from '../mail/send.js';
|
|
19
|
+
import { buildReplyDraft, buildForwardDraft, readDraftForSending, DRAFTS, } from '../mail/drafts.js';
|
|
20
|
+
import { describeRecipients, mintMessageId, parseRecipient, parseRecipients, MARKUP_NOTE, PLAIN_TEXT_NOTE, UTF8_NOTE, } from '../mail/compose.js';
|
|
21
|
+
import { clientCanConfirm, confirmationAnswer, confirmationRequest, digestOf, pendingSend, refuse, } from './confirm.js';
|
|
22
|
+
import { describeFailure, withSignIn } from './failures.js';
|
|
23
|
+
import { track } from '../in-flight.js';
|
|
24
|
+
const ok = (text) => ({ content: [{ type: 'text', text }] });
|
|
25
|
+
/**
|
|
26
|
+
* Measured: SMTP accepted a message about six seconds before IMAP showed it,
|
|
27
|
+
* and an appended message took about twenty seconds to reach "All Mail". A tool
|
|
28
|
+
* that checked its own send would describe a state that is not the outcome.
|
|
29
|
+
*/
|
|
30
|
+
const AFTER_NOTE = 'The Bridge has taken the message; Proton delivers it from there. It takes a moment to appear ' +
|
|
31
|
+
'in Sent, so do not look for it straight away. An immediate listing would be misleading rather ' +
|
|
32
|
+
'than informative.';
|
|
33
|
+
/**
|
|
34
|
+
* What went out.
|
|
35
|
+
*
|
|
36
|
+
* Built from the draft rather than cut out of the confirmation text by line
|
|
37
|
+
* number. The first version did the latter and lost the body: it printed "The
|
|
38
|
+
* message begins:" followed by nothing, because a slice by index breaks the
|
|
39
|
+
* moment the text it slices gains a line.
|
|
40
|
+
*/
|
|
41
|
+
function describeOutcome(draft, outcome) {
|
|
42
|
+
const lines = [
|
|
43
|
+
`Sent. The Bridge accepted the message for ${outcome.accepted.length} recipient(s).`,
|
|
44
|
+
'',
|
|
45
|
+
describeRecipients(draft),
|
|
46
|
+
`Subject: ${draft.subject || '(no subject)'}`,
|
|
47
|
+
...(draft.attachedMessage ? [`Attached: ${draft.attachedMessage.filename}`] : []),
|
|
48
|
+
`Id: ${outcome.messageId}`,
|
|
49
|
+
];
|
|
50
|
+
if (outcome.rejected.length) {
|
|
51
|
+
lines.push('', `Refused for: ${outcome.rejected.join(', ')}. The message went to the others, so it was ` +
|
|
52
|
+
'delivered in part rather than not at all.');
|
|
53
|
+
}
|
|
54
|
+
lines.push('', AFTER_NOTE);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
function requireFrom(deps) {
|
|
58
|
+
const address = deps.from();
|
|
59
|
+
if (!address) {
|
|
60
|
+
throw new BridgeError('Nobody is signed in, so there is no address to send from. Sign in through the ' +
|
|
61
|
+
'configuration page first.');
|
|
62
|
+
}
|
|
63
|
+
return address;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The whole confirmation flow, once.
|
|
67
|
+
*
|
|
68
|
+
* `build` produces the message from the call's arguments. It runs on both
|
|
69
|
+
* rounds, and it has to: the second round recomputes the digest from what the
|
|
70
|
+
* client is asking for now and compares it with what the user actually agreed
|
|
71
|
+
* to. Building once and trusting the client to hand it back would be trusting
|
|
72
|
+
* exactly the thing that needs checking.
|
|
73
|
+
*/
|
|
74
|
+
async function confirmed(deps, ctx, tool, what, build) {
|
|
75
|
+
// Asked before anything else. A confirmation for a send that read-only would
|
|
76
|
+
// refuse anyway is a question with no honest answer.
|
|
77
|
+
if (deps.readOnly()) {
|
|
78
|
+
return describeFailure(new BridgeError('The server is running read-only, so sending is refused. This is set by ' +
|
|
79
|
+
'PROTON_MCP_READ_ONLY and the web interface reports it as the current mode.'), 'sending');
|
|
80
|
+
}
|
|
81
|
+
const draft = await build();
|
|
82
|
+
const digest = digestOf(draft);
|
|
83
|
+
const pending = pendingSend(ctx);
|
|
84
|
+
if (!pending) {
|
|
85
|
+
// First round. Nothing is sent here under any circumstances.
|
|
86
|
+
if (!clientCanConfirm(ctx))
|
|
87
|
+
return refuse.noElicitation();
|
|
88
|
+
const state = await deps.codec.mint({ tool, digest }, ctx);
|
|
89
|
+
return confirmationRequest(draft, what, state);
|
|
90
|
+
}
|
|
91
|
+
// Second round. The seal has already been verified by the SDK, so what is
|
|
92
|
+
// left is whether it says the same thing this call is asking for.
|
|
93
|
+
if (pending.tool !== tool)
|
|
94
|
+
return refuse.wrongTool();
|
|
95
|
+
if (pending.digest !== digest)
|
|
96
|
+
return refuse.changed();
|
|
97
|
+
if (confirmationAnswer(ctx) !== 'yes')
|
|
98
|
+
return refuse.declined();
|
|
99
|
+
const outcome = await sendMessage(deps.config, deps.getCredentials, deps.readOnly(), draft);
|
|
100
|
+
return ok(describeOutcome(draft, outcome));
|
|
101
|
+
}
|
|
102
|
+
const addressList = (what) => z
|
|
103
|
+
.array(z.string())
|
|
104
|
+
.optional()
|
|
105
|
+
.describe(`${what} Each entry is an address, optionally as "Display Name <name@example.com>".`);
|
|
106
|
+
const CONFIRMATION_NOTE = 'Every send asks the user to confirm first, through the client, showing the final recipients, ' +
|
|
107
|
+
'the subject and the first lines. There is no way to switch that off, and a client that cannot ' +
|
|
108
|
+
'show the question cannot send at all.';
|
|
109
|
+
export function registerSendTools(server, deps) {
|
|
110
|
+
server.registerTool('send_message', {
|
|
111
|
+
title: 'Send a message',
|
|
112
|
+
description: `Composes a message and sends it. ${CONFIRMATION_NOTE} ${PLAIN_TEXT_NOTE} ${MARKUP_NOTE} ${UTF8_NOTE}`,
|
|
113
|
+
inputSchema: z.object({
|
|
114
|
+
to: addressList('The main recipients.'),
|
|
115
|
+
cc: addressList('Recipients in copy, visible to everyone.'),
|
|
116
|
+
bcc: addressList('Recipients in blind copy. They receive it; the others do not see them.'),
|
|
117
|
+
subject: z.string().default('').describe('The subject line.'),
|
|
118
|
+
text: z.string().default('').describe('The body, as plain text.'),
|
|
119
|
+
html: z
|
|
120
|
+
.string()
|
|
121
|
+
.optional()
|
|
122
|
+
.describe('The body as markup instead of plain text. Give this or text, not both. Permitted ' +
|
|
123
|
+
'are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, ' +
|
|
124
|
+
'links and images, with colour, font, alignment, spacing and borders. Images may ' +
|
|
125
|
+
'point at a full address; anything outside the permitted set is refused with a ' +
|
|
126
|
+
'reason rather than removed.'),
|
|
127
|
+
}),
|
|
128
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
129
|
+
}, async (input, ctx) => track(() => withSignIn(ctx, async () => {
|
|
130
|
+
try {
|
|
131
|
+
return await confirmed(deps, ctx, 'send_message', 'This message', async () => {
|
|
132
|
+
const from = parseRecipient(requireFrom(deps));
|
|
133
|
+
if (input.html !== undefined && input.text) {
|
|
134
|
+
throw new BridgeError('Both a text and a markup body were given. A message carries one or the ' +
|
|
135
|
+
'other: Proton drops the text half of a message that has both, so the half ' +
|
|
136
|
+
'that was confirmed would never arrive. Give whichever one this message is.');
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
from,
|
|
140
|
+
to: parseRecipients(input.to),
|
|
141
|
+
cc: parseRecipients(input.cc),
|
|
142
|
+
bcc: parseRecipients(input.bcc),
|
|
143
|
+
subject: input.subject,
|
|
144
|
+
// The readable rendering, so that everything downstream can
|
|
145
|
+
// read `text` and get something true.
|
|
146
|
+
text: input.html !== undefined ? htmlToText(input.html) : input.text,
|
|
147
|
+
messageId: mintMessageId(from.address),
|
|
148
|
+
...(input.html !== undefined ? { html: input.html } : {}),
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return describeFailure(error, 'sending the message');
|
|
154
|
+
}
|
|
155
|
+
}), 'send_message'));
|
|
156
|
+
server.registerTool('send_reply', {
|
|
157
|
+
title: 'Reply to a message',
|
|
158
|
+
description: 'Replies to a message and sends the reply, with the reference headers that put it in the ' +
|
|
159
|
+
'same conversation. Unlike a reply written as a draft, this one keeps those headers: ' +
|
|
160
|
+
'Proton rewrites them for anything it stores, and this is never stored before it goes. ' +
|
|
161
|
+
`${CONFIRMATION_NOTE} ${PLAIN_TEXT_NOTE} ${MARKUP_NOTE} ${UTF8_NOTE}`,
|
|
162
|
+
inputSchema: z.object({
|
|
163
|
+
messageId: z.string().describe('The message being replied to.'),
|
|
164
|
+
text: z.string().describe('The reply. The original is quoted below it.'),
|
|
165
|
+
replyAll: z
|
|
166
|
+
.boolean()
|
|
167
|
+
.default(false)
|
|
168
|
+
.describe('When true, the other recipients of the original are put in copy.'),
|
|
169
|
+
html: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.describe('Your part of the message as markup instead of plain text. The original is quoted ' +
|
|
173
|
+
'below it either way. The quote is always built from the original\'s text, never ' +
|
|
174
|
+
'from its own markup, so a message full of markup this server would not send can ' +
|
|
175
|
+
'still be replied to or forwarded.'),
|
|
176
|
+
mailbox: z.string().optional().describe('The mailbox the original is in, if known.'),
|
|
177
|
+
}),
|
|
178
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
179
|
+
}, async ({ messageId, text, replyAll, mailbox, html }, ctx) => track(() => withSignIn(ctx, async () => {
|
|
180
|
+
try {
|
|
181
|
+
return await confirmed(deps, ctx, 'send_reply', 'This reply', () => buildReplyDraft(deps.connection, requireFrom(deps), messageId, text, {
|
|
182
|
+
all: replyAll,
|
|
183
|
+
...(mailbox ? { mailbox } : {}),
|
|
184
|
+
...(html !== undefined ? { html } : {}),
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return describeFailure(error, 'sending the reply');
|
|
189
|
+
}
|
|
190
|
+
}), 'send_reply'));
|
|
191
|
+
server.registerTool('send_forward', {
|
|
192
|
+
title: 'Forward a message',
|
|
193
|
+
description: 'Forwards a message and sends it. The original is quoted, and when it carries attachments ' +
|
|
194
|
+
`the whole original travels along so nothing of it is lost. ${CONFIRMATION_NOTE} ${PLAIN_TEXT_NOTE} ${MARKUP_NOTE} ${UTF8_NOTE}`,
|
|
195
|
+
inputSchema: z.object({
|
|
196
|
+
messageId: z.string().describe('The message being forwarded.'),
|
|
197
|
+
to: z.array(z.string()).min(1).describe('Who to forward it to.'),
|
|
198
|
+
text: z.string().default('').describe('Anything to say above the forwarded message.'),
|
|
199
|
+
html: z
|
|
200
|
+
.string()
|
|
201
|
+
.optional()
|
|
202
|
+
.describe('Your part of the message as markup instead of plain text. The original is quoted ' +
|
|
203
|
+
'below it either way. The quote is always built from the original\'s text, never ' +
|
|
204
|
+
'from its own markup, so a message full of markup this server would not send can ' +
|
|
205
|
+
'still be replied to or forwarded.'),
|
|
206
|
+
mailbox: z.string().optional().describe('The mailbox the original is in, if known.'),
|
|
207
|
+
}),
|
|
208
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
209
|
+
}, async ({ messageId, to, text, mailbox, html }, ctx) => track(() => withSignIn(ctx, async () => {
|
|
210
|
+
try {
|
|
211
|
+
return await confirmed(deps, ctx, 'send_forward', 'This forward', () => buildForwardDraft(deps.connection, requireFrom(deps), messageId, to, text, { ...(mailbox ? { mailbox } : {}), ...(html !== undefined ? { html } : {}) }));
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return describeFailure(error, 'sending the forward');
|
|
215
|
+
}
|
|
216
|
+
}), 'send_forward'));
|
|
217
|
+
server.registerTool('send_draft', {
|
|
218
|
+
title: 'Send a draft',
|
|
219
|
+
description: `Sends a draft that is already in "${DRAFTS}". The draft is not removed afterwards: this ` +
|
|
220
|
+
'server does not follow a send it deliberately does not verify with a second write. ' +
|
|
221
|
+
'Use trash_messages on the draft once the message shows up in Sent. ' +
|
|
222
|
+
`${CONFIRMATION_NOTE}`,
|
|
223
|
+
inputSchema: z.object({
|
|
224
|
+
messageId: z.string().describe('The id of the draft, from list_drafts or create_draft.'),
|
|
225
|
+
}),
|
|
226
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
227
|
+
}, async ({ messageId }, ctx) => track(() => withSignIn(ctx, async () => {
|
|
228
|
+
try {
|
|
229
|
+
return await confirmed(deps, ctx, 'send_draft', 'This draft', () => readDraftForSending(deps.connection, requireFrom(deps), messageId));
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
return describeFailure(error, 'sending the draft');
|
|
233
|
+
}
|
|
234
|
+
}), 'send_draft'));
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/tools/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a tool does when nobody has signed in.
|
|
3
|
+
*
|
|
4
|
+
* The point is narrow and worth stating plainly: a model told only that
|
|
5
|
+
* credentials are missing will ask the user to type their Bridge password into
|
|
6
|
+
* the chat, where it lands in a transcript. So the answer always names the local
|
|
7
|
+
* page instead, and says not to ask.
|
|
8
|
+
*
|
|
9
|
+
* Two ways of getting the user there:
|
|
10
|
+
*
|
|
11
|
+
* 1. **URL elicitation**, which the specification provides for exactly this
|
|
12
|
+
* case. The client shows the address, the user signs in, and the tool call
|
|
13
|
+
* is retried.
|
|
14
|
+
* 2. **A plain failure naming the address**, for clients that cannot elicit.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this is a returned value and not a call
|
|
17
|
+
*
|
|
18
|
+
* The obvious-looking route, `ctx.mcpReq.elicitInput({mode:'url'})`, cannot work
|
|
19
|
+
* here. Protocol revision 2026-07-28 has no server-to-client request channel at
|
|
20
|
+
* all, and `serveStdio` marks its server instance as that era at construction.
|
|
21
|
+
* The push API therefore throws on every call this server will ever serve. That
|
|
22
|
+
* is why the earlier attempt at this never elicited anything and always fell
|
|
23
|
+
* back to the text.
|
|
24
|
+
*
|
|
25
|
+
* The replacement is a value the handler RETURNS: `inputRequired(...)` with an
|
|
26
|
+
* embedded URL elicitation. The client fulfils it and calls the tool again, so
|
|
27
|
+
* the retry is an ordinary second invocation rather than a loop inside the
|
|
28
|
+
* handler.
|
|
29
|
+
*
|
|
30
|
+
* The SDK's shim fulfils such a return on a 2025-era connection as well, and it
|
|
31
|
+
* does so under `serveStdio` too. An earlier note here said otherwise, on the
|
|
32
|
+
* strength of a measurement that must have been taken against a different
|
|
33
|
+
* serving mode; re-measured on 31.07.2026 against SDK 2.0.0, `serveStdio` pins
|
|
34
|
+
* one instance for the life of the connection, the returned request reaches the
|
|
35
|
+
* client as an `elicitation/create`, and the answer comes back. The text answer
|
|
36
|
+
* below is therefore the fallback for a client that declares nothing, not for
|
|
37
|
+
* every client. See clientCanShowUrl.
|
|
38
|
+
*
|
|
39
|
+
* ## No requestState
|
|
40
|
+
*
|
|
41
|
+
* `inputRequired` also accepts an opaque `requestState` that travels through the
|
|
42
|
+
* client and comes back. We do not use it. Whether someone is signed in is
|
|
43
|
+
* answered by asking the session, which is the truth; a claim carried back
|
|
44
|
+
* through the client would be attacker-controlled input that we would then have
|
|
45
|
+
* to integrity-protect for no gain. The embedded request alone satisfies the
|
|
46
|
+
* protocol's at-least-one rule.
|
|
47
|
+
*/
|
|
48
|
+
import type { CallToolResult, InputRequiredResult } from '@modelcontextprotocol/server';
|
|
49
|
+
/**
|
|
50
|
+
* The key our embedded elicitation is filed under.
|
|
51
|
+
*
|
|
52
|
+
* Also how a retry is recognised: a response under this key means the client has
|
|
53
|
+
* already taken the user to the page once.
|
|
54
|
+
*/
|
|
55
|
+
export declare const SIGN_IN_KEY = "protonMcpSignIn";
|
|
56
|
+
/** Which of the two situations a caller is in. */
|
|
57
|
+
export type MissingCredentials = 'not-signed-in' | 'locked';
|
|
58
|
+
/**
|
|
59
|
+
* Whether the client said it can show a URL.
|
|
60
|
+
*
|
|
61
|
+
* Asked in advance rather than attempted and caught, because there is nothing to
|
|
62
|
+
* catch: an embedded request the client never declared support for is refused by
|
|
63
|
+
* the SDK *after* the handler returns. The caller would get that refusal instead
|
|
64
|
+
* of an address, so an undeclared capability means the text answer.
|
|
65
|
+
*
|
|
66
|
+
* The paragraph that used to stand here claimed, as a measurement, that only the
|
|
67
|
+
* request's own envelope could be consulted and that a handshake declaration was
|
|
68
|
+
* unavailable under `serveStdio`. That was wrong, and it was wrong in the
|
|
69
|
+
* expensive direction: on this SDK the envelope does not exist at all, so the
|
|
70
|
+
* check answered no for every client and the elicitation never once fired. Where
|
|
71
|
+
* the declaration now comes from is in capabilities.ts.
|
|
72
|
+
*/
|
|
73
|
+
export declare function clientCanShowUrl(ctx: unknown): boolean;
|
|
74
|
+
/** The failure used when elicitation is unavailable, declined or fruitless. */
|
|
75
|
+
export declare function signInFailure(url: string | undefined, note?: string, state?: MissingCredentials): CallToolResult;
|
|
76
|
+
/**
|
|
77
|
+
* Asks the client to take the user to the sign-in page.
|
|
78
|
+
*
|
|
79
|
+
* Returned from the handler, not sent. See the note at the top of this file for
|
|
80
|
+
* why that is the only thing that can work here.
|
|
81
|
+
*/
|
|
82
|
+
export declare function signInElicitation(url: string, state?: MissingCredentials): InputRequiredResult;
|
|
83
|
+
/** What came back from a previous round, if this call is a retry. */
|
|
84
|
+
export type PriorAttempt = 'none' | 'accepted' | 'refused';
|
|
85
|
+
/**
|
|
86
|
+
* Whether the client has already been through the sign-in page for this call.
|
|
87
|
+
*
|
|
88
|
+
* "accepted" means the user was shown the page and said they were done, so a
|
|
89
|
+
* still-missing sign-in is a real failure rather than a reason to ask again.
|
|
90
|
+
* Asking twice in a row would be a loop the user cannot escape.
|
|
91
|
+
*/
|
|
92
|
+
export declare function priorAttempt(ctx: unknown): PriorAttempt;
|
|
93
|
+
/** The note explaining a retry that still found no credentials. */
|
|
94
|
+
export declare function retryNote(attempt: PriorAttempt, state?: MissingCredentials): string | undefined;
|