@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
package/dist/server.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry point of the MCP server.
|
|
4
|
+
*
|
|
5
|
+
* The server speaks over stdio, so the client starts it as a child process.
|
|
6
|
+
* That is not a workaround: the Bridge only listens on 127.0.0.1, so a server
|
|
7
|
+
* running in the cloud could not reach it at all.
|
|
8
|
+
*
|
|
9
|
+
* The local web interface runs for as long as this process does. It is not
|
|
10
|
+
* started on demand: it is where configuration and state live, and it is meant
|
|
11
|
+
* to be opened again and again rather than summoned for one sign-in.
|
|
12
|
+
*/
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { randomBytes } from 'node:crypto';
|
|
15
|
+
import { McpServer, createRequestStateCodec } from '@modelcontextprotocol/server';
|
|
16
|
+
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
17
|
+
import { loadConfig, credentialsFromEnvironment, portsSetExplicitly, ConfigError, } from './config.js';
|
|
18
|
+
import { Connection } from './bridge/connection.js';
|
|
19
|
+
import { loadPinned, pinPath } from './bridge/certificate.js';
|
|
20
|
+
import { Session } from './session.js';
|
|
21
|
+
import { encryptedFilePath } from './credentials/encrypted-file.js';
|
|
22
|
+
import { loadSettings, saveSettings } from './settings.js';
|
|
23
|
+
import * as activity from './activity.js';
|
|
24
|
+
import { WebInterface } from './web/server.js';
|
|
25
|
+
import { registerListFolders } from './tools/list-folders.js';
|
|
26
|
+
import { registerMessageTools } from './tools/messages.js';
|
|
27
|
+
import { registerInterfaceTool } from './tools/interface.js';
|
|
28
|
+
import { registerLabelTools } from './tools/labels.js';
|
|
29
|
+
import { registerActionTools } from './tools/actions.js';
|
|
30
|
+
import { registerDraftTools } from './tools/drafts.js';
|
|
31
|
+
import { registerSendTools } from './tools/send.js';
|
|
32
|
+
import { setSignInHint, setLockedCheck } from './tools/failures.js';
|
|
33
|
+
import { setHandshakeCapabilities } from './tools/capabilities.js';
|
|
34
|
+
import { registerGuidance, INSTRUCTIONS } from './tools/guidance.js';
|
|
35
|
+
import { waitForIdle } from './in-flight.js';
|
|
36
|
+
// The version number lives in package.json only.
|
|
37
|
+
// Writing it here a second time would mean forgetting it here. From
|
|
38
|
+
// dist/server.js the path points at the package.json of the project root, and
|
|
39
|
+
// in the published package at its own.
|
|
40
|
+
const require = createRequire(import.meta.url);
|
|
41
|
+
const pkg = require('../package.json');
|
|
42
|
+
/**
|
|
43
|
+
* What this server calls itself, which is deliberately not the package name.
|
|
44
|
+
*
|
|
45
|
+
* The package is scoped so that other servers can sit beside it under the same
|
|
46
|
+
* namespace. That scope is an ownership statement for a registry and has no
|
|
47
|
+
* business in a protocol handshake or at the front of every line on stderr,
|
|
48
|
+
* where it would only make both harder to read.
|
|
49
|
+
*
|
|
50
|
+
* Fixed here rather than derived, because deriving it would tie the name a
|
|
51
|
+
* client displays to a decision about where the package is published.
|
|
52
|
+
*/
|
|
53
|
+
const NAME = 'proton-mcp';
|
|
54
|
+
const VERSION = pkg.version;
|
|
55
|
+
const notify = (message) => {
|
|
56
|
+
// stderr, never stdout: the protocol runs over stdout.
|
|
57
|
+
process.stderr.write(`${NAME}: ${message}\n`);
|
|
58
|
+
};
|
|
59
|
+
async function main() {
|
|
60
|
+
const startedAt = Date.now();
|
|
61
|
+
let config;
|
|
62
|
+
try {
|
|
63
|
+
config = loadConfig();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof ConfigError) {
|
|
67
|
+
process.stderr.write(`${NAME}: ${error.message}\n`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
// Stored ports fill in only where the environment said nothing. An explicit
|
|
73
|
+
// BRIDGE_IMAP_PORT has to keep meaning what it says.
|
|
74
|
+
const explicitPorts = portsSetExplicitly();
|
|
75
|
+
// Held rather than read once: every write goes through the whole object, so
|
|
76
|
+
// that saving one setting cannot drop another. saveSettings replaces the file.
|
|
77
|
+
const settings = await loadSettings();
|
|
78
|
+
if (!explicitPorts.imapPort && settings.imapPort !== undefined)
|
|
79
|
+
config.imapPort = settings.imapPort;
|
|
80
|
+
if (!explicitPorts.smtpPort && settings.smtpPort !== undefined)
|
|
81
|
+
config.smtpPort = settings.smtpPort;
|
|
82
|
+
const environmentCredentials = credentialsFromEnvironment();
|
|
83
|
+
const session = new Session({
|
|
84
|
+
...(environmentCredentials ? { fromEnvironment: environmentCredentials } : {}),
|
|
85
|
+
notify,
|
|
86
|
+
});
|
|
87
|
+
// One connection for the whole process, opened on the first tool call rather
|
|
88
|
+
// than at startup: a client often spawns the server long before the user wants
|
|
89
|
+
// anything, and a locked Bridge should not prevent it from starting.
|
|
90
|
+
const connection = new Connection(config, () => session.credentials(), notify);
|
|
91
|
+
/**
|
|
92
|
+
* Tries credentials against the Bridge before they are trusted.
|
|
93
|
+
*
|
|
94
|
+
* Storing something that does not work would send the user looking in the
|
|
95
|
+
* wrong place, and on the unlock path it is the only way to tell a wrong
|
|
96
|
+
* master password from a Bridge that is simply locked.
|
|
97
|
+
*/
|
|
98
|
+
const verify = async (credentials) => {
|
|
99
|
+
const probe = new Connection(config, async () => credentials, notify);
|
|
100
|
+
try {
|
|
101
|
+
await probe.listMailboxes();
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return error.message;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await probe.close();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const web = new WebInterface({
|
|
112
|
+
port: config.webPort,
|
|
113
|
+
onSignIn: async (credentials, storeKind, masterPassword) => {
|
|
114
|
+
const failure = await verify(credentials);
|
|
115
|
+
if (failure)
|
|
116
|
+
return failure;
|
|
117
|
+
try {
|
|
118
|
+
await session.signIn(credentials, storeKind, masterPassword);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return error.message;
|
|
122
|
+
}
|
|
123
|
+
// Drop the old connection so the next call uses the new credentials.
|
|
124
|
+
await connection.close();
|
|
125
|
+
return undefined;
|
|
126
|
+
},
|
|
127
|
+
onSignOut: async () => {
|
|
128
|
+
await session.signOut();
|
|
129
|
+
await connection.close();
|
|
130
|
+
},
|
|
131
|
+
onUnlock: async (masterPassword) => {
|
|
132
|
+
let credentials;
|
|
133
|
+
try {
|
|
134
|
+
credentials = await session.unlock(masterPassword);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return error.message;
|
|
138
|
+
}
|
|
139
|
+
// The file opened, which only says the master password was right. Whether
|
|
140
|
+
// the Bridge still accepts what was in it is a separate question, and one
|
|
141
|
+
// the user would rather have answered here than at the next tool call.
|
|
142
|
+
const failure = await verify(credentials);
|
|
143
|
+
if (failure) {
|
|
144
|
+
await session.signOut();
|
|
145
|
+
return (`${failure}\n\nThe file was decrypted, so the master password was correct, but the ` +
|
|
146
|
+
'Bridge password inside it no longer works. Sign in again with a fresh one.');
|
|
147
|
+
}
|
|
148
|
+
await connection.close();
|
|
149
|
+
return undefined;
|
|
150
|
+
},
|
|
151
|
+
onDiscardEncrypted: async () => {
|
|
152
|
+
await session.discardEncrypted();
|
|
153
|
+
notify('the encrypted credentials file was deleted on request');
|
|
154
|
+
},
|
|
155
|
+
getStatus: async () => {
|
|
156
|
+
const pinned = loadPinned(pinPath());
|
|
157
|
+
const base = {
|
|
158
|
+
connected: session.signedIn,
|
|
159
|
+
locked: await session.locked(),
|
|
160
|
+
encryptedPath: encryptedFilePath(),
|
|
161
|
+
bridgeHost: config.host,
|
|
162
|
+
bridgeImapPort: config.imapPort,
|
|
163
|
+
bridgeSmtpPort: config.smtpPort,
|
|
164
|
+
readOnly: config.readOnly,
|
|
165
|
+
uptimeMs: Date.now() - startedAt,
|
|
166
|
+
...(session.address ? { address: session.address } : {}),
|
|
167
|
+
...(session.storeKind ? { storeKind: session.storeKind } : {}),
|
|
168
|
+
...(pinned
|
|
169
|
+
? {
|
|
170
|
+
certificate: {
|
|
171
|
+
fingerprint: pinned.fingerprint256,
|
|
172
|
+
subject: pinned.subject,
|
|
173
|
+
firstSeen: pinned.firstSeen,
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
};
|
|
178
|
+
if (!session.signedIn)
|
|
179
|
+
return base;
|
|
180
|
+
try {
|
|
181
|
+
const mailboxes = await connection.listMailboxes();
|
|
182
|
+
const inbox = await connection.status('INBOX');
|
|
183
|
+
return {
|
|
184
|
+
...base,
|
|
185
|
+
mailboxCount: mailboxes.filter((box) => box.selectable).length,
|
|
186
|
+
messageCount: inbox.messages,
|
|
187
|
+
unseenCount: inbox.unseen,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// The status page should still render when the Bridge is unreachable.
|
|
192
|
+
return base;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
// Absent when the user set the ports themselves: the form then disappears
|
|
196
|
+
// rather than quietly overriding what they configured.
|
|
197
|
+
...(explicitPorts.imapPort || explicitPorts.smtpPort
|
|
198
|
+
? {}
|
|
199
|
+
: {
|
|
200
|
+
onPorts: async (ports) => {
|
|
201
|
+
try {
|
|
202
|
+
await saveSettings({ ...settings, ...ports });
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return `The ports could not be saved: ${error.message}`;
|
|
206
|
+
}
|
|
207
|
+
settings.imapPort = ports.imapPort;
|
|
208
|
+
settings.smtpPort = ports.smtpPort;
|
|
209
|
+
// The Connection reads this object on every reconnect, so closing
|
|
210
|
+
// the current one is all that is needed for the change to take.
|
|
211
|
+
config.imapPort = ports.imapPort;
|
|
212
|
+
config.smtpPort = ports.smtpPort;
|
|
213
|
+
await connection.close();
|
|
214
|
+
notify(`ports changed to IMAP ${ports.imapPort}, SMTP ${ports.smtpPort}`);
|
|
215
|
+
return undefined;
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
noticeDismissed: () => settings.noticeDismissed === true,
|
|
219
|
+
onDismissNotice: async () => {
|
|
220
|
+
settings.noticeDismissed = true;
|
|
221
|
+
try {
|
|
222
|
+
await saveSettings(settings);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
// Worth a line, not worth failing over: the notice is hidden for this
|
|
226
|
+
// run either way, and the next start simply shows it again.
|
|
227
|
+
notify(`the dismissed notice could not be remembered: ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
onTest: async () => {
|
|
231
|
+
try {
|
|
232
|
+
const mailboxes = await connection.listMailboxes();
|
|
233
|
+
return {
|
|
234
|
+
ok: true,
|
|
235
|
+
message: `The Bridge answered on ${config.host}:${config.imapPort} and accepted the ` +
|
|
236
|
+
`credentials. ${mailboxes.filter((box) => box.selectable).length} mailboxes are visible.`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return { ok: false, message: error.message };
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
getMailboxes: () => connection.listMailboxes(),
|
|
244
|
+
getActivity: () => {
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
return {
|
|
247
|
+
running: activity.inProgress().map((call) => ({
|
|
248
|
+
tool: call.tool,
|
|
249
|
+
forMs: now - call.startedAt,
|
|
250
|
+
})),
|
|
251
|
+
recent: activity.recent().map((call) => ({
|
|
252
|
+
tool: call.tool,
|
|
253
|
+
at: call.startedAt,
|
|
254
|
+
durationMs: call.durationMs,
|
|
255
|
+
outcome: call.outcome,
|
|
256
|
+
})),
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
notify,
|
|
260
|
+
});
|
|
261
|
+
// Every tool answer about a missing sign-in names this address, so the model
|
|
262
|
+
// sends the user there instead of asking for the password in the chat.
|
|
263
|
+
setSignInHint(() => (web.running ? web.url : undefined));
|
|
264
|
+
// So that a tool called while an encrypted file is still closed says what is
|
|
265
|
+
// actually missing: the master password, not the Bridge password.
|
|
266
|
+
setLockedCheck(() => session.locked());
|
|
267
|
+
let webUrl;
|
|
268
|
+
try {
|
|
269
|
+
webUrl = await web.start();
|
|
270
|
+
notify(`web interface at ${webUrl}`);
|
|
271
|
+
if (!session.signedIn && !environmentCredentials) {
|
|
272
|
+
// Asking the session restores from a store, which is what makes this
|
|
273
|
+
// message true. Without it the answer is always "nothing stored": the
|
|
274
|
+
// restore is lazy and has not happened yet at startup, so a perfectly
|
|
275
|
+
// good credentials file was reported as absent every single start.
|
|
276
|
+
const restored = await session.credentials();
|
|
277
|
+
if (restored) {
|
|
278
|
+
notify(`credentials restored from the ${session.storeKind ?? 'store'}, signed in as ${session.address}`);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
notify((await session.locked())
|
|
282
|
+
? 'credentials are stored in an encrypted file, open the address above to unlock them'
|
|
283
|
+
: 'no credentials stored yet, open the address above to sign in');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
// A web interface that cannot start must not stop the server: with
|
|
289
|
+
// credentials in the environment everything else still works.
|
|
290
|
+
notify(`${error.message} Continuing without it.`);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Seals the confirmation that has to survive a round trip through the client.
|
|
294
|
+
*
|
|
295
|
+
* The key is random and lives only in this process, which is exactly right
|
|
296
|
+
* here: the server speaks over stdio, so the same process serves every round
|
|
297
|
+
* of a flow, and a key that never reaches disk cannot be stolen from it. A
|
|
298
|
+
* restart invalidates any confirmation in flight, which is the safe direction
|
|
299
|
+
* to fail in.
|
|
300
|
+
*
|
|
301
|
+
* Bound to the request method as well, so a sealed state cannot be lifted out
|
|
302
|
+
* of one kind of request and presented in another. Which tool it belongs to is
|
|
303
|
+
* checked separately, in the handler.
|
|
304
|
+
*/
|
|
305
|
+
const sendCodec = createRequestStateCodec({
|
|
306
|
+
key: randomBytes(32),
|
|
307
|
+
// Long enough to read a confirmation and think about it, short enough that
|
|
308
|
+
// a yes does not stay usable for the rest of the day.
|
|
309
|
+
ttlSeconds: 300,
|
|
310
|
+
bind: (requestCtx) => String(requestCtx.mcpReq?.method ?? ''),
|
|
311
|
+
});
|
|
312
|
+
const handle = serveStdio(() => {
|
|
313
|
+
const server = new McpServer({ name: NAME, version: VERSION }, {
|
|
314
|
+
// Verifies the seal before a handler ever runs, and hands the decoded
|
|
315
|
+
// payload to it. Without this the state would come back as a raw
|
|
316
|
+
// string the client could have written itself.
|
|
317
|
+
requestState: { verify: sendCodec.verify },
|
|
318
|
+
// Travels with every connection, so it costs context on every session.
|
|
319
|
+
// It names only what a model would otherwise get wrong before it had a
|
|
320
|
+
// reason to look anything up; the detail lives in the resources.
|
|
321
|
+
instructions: INSTRUCTIONS,
|
|
322
|
+
});
|
|
323
|
+
registerGuidance(server);
|
|
324
|
+
registerListFolders(server, connection);
|
|
325
|
+
registerMessageTools(server, connection);
|
|
326
|
+
// Read as a function rather than a value: whether the server may write is a
|
|
327
|
+
// property of the configuration, and the tools should ask at call time.
|
|
328
|
+
registerLabelTools(server, connection, () => config.readOnly);
|
|
329
|
+
registerActionTools(server, connection, () => config.readOnly);
|
|
330
|
+
registerDraftTools(server, connection, () => config.readOnly, () => session.address);
|
|
331
|
+
// What the client declared at handshake time. On this SDK's protocol
|
|
332
|
+
// revision it is the only place the declaration exists, and the confirmation
|
|
333
|
+
// before sending refuses when it cannot see one. Registered here because the
|
|
334
|
+
// instance is built here and does not exist earlier.
|
|
335
|
+
setHandshakeCapabilities(() => server.server?.getClientCapabilities?.());
|
|
336
|
+
registerSendTools(server, {
|
|
337
|
+
config,
|
|
338
|
+
connection,
|
|
339
|
+
getCredentials: () => session.credentials(),
|
|
340
|
+
readOnly: () => config.readOnly,
|
|
341
|
+
from: () => session.address,
|
|
342
|
+
codec: sendCodec,
|
|
343
|
+
});
|
|
344
|
+
registerInterfaceTool(server, {
|
|
345
|
+
url: () => web.url,
|
|
346
|
+
running: () => web.running,
|
|
347
|
+
signedIn: () => session.signedIn,
|
|
348
|
+
address: () => session.address,
|
|
349
|
+
storeKind: () => session.storeKind,
|
|
350
|
+
locked: () => session.locked(),
|
|
351
|
+
});
|
|
352
|
+
return server;
|
|
353
|
+
});
|
|
354
|
+
// Clean shutdown. stdin matters most: when the client dies the stream ends
|
|
355
|
+
// without any signal arriving. Without handling this, the server process
|
|
356
|
+
// would be left orphaned along with its open IMAP connection and its socket.
|
|
357
|
+
let shuttingDown = false;
|
|
358
|
+
const shutdown = async () => {
|
|
359
|
+
if (shuttingDown)
|
|
360
|
+
return;
|
|
361
|
+
shuttingDown = true;
|
|
362
|
+
// Give the event loop one turn first. When the input stream ends there may
|
|
363
|
+
// still be unread messages in the buffer that do not count as in-flight
|
|
364
|
+
// work yet. Without this pause the counter would be zero and the server
|
|
365
|
+
// would exit before the last request even starts.
|
|
366
|
+
await new Promise((next) => setTimeout(next, 50));
|
|
367
|
+
// Let running tool calls finish so their answer still goes out. Bounded,
|
|
368
|
+
// so that a stuck call cannot prevent shutdown.
|
|
369
|
+
const idle = await waitForIdle(5000);
|
|
370
|
+
if (!idle)
|
|
371
|
+
notify('shutdown forced, a call was still running.');
|
|
372
|
+
try {
|
|
373
|
+
await web.stop();
|
|
374
|
+
await connection.close();
|
|
375
|
+
await handle.close();
|
|
376
|
+
}
|
|
377
|
+
finally {
|
|
378
|
+
process.exit(idle ? 0 : 1);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
process.stdin.on('end', () => void shutdown());
|
|
382
|
+
process.stdin.on('close', () => void shutdown());
|
|
383
|
+
process.on('SIGINT', () => void shutdown());
|
|
384
|
+
process.on('SIGTERM', () => void shutdown());
|
|
385
|
+
}
|
|
386
|
+
void main();
|
|
387
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAC/D,OAAO,EACL,UAAU,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,WAAW,GACZ,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,YAAY,EAAuB,MAAM,iBAAiB,CAAA;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,iDAAiD;AACjD,oEAAoE;AACpE,8EAA8E;AAC9E,uCAAuC;AACvC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAsC,CAAA;AAE3E;;;;;;;;;;GAUG;AACH,MAAM,IAAI,GAAG,YAAY,CAAA;AACzB,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;AAE3B,MAAM,MAAM,GAAG,CAAC,OAAe,EAAQ,EAAE;IACvC,uDAAuD;IACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,IAAI,CAAC,CAAA;AAC/C,CAAC,CAAA;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAE5B,IAAI,MAAM,CAAA;IACV,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAA;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;YACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,MAAM,KAAK,CAAA;IACb,CAAC;IAED,4EAA4E;IAC5E,qDAAqD;IACrD,MAAM,aAAa,GAAG,kBAAkB,EAAE,CAAA;IAC1C,4EAA4E;IAC5E,+EAA+E;IAC/E,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAA;IACrC,IAAI,CAAC,aAAa,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS;QAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAA;IACnG,IAAI,CAAC,aAAa,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS;QAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAA;IAEnG,MAAM,sBAAsB,GAAG,0BAA0B,EAAE,CAAA;IAC3D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;QAC1B,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,MAAM;KACP,CAAC,CAAA;IAEF,6EAA6E;IAC7E,+EAA+E;IAC/E,qEAAqE;IACrE,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAA;IAE9E;;;;;;OAMG;IACH,MAAM,MAAM,GAAG,KAAK,EAAE,WAA2C,EAA+B,EAAE;QAChG,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;QACrE,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,aAAa,EAAE,CAAA;YAC3B,OAAO,SAAS,CAAA;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAQ,KAAe,CAAC,OAAO,CAAA;QACjC,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,CAAC,KAAK,EAAE,CAAA;QACrB,CAAC;IACH,CAAC,CAAA;IAED,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,OAAO;QACpB,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE;YACzD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;YACzC,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAA;YAC3B,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,CAAA;YAC9D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAQ,KAAe,CAAC,OAAO,CAAA;YACjC,CAAC;YACD,qEAAqE;YACrE,MAAM,UAAU,CAAC,KAAK,EAAE,CAAA;YACxB,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,SAAS,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;YACvB,MAAM,UAAU,CAAC,KAAK,EAAE,CAAA;QAC1B,CAAC;QACD,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;YACjC,IAAI,WAAW,CAAA;YACf,IAAI,CAAC;gBACH,WAAW,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAQ,KAAe,CAAC,OAAO,CAAA;YACjC,CAAC;YACD,0EAA0E;YAC1E,0EAA0E;YAC1E,uEAAuE;YACvE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;YACzC,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;gBACvB,OAAO,CACL,GAAG,OAAO,0EAA0E;oBACpF,4EAA4E,CAC7E,CAAA;YACH,CAAC;YACD,MAAM,UAAU,CAAC,KAAK,EAAE,CAAA;YACxB,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,kBAAkB,EAAE,KAAK,IAAI,EAAE;YAC7B,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAA;YAChC,MAAM,CAAC,uDAAuD,CAAC,CAAA;QACjE,CAAC;QACD,SAAS,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAA;YACpC,MAAM,IAAI,GAAmB;gBAC3B,SAAS,EAAE,OAAO,CAAC,QAAQ;gBAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE;gBAC9B,aAAa,EAAE,iBAAiB,EAAE;gBAClC,UAAU,EAAE,MAAM,CAAC,IAAI;gBACvB,cAAc,EAAE,MAAM,CAAC,QAAQ;gBAC/B,cAAc,EAAE,MAAM,CAAC,QAAQ;gBAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAChC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9D,GAAG,CAAC,MAAM;oBACR,CAAC,CAAC;wBACE,WAAW,EAAE;4BACX,WAAW,EAAE,MAAM,CAAC,cAAc;4BAClC,OAAO,EAAE,MAAM,CAAC,OAAO;4BACvB,SAAS,EAAE,MAAM,CAAC,SAAS;yBAC5B;qBACF;oBACH,CAAC,CAAC,EAAE,CAAC;aACR,CAAA;YACD,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAClC,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,CAAA;gBAClD,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;gBAC9C,OAAO;oBACL,GAAG,IAAI;oBACP,YAAY,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM;oBAC9D,YAAY,EAAE,KAAK,CAAC,QAAQ;oBAC5B,WAAW,EAAE,KAAK,CAAC,MAAM;iBAC1B,CAAA;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,sEAAsE;gBACtE,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QACD,0EAA0E;QAC1E,uDAAuD;QACvD,GAAG,CAAC,aAAa,CAAC,QAAQ,IAAI,aAAa,CAAC,QAAQ;YAClD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;oBACvB,IAAI,CAAC;wBACH,MAAM,YAAY,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;oBAC/C,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,OAAO,iCAAkC,KAAe,CAAC,OAAO,EAAE,CAAA;oBACpE,CAAC;oBACD,QAAQ,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;oBAClC,QAAQ,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;oBAClC,kEAAkE;oBAClE,gEAAgE;oBAChE,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;oBAChC,MAAM,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;oBAChC,MAAM,UAAU,CAAC,KAAK,EAAE,CAAA;oBACxB,MAAM,CAAC,yBAAyB,KAAK,CAAC,QAAQ,UAAU,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;oBACzE,OAAO,SAAS,CAAA;gBAClB,CAAC;aACF,CAAC;QACN,eAAe,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,KAAK,IAAI;QACxD,eAAe,EAAE,KAAK,IAAI,EAAE;YAC1B,QAAQ,CAAC,eAAe,GAAG,IAAI,CAAA;YAC/B,IAAI,CAAC;gBACH,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAA;YAC9B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,sEAAsE;gBACtE,4DAA4D;gBAC5D,MAAM,CAAC,iDAAkD,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;YACrF,CAAC;QACH,CAAC;QACD,MAAM,EAAE,KAAK,IAAI,EAAE;YACjB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,CAAA;gBAClD,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,OAAO,EACL,0BAA0B,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,oBAAoB;wBAC5E,gBAAgB,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,yBAAyB;iBAC5F,CAAA;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAG,KAAe,CAAC,OAAO,EAAE,CAAA;YACzD,CAAC;QACH,CAAC;QACD,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE;QAC9C,WAAW,EAAE,GAAG,EAAE;YAChB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACtB,OAAO;gBACL,OAAO,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBAC5C,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC,SAAS;iBAC5B,CAAC,CAAC;gBACH,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;oBACvC,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,EAAE,EAAE,IAAI,CAAC,SAAS;oBAClB,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;iBACtB,CAAC,CAAC;aACJ,CAAA;QACH,CAAC;QACD,MAAM;KACP,CAAC,CAAA;IAEF,6EAA6E;IAC7E,uEAAuE;IACvE,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IACxD,6EAA6E;IAC7E,kEAAkE;IAClE,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAEtC,IAAI,MAA0B,CAAA;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAA;QAC1B,MAAM,CAAC,oBAAoB,MAAM,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,sBAAsB,EAAE,CAAC;YACjD,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtE,mEAAmE;YACnE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAA;YAC5C,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,iCAAiC,OAAO,CAAC,SAAS,IAAI,OAAO,kBAAkB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;YAC1G,CAAC;iBAAM,CAAC;gBACN,MAAM,CACJ,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;oBACtB,CAAC,CAAC,oFAAoF;oBACtF,CAAC,CAAC,8DAA8D,CACnE,CAAA;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,mEAAmE;QACnE,8DAA8D;QAC9D,MAAM,CAAC,GAAI,KAAe,CAAC,OAAO,yBAAyB,CAAC,CAAA;IAC9D,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,SAAS,GAAG,uBAAuB,CAAc;QACrD,GAAG,EAAE,WAAW,CAAC,EAAE,CAAC;QACpB,2EAA2E;QAC3E,sDAAsD;QACtD,UAAU,EAAE,GAAG;QACf,IAAI,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,CAAC;KAC9D,CAAC,CAAA;IAEF,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;QAC7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAChC;YACE,sEAAsE;YACtE,iEAAiE;YACjE,+CAA+C;YAC/C,YAAY,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE;YAC1C,uEAAuE;YACvE,uEAAuE;YACvE,iEAAiE;YACjE,YAAY,EAAE,YAAY;SAC3B,CACF,CAAA;QACD,gBAAgB,CAAC,MAAM,CAAC,CAAA;QACxB,mBAAmB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;QACvC,oBAAoB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;QACxC,4EAA4E;QAC5E,wEAAwE;QACxE,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC7D,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC9D,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACpF,qEAAqE;QACrE,6EAA6E;QAC7E,6EAA6E;QAC7E,qDAAqD;QACrD,wBAAwB,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAA;QAExE,iBAAiB,CAAC,MAAM,EAAE;YACxB,MAAM;YACN,UAAU;YACV,cAAc,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE;YAC3C,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ;YAC/B,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO;YAC3B,KAAK,EAAE,SAAS;SACjB,CAAC,CAAA;QACF,qBAAqB,CAAC,MAAM,EAAE;YAC5B,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG;YAClB,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO;YAC1B,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ;YAChC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO;YAC9B,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS;YAClC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE;SAC/B,CAAC,CAAA;QACF,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;IAEF,2EAA2E;IAC3E,yEAAyE;IACzE,6EAA6E;IAC7E,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,MAAM,QAAQ,GAAG,KAAK,IAAmB,EAAE;QACzC,IAAI,YAAY;YAAE,OAAM;QACxB,YAAY,GAAG,IAAI,CAAA;QAEnB,2EAA2E;QAC3E,wEAAwE;QACxE,wEAAwE;QACxE,kDAAkD;QAClD,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;QAEjD,yEAAyE;QACzE,gDAAgD;QAChD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAA;QACpC,IAAI,CAAC,IAAI;YAAE,MAAM,CAAC,4CAA4C,CAAC,CAAA;QAE/D,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;YAChB,MAAM,UAAU,CAAC,KAAK,EAAE,CAAA;YACxB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QACtB,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5B,CAAC;IACH,CAAC,CAAA;IAED,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAA;IAC9C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAA;IAChD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,KAAK,IAAI,EAAE,CAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed-in state of the server.
|
|
3
|
+
*
|
|
4
|
+
* Credentials used to be part of the configuration, read once at startup. That
|
|
5
|
+
* no longer works: with the web interface they can arrive at any time, and they
|
|
6
|
+
* can be revoked while the server keeps running. This is the one place that
|
|
7
|
+
* knows whether we are signed in.
|
|
8
|
+
*
|
|
9
|
+
* It also decides where credentials come from on startup, in this order:
|
|
10
|
+
*
|
|
11
|
+
* 1. Environment variables, when set. An explicit setting is never overruled.
|
|
12
|
+
* 2. The plain file, which is the interim path and the one existing setups use.
|
|
13
|
+
* 3. The system keyring, if something was stored there.
|
|
14
|
+
*
|
|
15
|
+
* The encrypted file is deliberately not in that list, because it cannot be:
|
|
16
|
+
* decrypting it needs a master password, and there is nowhere to ask for one
|
|
17
|
+
* while a stdio server starts up. It is instead reported as **locked**, which
|
|
18
|
+
* the web interface turns into a page asking for that one password. This is
|
|
19
|
+
* the whole reason `locked()` and `unlock()` exist rather than a fourth entry
|
|
20
|
+
* in the loop above.
|
|
21
|
+
*/
|
|
22
|
+
import type { Credentials, CredentialStore, StoreKind } from './credentials/store.js';
|
|
23
|
+
export interface SessionOptions {
|
|
24
|
+
/** Credentials from the environment, when they were set. */
|
|
25
|
+
fromEnvironment?: Credentials;
|
|
26
|
+
/** Where notices go. stderr, never stdout, which carries the protocol. */
|
|
27
|
+
notify?: (message: string) => void;
|
|
28
|
+
/** Replaces the stores. Used by tests. */
|
|
29
|
+
stores?: Partial<Record<StoreKind, CredentialStore>>;
|
|
30
|
+
}
|
|
31
|
+
export declare class Session {
|
|
32
|
+
#private;
|
|
33
|
+
constructor(options?: SessionOptions);
|
|
34
|
+
get storeKind(): StoreKind | undefined;
|
|
35
|
+
get signedIn(): boolean;
|
|
36
|
+
/** The address we are signed in as, for display. Never the password. */
|
|
37
|
+
get address(): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the credentials, restoring them from a store on first use.
|
|
40
|
+
*
|
|
41
|
+
* Restoring happens here rather than at startup so that a locked keyring
|
|
42
|
+
* cannot block the server from starting. It costs one attempt on the first
|
|
43
|
+
* tool call instead.
|
|
44
|
+
*/
|
|
45
|
+
credentials(): Promise<Credentials | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* Whether credentials are stored that only a master password can open.
|
|
48
|
+
*
|
|
49
|
+
* Distinct from "not signed in": the user does not need to fetch their Bridge
|
|
50
|
+
* password from the Bridge again, they need to type one thing. A page that
|
|
51
|
+
* asked for everything would make the encrypted option feel broken.
|
|
52
|
+
*/
|
|
53
|
+
locked(): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Opens the encrypted file with the given master password.
|
|
56
|
+
*
|
|
57
|
+
* Returns the credentials so the caller can verify them against the Bridge.
|
|
58
|
+
* On failure the password is dropped again: keeping a wrong one would make
|
|
59
|
+
* every later attempt fail with a message about the file instead of about
|
|
60
|
+
* the password.
|
|
61
|
+
*/
|
|
62
|
+
unlock(masterPassword: string): Promise<Credentials>;
|
|
63
|
+
/**
|
|
64
|
+
* Throws away the encrypted file without opening it.
|
|
65
|
+
*
|
|
66
|
+
* The way out for someone who forgot their master password. Without it they
|
|
67
|
+
* would be stuck on the unlock page for good, because nothing anywhere can
|
|
68
|
+
* decrypt that file. What is destroyed is a Bridge password that can be
|
|
69
|
+
* looked up in the Bridge again, so this is recoverable in the only sense
|
|
70
|
+
* that matters.
|
|
71
|
+
*/
|
|
72
|
+
discardEncrypted(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Stores credentials and marks the session as signed in.
|
|
75
|
+
*
|
|
76
|
+
* The caller has already verified them against the Bridge: storing something
|
|
77
|
+
* that does not work would be worse than not storing it.
|
|
78
|
+
*/
|
|
79
|
+
signIn(credentials: Credentials, storeKind: StoreKind,
|
|
80
|
+
/** Required for the encrypted file, meaningless for every other store. */
|
|
81
|
+
masterPassword?: string): Promise<void>;
|
|
82
|
+
/** Forgets the credentials everywhere they might be. */
|
|
83
|
+
signOut(): Promise<void>;
|
|
84
|
+
/** Replaces a store. Used by tests and by the startup wiring. */
|
|
85
|
+
setStore(kind: StoreKind, store: CredentialStore): void;
|
|
86
|
+
}
|