@zeph-to/mcp-server 1.15.2 → 2.0.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/dist/tools/ask.js CHANGED
@@ -5,7 +5,6 @@ const zod_1 = require("zod");
5
5
  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
- const crypto_js_1 = require("../crypto.js");
9
8
  const mime_js_1 = require("../mime.js");
10
9
  const sanitize_js_1 = require("../sanitize.js");
11
10
  // The device feed shows a short preview of the body. Anything longer than
@@ -23,7 +22,7 @@ const buildAskMarkdown = (title, body, actions) => {
23
22
  };
24
23
  const registerAskTool = (server, client, config, waiter) => {
25
24
  server.registerTool('zeph_ask', {
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.',
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.',
27
26
  annotations: {
28
27
  readOnlyHint: false,
29
28
  destructiveHint: false,
@@ -77,30 +76,21 @@ const registerAskTool = (server, client, config, waiter) => {
77
76
  if (exceedsPreview && cleanBody) {
78
77
  const fileName = 'response.md';
79
78
  const fileType = (0, mime_js_1.inferMimeType)(fileName);
80
- const canEncrypt = !!(0, crypto_js_1.getKeyPair)() && !!(0, crypto_js_1.getPublicKey)();
81
79
  // Self-contained Markdown so the file alone tells the whole story.
82
80
  const fileMarkdown = buildAskMarkdown(title, cleanBody, effectiveActions);
83
81
  const fileBytes = new TextEncoder().encode(fileMarkdown).byteLength;
84
- let uploadContent = fileMarkdown;
85
- let uploadContentType = fileType;
86
- let fileIv;
87
- let fileEncryptedKey;
88
- if (canEncrypt) {
89
- try {
90
- const encrypted = await (0, crypto_js_1.encryptFileForSelf)(fileMarkdown);
91
- uploadContent = encrypted.ciphertext;
92
- uploadContentType = 'application/octet-stream';
93
- fileIv = encrypted.iv;
94
- fileEncryptedKey = encrypted.encryptedKey;
95
- }
96
- catch (err) {
97
- console.error('[Crypto] File encryption failed, sending plaintext:', err);
98
- }
99
- }
100
- const upload = await client.requestUpload({ fileName, fileType: uploadContentType, fileSize: typeof uploadContent === 'string' ? fileBytes : uploadContent.length });
101
- await client.uploadToS3(upload.data.uploadUrl, uploadContent, uploadContentType);
82
+ // Deliberately NOT encrypted, unlike zeph_notify / zeph_file.
83
+ //
84
+ // This attachment rides `POST /hooks/:id/trigger`, which creates a
85
+ // plaintext push: it neither accepts nor persists `isEncrypted` or
86
+ // `senderPublicKey` (apps/server/src/functions/hooks.ts). Clients
87
+ // gate decryption on both, so an encrypted attachment here would be
88
+ // downloaded as raw ciphertext named response.md. Encrypting this
89
+ // path needs the hook route to carry the sender key first.
90
+ const upload = await client.requestUpload({ fileName, fileType, fileSize: fileBytes });
91
+ await client.uploadToS3(upload.data.uploadUrl, fileMarkdown, fileType);
102
92
  triggerBody = cleanBody.slice(0, PREVIEW_LENGTH) + '...';
103
- files = [{ fileKey: upload.data.fileKey, fileName, fileSize: fileBytes, fileType, iv: fileIv, encryptedKey: fileEncryptedKey }];
93
+ files = [{ fileKey: upload.data.fileKey, fileName, fileSize: fileBytes, fileType }];
104
94
  }
105
95
  const trigger = await client.triggerHook(config.hookId, {
106
96
  title: pushTitle,
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/tools/file.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAMrE,eAAO,MAAM,gBAAgB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SAiFjG,CAAC"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/tools/file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AA4BrE,eAAO,MAAM,gBAAgB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SAmIjG,CAAC"}
@@ -2,77 +2,144 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerFileTool = void 0;
4
4
  const zod_1 = require("zod");
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_path_1 = require("node:path");
5
7
  const config_js_1 = require("../config.js");
6
8
  const error_format_js_1 = require("../error-format.js");
7
9
  const crypto_js_1 = require("../crypto.js");
8
10
  const e2e_fallback_js_1 = require("../e2e-fallback.js");
9
11
  const mime_js_1 = require("../mime.js");
12
+ /**
13
+ * Collapse the two accepted input shapes into one payload.
14
+ *
15
+ * A `filePath` stays a Buffer end to end — images and PDFs only survive the
16
+ * trip as raw bytes, and reading from disk keeps a several-hundred-KB
17
+ * attachment out of the tool call itself (base64 in the arguments would
18
+ * inflate it by a third and burn the agent's context).
19
+ */
20
+ const resolvePayload = async (args) => {
21
+ if (args.filePath) {
22
+ const body = await (0, promises_1.readFile)(args.filePath);
23
+ return { fileName: args.fileName ?? (0, node_path_1.basename)(args.filePath), body, size: body.byteLength };
24
+ }
25
+ return {
26
+ fileName: args.fileName,
27
+ body: args.content,
28
+ size: new TextEncoder().encode(args.content).byteLength,
29
+ };
30
+ };
10
31
  const registerFileTool = (server, client, config) => {
11
32
  server.registerTool('zeph_file', {
12
- description: 'Send a text file to the user\'s device. The content is uploaded and delivered as a file push. Use for logs, reports, code snippets, or any text content.',
33
+ description: 'Send a file to the user\'s device. Pass `filePath` to send a file that already exists on disk images (png/jpg/gif/webp/heic), PDFs, logs, anything. Pass `content` instead to send text you generated. Images render inline on the device.',
13
34
  annotations: {
14
35
  readOnlyHint: false,
15
36
  destructiveHint: false,
16
37
  openWorldHint: true,
17
38
  },
18
39
  inputSchema: {
19
- fileName: zod_1.z.string().describe('File name with extension (e.g., "report.txt", "output.json")'),
20
- content: zod_1.z.string().describe('Text content of the file'),
40
+ filePath: zod_1.z
41
+ .string()
42
+ .optional()
43
+ .describe('Absolute path to a local file to send. Required for images, PDFs, and any other binary — never base64 a file into `content`.'),
44
+ content: zod_1.z.string().optional().describe('Text content of the file. Use only for text you generated; requires `fileName`.'),
45
+ fileName: zod_1.z
46
+ .string()
47
+ .optional()
48
+ .describe('File name with extension (e.g., "report.txt"). Required with `content`; defaults to the basename of `filePath`.'),
21
49
  title: zod_1.z.string().optional().describe('Notification title (defaults to fileName)'),
22
50
  targetDeviceId: zod_1.z.string().optional().describe('Target device ID. Omit to use configured default or send to all devices.'),
23
51
  },
24
- }, async ({ fileName, content, title, targetDeviceId }) => {
52
+ }, async ({ filePath, fileName: fileNameArg, content, title, targetDeviceId }) => {
53
+ if (!filePath && content === undefined) {
54
+ return (0, error_format_js_1.errorResult)({
55
+ error: 'INVALID_INPUT',
56
+ message: 'Either filePath or content is required',
57
+ suggestion: 'Pass filePath for a file on disk (images, PDFs), or content + fileName for generated text',
58
+ });
59
+ }
60
+ if (!filePath && !fileNameArg) {
61
+ return (0, error_format_js_1.errorResult)({
62
+ error: 'INVALID_INPUT',
63
+ message: 'fileName is required when sending content',
64
+ suggestion: 'Add fileName with an extension, e.g. "report.md"',
65
+ });
66
+ }
67
+ let payload;
68
+ try {
69
+ // Resolved once, outside `send`, so the plaintext retry below doesn't
70
+ // read the file from disk a second time.
71
+ payload = await resolvePayload({ filePath, content, fileName: fileNameArg });
72
+ }
73
+ catch (err) {
74
+ return (0, error_format_js_1.errorResult)({
75
+ error: 'FILE_READ_FAILED',
76
+ message: err instanceof Error ? err.message : String(err),
77
+ suggestion: 'Check that filePath is an absolute path to a readable file',
78
+ });
79
+ }
80
+ const { fileName, body, size: originalSize } = payload;
25
81
  // Runs a second time as plaintext if the server refuses E2E (Pro-only,
26
82
  // ADR-0008). The retry re-uploads the file unencrypted, so the whole
27
83
  // upload-then-send sequence has to sit inside this closure.
28
- const send = async (canEncrypt) => {
29
- let fileType = (0, mime_js_1.inferMimeType)(fileName);
30
- const originalSize = new TextEncoder().encode(content).byteLength;
31
- // Step 1: Optionally encrypt file content
32
- let uploadContent = content;
33
- let uploadSize = originalSize;
34
- let fileIv;
35
- let fileEncryptedKey;
36
- if (canEncrypt) {
84
+ const send = async (recipients) => {
85
+ const pushTitle = (0, config_js_1.formatPushTitle)(config.projectName, title ?? fileName);
86
+ // Step 1: Encrypt the attachment and the push body together, before
87
+ // anything is uploaded. Doing them one at a time around the upload let
88
+ // a failure land in between and ship ciphertext under a push with no
89
+ // `isEncrypted` an attachment no client would even try to open.
90
+ let encrypted = null;
91
+ if (recipients) {
37
92
  try {
38
- const encrypted = await (0, crypto_js_1.encryptFileForSelf)(content);
39
- uploadContent = encrypted.ciphertext;
40
- uploadSize = encrypted.ciphertext.length;
41
- fileType = 'application/octet-stream';
42
- fileIv = encrypted.iv;
43
- fileEncryptedKey = encrypted.encryptedKey;
93
+ encrypted = {
94
+ file: await (0, crypto_js_1.encryptFileForDevices)(body, recipients),
95
+ push: await (0, crypto_js_1.encryptPushBodyForDevices)({ title: pushTitle }, recipients),
96
+ };
44
97
  }
45
98
  catch (err) {
46
- console.error('[Crypto] File encryption failed, sending plaintext:', err);
99
+ console.error('[Crypto] Encryption failed, sending plaintext:', err);
47
100
  }
48
101
  }
102
+ const uploadContent = encrypted?.file.ciphertext ?? body;
103
+ const uploadType = encrypted ? 'application/octet-stream' : (0, mime_js_1.inferMimeType)(fileName);
104
+ const uploadSize = encrypted ? encrypted.file.ciphertext.length : originalSize;
49
105
  // Step 2: Request upload URL
50
- const upload = await client.requestUpload({ fileName, fileType, fileSize: uploadSize });
106
+ const upload = await client.requestUpload({ fileName, fileType: uploadType, fileSize: uploadSize });
51
107
  // Step 3: Upload content to S3
52
- await client.uploadToS3(upload.data.uploadUrl, uploadContent, fileType);
53
- // Step 4: Send file push (encrypt push body if possible)
54
- const pushTitle = (0, config_js_1.formatPushTitle)(config.projectName, title ?? fileName);
55
- let pushPayload = {
56
- title: pushTitle,
108
+ await client.uploadToS3(upload.data.uploadUrl, uploadContent, uploadType);
109
+ // Step 4: Send the push. `fileType` on the descriptor stays the real
110
+ // type it drives how the client renders the decrypted bytes.
111
+ const pushPayload = {
112
+ title: encrypted ? undefined : pushTitle,
57
113
  type: 'file',
58
- files: [{ fileKey: upload.data.fileKey, fileName, fileSize: originalSize, fileType: (0, mime_js_1.inferMimeType)(fileName), iv: fileIv, encryptedKey: fileEncryptedKey }],
114
+ files: [{
115
+ fileKey: upload.data.fileKey,
116
+ fileName,
117
+ fileSize: originalSize,
118
+ fileType: (0, mime_js_1.inferMimeType)(fileName),
119
+ iv: encrypted?.file.iv,
120
+ deviceKeyMap: encrypted?.file.deviceKeyMap,
121
+ }],
59
122
  targetDeviceId: targetDeviceId ?? config.deviceId,
60
123
  sessionId: config.sessionId,
124
+ ...(encrypted && {
125
+ body: encrypted.push.body,
126
+ isEncrypted: encrypted.push.isEncrypted,
127
+ deviceKeyMap: encrypted.push.deviceKeyMap,
128
+ senderPublicKey: encrypted.push.senderPublicKey,
129
+ }),
61
130
  };
62
- if (canEncrypt) {
63
- try {
64
- const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: pushTitle });
65
- pushPayload = { ...pushPayload, title: undefined, body: enc.body, isEncrypted: enc.isEncrypted, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
66
- }
67
- catch (err) {
68
- console.error('[Crypto] Push encryption failed, sending plaintext:', err);
69
- }
70
- }
71
131
  const result = await client.sendPush(pushPayload);
72
- return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, fileKey: upload.data.fileKey, fileSize: originalSize, encrypted: canEncrypt });
132
+ // Report what actually went out an encryption failure above falls
133
+ // back to plaintext, so the recipient list alone would over-claim.
134
+ return (0, error_format_js_1.textResult)({
135
+ pushId: result.data.pushId,
136
+ fileKey: upload.data.fileKey,
137
+ fileSize: originalSize,
138
+ encrypted: !!encrypted,
139
+ });
73
140
  };
74
141
  try {
75
- return await (0, e2e_fallback_js_1.withPlaintextFallback)(send);
142
+ return await (0, e2e_fallback_js_1.withPlaintextFallback)(client, send);
76
143
  }
77
144
  catch (err) {
78
145
  return (0, error_format_js_1.formatToolError)(err);
@@ -1 +1 @@
1
- {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/tools/notify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAUrE,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SAgInG,CAAC"}
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/tools/notify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAUrE,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SAuInG,CAAC"}
@@ -32,7 +32,7 @@ const registerNotifyTool = (server, client, config) => {
32
32
  }, async ({ title, body, url, priority, targetDeviceId }) => {
33
33
  // Runs a second time as plaintext if the server refuses E2E (Pro-only,
34
34
  // ADR-0008) — hence everything after the encryption decision lives here.
35
- const send = async (canEncrypt) => {
35
+ const send = async (recipients) => {
36
36
  const deviceId = targetDeviceId ?? config.deviceId;
37
37
  const pushTitle = (0, config_js_1.formatPushTitle)(config.projectName, title);
38
38
  // Strip any tool-call markup that leaked into the body argument.
@@ -45,70 +45,74 @@ const registerNotifyTool = (server, client, config) => {
45
45
  // Self-contained Markdown so the file alone carries the full text.
46
46
  const fileMarkdown = `# ${title}\n\n${cleanBody}`;
47
47
  const fileBytes = new TextEncoder().encode(fileMarkdown).byteLength;
48
- // Encrypt file content if keys available
49
- let uploadContent = fileMarkdown;
50
- let uploadContentType = fileType;
51
- let fileIv;
52
- let fileEncryptedKey;
53
- let fileEncrypted = false;
54
- if (canEncrypt) {
48
+ const preview = cleanBody.slice(0, PREVIEW_LENGTH) + '...';
49
+ // Encrypt the attachment and the push body together, before anything
50
+ // is uploaded. Doing them one at a time around the upload let a
51
+ // failure land in between and ship ciphertext under a push with no
52
+ // `isEncrypted` — an attachment no client would even try to open.
53
+ let encrypted = null;
54
+ if (recipients) {
55
55
  try {
56
- const encrypted = await (0, crypto_js_1.encryptFileForSelf)(fileMarkdown);
57
- uploadContent = encrypted.ciphertext;
58
- uploadContentType = 'application/octet-stream';
59
- fileIv = encrypted.iv;
60
- fileEncryptedKey = encrypted.encryptedKey;
61
- fileEncrypted = true;
56
+ encrypted = {
57
+ file: await (0, crypto_js_1.encryptFileForDevices)(fileMarkdown, recipients),
58
+ push: await (0, crypto_js_1.encryptPushBodyForDevices)({ title: pushTitle, body: preview, url }, recipients),
59
+ };
62
60
  }
63
61
  catch (err) {
64
- console.error('[Crypto] File encryption failed, sending plaintext:', err);
62
+ console.error('[Crypto] Encryption failed, sending plaintext:', err);
65
63
  }
66
64
  }
65
+ const uploadContent = encrypted?.file.ciphertext ?? fileMarkdown;
66
+ const uploadContentType = encrypted ? 'application/octet-stream' : fileType;
67
67
  const upload = await client.requestUpload({ fileName, fileType: uploadContentType, fileSize: typeof uploadContent === 'string' ? fileBytes : uploadContent.length });
68
68
  await client.uploadToS3(upload.data.uploadUrl, uploadContent, uploadContentType);
69
- const preview = cleanBody.slice(0, PREVIEW_LENGTH) + '...';
70
- // Encrypt push body (title/preview/url) if keys available
71
- let pushPayload = {
72
- title: pushTitle,
73
- body: preview,
74
- url,
69
+ const pushPayload = {
70
+ title: encrypted ? undefined : pushTitle,
71
+ body: encrypted ? encrypted.push.body : preview,
72
+ // Dropped alongside the title when encrypted: the url is already
73
+ // sealed inside the ciphertext, and for a link push it is the whole
74
+ // payload. Leaving the plaintext copy here would hand the server the
75
+ // one thing `isEncrypted` promises it cannot see.
76
+ url: encrypted ? undefined : url,
75
77
  type: 'file',
76
78
  priority,
77
- files: [{ fileKey: upload.data.fileKey, fileName, fileSize: fileBytes, fileType, iv: fileIv, encryptedKey: fileEncryptedKey }],
79
+ files: [{
80
+ fileKey: upload.data.fileKey,
81
+ fileName,
82
+ fileSize: fileBytes,
83
+ fileType,
84
+ iv: encrypted?.file.iv,
85
+ deviceKeyMap: encrypted?.file.deviceKeyMap,
86
+ }],
78
87
  targetDeviceId: deviceId,
79
88
  sessionId: config.sessionId,
89
+ ...(encrypted && {
90
+ isEncrypted: encrypted.push.isEncrypted,
91
+ deviceKeyMap: encrypted.push.deviceKeyMap,
92
+ senderPublicKey: encrypted.push.senderPublicKey,
93
+ }),
80
94
  };
81
- let pushEncrypted = false;
82
- if (canEncrypt) {
83
- try {
84
- const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: pushTitle, body: preview, url });
85
- pushPayload = { ...pushPayload, title: undefined, body: enc.body, isEncrypted: enc.isEncrypted, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
86
- pushEncrypted = true;
87
- }
88
- catch (err) {
89
- console.error('[Crypto] Push encryption failed, sending plaintext:', err);
90
- }
91
- }
92
95
  const result = await client.sendPush(pushPayload);
93
96
  // Report what actually went out — an encryption failure above falls
94
- // back to plaintext, so `canEncrypt` alone would over-claim.
95
- return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, fileKey: upload.data.fileKey, autoFile: true, encrypted: fileEncrypted && pushEncrypted });
97
+ // back to plaintext, so the recipient list alone would over-claim.
98
+ return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, fileKey: upload.data.fileKey, autoFile: true, encrypted: !!encrypted });
96
99
  }
97
100
  // Short body — encrypt push only
98
101
  let pushPayload = {
99
102
  title: pushTitle,
100
103
  body: cleanBody,
101
- url,
104
+ url, // replaced below when the encrypted envelope takes over
102
105
  type: 'hook',
103
106
  priority,
104
107
  targetDeviceId: deviceId,
105
108
  sessionId: config.sessionId,
106
109
  };
107
110
  let pushEncrypted = false;
108
- if (canEncrypt) {
111
+ if (recipients) {
109
112
  try {
110
- const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: pushTitle, body: cleanBody, url });
111
- pushPayload = { ...pushPayload, title: undefined, body: enc.body, isEncrypted: enc.isEncrypted, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
113
+ const enc = await (0, crypto_js_1.encryptPushBodyForDevices)({ title: pushTitle, body: cleanBody, url }, recipients);
114
+ // `url: undefined` for the same reason as `title` see the file branch.
115
+ pushPayload = { ...pushPayload, title: undefined, url: undefined, body: enc.body, isEncrypted: enc.isEncrypted, deviceKeyMap: enc.deviceKeyMap, senderPublicKey: enc.senderPublicKey };
112
116
  pushEncrypted = true;
113
117
  }
114
118
  catch (err) {
@@ -119,7 +123,7 @@ const registerNotifyTool = (server, client, config) => {
119
123
  return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, encrypted: pushEncrypted });
120
124
  };
121
125
  try {
122
- return await (0, e2e_fallback_js_1.withPlaintextFallback)(send);
126
+ return await (0, e2e_fallback_js_1.withPlaintextFallback)(client, send);
123
127
  }
124
128
  catch (err) {
125
129
  return (0, error_format_js_1.formatToolError)(err);
package/dist/types.d.ts CHANGED
@@ -95,7 +95,13 @@ export interface AttachedFile {
95
95
  fileSize: number;
96
96
  fileType: string;
97
97
  iv?: string;
98
- encryptedKey?: string;
98
+ /**
99
+ * E2E: the file AES key wrapped once per recipient device, keyed by
100
+ * `deviceId`. Each value is a JSON string `{ encryptedKey, keyIv }`.
101
+ * (The superseded account-wide `encryptedKey` field is not written here —
102
+ * nothing can unwrap it since key escrow was removed.)
103
+ */
104
+ deviceKeyMap?: Record<string, string>;
99
105
  }
100
106
  export interface ToolError {
101
107
  error: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;QAC5D,QAAQ,CAAC,EAAE;YACT,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,UAAU,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,aAAa,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
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": "1.15.2",
3
+ "version": "2.0.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",