@zeph-to/mcp-server 1.4.0 → 1.5.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 +22 -3
- package/dist/crypto.d.ts +0 -23
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +20 -43
- package/dist/tools/dismiss.js +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,11 +49,11 @@ Add to `~/.claude/settings.json`:
|
|
|
49
49
|
| Variable | Required | Description |
|
|
50
50
|
|----------|----------|-------------|
|
|
51
51
|
| `ZEPH_API_KEY` | Yes* | API key from Settings > API Keys |
|
|
52
|
-
| `ZEPH_HOOK_ID` | No | Hook ID for interactive tools
|
|
53
|
-
| `ZEPH_DEVICE_ID` | No | Target device ID. Omit to send to all devices |
|
|
52
|
+
| `ZEPH_HOOK_ID` | No | Hook ID (optional — only needed for interactive tools like `zeph_ask`/`zeph_prompt`/`zeph_input`) |
|
|
53
|
+
| `ZEPH_DEVICE_ID` | No | Target device ID (optional — only needed for interactive tools like `zeph_ask`/`zeph_prompt`/`zeph_input`). Omit to send to all devices |
|
|
54
54
|
| `ZEPH_BASE_URL` | No | API base URL (default: `https://api.zeph.to/v1`) |
|
|
55
55
|
|
|
56
|
-
\*
|
|
56
|
+
\* If env vars are not set, the server reads from `~/.zeph/config.json` (created by `npx @zeph-to/hook-sdk install`). Unresolved `${...}` interpolations are also treated as unset.
|
|
57
57
|
|
|
58
58
|
## Tools
|
|
59
59
|
|
|
@@ -145,6 +145,25 @@ fallback: "no" (auto-select on timeout, optional)
|
|
|
145
145
|
|
|
146
146
|
Returns: `{ actionId: "yes", timedOut: false }`
|
|
147
147
|
|
|
148
|
+
### zeph_ask
|
|
149
|
+
|
|
150
|
+
Ask the user a question with optional quick-reply buttons and a text input field. Combines prompt (buttons) and input (text) in a single notification. Blocks until response or timeout.
|
|
151
|
+
|
|
152
|
+
Requires `ZEPH_HOOK_ID`.
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
title: "What should we do?"
|
|
156
|
+
body: "3 tests failed in auth module" (optional)
|
|
157
|
+
actions: [{ id: "fix", label: "Fix now", style: "primary" },
|
|
158
|
+
{ id: "skip", label: "Skip", style: "secondary" }] (optional, 1-4)
|
|
159
|
+
placeholder: "Or type a custom response..." (optional)
|
|
160
|
+
inputType: "text" | "multiline" (default: text)
|
|
161
|
+
timeout: 120 (seconds, default: 120, max: 600)
|
|
162
|
+
fallback: "skip" (auto-select on timeout, optional)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Returns: `{ actionId: "fix", timedOut: false }` or `{ value: "custom text", timedOut: false }`
|
|
166
|
+
|
|
148
167
|
### zeph_input
|
|
149
168
|
|
|
150
169
|
Request free-form text input from the user. Blocks until response or timeout.
|
package/dist/crypto.d.ts
CHANGED
|
@@ -12,20 +12,6 @@
|
|
|
12
12
|
export declare const initCrypto: (apiKey?: string, baseUrl?: string) => Promise<string>;
|
|
13
13
|
export declare const getKeyPair: () => CryptoKeyPair | null;
|
|
14
14
|
export declare const getPublicKey: () => string | null;
|
|
15
|
-
/**
|
|
16
|
-
* Encrypt push body for a recipient.
|
|
17
|
-
* Returns fields ready to merge into the sendPush payload.
|
|
18
|
-
*/
|
|
19
|
-
export declare const encryptPushBody: (input: {
|
|
20
|
-
title?: string;
|
|
21
|
-
body?: string;
|
|
22
|
-
url?: string;
|
|
23
|
-
}, recipientPublicKeyRaw: string) => Promise<{
|
|
24
|
-
body: string;
|
|
25
|
-
encryptedKey: string;
|
|
26
|
-
senderPublicKey: string;
|
|
27
|
-
isEncrypted: true;
|
|
28
|
-
}>;
|
|
29
15
|
/**
|
|
30
16
|
* Encrypt push body for self (all own devices).
|
|
31
17
|
*/
|
|
@@ -39,15 +25,6 @@ export declare const encryptPushBodyForSelf: (input: {
|
|
|
39
25
|
senderPublicKey: string;
|
|
40
26
|
isEncrypted: true;
|
|
41
27
|
}>;
|
|
42
|
-
/**
|
|
43
|
-
* Encrypt file content for a recipient.
|
|
44
|
-
* Returns encrypted buffer + key material for file attachment metadata.
|
|
45
|
-
*/
|
|
46
|
-
export declare const encryptFileForRecipient: (content: string, recipientPublicKeyRaw: string) => Promise<{
|
|
47
|
-
ciphertext: Buffer;
|
|
48
|
-
iv: string;
|
|
49
|
-
encryptedKey: string;
|
|
50
|
-
}>;
|
|
51
28
|
/**
|
|
52
29
|
* Encrypt file content for self (all own devices).
|
|
53
30
|
*/
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0JH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0JH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAiE5E,CAAC;AAqCF,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAqB,CAAC;AACpE,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA+B,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KACrD,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAaA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,KACd,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAQlE,CAAC"}
|
package/dist/crypto.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Uses Web Crypto API (globalThis.crypto.subtle) — Node.js 18+.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.encryptFileForSelf = exports.
|
|
8
|
+
exports.encryptFileForSelf = exports.encryptPushBodyForSelf = exports.getPublicKey = exports.getKeyPair = exports.initCrypto = void 0;
|
|
9
9
|
/// <reference lib="dom" />
|
|
10
10
|
const fs_1 = require("fs");
|
|
11
11
|
const path_1 = require("path");
|
|
@@ -111,15 +111,22 @@ const initCrypto = (apiKey, baseUrl) => {
|
|
|
111
111
|
const stored = loadStoredKeys();
|
|
112
112
|
// Try server sync if API key available
|
|
113
113
|
if (apiKey) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
const serverResult = await fetchServerKeys(apiKey, baseUrl);
|
|
115
|
+
// Server says encryption disabled — skip crypto init
|
|
116
|
+
if (serverResult && !serverResult.encryptionEnabled) {
|
|
117
|
+
cachedKeyPair = null;
|
|
118
|
+
cachedExportedPublicKey = null;
|
|
119
|
+
cachedOwnPublicKey = null;
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
if (serverResult?.keys) {
|
|
123
|
+
if (!stored || stored.publicKey !== serverResult.keys.publicKey) {
|
|
124
|
+
storeKeys(serverResult.keys);
|
|
118
125
|
}
|
|
119
|
-
cachedKeyPair = await importKeyPair(
|
|
120
|
-
cachedExportedPublicKey =
|
|
126
|
+
cachedKeyPair = await importKeyPair(serverResult.keys);
|
|
127
|
+
cachedExportedPublicKey = serverResult.keys.publicKey;
|
|
121
128
|
cachedOwnPublicKey = cachedKeyPair.publicKey;
|
|
122
|
-
return
|
|
129
|
+
return serverResult.keys.publicKey;
|
|
123
130
|
}
|
|
124
131
|
if (stored) {
|
|
125
132
|
await uploadServerKeys(stored, apiKey, baseUrl);
|
|
@@ -158,7 +165,6 @@ const initCrypto = (apiKey, baseUrl) => {
|
|
|
158
165
|
return initPromise;
|
|
159
166
|
};
|
|
160
167
|
exports.initCrypto = initCrypto;
|
|
161
|
-
// ─── Server key sync helpers ───
|
|
162
168
|
const fetchServerKeys = async (apiKey, baseUrl) => {
|
|
163
169
|
try {
|
|
164
170
|
const url = `${(baseUrl ?? 'https://api.zeph.to/v1').replace(/\/$/, '')}/users/me/keys`;
|
|
@@ -167,7 +173,11 @@ const fetchServerKeys = async (apiKey, baseUrl) => {
|
|
|
167
173
|
return null;
|
|
168
174
|
const json = await res.json();
|
|
169
175
|
const keys = json.data?.encryptionKeys;
|
|
170
|
-
|
|
176
|
+
const encryptionEnabled = json.data?.encryptionEnabled ?? (keys ? true : false);
|
|
177
|
+
return {
|
|
178
|
+
keys: keys?.publicKey && keys?.privateKey ? keys : null,
|
|
179
|
+
encryptionEnabled,
|
|
180
|
+
};
|
|
171
181
|
}
|
|
172
182
|
catch {
|
|
173
183
|
return null;
|
|
@@ -188,23 +198,6 @@ const getKeyPair = () => cachedKeyPair;
|
|
|
188
198
|
exports.getKeyPair = getKeyPair;
|
|
189
199
|
const getPublicKey = () => cachedExportedPublicKey;
|
|
190
200
|
exports.getPublicKey = getPublicKey;
|
|
191
|
-
/**
|
|
192
|
-
* Encrypt push body for a recipient.
|
|
193
|
-
* Returns fields ready to merge into the sendPush payload.
|
|
194
|
-
*/
|
|
195
|
-
const encryptPushBody = async (input, recipientPublicKeyRaw) => {
|
|
196
|
-
if (!cachedKeyPair || !cachedExportedPublicKey)
|
|
197
|
-
throw new Error('Crypto not initialized');
|
|
198
|
-
const recipientKey = await importPublicKey(recipientPublicKeyRaw);
|
|
199
|
-
const payload = await encrypt(JSON.stringify({ title: input.title, body: input.body, url: input.url }), cachedKeyPair.privateKey, recipientKey);
|
|
200
|
-
return {
|
|
201
|
-
body: JSON.stringify({ ciphertext: payload.ciphertext, iv: payload.iv }),
|
|
202
|
-
encryptedKey: JSON.stringify({ encryptedKey: payload.encryptedKey, keyIv: payload.keyIv }),
|
|
203
|
-
senderPublicKey: cachedExportedPublicKey,
|
|
204
|
-
isEncrypted: true,
|
|
205
|
-
};
|
|
206
|
-
};
|
|
207
|
-
exports.encryptPushBody = encryptPushBody;
|
|
208
201
|
/**
|
|
209
202
|
* Encrypt push body for self (all own devices).
|
|
210
203
|
*/
|
|
@@ -220,22 +213,6 @@ const encryptPushBodyForSelf = async (input) => {
|
|
|
220
213
|
};
|
|
221
214
|
};
|
|
222
215
|
exports.encryptPushBodyForSelf = encryptPushBodyForSelf;
|
|
223
|
-
/**
|
|
224
|
-
* Encrypt file content for a recipient.
|
|
225
|
-
* Returns encrypted buffer + key material for file attachment metadata.
|
|
226
|
-
*/
|
|
227
|
-
const encryptFileForRecipient = async (content, recipientPublicKeyRaw) => {
|
|
228
|
-
if (!cachedKeyPair)
|
|
229
|
-
throw new Error('Crypto not initialized');
|
|
230
|
-
const recipientKey = await importPublicKey(recipientPublicKeyRaw);
|
|
231
|
-
const result = await encryptFileContent(content, cachedKeyPair.privateKey, recipientKey);
|
|
232
|
-
return {
|
|
233
|
-
ciphertext: result.ciphertext,
|
|
234
|
-
iv: result.iv,
|
|
235
|
-
encryptedKey: JSON.stringify({ encryptedKey: result.encryptedKey, keyIv: result.keyIv }),
|
|
236
|
-
};
|
|
237
|
-
};
|
|
238
|
-
exports.encryptFileForRecipient = encryptFileForRecipient;
|
|
239
216
|
/**
|
|
240
217
|
* Encrypt file content for self (all own devices).
|
|
241
218
|
*/
|
package/dist/tools/dismiss.js
CHANGED
|
@@ -31,7 +31,7 @@ const registerDismissAllTool = (server, client) => {
|
|
|
31
31
|
description: 'Dismiss all push notifications at once. Clears the entire notification feed.',
|
|
32
32
|
annotations: {
|
|
33
33
|
readOnlyHint: false,
|
|
34
|
-
destructiveHint:
|
|
34
|
+
destructiveHint: true,
|
|
35
35
|
idempotentHint: true,
|
|
36
36
|
openWorldHint: true,
|
|
37
37
|
},
|
package/dist/tools/list.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/tools/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,eAAO,MAAM,gBAAgB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/tools/list.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,eAAO,MAAM,gBAAgB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,SAyCxE,CAAC"}
|
package/dist/tools/list.js
CHANGED