@zeph-to/mcp-server 2.2.0 → 2.3.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 +5 -3
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -2
- package/dist/index.js +5 -0
- package/dist/tools/ask.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ e.g. a second account:
|
|
|
70
70
|
| `ZEPH_HOOK_ID` | No | Hook ID (optional — only needed for interactive tools like `zeph_ask`/`zeph_prompt`/`zeph_input`) |
|
|
71
71
|
| `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 |
|
|
72
72
|
| `ZEPH_BASE_URL` | No | API base URL (default: `https://api.zeph.to/v1`) |
|
|
73
|
-
| `ZEPH_WS_URL` | No | WebSocket endpoint for the hook-response fast path
|
|
73
|
+
| `ZEPH_WS_URL` | No | **Deprecated.** WebSocket endpoint for the hook-response fast path. `wsUrl` in `~/.zeph/config.json` wins over it and is where the value belongs; this is read only when the file has none, so a machine that predates the config field keeps working. It will stop being read |
|
|
74
74
|
| `ZEPH_DISABLE_SESSION_CACHE` | No | Set to `1`/`true` to skip writing the session-id handoff file under `~/.cache/zeph/`. Useful for read-only filesystems, ephemeral CI runners, or sandboxed envs that audit filesystem writes. The plugin's stop hook still works without it (transcript-path UUID extraction is the primary path; the cache is a fallback for older Claude Code versions). |
|
|
75
75
|
| `ZEPH_SESSION_ID` | No | Override the session id attached to pushes (grouping in the app). Auto-detected from the newest Claude Code transcript when unset |
|
|
76
76
|
| `ZEPH_DISABLE_ENCRYPTION` | No | Set to `1`/`true` to force push encryption off even when the account has it enabled. A local override for debugging what the server actually received — encryption is already off unless the account opted in (see [Encryption](#encryption)) |
|
|
@@ -187,7 +187,9 @@ Returns: `{ actionId: "yes", timedOut: false }`
|
|
|
187
187
|
|
|
188
188
|
### zeph_ask
|
|
189
189
|
|
|
190
|
-
Ask the user a question with
|
|
190
|
+
Ask the user a question with quick-reply buttons and a text input field. Combines prompt (buttons) and input (text) in a single notification. Blocks until response or timeout.
|
|
191
|
+
|
|
192
|
+
`actions` is the steering surface: pass 2–4 buttons on nearly every ask (the next-step candidates plus a safe Done-like `fallback`) and leave it out only when the answer is inherently free-form text — a bare text box on a "done — what next?" ask gives the phone nothing to tap.
|
|
191
193
|
|
|
192
194
|
Requires `ZEPH_HOOK_ID`.
|
|
193
195
|
|
|
@@ -236,7 +238,7 @@ Returns: `{ value: "feat: add clipboard sync", timedOut: false }` — plus `atta
|
|
|
236
238
|
|
|
237
239
|
### Client timeouts
|
|
238
240
|
|
|
239
|
-
`zeph_ask`, `zeph_prompt`, and `zeph_input` block until the user responds, up to their `timeout` (max 600s). With `
|
|
241
|
+
`zeph_ask`, `zeph_prompt`, and `zeph_input` block until the user responds, up to their `timeout` (max 600s). With `wsUrl` set in `~/.zeph/config.json` the response arrives over WebSocket the instant it's submitted; otherwise the server polls. Either way the MCP request stays open the whole time. To keep the client from giving up early, the server emits a `notifications/progress` every 5s while waiting. Clients must either set a per-request timeout above the tool's `timeout`, or reset their timeout on progress notifications. Claude Code does the latter by default.
|
|
240
242
|
|
|
241
243
|
## Resources
|
|
242
244
|
|
package/dist/config.d.ts
CHANGED
|
@@ -40,4 +40,20 @@ export declare const loadConfig: () => McpServerConfig;
|
|
|
40
40
|
* back to a hostname hash pinned in a sticky file; mirror that order, reading
|
|
41
41
|
* (never writing) the file so both processes resolve the same id. */
|
|
42
42
|
export declare const listenerDeviceId: () => string;
|
|
43
|
+
/**
|
|
44
|
+
* A one-line notice for a machine that still exports `ZEPH_WS_URL`, or null.
|
|
45
|
+
*
|
|
46
|
+
* The variable is no longer read. Dropping it in silence would repeat the
|
|
47
|
+
* failure it caused — a value that looks like it is in effect while something
|
|
48
|
+
* else decides — so it is read here only to say it is being ignored.
|
|
49
|
+
*/
|
|
50
|
+
export declare const legacyWsEnv: (env?: NodeJS.ProcessEnv) => string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* What to tell a machine that still exports `ZEPH_WS_URL`, or null. Two
|
|
53
|
+
* messages, because the two situations differ: an export beside a config file
|
|
54
|
+
* is dead weight, while an export on its own is still carrying the value.
|
|
55
|
+
*/
|
|
56
|
+
export declare const legacyWsEnvNotice: (config?: {
|
|
57
|
+
wsUrl?: string;
|
|
58
|
+
}, env?: NodeJS.ProcessEnv) => string | null;
|
|
43
59
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;0EACsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;uCAGmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACvB;AAID,wEAAwE;AACxE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAMlD,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,MAMnC,CAAC;AAMF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,aAAa,MAAM,EAAE,OAAO,MAAM,KAAG,MAGpE,CAAC;AAwGF,eAAO,MAAM,UAAU,QAAO,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;0EACsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;uCAGmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACvB;AAID,wEAAwE;AACxE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAMlD,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,MAMnC,CAAC;AAMF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,aAAa,MAAM,EAAE,OAAO,MAAM,KAAG,MAGpE,CAAC;AAwGF,eAAO,MAAM,UAAU,QAAO,eAsC7B,CAAC;AA4BF;;;;;sEAKsE;AACtE,eAAO,MAAM,gBAAgB,QAAO,MAQnC,CAAC;AAcF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,MAAM,GAAG,SAK3E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC1B,SAAQ;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,EAC/B,MAAK,MAAM,CAAC,UAAwB,KACrC,MAAM,GAAG,IAKX,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listenerDeviceId = exports.loadConfig = exports.formatPushTitle = exports.detectProjectDir = exports.projectHash = void 0;
|
|
3
|
+
exports.legacyWsEnvNotice = exports.legacyWsEnv = exports.listenerDeviceId = exports.loadConfig = exports.formatPushTitle = exports.detectProjectDir = exports.projectHash = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const os_1 = require("os");
|
|
6
6
|
const crypto_1 = require("crypto");
|
|
@@ -161,7 +161,12 @@ const loadConfig = () => {
|
|
|
161
161
|
return {
|
|
162
162
|
apiKey,
|
|
163
163
|
baseUrl: (resolvedEnv('ZEPH_BASE_URL') ?? fileConfig.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ''),
|
|
164
|
-
|
|
164
|
+
// Config first, environment second — the inverse of what this used to
|
|
165
|
+
// be. `ZEPH_WS_URL` sat ABOVE ~/.zeph/config.json, so an `export` in a
|
|
166
|
+
// shell profile silently outranked the file with nothing anywhere to
|
|
167
|
+
// say it had. It still answers when the file has nothing, so a
|
|
168
|
+
// self-hosted machine does not quietly lose its WebSocket fast path.
|
|
169
|
+
wsUrl: fileConfig.wsUrl ?? (0, exports.legacyWsEnv)(),
|
|
165
170
|
hookId: resolvedEnv('ZEPH_HOOK_ID') ?? fileConfig.hookId,
|
|
166
171
|
deviceId: resolvedEnv('ZEPH_DEVICE_ID') ?? fileConfig.deviceId,
|
|
167
172
|
sessionId,
|
|
@@ -229,3 +234,30 @@ const detectTmuxSessionName = () => {
|
|
|
229
234
|
return undefined;
|
|
230
235
|
}
|
|
231
236
|
};
|
|
237
|
+
/**
|
|
238
|
+
* A one-line notice for a machine that still exports `ZEPH_WS_URL`, or null.
|
|
239
|
+
*
|
|
240
|
+
* The variable is no longer read. Dropping it in silence would repeat the
|
|
241
|
+
* failure it caused — a value that looks like it is in effect while something
|
|
242
|
+
* else decides — so it is read here only to say it is being ignored.
|
|
243
|
+
*/
|
|
244
|
+
const legacyWsEnv = (env = process.env) => {
|
|
245
|
+
const value = env.ZEPH_WS_URL;
|
|
246
|
+
// Same guard resolvedEnv applies: an unexpanded "${ZEPH_WS_URL}" out of an
|
|
247
|
+
// agent's env block is a placeholder, not a value.
|
|
248
|
+
return value && !value.startsWith('${') ? value : undefined;
|
|
249
|
+
};
|
|
250
|
+
exports.legacyWsEnv = legacyWsEnv;
|
|
251
|
+
/**
|
|
252
|
+
* What to tell a machine that still exports `ZEPH_WS_URL`, or null. Two
|
|
253
|
+
* messages, because the two situations differ: an export beside a config file
|
|
254
|
+
* is dead weight, while an export on its own is still carrying the value.
|
|
255
|
+
*/
|
|
256
|
+
const legacyWsEnvNotice = (config = {}, env = process.env) => {
|
|
257
|
+
if (!(0, exports.legacyWsEnv)(env))
|
|
258
|
+
return null;
|
|
259
|
+
return config.wsUrl
|
|
260
|
+
? '[Config] ZEPH_WS_URL is set but ~/.zeph/config.json wins now — the export does nothing and can go.'
|
|
261
|
+
: '[Config] ZEPH_WS_URL is deprecated and will stop being read — move the value to "wsUrl" in ~/.zeph/config.json.';
|
|
262
|
+
};
|
|
263
|
+
exports.legacyWsEnvNotice = legacyWsEnvNotice;
|
package/dist/index.js
CHANGED
|
@@ -77,6 +77,11 @@ const createServer = (config) => {
|
|
|
77
77
|
};
|
|
78
78
|
const main = async () => {
|
|
79
79
|
const config = (0, config_js_1.loadConfig)();
|
|
80
|
+
// Read only to report: the variable used to outrank the config file, and
|
|
81
|
+
// removing it without a word would leave the same silence behind.
|
|
82
|
+
const staleWsEnv = (0, config_js_1.legacyWsEnvNotice)(config);
|
|
83
|
+
if (staleWsEnv)
|
|
84
|
+
console.error(staleWsEnv);
|
|
80
85
|
// Load or create this host's keypair, if the account has opted in. Runs once
|
|
81
86
|
// per process and caches, so toggling E2E in the app while this server is
|
|
82
87
|
// running has no effect until it restarts.
|
package/dist/tools/ask.js
CHANGED
|
@@ -46,7 +46,7 @@ const settleRemoteState = (answer) => {
|
|
|
46
46
|
};
|
|
47
47
|
const registerAskTool = (server, client, config, waiter) => {
|
|
48
48
|
server.registerTool('zeph_ask', {
|
|
49
|
-
description: 'Ask the user a question with
|
|
49
|
+
description: 'Ask the user a question with 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. `actions` is the steering surface, not decoration: pass 2–4 buttons on nearly every ask — the next-step candidates you would otherwise write as prose (next command, review, stop) plus a safe Done-like `fallback`. Leave `actions` out ONLY when the answer is inherently free-form text (a name, a path, a paragraph); a bare text box on a "done — what next?" ask leaves the phone with nothing to tap. 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.',
|
|
50
50
|
annotations: {
|
|
51
51
|
readOnlyHint: false,
|
|
52
52
|
destructiveHint: false,
|
|
@@ -65,7 +65,7 @@ const registerAskTool = (server, client, config, waiter) => {
|
|
|
65
65
|
.min(1)
|
|
66
66
|
.max(4)
|
|
67
67
|
.optional()
|
|
68
|
-
.describe('Quick-reply buttons (1-4). Omit
|
|
68
|
+
.describe('Quick-reply buttons (1-4). Expected on nearly every ask — the phone steers by tapping, so put the next-step candidates here (next command, review, stop) plus a Done-like fallback. Omit ONLY when the answer is inherently free-form text; never omit on a "done — what next?" ask.'),
|
|
69
69
|
placeholder: zod_1.z.string().optional().describe('Input field placeholder hint'),
|
|
70
70
|
inputType: zod_1.z
|
|
71
71
|
.enum(['text', 'multiline'])
|