@zeph-to/mcp-server 1.16.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/api-client.d.ts +7 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +16 -0
- package/dist/crypto.d.ts +65 -42
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +169 -149
- package/dist/e2e-fallback.d.ts +18 -5
- package/dist/e2e-fallback.d.ts.map +1 -1
- package/dist/e2e-fallback.js +38 -11
- package/dist/index.js +7 -2
- package/dist/response-files.d.ts +36 -0
- package/dist/response-files.d.ts.map +1 -0
- package/dist/response-files.js +75 -0
- package/dist/tools/ask.d.ts.map +1 -1
- package/dist/tools/ask.js +21 -25
- package/dist/tools/file.d.ts.map +1 -1
- package/dist/tools/file.js +45 -33
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +8 -2
- package/dist/tools/notify.d.ts.map +1 -1
- package/dist/tools/notify.js +44 -40
- package/dist/types.d.ts +15 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,6 +205,16 @@ fallback: "skip" (auto-select on timeout, optional)
|
|
|
205
205
|
|
|
206
206
|
Returns: `{ actionId: "fix", timedOut: false }` or `{ value: "custom text", timedOut: false }`
|
|
207
207
|
|
|
208
|
+
The user can also attach screenshots or files to their answer. Those are downloaded to `~/.zeph/attachments/hook-<eventId>/` and the result gains an `attachments` array of absolute local paths, alongside the button or the text:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
{ value: "look at this", attachments: ["/Users/you/.zeph/attachments/hook-hevt_1/screen.png"],
|
|
212
|
+
attachmentsNote: "The user attached 1 file(s) to this answer. Read each path above to see them.",
|
|
213
|
+
timedOut: false }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Reading those paths is part of reading the answer. Note that hook attachments are never end-to-end encrypted — the same limitation as the question itself, since the hook route carries no sender key.
|
|
217
|
+
|
|
208
218
|
### zeph_input
|
|
209
219
|
|
|
210
220
|
Request free-form text input from the user. Blocks until response or timeout.
|
|
@@ -219,7 +229,7 @@ inputType: "text" | "password" | "multiline"
|
|
|
219
229
|
timeout: 120 (seconds, default: 120, max: 600)
|
|
220
230
|
```
|
|
221
231
|
|
|
222
|
-
Returns: `{ value: "feat: add clipboard sync", timedOut: false }`
|
|
232
|
+
Returns: `{ value: "feat: add clipboard sync", timedOut: false }` — plus `attachments` when the user attached files, exactly as in `zeph_ask` above.
|
|
223
233
|
|
|
224
234
|
### Client timeouts
|
|
225
235
|
|
|
@@ -329,9 +339,16 @@ Create an API key with the **MCP** preset in Settings > API Keys for the correct
|
|
|
329
339
|
|
|
330
340
|
## Encryption
|
|
331
341
|
|
|
332
|
-
Push bodies are encrypted with AES-256-GCM.
|
|
342
|
+
Push bodies and file attachments are encrypted with AES-256-GCM. This server holds its own ECDH P-256 keypair, generated on first use and stored in `~/.config/zeph/device-keys.json` — the private half never leaves the machine. Each push is encrypted once, and its AES key is wrapped separately for every device on your account using ECDH against that device's public key.
|
|
343
|
+
|
|
344
|
+
Toggle encryption in the Zeph app (Settings → Encryption); when it is off, pushes go out as plaintext. No configuration needed. The opt-in is read once at startup, so **turning it on while this server is running takes effect only after a restart.**
|
|
345
|
+
|
|
346
|
+
**Threat model:** against a passive backend — a leaked snapshot, an operator reading the table — the stored ciphertext and wrapped keys are useless, so push contents stay private. Three limits worth knowing:
|
|
347
|
+
- **No protection from an active malicious operator.** Recipient public keys come from `GET /devices` on that same server, unsigned and unpinned. A backend that injects a device record carrying its own key gets the message key wrapped for it, and reads everything. Closing this needs out-of-band device verification (ADR-0007 Phase 4, not built).
|
|
348
|
+
- **No forward secrecy.** The ECDH secret for a given sender/device pair is static, so compromising either private key opens every past push wrapped for that pair.
|
|
349
|
+
- **`senderPublicKey` is unsigned**, so a swapped one makes a push undecryptable — that direction fails closed rather than leaking.
|
|
333
350
|
|
|
334
|
-
|
|
351
|
+
A device that has not registered a per-device public key cannot be sent to; it is skipped, and if no device qualifies the push goes out in the clear rather than arriving as something nothing can open.
|
|
335
352
|
|
|
336
353
|
## License
|
|
337
354
|
|
package/dist/api-client.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export declare class ZephApiClient {
|
|
|
20
20
|
sessionId?: string;
|
|
21
21
|
files?: AttachedFile[];
|
|
22
22
|
isEncrypted?: boolean;
|
|
23
|
-
encryptedKey?: string;
|
|
24
23
|
senderPublicKey?: string;
|
|
24
|
+
deviceKeyMap?: Record<string, string>;
|
|
25
25
|
}): Promise<PushResponse>;
|
|
26
26
|
triggerHook(hookId: string, params: {
|
|
27
27
|
title: string;
|
|
@@ -57,6 +57,12 @@ export declare class ZephApiClient {
|
|
|
57
57
|
fileType: string;
|
|
58
58
|
fileSize: number;
|
|
59
59
|
}): Promise<UploadRequestResponse>;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a fileKey to its bytes. Two hops by design: the API hands back a
|
|
62
|
+
* presigned URL (files never travel through Lambda), and that URL is
|
|
63
|
+
* self-authenticating, so the second fetch carries no API key.
|
|
64
|
+
*/
|
|
65
|
+
downloadFile(fileKey: string): Promise<Uint8Array>;
|
|
60
66
|
uploadToS3(url: string, content: string | Buffer, contentType: string): Promise<void>;
|
|
61
67
|
private request;
|
|
62
68
|
}
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAEV,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,0BAA0B,EAC1B,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/api-client.js
CHANGED
|
@@ -59,6 +59,22 @@ class ZephApiClient {
|
|
|
59
59
|
async requestUpload(params) {
|
|
60
60
|
return this.request('POST', '/files/upload-request', params);
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a fileKey to its bytes. Two hops by design: the API hands back a
|
|
64
|
+
* presigned URL (files never travel through Lambda), and that URL is
|
|
65
|
+
* self-authenticating, so the second fetch carries no API key.
|
|
66
|
+
*/
|
|
67
|
+
async downloadFile(fileKey) {
|
|
68
|
+
const meta = await this.request('GET', `/files/${encodeURIComponent(fileKey)}`);
|
|
69
|
+
const downloadUrl = meta.data?.downloadUrl;
|
|
70
|
+
if (!downloadUrl)
|
|
71
|
+
throw new ApiError('Download URL missing', 'DOWNLOAD_FAILED', 502);
|
|
72
|
+
const response = await fetch(downloadUrl, { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) });
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new ApiError(`File download failed with status ${response.status}`, 'DOWNLOAD_FAILED', response.status);
|
|
75
|
+
}
|
|
76
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
77
|
+
}
|
|
62
78
|
async uploadToS3(url, content, contentType) {
|
|
63
79
|
const isText = typeof content === 'string';
|
|
64
80
|
const body = isText ? content : new Uint8Array(content);
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,57 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Per-device encryption for the MCP server — self-contained ECDH P-256 +
|
|
3
3
|
* AES-256-GCM. Mirrors @zeph/crypto API but bundled inline (no external
|
|
4
4
|
* dependency). Uses Web Crypto API via node:crypto webcrypto — Node.js 18+
|
|
5
5
|
* (the `crypto` global only exists unflagged from Node 19, so we import it).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* How it works (ADR-0007):
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* • Protection against a leaked DB snapshot taken without the key store
|
|
15
|
-
* • Cross-device readability (all your devices share one keypair)
|
|
16
|
-
* What it does NOT give you:
|
|
17
|
-
* • Protection against the Zeph backend itself
|
|
18
|
-
* • Forward secrecy — encryptPushBodyForSelf / encryptFileForSelf do
|
|
19
|
-
* ECDH(self, self), which collapses to a static derived key. A single
|
|
20
|
-
* device compromise (since all your devices share the same keypair)
|
|
21
|
-
* lets the attacker decrypt every past push for which they have the
|
|
22
|
-
* ciphertext. The per-message AES key is random, but its wrap key is
|
|
23
|
-
* static, so wrapped keys are decryptable forever.
|
|
9
|
+
* This process holds its own ECDH keypair. The private half is generated
|
|
10
|
+
* here, written to ~/.config/zeph/device-keys.json, and never leaves the
|
|
11
|
+
* host — the server only ever sees public keys. A push is encrypted once
|
|
12
|
+
* with a random AES key, and that key is wrapped separately for each of the
|
|
13
|
+
* user's registered devices using ECDH(this host, that device).
|
|
24
14
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
15
|
+
* That makes it end-to-end in the standard sense: the backend stores
|
|
16
|
+
* ciphertext plus wrapped keys it cannot unwrap.
|
|
17
|
+
*
|
|
18
|
+
* What it still does not give you:
|
|
19
|
+
* • Forward secrecy — the ECDH secret for a given (sender, device) pair is
|
|
20
|
+
* static, so a compromise of either private key retroactively opens every
|
|
21
|
+
* push wrapped for that pair. The per-message AES key is random; its wrap
|
|
22
|
+
* key is not.
|
|
23
|
+
* • Authenticity beyond the key pairing — nothing signs `senderPublicKey`,
|
|
24
|
+
* so a server that swapped it could make a push undecryptable, though not
|
|
25
|
+
* readable.
|
|
26
|
+
*
|
|
27
|
+
* Superseded scheme: a single account-wide keypair whose private half the
|
|
28
|
+
* backend escrowed so it could sync to new devices. Key escrow was removed
|
|
29
|
+
* server-side (zeph@8a6d21b), which left this client waiting for a private key
|
|
30
|
+
* the API stopped returning — encryption was silently off for months. Nothing
|
|
31
|
+
* here asks for that key any more.
|
|
29
32
|
*/
|
|
33
|
+
/** One target device, as returned by `GET /devices`. */
|
|
34
|
+
export interface DeviceRecipient {
|
|
35
|
+
deviceId: string;
|
|
36
|
+
/** Base64 SPKI of that device's per-device public key. */
|
|
37
|
+
publicKey: string;
|
|
38
|
+
}
|
|
39
|
+
/** deviceId → JSON `{ encryptedKey, keyIv }`, the wire shape the clients parse. */
|
|
40
|
+
export type DeviceKeyMap = Record<string, string>;
|
|
30
41
|
/**
|
|
31
42
|
* Initialize crypto.
|
|
32
43
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
44
|
+
* Encryption turns on only when the account has explicitly opted in —
|
|
45
|
+
* `encryptionEnabled` from `GET /users/me/keys` is the single authoritative
|
|
46
|
+
* signal (ADR-0008), set from the Zeph app. Server unreachable, flag off, or
|
|
47
|
+
* the hard opt-out below all leave the cache empty and every send plaintext.
|
|
37
48
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* anywhere" path; combined with a transient fetch failure, that silently
|
|
42
|
-
* turned encryption on without user consent and locked the account into
|
|
43
|
-
* an "encryption enabled" state on the server.
|
|
49
|
+
* When it is on, this host generates its own keypair on first use and keeps
|
|
50
|
+
* it. Unlike the superseded scheme this asks the server for nothing but the
|
|
51
|
+
* flag: the private key is created here and stays here.
|
|
44
52
|
*
|
|
45
53
|
* Opt-out: `ZEPH_DISABLE_ENCRYPTION=1` forces crypto off regardless of
|
|
46
|
-
* server state
|
|
47
|
-
* never want encryption.
|
|
54
|
+
* server state.
|
|
48
55
|
*
|
|
49
|
-
* Safe to call concurrently — deduplicates to single init.
|
|
50
|
-
* Returns
|
|
56
|
+
* Safe to call concurrently — deduplicates to a single init.
|
|
57
|
+
* Returns this host's public key when encryption is active, '' otherwise.
|
|
51
58
|
*
|
|
52
59
|
* NOTE: when `apiKey` is provided, `baseUrl` is required.
|
|
53
60
|
*/
|
|
54
61
|
export declare const initCrypto: (apiKey?: string, baseUrl?: string) => Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Keep only devices this host can actually encrypt for.
|
|
64
|
+
*
|
|
65
|
+
* A device without a public key has never run a build that registers one, and
|
|
66
|
+
* a device still advertising the account-wide key has not migrated to
|
|
67
|
+
* per-device E2E — wrapping for either produces a push it cannot open, which
|
|
68
|
+
* is worse than sending plaintext it can read.
|
|
69
|
+
*/
|
|
70
|
+
export declare const selectRecipients: (devices: {
|
|
71
|
+
deviceId: string;
|
|
72
|
+
publicKey?: string;
|
|
73
|
+
}[]) => DeviceRecipient[];
|
|
55
74
|
export declare const getKeyPair: () => CryptoKeyPair | null;
|
|
56
75
|
export declare const getPublicKey: () => string | null;
|
|
57
76
|
/**
|
|
@@ -65,24 +84,28 @@ export declare const getPublicKey: () => string | null;
|
|
|
65
84
|
*/
|
|
66
85
|
export declare const disableCrypto: () => void;
|
|
67
86
|
/**
|
|
68
|
-
* Encrypt push body for
|
|
87
|
+
* Encrypt a push body for the given recipient devices.
|
|
88
|
+
*
|
|
89
|
+
* Returns the wire fields the API expects: `body` carries the ciphertext and
|
|
90
|
+
* IV, `deviceKeyMap` the per-device wrapped keys, `senderPublicKey` the half
|
|
91
|
+
* recipients need to derive the same secret back.
|
|
69
92
|
*/
|
|
70
|
-
export declare const
|
|
93
|
+
export declare const encryptPushBodyForDevices: (input: {
|
|
71
94
|
title?: string;
|
|
72
95
|
body?: string;
|
|
73
96
|
url?: string;
|
|
74
|
-
}) => Promise<{
|
|
97
|
+
}, recipients: DeviceRecipient[]) => Promise<{
|
|
75
98
|
body: string;
|
|
76
|
-
|
|
99
|
+
deviceKeyMap: DeviceKeyMap;
|
|
77
100
|
senderPublicKey: string;
|
|
78
101
|
isEncrypted: true;
|
|
79
102
|
}>;
|
|
80
103
|
/**
|
|
81
|
-
* Encrypt file content for
|
|
104
|
+
* Encrypt file content for the given recipient devices.
|
|
82
105
|
*/
|
|
83
|
-
export declare const
|
|
106
|
+
export declare const encryptFileForDevices: (content: string | Buffer, recipients: DeviceRecipient[]) => Promise<{
|
|
84
107
|
ciphertext: Buffer;
|
|
85
108
|
iv: string;
|
|
86
|
-
|
|
109
|
+
deviceKeyMap: DeviceKeyMap;
|
|
87
110
|
}>;
|
|
88
111
|
//# sourceMappingURL=crypto.d.ts.map
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAuEH,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA6ElD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CA2D5E,CAAC;AAuCF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAClD,eAAe,EAG8C,CAAC;AAEjE,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAqB,CAAC;AACpE,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA+B,CAAC;AAEzE;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,QAAO,IAGhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAkBA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,GAAG,MAAM,EACxB,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,CAsBxE,CAAC"}
|