@zeph-to/mcp-server 2.0.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 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
 
@@ -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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -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);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Saving the files a user attached to a hook answer.
3
+ *
4
+ * `zeph_ask` and `zeph_input` hand the agent a JSON result, and an agent
5
+ * cannot look at an S3 key — so an answered screenshot only becomes useful
6
+ * once it is a path on this machine. This module downloads each attachment
7
+ * to `~/.zeph/attachments/hook-<eventId>/` and returns the absolute paths,
8
+ * which the tool then names in its result for the agent to read.
9
+ *
10
+ * The files are plaintext by contract: the hook route carries no sender key,
11
+ * so the server rejects an encrypted attachment on a response rather than
12
+ * let one arrive here as bytes nothing can open.
13
+ *
14
+ * Failure is per file, never fatal. A question that was answered has been
15
+ * answered; losing one image must not turn that into a tool error and make
16
+ * the user answer again.
17
+ */
18
+ import type { ZephApiClient } from './api-client.js';
19
+ import type { AttachedFile } from './types.js';
20
+ /**
21
+ * The part of a tool result that tells the agent about saved attachments.
22
+ * Spread into the result object; empty when nothing was attached, so a plain
23
+ * answer keeps the shape it has always had.
24
+ *
25
+ * The instruction is explicit because a bare list of paths in a JSON result
26
+ * reads as metadata — the agent has to be told these are the user's answer
27
+ * and that opening them is part of reading it.
28
+ */
29
+ export declare const attachmentNote: (paths: string[]) => Record<string, unknown>;
30
+ export interface SaveResponseFilesDeps {
31
+ /** Test seam for the two-step fetch (metadata → presigned URL → bytes). */
32
+ fetchBytes?: (fileKey: string) => Promise<Uint8Array>;
33
+ dir?: string;
34
+ }
35
+ export declare const saveResponseFiles: (client: ZephApiClient, eventId: string, files: AttachedFile[] | undefined, deps?: SaveResponseFilesDeps) => Promise<string[]>;
36
+ //# sourceMappingURL=response-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-files.d.ts","sourceRoot":"","sources":["../src/response-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmB/C;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAM/D,CAAC;AAET,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,aAAa,EACrB,SAAS,MAAM,EACf,OAAO,YAAY,EAAE,GAAG,SAAS,EACjC,OAAM,qBAA0B,KAC/B,OAAO,CAAC,MAAM,EAAE,CAiBlB,CAAC"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Saving the files a user attached to a hook answer.
4
+ *
5
+ * `zeph_ask` and `zeph_input` hand the agent a JSON result, and an agent
6
+ * cannot look at an S3 key — so an answered screenshot only becomes useful
7
+ * once it is a path on this machine. This module downloads each attachment
8
+ * to `~/.zeph/attachments/hook-<eventId>/` and returns the absolute paths,
9
+ * which the tool then names in its result for the agent to read.
10
+ *
11
+ * The files are plaintext by contract: the hook route carries no sender key,
12
+ * so the server rejects an encrypted attachment on a response rather than
13
+ * let one arrive here as bytes nothing can open.
14
+ *
15
+ * Failure is per file, never fatal. A question that was answered has been
16
+ * answered; losing one image must not turn that into a tool error and make
17
+ * the user answer again.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.saveResponseFiles = exports.attachmentNote = void 0;
21
+ const node_fs_1 = require("node:fs");
22
+ const node_os_1 = require("node:os");
23
+ const node_path_1 = require("node:path");
24
+ /** Shared with the `zeph listener` daemon, which writes push attachments here. */
25
+ const ATTACHMENTS_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.zeph', 'attachments');
26
+ /**
27
+ * Make a filesystem-safe single path segment: take the basename (drops any
28
+ * `../` prefix), strip control characters and embedded separators, remove
29
+ * leading dots, and cap the length. Empty or dot-only names use the fallback.
30
+ */
31
+ const safeSegment = (raw, fallback) => {
32
+ const cleaned = (0, node_path_1.basename)(raw)
33
+ .replace(/[\x00-\x1f\x7f]/g, '')
34
+ .replace(/[/\\]/g, '_')
35
+ .replace(/^\.+/, '')
36
+ .trim();
37
+ return (cleaned || fallback).slice(0, 200);
38
+ };
39
+ /**
40
+ * The part of a tool result that tells the agent about saved attachments.
41
+ * Spread into the result object; empty when nothing was attached, so a plain
42
+ * answer keeps the shape it has always had.
43
+ *
44
+ * The instruction is explicit because a bare list of paths in a JSON result
45
+ * reads as metadata — the agent has to be told these are the user's answer
46
+ * and that opening them is part of reading it.
47
+ */
48
+ const attachmentNote = (paths) => paths.length
49
+ ? {
50
+ attachments: paths,
51
+ attachmentsNote: `The user attached ${paths.length} file(s) to this answer. Read each path above to see them.`,
52
+ }
53
+ : {};
54
+ exports.attachmentNote = attachmentNote;
55
+ const saveResponseFiles = async (client, eventId, files, deps = {}) => {
56
+ if (!files?.length)
57
+ return [];
58
+ const fetchBytes = deps.fetchBytes ?? ((key) => client.downloadFile(key));
59
+ const dir = (0, node_path_1.join)(deps.dir ?? ATTACHMENTS_DIR, safeSegment(`hook-${eventId}`, 'hook'));
60
+ const paths = [];
61
+ for (const [i, file] of files.entries()) {
62
+ try {
63
+ const bytes = await fetchBytes(file.fileKey);
64
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
65
+ const abs = (0, node_path_1.join)(dir, safeSegment(file.fileName || `file-${i}`, `file-${i}`));
66
+ (0, node_fs_1.writeFileSync)(abs, bytes);
67
+ paths.push(abs);
68
+ }
69
+ catch (err) {
70
+ console.error(`[zeph] attachment "${file.fileName}" failed to save: ${err.message}`);
71
+ }
72
+ }
73
+ return paths;
74
+ };
75
+ exports.saveResponseFiles = saveResponseFiles;
@@ -1 +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;AAItD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAuBxD,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,EAAE,SAAS,kBAAkB,SAyH7H,CAAC"}
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;AAItD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAwBxD,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,EAAE,SAAS,kBAAkB,SA+H7H,CAAC"}
package/dist/tools/ask.js CHANGED
@@ -7,6 +7,7 @@ const poll_js_1 = require("../poll.js");
7
7
  const config_js_1 = require("../config.js");
8
8
  const mime_js_1 = require("../mime.js");
9
9
  const sanitize_js_1 = require("../sanitize.js");
10
+ const response_files_js_1 = require("../response-files.js");
10
11
  // The device feed shows a short preview of the body. Anything longer than
11
12
  // this gets truncated there, so we attach the full text as a file — the
12
13
  // user can always open the complete content instead of squinting at a
@@ -22,7 +23,7 @@ const buildAskMarkdown = (title, body, actions) => {
22
23
  };
23
24
  const registerAskTool = (server, client, config, waiter) => {
24
25
  server.registerTool('zeph_ask', {
25
- 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. NOTE: unlike zeph_notify and zeph_file, this tool is never end-to-end encrypted — the hook route it uses cannot carry the sender key — so do not put secrets in the question or expect a private answer.',
26
+ 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. The user may also attach screenshots or files to the answer: those arrive as local absolute paths in the `attachments` field of the result, and reading them is part of reading the answer. NOTE: unlike zeph_notify and zeph_file, this tool is never end-to-end encrypted — the hook route it uses cannot carry the sender key — so do not put secrets in the question or expect a private answer.',
26
27
  annotations: {
27
28
  readOnlyHint: false,
28
29
  destructiveHint: false,
@@ -112,9 +113,14 @@ const registerAskTool = (server, client, config, waiter) => {
112
113
  return (0, error_format_js_1.timeoutError)(timeout, 'Try again or use zeph_notify for one-way communication');
113
114
  }
114
115
  const response = event.data.response;
115
- if (response?.actionId)
116
- return (0, error_format_js_1.textResult)({ actionId: response.actionId, timedOut: false });
117
- return (0, error_format_js_1.textResult)({ value: response?.value ?? '', timedOut: false });
116
+ // Saved before the button branch returns: a user can tap a quick reply
117
+ // AND attach a screenshot, and dropping the files there would lose them
118
+ // silently for the one caller that never looks at `value`.
119
+ const attachments = await (0, response_files_js_1.saveResponseFiles)(client, trigger.data.eventId, response?.files);
120
+ if (response?.actionId) {
121
+ return (0, error_format_js_1.textResult)({ actionId: response.actionId, timedOut: false, ...(0, response_files_js_1.attachmentNote)(attachments) });
122
+ }
123
+ return (0, error_format_js_1.textResult)({ value: response?.value ?? '', timedOut: false, ...(0, response_files_js_1.attachmentNote)(attachments) });
118
124
  }
119
125
  catch (err) {
120
126
  return (0, error_format_js_1.formatToolError)(err);
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/tools/input.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,eAAO,MAAM,iBAAiB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,EAAE,SAAS,kBAAkB,SA2D/H,CAAC"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/tools/input.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAGrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,eAAO,MAAM,iBAAiB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,EAAE,SAAS,kBAAkB,SAgE/H,CAAC"}
@@ -6,9 +6,10 @@ const error_format_js_1 = require("../error-format.js");
6
6
  const poll_js_1 = require("../poll.js");
7
7
  const config_js_1 = require("../config.js");
8
8
  const sanitize_js_1 = require("../sanitize.js");
9
+ const response_files_js_1 = require("../response-files.js");
9
10
  const registerInputTool = (server, client, config, waiter) => {
10
11
  server.registerTool('zeph_input', {
11
- description: 'Request text input from the user via push notification. The tool blocks until the user responds or the timeout is reached. Requires ZEPH_HOOK_ID environment variable.',
12
+ description: 'Request text input from the user via push notification. The tool blocks until the user responds or the timeout is reached. Requires ZEPH_HOOK_ID environment variable. The user may also attach screenshots or files: those arrive as local absolute paths in the `attachments` field of the result, and reading them is part of reading the answer.',
12
13
  annotations: {
13
14
  readOnlyHint: false,
14
15
  destructiveHint: false,
@@ -46,7 +47,12 @@ const registerInputTool = (server, client, config, waiter) => {
46
47
  const event = await (0, poll_js_1.pollForResponse)(client, config.hookId, trigger.data.eventId, timeout, ctx, waiter);
47
48
  if (!event)
48
49
  return (0, error_format_js_1.timeoutError)(timeout, 'Try again with a longer timeout');
49
- return (0, error_format_js_1.textResult)({ value: event.data.response?.value ?? '', timedOut: false });
50
+ const attachments = await (0, response_files_js_1.saveResponseFiles)(client, trigger.data.eventId, event.data.response?.files);
51
+ return (0, error_format_js_1.textResult)({
52
+ value: event.data.response?.value ?? '',
53
+ timedOut: false,
54
+ ...(0, response_files_js_1.attachmentNote)(attachments),
55
+ });
50
56
  }
51
57
  catch (err) {
52
58
  return (0, error_format_js_1.formatToolError)(err);
package/dist/types.d.ts CHANGED
@@ -23,6 +23,9 @@ export interface HookEventResponse {
23
23
  response?: {
24
24
  actionId?: string;
25
25
  value?: string;
26
+ /** Screenshots and files the user attached to the answer — plaintext,
27
+ * since the hook route carries no key this end could decrypt with. */
28
+ files?: AttachedFile[];
26
29
  respondedDeviceId?: string;
27
30
  };
28
31
  };
@@ -89,6 +92,11 @@ export interface UploadRequestResponse {
89
92
  uploadUrl: string;
90
93
  };
91
94
  }
95
+ export interface DownloadUrlResponse {
96
+ data: {
97
+ downloadUrl: string;
98
+ };
99
+ }
92
100
  export interface AttachedFile {
93
101
  fileKey: string;
94
102
  fileName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/mcp-server",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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",