@paigy/mcp 0.14.1 → 0.15.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.
@@ -8,7 +8,7 @@ import {
8
8
  readKeyFile,
9
9
  sealFields,
10
10
  verifiedRecipients
11
- } from "./chunk-WGOGS2JC.js";
11
+ } from "./chunk-W2ZS7IBA.js";
12
12
 
13
13
  // src/client.ts
14
14
  import { existsSync, readFileSync } from "fs";
@@ -2531,6 +2531,9 @@ var InboxItemSchema = z.object({
2531
2531
  options: z.array(OptionSchema).optional(),
2532
2532
  /** The ask's declared coverage points (#396), when the agent sent them. */
2533
2533
  points: z.array(z.string()).optional(),
2534
+ /** Natural spoken questions for the points (#417 follow-up), phrased at ring time
2535
+ * and index-aligned — the call bot prefers these over the raw shorthand. */
2536
+ pointsSpeak: z.array(z.string()).optional(),
2534
2537
  /** On a replied detail (#397): the next steps the user attached to the answer
2535
2538
  * ("call back after lunch") — shown so they can see the commitment was captured. */
2536
2539
  intents: z.array(IntentSchema).optional(),
@@ -2825,7 +2828,7 @@ async function reach(url, init) {
2825
2828
  import { execFile } from "child_process";
2826
2829
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2827
2830
  import { homedir, platform } from "os";
2828
- import { join } from "path";
2831
+ import { join, dirname } from "path";
2829
2832
 
2830
2833
  // ../../packages/crypto/dist/index.js
2831
2834
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
@@ -3316,6 +3319,74 @@ function openSealedAnswer(sealed, keypair) {
3316
3319
  }
3317
3320
  return answer;
3318
3321
  }
3322
+ function withPaigyMcpJson(existing, agent) {
3323
+ let config = {};
3324
+ if (existing) {
3325
+ try {
3326
+ config = JSON.parse(existing);
3327
+ } catch {
3328
+ return null;
3329
+ }
3330
+ }
3331
+ config.mcpServers = {
3332
+ ...config.mcpServers ?? {},
3333
+ paigy: { command: "npx", args: ["-y", "@paigy/mcp@latest"], env: { PAIGY_AGENT: agent } }
3334
+ };
3335
+ return JSON.stringify(config, null, 2);
3336
+ }
3337
+ function withPaigyMcpToml(existing, agent) {
3338
+ if (existing.includes("[mcp_servers.paigy]")) return null;
3339
+ const entry = [
3340
+ "",
3341
+ "[mcp_servers.paigy]",
3342
+ 'command = "npx"',
3343
+ 'args = ["-y", "@paigy/mcp@latest"]',
3344
+ `env = { PAIGY_AGENT = "${agent}" }`,
3345
+ ""
3346
+ ].join("\n");
3347
+ return existing + entry;
3348
+ }
3349
+ function autoConfigureClients(skip = AGENT_NAME) {
3350
+ const home = homedir();
3351
+ const registered = [];
3352
+ if (skip !== "antigravity") {
3353
+ const antigravityConfigs = [
3354
+ join(home, ".gemini", "config", "mcp_config.json"),
3355
+ join(home, ".gemini", "antigravity-cli", "mcp_config.json")
3356
+ ];
3357
+ for (const path of antigravityConfigs) {
3358
+ try {
3359
+ if (!existsSync(dirname(path))) continue;
3360
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : null;
3361
+ const updated = withPaigyMcpJson(existing, "antigravity");
3362
+ if (updated === null) continue;
3363
+ writeFileSync(path, updated);
3364
+ registered.push(`Google Antigravity (${path})`);
3365
+ } catch {
3366
+ }
3367
+ }
3368
+ }
3369
+ if (skip !== "codex") {
3370
+ const codexConfig = join(home, ".codex", "config.toml");
3371
+ try {
3372
+ if (existsSync(dirname(codexConfig))) {
3373
+ const existing = existsSync(codexConfig) ? readFileSync(codexConfig, "utf8") : "";
3374
+ const updated = withPaigyMcpToml(existing, "codex");
3375
+ if (updated !== null) {
3376
+ writeFileSync(codexConfig, updated);
3377
+ registered.push(`OpenAI Codex (${codexConfig})`);
3378
+ }
3379
+ }
3380
+ } catch {
3381
+ }
3382
+ }
3383
+ return registered;
3384
+ }
3385
+ function claudeInstallHint(skip = AGENT_NAME) {
3386
+ if (skip.startsWith("claude")) return null;
3387
+ if (!existsSync(join(homedir(), ".claude"))) return null;
3388
+ return "Claude Code detected \u2014 to add Paigy there, run /plugin marketplace add paigy-ai/claude then /plugin install paigy (pairing carries over; no need to pair again).";
3389
+ }
3319
3390
 
3320
3391
  export {
3321
3392
  NotifyRequestSchema,
@@ -3349,5 +3420,7 @@ export {
3349
3420
  fetchRoster,
3350
3421
  verifiedRecipients,
3351
3422
  sealFields,
3352
- openSealedAnswer
3423
+ openSealedAnswer,
3424
+ autoConfigureClients,
3425
+ claudeInstallHint
3353
3426
  };
package/dist/index.js CHANGED
@@ -6,11 +6,13 @@ import {
6
6
  scheduleCallback,
7
7
  setTaskState,
8
8
  submitNotification
9
- } from "./chunk-OSQ3UXY6.js";
9
+ } from "./chunk-7S6W7NEZ.js";
10
10
  import {
11
11
  NotifyRequestSchema,
12
12
  ScheduleCallbackSchema,
13
13
  SetTaskStateSchema,
14
+ autoConfigureClients,
15
+ claudeInstallHint,
14
16
  clearSurface,
15
17
  deleteKeyFile,
16
18
  deleteToken,
@@ -26,7 +28,7 @@ import {
26
28
  sleep,
27
29
  startE2ee,
28
30
  writeSurface
29
- } from "./chunk-WGOGS2JC.js";
31
+ } from "./chunk-W2ZS7IBA.js";
30
32
 
31
33
  // src/index.ts
32
34
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -151,23 +153,37 @@ function pairedResult(token, sas, note, e2ee) {
151
153
  base.verify_message = `This pairing is end-to-end encrypted. Ask the user to verify this code matches the one on their phone: ${sas}` + (e2ee ? "" : " (identity key not yet confirmed on the phone).");
152
154
  }
153
155
  if (note) base.note = note;
156
+ try {
157
+ const registered = autoConfigureClients();
158
+ if (registered.length) {
159
+ base.auto_registered = registered;
160
+ base.auto_register_message = `Also registered the Paigy MCP server in: ${registered.join("; ")}. Tell the user \u2014 those tools pick it up on their next restart/reload.`;
161
+ }
162
+ const hint = claudeInstallHint();
163
+ if (hint) base.claude_hint = hint;
164
+ } catch {
165
+ }
154
166
  return { content: [{ type: "text", text: JSON.stringify(base) }] };
155
167
  }
156
168
  function awaitingConfirmResult(sas, device_code) {
157
169
  writeSurface("verify code", sas, 600);
158
170
  return {
159
- content: [{
160
- type: "text",
161
- text: JSON.stringify({
162
- status: "awaiting_confirmation",
163
- e2ee: true,
164
- sas,
165
- device_code,
166
- user_message: `# ${sas}`,
167
- verify_message: `Send \`user_message\` to the user VERBATIM as your entire next message \u2014 it is pre-composed; do not rewrite or wrap it. YOUR NEXT MESSAGE TO THE USER MUST BE EXACTLY THIS CODE AND NOTHING ELSE, displayed large (a markdown heading): ${sas} \u2014 it is the verification code they must compare and type on their phone. Do not wrap it in explanation; every extra sentence buries it. This code REPLACES any earlier one (older pairing attempts are now stale).`,
168
- message: "No token is issued until the user confirms the code match on their phone (E2EE account). After sending the bare code, call pair again with this device_code to keep waiting \u2014 the token arrives only after they confirm."
169
- })
170
- }]
171
+ content: [
172
+ { type: "text", text: `VERIFY CODE: ${sas}
173
+ Check it matches the code on the phone, then type it there to confirm.` },
174
+ {
175
+ type: "text",
176
+ text: JSON.stringify({
177
+ status: "awaiting_confirmation",
178
+ e2ee: true,
179
+ sas,
180
+ device_code,
181
+ user_message: `# ${sas}`,
182
+ verify_message: `STOP HERE: end your turn now with \`user_message\` (the bare code) as your entire reply \u2014 do NOT call pair again in this same turn, or the code text is dropped before the user sees it. This is the verification code they compare + type on their phone; it REPLACES any earlier one. Poll for their confirmation by calling pair with this device_code on your NEXT turn.`,
183
+ message: "No token is issued until the user confirms the code match on their phone (E2EE account)."
184
+ })
185
+ }
186
+ ]
171
187
  };
172
188
  }
173
189
  var server = new Server(
@@ -181,7 +197,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
181
197
  tools: [
182
198
  {
183
199
  name: "pair",
184
- description: "Pair this agent with the user's Paigy account (one-time) \u2014 required before notify_user/await_reply work. Two steps: (1) call with NO args to start; it attempts to open the user's browser and returns { verification_uri_complete, user_code, device_code, qr }. That open attempt can silently fail in headless/remote environments (no browser to open) \u2014 always show the user verification_uri_complete AND user_code regardless of whether it opened, so they can go there manually and enter the code themselves if needed; ask them to approve. `qr` is a terminal-renderable ASCII QR code of the same link \u2014 a scan-to-pair alternative to opening a browser at all, handy when the user's phone is right there; print it verbatim in a fenced code block. (2) call again passing that device_code to finish; it waits for approval and saves the token. If it returns { status:'pending' }, the user hasn't approved yet \u2014 call again with the same device_code to keep waiting.",
200
+ description: "Pair this agent with the user's Paigy account (one-time) \u2014 required before notify_user/await_reply work. It does NOT open a browser; the user enters the code in the Paigy app (or scans `qr`). Step 1: call with NO args \u2014 returns { user_code, device_code, qr, user_message }. Then END YOUR TURN immediately, replying with `user_message` (the bare code) as your ENTIRE message \u2014 do NOT chain the step-2 call in the same turn, or the code text is dropped before the user ever sees it (this is the #1 pairing bug). Step 2 (a SEPARATE later turn, after the user says they've entered it): call with that device_code to poll for approval; on { status:'pending' } call again to keep waiting; on { status:'awaiting_confirmation' } (E2EE) show the bare `user_message` verify code and end the turn again, polling on the next. The leading text block of every result already states the code plainly, so it shows even if you emit no prose.",
185
201
  inputSchema: json(PairSchema)
186
202
  },
187
203
  {
@@ -229,21 +245,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
229
245
  qr.addData(code.verification_uri_complete);
230
246
  qr.make();
231
247
  return {
232
- content: [{
233
- type: "text",
234
- text: JSON.stringify({
235
- status: "awaiting_approval",
236
- verification_uri_complete: code.verification_uri_complete,
237
- user_code: code.user_code,
238
- device_code: code.device_code,
239
- expires_in: code.expires_in,
240
- qr: qr.createASCII(1, 2),
241
- user_message: `# ${code.user_code}
248
+ content: [
249
+ { type: "text", text: `PAIRING CODE: ${code.user_code}
250
+ Enter it in the Paigy app: Inbox \u2192 Add a new agent.` },
251
+ {
252
+ type: "text",
253
+ text: JSON.stringify({
254
+ status: "awaiting_approval",
255
+ verification_uri_complete: code.verification_uri_complete,
256
+ user_code: code.user_code,
257
+ device_code: code.device_code,
258
+ expires_in: code.expires_in,
259
+ qr: qr.createASCII(1, 2),
260
+ user_message: `# ${code.user_code}
242
261
 
243
262
  Enter it in the Paigy app (Inbox \u2192 Add a new agent).`,
244
- message: `Send \`user_message\` to the user VERBATIM as your entire next message \u2014 it is pre-composed; do not rewrite or wrap it. YOUR NEXT MESSAGE TO THE USER MUST BE EXACTLY THIS CODE displayed large (a markdown heading): ${code.user_code} \u2014 plus ONE short line: enter it in the Paigy app (Inbox \u2192 Add a new agent). No other prose. Do NOT open a browser for the user. If their phone is handy they may prefer scanning \`qr\` (print it verbatim in a fenced code block on request). Then immediately call pair again with this device_code \u2014 it polls for the approval.`
245
- })
246
- }]
263
+ message: `STOP HERE: end your turn now with \`user_message\` as your entire reply (the bare code, nothing else) \u2014 do NOT call pair again in this same turn, or the code text is dropped before the user sees it. Do NOT open a browser. Poll for approval by calling pair with this device_code on your NEXT turn, after you've shown the code. The user may prefer scanning \`qr\` (print it verbatim in a fenced code block on request).`
264
+ })
265
+ }
266
+ ]
247
267
  };
248
268
  }
249
269
  let kf = readKeyFile();
package/dist/listen.js CHANGED
@@ -3,11 +3,11 @@ import { createRequire as __createRequire } from 'node:module'; const require =
3
3
  import {
4
4
  checkReplies,
5
5
  registerDelivery
6
- } from "./chunk-OSQ3UXY6.js";
6
+ } from "./chunk-7S6W7NEZ.js";
7
7
  import {
8
8
  WAKE_EVENT,
9
9
  wakeChannel
10
- } from "./chunk-WGOGS2JC.js";
10
+ } from "./chunk-W2ZS7IBA.js";
11
11
 
12
12
  // src/listen.ts
13
13
  import { createClient } from "@supabase/supabase-js";
package/dist/onboard.js CHANGED
@@ -3,12 +3,14 @@ import { createRequire as __createRequire } from 'node:module'; const require =
3
3
  import {
4
4
  AGENT_NAME,
5
5
  TOKEN_PATH,
6
+ autoConfigureClients,
7
+ claudeInstallHint,
6
8
  openBrowser,
7
9
  pollToken,
8
10
  requestCode,
9
11
  saveToken,
10
12
  sleep
11
- } from "./chunk-WGOGS2JC.js";
13
+ } from "./chunk-W2ZS7IBA.js";
12
14
 
13
15
  // src/onboard.ts
14
16
  async function main() {
@@ -39,6 +41,15 @@ Paired! Token saved to ${TOKEN_PATH} (mode 0600).`);
39
41
  console.log(` Nickname : ${token.nickname}`);
40
42
  console.log(` Agent : ${token.agent}`);
41
43
  console.log(` Device : ${token.device ?? "(none)"}
44
+ `);
45
+ const registered = autoConfigureClients();
46
+ if (registered.length) {
47
+ console.log("Also registered the Paigy MCP server in:");
48
+ for (const r of registered) console.log(` - ${r}`);
49
+ console.log("(Restart those tools to pick it up.)\n");
50
+ }
51
+ const hint = claudeInstallHint();
52
+ if (hint) console.log(`${hint}
42
53
  `);
43
54
  return;
44
55
  }
@@ -4,7 +4,7 @@ import {
4
4
  BACKEND_URL,
5
5
  readSurface,
6
6
  readToken
7
- } from "./chunk-WGOGS2JC.js";
7
+ } from "./chunk-W2ZS7IBA.js";
8
8
 
9
9
  // src/statusline.ts
10
10
  import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paigy/mcp",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "Paigy MCP server — a voice inbox for your AI agents. Lets an agent notify a user and await their reply.",
5
5
  "license": "MIT",
6
6
  "type": "module",