@zeph-to/mcp-server 1.3.0 → 1.4.1

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 CHANGED
@@ -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.
@@ -39,7 +39,7 @@ export declare class ZephApiClient {
39
39
  timeout?: number;
40
40
  fallback?: string;
41
41
  metadata?: Record<string, unknown>;
42
- hookType?: 'one-way' | 'interactive' | 'input';
42
+ hookType?: 'one-way' | 'interactive' | 'input' | 'combo';
43
43
  }): Promise<HookTriggerResponse>;
44
44
  getHookEvent(hookId: string, eventId: string): Promise<HookEventResponse>;
45
45
  listDevices(): Promise<DevicesResponse>;
@@ -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,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB,qBAAa,QAAS,SAAQ,KAAK;aAGf,IAAI,EAAE,MAAM;aACZ,MAAM,EAAE,MAAM;gBAF9B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM;CAKjC;AAKD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,eAAe;IAK7B,QAAQ,CAAC,MAAM,EAAE;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,EAAE,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACxH,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,GAAG,OAAO,CAAC,YAAY,CAAC;IAInB,WAAW,CACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,QAAQ,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,OAAO,CAAC;KAChD,GACA,OAAO,CAAC,mBAAmB,CAAC;IAIzB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIzE,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;IAIvC,UAAU,CAAC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAQjF,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAIrD,gBAAgB,IAAI,OAAO,CAAC,eAAe,CAAC;IAI5C,YAAY,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIzC,aAAa,CAAC,MAAM,EAAE;QAC1B,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI5B,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAc7E,OAAO;CAsCtB"}
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,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB,qBAAa,QAAS,SAAQ,KAAK;aAGf,IAAI,EAAE,MAAM;aACZ,MAAM,EAAE,MAAM;gBAF9B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM;CAKjC;AAKD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,eAAe;IAK7B,QAAQ,CAAC,MAAM,EAAE;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,EAAE,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACxH,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,GAAG,OAAO,CAAC,YAAY,CAAC;IAInB,WAAW,CACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnC,QAAQ,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,OAAO,CAAC;KAC1D,GACA,OAAO,CAAC,mBAAmB,CAAC;IAIzB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIzE,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;IAIvC,UAAU,CAAC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAQjF,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAIrD,gBAAgB,IAAI,OAAO,CAAC,eAAe,CAAC;IAI5C,YAAY,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAIzC,aAAa,CAAC,MAAM,EAAE;QAC1B,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI5B,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAc7E,OAAO;CAsCtB"}
package/dist/crypto.d.ts CHANGED
@@ -4,27 +4,14 @@
4
4
  * Uses Web Crypto API (globalThis.crypto.subtle) — Node.js 18+.
5
5
  */
6
6
  /**
7
- * Initialize crypto: load or generate ECDH key pair.
7
+ * Initialize crypto: sync keys with server, then fallback to local/generate.
8
+ * Server is source of truth for per-user key pair.
8
9
  * Safe to call concurrently — deduplicates to single init.
9
10
  * Returns the exported public key (Base64 SPKI).
10
11
  */
11
- export declare const initCrypto: () => Promise<string>;
12
+ export declare const initCrypto: (apiKey?: string, baseUrl?: string) => Promise<string>;
12
13
  export declare const getKeyPair: () => CryptoKeyPair | null;
13
14
  export declare const getPublicKey: () => string | null;
14
- /**
15
- * Encrypt push body for a recipient.
16
- * Returns fields ready to merge into the sendPush payload.
17
- */
18
- export declare const encryptPushBody: (input: {
19
- title?: string;
20
- body?: string;
21
- url?: string;
22
- }, recipientPublicKeyRaw: string) => Promise<{
23
- body: string;
24
- encryptedKey: string;
25
- senderPublicKey: string;
26
- isEncrypted: true;
27
- }>;
28
15
  /**
29
16
  * Encrypt push body for self (all own devices).
30
17
  */
@@ -38,15 +25,6 @@ export declare const encryptPushBodyForSelf: (input: {
38
25
  senderPublicKey: string;
39
26
  isEncrypted: true;
40
27
  }>;
41
- /**
42
- * Encrypt file content for a recipient.
43
- * Returns encrypted buffer + key material for file attachment metadata.
44
- */
45
- export declare const encryptFileForRecipient: (content: string, recipientPublicKeyRaw: string) => Promise<{
46
- ciphertext: Buffer;
47
- iv: string;
48
- encryptedKey: string;
49
- }>;
50
28
  /**
51
29
  * Encrypt file content for self (all own devices).
52
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0JH;;;;GAIG;AACH,eAAO,MAAM,UAAU,QAAO,OAAO,CAAC,MAAM,CAoB3C,CAAC;AAEF,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAqB,CAAC;AACpE,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA+B,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,uBAAuB,MAAM,KAC5B,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAeA,CAAC;AAEF;;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;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,MAAM,EACf,uBAAuB,MAAM,KAC5B,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CASlE,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"}
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,CAwD5E,CAAC;AA4BF,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.encryptFileForRecipient = exports.encryptPushBodyForSelf = exports.encryptPushBody = exports.getPublicKey = exports.getKeyPair = exports.initCrypto = void 0;
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");
@@ -90,7 +90,7 @@ const loadStoredKeys = () => {
90
90
  }
91
91
  };
92
92
  const storeKeys = (exported) => {
93
- (0, fs_1.mkdirSync)(KEYS_DIR, { recursive: true });
93
+ (0, fs_1.mkdirSync)(KEYS_DIR, { recursive: true, mode: 0o700 });
94
94
  (0, fs_1.writeFileSync)(KEYS_PATH, JSON.stringify(exported, null, 2), { mode: 0o600 });
95
95
  };
96
96
  // ─── Cached state ───
@@ -99,15 +99,45 @@ let cachedExportedPublicKey = null;
99
99
  let cachedOwnPublicKey = null;
100
100
  let initPromise = null;
101
101
  /**
102
- * Initialize crypto: load or generate ECDH key pair.
102
+ * Initialize crypto: sync keys with server, then fallback to local/generate.
103
+ * Server is source of truth for per-user key pair.
103
104
  * Safe to call concurrently — deduplicates to single init.
104
105
  * Returns the exported public key (Base64 SPKI).
105
106
  */
106
- const initCrypto = () => {
107
+ const initCrypto = (apiKey, baseUrl) => {
107
108
  if (initPromise)
108
109
  return initPromise;
109
110
  initPromise = (async () => {
110
111
  const stored = loadStoredKeys();
112
+ // Try server sync if API key available
113
+ if (apiKey) {
114
+ const serverKeys = await fetchServerKeys(apiKey, baseUrl);
115
+ if (serverKeys) {
116
+ if (!stored || stored.publicKey !== serverKeys.publicKey) {
117
+ storeKeys(serverKeys);
118
+ }
119
+ cachedKeyPair = await importKeyPair(serverKeys);
120
+ cachedExportedPublicKey = serverKeys.publicKey;
121
+ cachedOwnPublicKey = cachedKeyPair.publicKey;
122
+ return serverKeys.publicKey;
123
+ }
124
+ if (stored) {
125
+ await uploadServerKeys(stored, apiKey, baseUrl);
126
+ cachedKeyPair = await importKeyPair(stored);
127
+ cachedExportedPublicKey = stored.publicKey;
128
+ cachedOwnPublicKey = cachedKeyPair.publicKey;
129
+ return stored.publicKey;
130
+ }
131
+ const keyPair = await generateKeyPair();
132
+ const exported = await exportKeyPair(keyPair);
133
+ storeKeys(exported);
134
+ await uploadServerKeys(exported, apiKey, baseUrl);
135
+ cachedKeyPair = keyPair;
136
+ cachedExportedPublicKey = exported.publicKey;
137
+ cachedOwnPublicKey = keyPair.publicKey;
138
+ return exported.publicKey;
139
+ }
140
+ // No API key — local-only mode
111
141
  if (stored) {
112
142
  cachedKeyPair = await importKeyPair(stored);
113
143
  cachedExportedPublicKey = stored.publicKey;
@@ -121,31 +151,43 @@ const initCrypto = () => {
121
151
  cachedExportedPublicKey = exported.publicKey;
122
152
  cachedOwnPublicKey = keyPair.publicKey;
123
153
  return exported.publicKey;
124
- })();
154
+ })().catch((err) => {
155
+ initPromise = null;
156
+ throw err;
157
+ });
125
158
  return initPromise;
126
159
  };
127
160
  exports.initCrypto = initCrypto;
161
+ // ─── Server key sync helpers ───
162
+ const fetchServerKeys = async (apiKey, baseUrl) => {
163
+ try {
164
+ const url = `${(baseUrl ?? 'https://api.zeph.to/v1').replace(/\/$/, '')}/users/me/keys`;
165
+ const res = await fetch(url, { headers: { 'X-API-Key': apiKey } });
166
+ if (!res.ok)
167
+ return null;
168
+ const json = await res.json();
169
+ const keys = json.data?.encryptionKeys;
170
+ return keys?.publicKey && keys?.privateKey ? keys : null;
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ };
176
+ const uploadServerKeys = async (keys, apiKey, baseUrl) => {
177
+ try {
178
+ const url = `${(baseUrl ?? 'https://api.zeph.to/v1').replace(/\/$/, '')}/users/me/keys`;
179
+ await fetch(url, {
180
+ method: 'PUT',
181
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
182
+ body: JSON.stringify(keys),
183
+ });
184
+ }
185
+ catch { /* non-critical */ }
186
+ };
128
187
  const getKeyPair = () => cachedKeyPair;
129
188
  exports.getKeyPair = getKeyPair;
130
189
  const getPublicKey = () => cachedExportedPublicKey;
131
190
  exports.getPublicKey = getPublicKey;
132
- /**
133
- * Encrypt push body for a recipient.
134
- * Returns fields ready to merge into the sendPush payload.
135
- */
136
- const encryptPushBody = async (input, recipientPublicKeyRaw) => {
137
- if (!cachedKeyPair || !cachedExportedPublicKey)
138
- throw new Error('Crypto not initialized');
139
- const recipientKey = await importPublicKey(recipientPublicKeyRaw);
140
- const payload = await encrypt(JSON.stringify({ title: input.title, body: input.body, url: input.url }), cachedKeyPair.privateKey, recipientKey);
141
- return {
142
- body: JSON.stringify({ ciphertext: payload.ciphertext, iv: payload.iv }),
143
- encryptedKey: JSON.stringify({ encryptedKey: payload.encryptedKey, keyIv: payload.keyIv }),
144
- senderPublicKey: cachedExportedPublicKey,
145
- isEncrypted: true,
146
- };
147
- };
148
- exports.encryptPushBody = encryptPushBody;
149
191
  /**
150
192
  * Encrypt push body for self (all own devices).
151
193
  */
@@ -161,22 +203,6 @@ const encryptPushBodyForSelf = async (input) => {
161
203
  };
162
204
  };
163
205
  exports.encryptPushBodyForSelf = encryptPushBodyForSelf;
164
- /**
165
- * Encrypt file content for a recipient.
166
- * Returns encrypted buffer + key material for file attachment metadata.
167
- */
168
- const encryptFileForRecipient = async (content, recipientPublicKeyRaw) => {
169
- if (!cachedKeyPair)
170
- throw new Error('Crypto not initialized');
171
- const recipientKey = await importPublicKey(recipientPublicKeyRaw);
172
- const result = await encryptFileContent(content, cachedKeyPair.privateKey, recipientKey);
173
- return {
174
- ciphertext: result.ciphertext,
175
- iv: result.iv,
176
- encryptedKey: JSON.stringify({ encryptedKey: result.encryptedKey, keyIv: result.keyIv }),
177
- };
178
- };
179
- exports.encryptFileForRecipient = encryptFileForRecipient;
180
206
  /**
181
207
  * Encrypt file content for self (all own devices).
182
208
  */
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ const list_js_1 = require("./tools/list.js");
16
16
  const dismiss_js_1 = require("./tools/dismiss.js");
17
17
  const broadcast_js_1 = require("./tools/broadcast.js");
18
18
  const file_js_1 = require("./tools/file.js");
19
+ const ask_js_1 = require("./tools/ask.js");
19
20
  const devices_js_1 = require("./resources/devices.js");
20
21
  const channels_js_1 = require("./resources/channels.js");
21
22
  const getVersion = () => {
@@ -27,8 +28,7 @@ const getVersion = () => {
27
28
  return '0.0.0';
28
29
  }
29
30
  };
30
- const createServer = () => {
31
- const config = (0, config_js_1.loadConfig)();
31
+ const createServer = (config) => {
32
32
  const client = new api_client_js_1.ZephApiClient(config);
33
33
  const server = new mcp_js_1.McpServer({
34
34
  name: 'zeph',
@@ -47,6 +47,7 @@ const createServer = () => {
47
47
  '- zeph_file: Send a text file (logs, reports, code)',
48
48
  '- zeph_prompt: Ask user to choose from options (requires ZEPH_HOOK_ID)',
49
49
  '- zeph_input: Request text input from user (requires ZEPH_HOOK_ID)',
50
+ '- zeph_ask: Ask user with buttons + text input combined (requires ZEPH_HOOK_ID). Prefer this over zeph_prompt/zeph_input when you need both options and free-text.',
50
51
  '',
51
52
  'Resources:',
52
53
  '- zeph://devices: Check which devices are online',
@@ -62,20 +63,22 @@ const createServer = () => {
62
63
  (0, file_js_1.registerFileTool)(server, client, config);
63
64
  (0, prompt_js_1.registerPromptTool)(server, client, config);
64
65
  (0, input_js_1.registerInputTool)(server, client, config);
66
+ (0, ask_js_1.registerAskTool)(server, client, config);
65
67
  (0, devices_js_1.registerDevicesResource)(server, client);
66
68
  (0, channels_js_1.registerChannelsResource)(server, client);
67
69
  return server;
68
70
  };
69
71
  const main = async () => {
70
- // Initialize E2E encryption keys (load or generate)
72
+ const config = (0, config_js_1.loadConfig)();
73
+ // Initialize E2E encryption keys (sync with server)
71
74
  try {
72
- const publicKey = await (0, crypto_js_1.initCrypto)();
75
+ const publicKey = await (0, crypto_js_1.initCrypto)(config.apiKey, config.baseUrl);
73
76
  console.error(`[Crypto] E2E encryption ready (publicKey: ${publicKey.slice(0, 20)}...)`);
74
77
  }
75
78
  catch (err) {
76
79
  console.error('[Crypto] E2E encryption unavailable:', err);
77
80
  }
78
- const server = createServer();
81
+ const server = createServer(config);
79
82
  const transport = new stdio_js_1.StdioServerTransport();
80
83
  await server.connect(transport);
81
84
  console.error('Zeph MCP Server running on stdio');
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ZephApiClient } from '../api-client.js';
3
+ import type { McpServerConfig } from '../config.js';
4
+ export declare const registerAskTool: (server: McpServer, client: ZephApiClient, config: McpServerConfig) => void;
5
+ //# sourceMappingURL=ask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../src/tools/ask.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SA+EhG,CAAC"}
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAskTool = void 0;
4
+ const zod_1 = require("zod");
5
+ const error_format_js_1 = require("../error-format.js");
6
+ const poll_js_1 = require("../poll.js");
7
+ const registerAskTool = (server, client, config) => {
8
+ server.registerTool('zeph_ask', {
9
+ description: '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. The user can either tap a button or type a response. Blocks until the user responds or the timeout is reached. Requires ZEPH_HOOK_ID environment variable.',
10
+ annotations: {
11
+ readOnlyHint: false,
12
+ destructiveHint: false,
13
+ openWorldHint: true,
14
+ },
15
+ inputSchema: {
16
+ title: zod_1.z.string().describe('Question or request title'),
17
+ body: zod_1.z.string().optional().describe('Context or instructions'),
18
+ actions: zod_1.z
19
+ .array(zod_1.z.object({
20
+ id: zod_1.z.string().describe('Unique action identifier'),
21
+ label: zod_1.z.string().describe('Display label for the button'),
22
+ style: zod_1.z.enum(['primary', 'secondary', 'danger']).default('secondary')
23
+ .describe('Button style (default: secondary)'),
24
+ }))
25
+ .min(1)
26
+ .max(4)
27
+ .optional()
28
+ .describe('Quick-reply buttons (1-4). Omit for text-only input'),
29
+ placeholder: zod_1.z.string().optional().describe('Input field placeholder hint'),
30
+ inputType: zod_1.z
31
+ .enum(['text', 'multiline'])
32
+ .default('text')
33
+ .describe('Input field type (default: text)'),
34
+ timeout: zod_1.z
35
+ .number()
36
+ .min(10)
37
+ .max(600)
38
+ .default(120)
39
+ .describe('Seconds to wait for response (default: 120)'),
40
+ fallback: zod_1.z
41
+ .string()
42
+ .optional()
43
+ .describe('Action ID to auto-select on timeout'),
44
+ },
45
+ }, async ({ title, body, actions, placeholder, inputType, timeout, fallback }, ctx) => {
46
+ if (!config.hookId)
47
+ return (0, error_format_js_1.hookNotConfiguredError)();
48
+ try {
49
+ const trigger = await client.triggerHook(config.hookId, {
50
+ title,
51
+ body,
52
+ actions,
53
+ timeout,
54
+ fallback,
55
+ hookType: 'combo',
56
+ metadata: { placeholder, inputType },
57
+ });
58
+ const event = await (0, poll_js_1.pollForResponse)(client, config.hookId, trigger.data.eventId, timeout, ctx);
59
+ if (!event) {
60
+ if (fallback)
61
+ return (0, error_format_js_1.textResult)({ actionId: fallback, timedOut: true });
62
+ return (0, error_format_js_1.timeoutError)(timeout, 'Try again or use zeph_notify for one-way communication');
63
+ }
64
+ const response = event.data.response;
65
+ if (response?.actionId)
66
+ return (0, error_format_js_1.textResult)({ actionId: response.actionId, timedOut: false });
67
+ return (0, error_format_js_1.textResult)({ value: response?.value ?? '', timedOut: false });
68
+ }
69
+ catch (err) {
70
+ return (0, error_format_js_1.formatToolError)(err);
71
+ }
72
+ });
73
+ };
74
+ exports.registerAskTool = registerAskTool;
@@ -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: false,
34
+ destructiveHint: true,
35
35
  idempotentHint: true,
36
36
  openWorldHint: true,
37
37
  },
@@ -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,SAwCxE,CAAC"}
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"}
@@ -9,6 +9,7 @@ const registerListTool = (server, client) => {
9
9
  annotations: {
10
10
  readOnlyHint: true,
11
11
  destructiveHint: false,
12
+ idempotentHint: true,
12
13
  openWorldHint: true,
13
14
  },
14
15
  inputSchema: {
@@ -4,7 +4,7 @@ exports.registerNotifyTool = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const error_format_js_1 = require("../error-format.js");
6
6
  const crypto_js_1 = require("../crypto.js");
7
- const BODY_FILE_THRESHOLD = 0;
7
+ const BODY_FILE_THRESHOLD = 512;
8
8
  const PREVIEW_LENGTH = 200;
9
9
  const inferMimeType = (fileName) => {
10
10
  const ext = fileName.split('.').pop()?.toLowerCase();
@@ -13,7 +13,7 @@ const inferMimeType = (fileName) => {
13
13
  };
14
14
  const registerNotifyTool = (server, client, config) => {
15
15
  server.registerTool('zeph_notify', {
16
- description: 'Send a one-way push notification to the user\'s devices. Use this to inform the user about task completion, errors, or status updates. Long bodies (>1KB) are automatically uploaded as a file for full viewing.',
16
+ description: 'Send a one-way push notification to the user\'s devices. Use this to inform the user about task completion, errors, or status updates. Long bodies (>512B) are automatically uploaded as a file for full viewing.',
17
17
  annotations: {
18
18
  readOnlyHint: false,
19
19
  destructiveHint: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Zeph MCP server — AI agent notifications, prompts, and input via MCP protocol",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",