@quackai/q402-mcp 0.6.1 → 0.6.3

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.
Files changed (3) hide show
  1. package/README.md +51 -24
  2. package/dist/index.js +427 -23
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -57,35 +57,55 @@ The agent calls `q402_doctor`. On first install, the tool tells the agent to:
57
57
 
58
58
  1. Offer to create `~/.q402/mcp.env` (placeholder values only)
59
59
  2. Open the file in your editor (`code` / `open` / `start` / `xdg-open`)
60
- 3. Walk you through pasting in (a) your API key from <https://q402.quackai.ai/event> (free Trial) or <https://q402.quackai.ai/payment> (paid Multichain), and (b) your wallet private key — **into the file, not into chat**
60
+ 3. Walk you through pasting in (a) your API key from <https://q402.quackai.ai/event> (free Trial) or <https://q402.quackai.ai/payment> (paid Multichain), and (b) a signing path — **into the file, not into chat**
61
61
  4. Restart the client and run `q402_doctor` again to verify
62
62
 
63
- 🔒 **Q402 never asks you to paste your private key into chat.** The MCP server signs payments LOCALLY on your machine — your key never leaves your device.
63
+ 🔒 **Q402 never asks you to paste your private key into chat.** When you use a local signing mode the MCP server signs payments LOCALLY on your machine — your key never leaves your device. Mode C (server-managed Agent Wallet) requires no private key on the client at all.
64
+
65
+ ### Pick a signing mode
66
+
67
+ Q402 supports three signing modes — pick ONE:
68
+
69
+ | Mode | Env you set | Signer | Notes |
70
+ |---|---|---|---|
71
+ | **A** | `Q402_PRIVATE_KEY` | your MetaMask EOA, local | Simplest. After the first payment that EOA shows "Smart account" in MetaMask (EIP-7702 delegation, reversible via `q402_clear_delegation`). |
72
+ | **B** | `Q402_AGENTIC_PRIVATE_KEY` | dedicated Agent Wallet, local | Export the PK from the [dashboard](https://q402.quackai.ai/dashboard) Agent tab → Export. Signs locally just like Mode A, but the signer is your Agent Wallet — MetaMask is never touched. Recommended for AI-agent automation. |
73
+ | **C** | (just the paid API key) | dedicated Agent Wallet, server-managed | The encrypted Agent Wallet key lives on the Q402 server and signs on your behalf. No private key on the client. Optionally set `Q402_AGENT_WALLET_ADDRESS` to pick among multiple wallets (max 10 per owner). Mode C requires a paid Multichain API key — not available on the free Trial. |
74
+
75
+ When more than one signing mode is set at once, `q402_pay` asks the user which to use rather than picking silently. The picker lives in the `walletMode` argument: `"agentic-server"` (Mode C), `"agentic-local"` (Mode B), `"eoa"` (Mode A).
64
76
 
65
77
  ### Manual setup (no AI)
66
78
 
67
- Create `~/.q402/mcp.env` yourself. The template below matches what `q402_doctor` writes — the three secret lines (`Q402_TRIAL_API_KEY`, `Q402_MULTICHAIN_API_KEY`, `Q402_PRIVATE_KEY`) ship empty, with `Q402_ENABLE_REAL_PAYMENTS=1`. Paste real values on the right of `=` for the key(s) you have and your wallet key. The server only flips into live mode once both a `q402_live_*` API key AND a valid 32-byte private key are present, so saving the template as-is is safe (empty values fail the gate and stay in sandbox). Change the flag to `0` if you want to force sandbox even with real keys (e.g. for chained testing).
79
+ Create `~/.q402/mcp.env` yourself. The template below matches what `q402_doctor` writes — every secret line ships empty, with `Q402_ENABLE_REAL_PAYMENTS=1`. Fill in (a) at least one API key and (b) the row(s) for the signing mode you picked. The server only flips into live mode once both a `q402_live_*` API key AND a valid signing path are present, so saving the template as-is is safe (empty values fail the gate and stay in sandbox). Change the flag to `0` to force sandbox even with real keys (e.g. for chained testing).
68
80
 
69
81
  ```bash
70
82
  # ~/.q402/mcp.env
71
83
 
72
- # Free Trial BNB only, 2,000 sponsored TX (from /event)
73
- Q402_TRIAL_API_KEY=
84
+ # ── API key (pick one or both for auto-routing) ──
85
+ Q402_TRIAL_API_KEY= # Free Trial, BNB only (from /event)
86
+ Q402_MULTICHAIN_API_KEY= # Paid Multichain, all 9 chains (from /payment)
74
87
 
75
- # Paid Multichainall 9 chains (from /payment)
76
- Q402_MULTICHAIN_API_KEY=
77
-
78
- # Hex EVM private key (0x + 64 hex). A separate MetaMask account
79
- # dedicated to Q402 keeps your existing balances and history tidy.
80
- # Hardware wallets (Ledger / Trezor) are not supported yet — Q402
88
+ # ── Signing path pick ONE of Mode A / B / C ──
89
+ # Mode A: your MetaMask EOA's hex private key.
90
+ # Hardware wallets (Ledger / Trezor) are NOT supported here — Q402
81
91
  # needs a raw hex key it can sign EIP-7702 type-4 authorizations with.
82
92
  Q402_PRIVATE_KEY=
83
93
 
94
+ # Mode B: exported Agent Wallet pk from the dashboard. Keeps your
95
+ # MetaMask untouched. Get it at:
96
+ # https://q402.quackai.ai/dashboard → Agent tab → Export
97
+ Q402_AGENTIC_PRIVATE_KEY=
98
+
99
+ # Mode C: no PK needed. Set ONLY the paid Multichain key above, leave
100
+ # both PK lines blank. Q402 signs with the server-managed Agent Wallet.
101
+ # Optional: pin one of your Agent Wallets when you have multiple (max 10).
102
+ # Q402_AGENT_WALLET_ADDRESS=0x...
103
+
84
104
  # Live mode switch:
85
105
  # 0 = sandbox (test mode, no funds move)
86
106
  # 1 = real on-chain payments
87
- # Default 1 — safe because mode only flips to live when BOTH a live
88
- # API key AND a valid 32-byte private key are populated above.
107
+ # Default 1 — safe because mode only flips to live when an API key AND
108
+ # at least one valid signing path (A/B/C) are populated above.
89
109
  Q402_ENABLE_REAL_PAYMENTS=1
90
110
 
91
111
  # Default Q402 deployment. Only change for self-hosted.
@@ -114,6 +134,8 @@ env_vars = [
114
134
  "Q402_TRIAL_API_KEY",
115
135
  "Q402_MULTICHAIN_API_KEY",
116
136
  "Q402_PRIVATE_KEY",
137
+ "Q402_AGENTIC_PRIVATE_KEY",
138
+ "Q402_AGENT_WALLET_ADDRESS",
117
139
  "Q402_ENABLE_REAL_PAYMENTS",
118
140
  "Q402_RELAY_BASE_URL",
119
141
  ]
@@ -146,6 +168,7 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
146
168
  | `q402_batch_pay` | API key + private key + flag | Send a gasless payment to **multiple** recipients in one call on a single chain × token. Trial keys: 5 rows max. Paid keys: 20 rows max. **Auto-routing:** same rule as `q402_pay` (BNB + Trial key set ⇒ Trial, else Multichain). **Ambiguity gate:** 6+ recipient BNB batches with Trial set return `status="ambiguous"` instead of executing — the agent asks the user to pick `keyScope="trial"` (first 5), `"multichain"` (all paid), or two calls (5 free + remainder paid). **Supported chains: avax, bnb, eth, mantle, injective, monad, scroll** (default EIP-7702 mode). xlayer + stable are NOT batchable — use `q402_pay` in a loop for those. Same sandbox gating as `q402_pay`. **Rate-limit note:** the inner `/api/relay` budget (30/min per key) is consumed per row, so a paid 20-row batch leaves ~10 inner slots for the next minute. |
147
169
  | `q402_receipt` | none | Look up a Trust Receipt by `rct_…` id and locally verify its ECDSA signature against the relayer EOA. Returns the public settlement record + a `verified` boolean. *receiptId-only today; tx-hash lookup reserved for a future release.* |
148
170
  | `q402_wallet_status` | private key | Per-chain EIP-7702 delegation status for the EOA derived from `Q402_PRIVATE_KEY`, across all 9 Q402 chains. Read-only — no on-chain action, no quota consumption. Useful as the diagnostic step before `q402_clear_delegation`. |
171
+ | `q402_agentic_info` | API key | Introspect your server-managed Agent Wallets — addresses, per-wallet caps, daily-spend used, ERC-8004 agent id, per-chain sub-balances. Drives Mode C (server-managed) so the agent can see what wallet it's about to pay from without ever holding the private key. Auth is API-key only; no PK needed. Read-only. |
149
172
  | `q402_clear_delegation` | private key | Clear the EIP-7702 delegation on a single chain for the configured wallet. Local signing with `Q402_PRIVATE_KEY`; Q402 sponsors the on-chain TX so the user pays $0 gas. After clearing, the next `q402_pay` on that chain recreates a fresh delegation automatically. |
150
173
 
151
174
  `q402_pay` and `q402_batch_pay` follow a "confirm in chat first" contract: the tool description instructs the model to never call it without explicit user approval of the recipient address(es), amount(s), chain, and token. For batch calls the user must approve the **full batch**, not the individual rows.
@@ -165,30 +188,32 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
165
188
 
166
189
  By default the MCP server operates in **sandbox mode**: `q402_pay` returns a random fake transaction hash with `success: false` and `sandbox: true`, no funds move, no gas-tank credit is consumed. That makes it safe to plug into any MCP client without worrying about an accidental payment — if the agent misreads the conversation and fires `q402_pay` before you intended, nothing moves AND the response cannot be mistaken for a confirmed settlement.
167
190
 
168
- To enable real on-chain transactions, the resolved API key must be live (`q402_live_*`), `Q402_PRIVATE_KEY` must be set to a valid 32-byte hex key, and `Q402_ENABLE_REAL_PAYMENTS=1`. The block below is the template `q402_doctor` writes to `~/.q402/mcp.env` the three secret lines ship empty (no `#` to remove, just paste the value on the right of `=`) and the live flag defaults to `1`. The live-mode gate only flips once a real key + valid 32-byte PK are populated, so saving the template as-is stays in sandbox automatically. Change the flag to `0` if you want to force sandbox even with real keys (e.g. for chained testing on a paid plan):
191
+ To enable real on-chain transactions you need (a) a live API key (`q402_live_*`), (b) at least one valid signing path — Mode A `Q402_PRIVATE_KEY`, Mode B `Q402_AGENTIC_PRIVATE_KEY`, or Mode C (paid Multichain key alone, server-managed Agent Wallet, no PK on the client) — and (c) `Q402_ENABLE_REAL_PAYMENTS=1`. The block below is the template `q402_doctor` writes to `~/.q402/mcp.env`; every secret line ships empty, the live flag defaults to `1`, and the gate only flips when an API key + a valid signing path are populated. Change the flag to `0` to force sandbox even with real keys (e.g. for chained testing on a paid plan):
169
192
 
170
193
  ```bash
171
- # Two-key model — fill ONE (or both for auto-routing).
194
+ # ── API key — fill ONE (or both for auto-routing) ──
172
195
  # Auto-routing (same for q402_pay AND q402_batch_pay):
173
196
  # chain="bnb" + Q402_TRIAL_API_KEY set → Trial (free sponsored)
174
197
  # anything else → Multichain (paid 9-chain)
175
198
  # Batch ambiguity: 6+ recipient BNB batch with Trial set returns
176
199
  # status="ambiguous" instead of executing — agent asks user to pick.
177
200
  # Override per call with keyScope: "auto" | "trial" | "multichain".
178
-
179
201
  Q402_TRIAL_API_KEY= # BNB-only sponsored Trial key (from /event)
180
202
  Q402_MULTICHAIN_API_KEY= # paid 9-chain key (per-chain Gas Tank)
181
203
 
182
- Q402_PRIVATE_KEY= # signer for the payer EOA (0x + 64 hex chars)
204
+ # ── Signing path pick ONE of Mode A / B / C ──
205
+ Q402_PRIVATE_KEY= # Mode A: real EOA pk (0x + 64 hex)
206
+ Q402_AGENTIC_PRIVATE_KEY= # Mode B: exported Agent Wallet pk (from dashboard)
207
+ # Mode C: leave both PK lines blank, set only the paid Multichain key
208
+ # above. Q402 signs with the server-managed Agent Wallet. Optionally:
209
+ # Q402_AGENT_WALLET_ADDRESS=0x... # pin one of your wallets when you have multiple
183
210
 
184
211
  # Live mode switch:
185
212
  # 0 = sandbox (test mode, no funds move — every q402_pay returns a fake hash)
186
213
  # 1 = real on-chain payments (live mode)
187
- # Default 1: real payments enabled. Safe because mode only flips to live
188
- # when BOTH a live API key (q402_live_*) AND a valid 32-byte private
189
- # key are populated above. Empty values fail the gate, so partial setups
190
- # stay in sandbox with a hint. Change to 0 to force sandbox even with
191
- # real keys (e.g. for chained testing on a paid plan).
214
+ # Default 1. Safe because the gate only flips to live when an API key AND
215
+ # at least one valid signing path (A/B/C) are populated. Empty values
216
+ # fail the gate, so partial setups stay in sandbox with a hint.
192
217
  Q402_ENABLE_REAL_PAYMENTS=1
193
218
  ```
194
219
 
@@ -214,8 +239,10 @@ Combined with the `confirm: true` argument the tool requires, this means the mod
214
239
  | Env var | Required for | Notes |
215
240
  |---|---|---|
216
241
  | `Q402_TRIAL_API_KEY` | live-pay (BNB) | BNB-only sponsored Trial key. Free at https://q402.quackai.ai/event. Auto-routed for `chain="bnb"` in both `q402_pay` and `q402_batch_pay` (≤5 recipients) when set. 6+ recipient BNB batches return `status="ambiguous"` so the agent can ask the user how to split. |
217
- | `Q402_MULTICHAIN_API_KEY` | live-pay (9-chain) | Paid 9-chain key. Get one at https://q402.quackai.ai/payment. Auto-routed for non-BNB chains AND for BNB when no Trial key is set. Cap: 20 recipients per batch. |
218
- | `Q402_PRIVATE_KEY` | live-pay | Signer for the payer EOA. **Never share. Never paste in chat.** |
242
+ | `Q402_MULTICHAIN_API_KEY` | live-pay (9-chain) | Paid 9-chain key. Get one at https://q402.quackai.ai/payment. Auto-routed for non-BNB chains AND for BNB when no Trial key is set. Cap: 20 recipients per batch. Required for Mode C (server-managed Agent Wallet). |
243
+ | `Q402_PRIVATE_KEY` | Mode A | Hex private key of your MetaMask EOA. Signer for local Mode A. **Never share. Never paste in chat.** |
244
+ | `Q402_AGENTIC_PRIVATE_KEY` | Mode B | Exported Agent Wallet hex private key from the dashboard (Agent tab → Export). Signs locally, but the signer is your dedicated Agent Wallet — MetaMask is never touched. **Never share. Never paste in chat.** |
245
+ | `Q402_AGENT_WALLET_ADDRESS` | Mode C (optional) | When you have multiple server-managed Agent Wallets (max 10 per owner), set this to the lowercased 0x… address of the one Q402 should spend from. Omit to use the default wallet. Ignored in Modes A/B. |
219
246
  | `Q402_ENABLE_REAL_PAYMENTS` | live-pay | Set to `1` to opt in. Any other value (or unset) → sandbox. |
220
247
  | `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `200`. Lower for tighter agent blast-radius. |
221
248
  | `Q402_ALLOWED_RECIPIENTS` | optional | Comma-separated lowercase addresses. Defaults to no allowlist. |
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
211
211
 
212
212
  // src/version.ts
213
213
  var PACKAGE_NAME = "@quackai/q402-mcp";
214
- var PACKAGE_VERSION = "0.6.1";
214
+ var PACKAGE_VERSION = "0.6.3";
215
215
 
216
216
  // src/tools/quote.ts
217
217
  import { z } from "zod";
@@ -663,13 +663,12 @@ var Q402NodeClient = class _Q402NodeClient {
663
663
  * delegation), after which the remaining transfers are surfaced in
664
664
  * the result array even if individual ones fail.
665
665
  *
666
- * Signature shape: `{ token, recipients }`. The previous revision took
667
- * `PayInput[]` (with token on each row), which read as if rows could
668
- * carry different tokens but the request body only ships one token
669
- * field, so the per-row token on rows 1..N was silently ignored. The
670
- * new shape surfaces the constraint in the type so consumers can't
671
- * accidentally build a "mixed-token batch" that quietly drops the
672
- * second token. Same chain + same token across one batch, full stop.
666
+ * Signature shape: `{ token, recipients }`. The request body only ships
667
+ * one token field, so a per-row token would be silently ignored. The
668
+ * shape surfaces the constraint in the type so consumers can't
669
+ * accidentally build a "mixed-token batch" that would quietly drop
670
+ * the second token. Same chain + same token across one batch, full
671
+ * stop.
673
672
  */
674
673
  async batchPay(input) {
675
674
  const { token, recipients: rows } = input;
@@ -1272,9 +1271,10 @@ async function runBatchPay(input) {
1272
1271
  guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
1273
1272
  }
1274
1273
  let senderWallet;
1275
- if (CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
1274
+ const echoPk = CONFIG.agenticPrivateKey && isValidPrivateKey(CONFIG.agenticPrivateKey) ? CONFIG.agenticPrivateKey : CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey) ? CONFIG.privateKey : null;
1275
+ if (echoPk) {
1276
1276
  try {
1277
- const addr = new Wallet3(CONFIG.privateKey).address;
1277
+ const addr = new Wallet3(echoPk).address;
1278
1278
  senderWallet = {
1279
1279
  address: addr,
1280
1280
  addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
@@ -1325,9 +1325,25 @@ async function runBatchPay(input) {
1325
1325
  setupHint: reason
1326
1326
  };
1327
1327
  }
1328
+ const signingPk = CONFIG.agenticPrivateKey ?? CONFIG.privateKey;
1329
+ if (!signingPk) {
1330
+ guardsApplied.push("mode=sandbox");
1331
+ const sandboxResults = input.recipients.map(
1332
+ (r) => sandboxPay(chain, { to: r.to, amount: r.amount, token: input.token })
1333
+ );
1334
+ const reason = "No local signing key configured. Set Q402_AGENTIC_PRIVATE_KEY (Mode B) or Q402_PRIVATE_KEY (Mode A) to sign batch payments locally.";
1335
+ return {
1336
+ mode: "sandbox",
1337
+ status: "sandbox",
1338
+ result: { sandbox: sandboxResults, reason },
1339
+ senderWallet,
1340
+ guardsApplied,
1341
+ setupHint: reason
1342
+ };
1343
+ }
1328
1344
  const client = new Q402NodeClient({
1329
1345
  apiKey: resolved.apiKey,
1330
- privateKey: CONFIG.privateKey,
1346
+ privateKey: signingPk,
1331
1347
  chain,
1332
1348
  relayBaseUrl: CONFIG.relayBaseUrl
1333
1349
  });
@@ -1928,7 +1944,8 @@ Q402_MULTICHAIN_API_KEY=
1928
1944
  # Pros: simplest mental model \u2014 same wallet you already use
1929
1945
  # Cons: after first payment, MetaMask will show this account as
1930
1946
  # "Smart account" (EIP-7702 delegation, reversible via
1931
- # q402_clear_delegation but visually alarming on first sight)
1947
+ # q402_clear_delegation \u2014 the visual change can be surprising
1948
+ # the first time you see it)
1932
1949
  #
1933
1950
  # B. Agent Wallet \u2014 local signing (recommended for AI agents)
1934
1951
  # Set: Q402_AGENTIC_PRIVATE_KEY = 0x... (Agent Wallet pk from dashboard export)
@@ -2032,7 +2049,7 @@ function envSlot(name, purpose) {
2032
2049
  }
2033
2050
  function mask2(key) {
2034
2051
  if (!key || key.length < 12) return key ?? "";
2035
- return `${key.slice(0, 12)}\u2026${key.slice(-4)}`;
2052
+ return `${key.slice(0, 12)}...${key.slice(-4)}`;
2036
2053
  }
2037
2054
  function detectPhase() {
2038
2055
  const anyKey = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
@@ -2160,7 +2177,15 @@ async function runDoctor() {
2160
2177
  ),
2161
2178
  Q402_PRIVATE_KEY: envSlot(
2162
2179
  "Q402_PRIVATE_KEY",
2163
- "Hex EVM private key. Signs LOCALLY on your machine \u2014 never leaves your device."
2180
+ "Mode A signing key \u2014 your real EOA's private key (e.g. MetaMask). EIP-7702 delegates your wallet to Q402 impl. Signs LOCALLY, never leaves your device."
2181
+ ),
2182
+ Q402_AGENTIC_PRIVATE_KEY: envSlot(
2183
+ "Q402_AGENTIC_PRIVATE_KEY",
2184
+ "Mode B signing key \u2014 your Agent Wallet's exported private key from the dashboard. Signs LOCALLY, never leaves your device. Your MetaMask stays untouched."
2185
+ ),
2186
+ Q402_AGENT_WALLET_ADDRESS: envSlot(
2187
+ "Q402_AGENT_WALLET_ADDRESS",
2188
+ "Mode C target \u2014 lowercased Agent Wallet address when you hold multiple. Omit to use the server-default wallet. No PK needed; the server signs."
2164
2189
  ),
2165
2190
  Q402_ENABLE_REAL_PAYMENTS: envSlot(
2166
2191
  "Q402_ENABLE_REAL_PAYMENTS",
@@ -2235,6 +2260,34 @@ async function runDoctor() {
2235
2260
  "You have a legacy Q402_API_KEY set, and Q402_ENABLE_REAL_PAYMENTS wasn't explicitly set by you \u2014 so it's defaulting to 1 (real payments) since v0.5.11. To stay in sandbox while you check this, add `Q402_ENABLE_REAL_PAYMENTS=0` to ~/.q402/mcp.env (or your shell) and restart the MCP client."
2236
2261
  );
2237
2262
  }
2263
+ const walletModePicker = {
2264
+ question: "How would you like Q402 to sign your payments?",
2265
+ helpText: "Pick once \u2014 you can change later by editing ~/.q402/mcp.env. Most users want C (simplest).",
2266
+ recommendedPick: modes.modeC && !modes.modeA && !modes.modeB ? "C" : modes.primary ?? "C",
2267
+ options: [
2268
+ {
2269
+ pick: "C",
2270
+ title: "Q402 server signs for me (recommended, simplest)",
2271
+ description: "Q402 holds an encrypted Agent Wallet for you. You only set an API key \u2014 no private key in your env, no MetaMask popup, no Smart-account marker on your wallet. Best for AI agents, automations, and anyone who just wants payments to work.",
2272
+ env: ["Q402_MULTICHAIN_API_KEY (paid)", "or Q402_TRIAL_API_KEY (free BNB)"],
2273
+ privateKeyRequired: false
2274
+ },
2275
+ {
2276
+ pick: "B",
2277
+ title: "I'll export the Agent Wallet's private key and sign locally",
2278
+ description: "Same Agent Wallet as Mode C, but YOU hold the private key. Export it from the dashboard once. MCP signs locally so the key never leaves your machine. Your MetaMask is never touched.",
2279
+ env: ["Q402_AGENTIC_PRIVATE_KEY (export from dashboard)", "+ Q402_MULTICHAIN_API_KEY or Q402_TRIAL_API_KEY"],
2280
+ privateKeyRequired: true
2281
+ },
2282
+ {
2283
+ pick: "A",
2284
+ title: "Use my own EOA (MetaMask) as the signer",
2285
+ description: "Your existing wallet signs directly. EIP-7702 delegates it to Q402 for the call \u2014 your wallet shows a 'Smart account' marker after first use (normal, reversible). Best for power users who want their MetaMask address to be the on-chain payer. Use a FRESH wallet \u2014 not the one with your main funds.",
2286
+ env: ["Q402_PRIVATE_KEY (your EOA's private key)", "+ Q402_MULTICHAIN_API_KEY or Q402_TRIAL_API_KEY"],
2287
+ privateKeyRequired: true
2288
+ }
2289
+ ]
2290
+ };
2238
2291
  if (phase !== "live-check") {
2239
2292
  return {
2240
2293
  package: PACKAGE_NAME,
@@ -2243,6 +2296,7 @@ async function runDoctor() {
2243
2296
  ready: false,
2244
2297
  envFile,
2245
2298
  envState,
2299
+ walletModePicker,
2246
2300
  missing,
2247
2301
  warnings,
2248
2302
  recommendedActions,
@@ -2275,13 +2329,15 @@ async function runDoctor() {
2275
2329
  }
2276
2330
  let walletAddress;
2277
2331
  let walletError;
2278
- try {
2279
- walletAddress = new Wallet6(CONFIG.privateKey).address;
2280
- } catch (e) {
2281
- walletError = e instanceof Error ? e.message : String(e);
2282
- warnings.push(
2283
- `Q402_PRIVATE_KEY is set but does not parse as a 32-byte hex key: ${walletError}. Open ~/.q402/mcp.env in your editor and paste a real key (0x + 64 hex chars). Live calls will fail until this is fixed.`
2284
- );
2332
+ if (CONFIG.privateKey) {
2333
+ try {
2334
+ walletAddress = new Wallet6(CONFIG.privateKey).address;
2335
+ } catch (e) {
2336
+ walletError = e instanceof Error ? e.message : String(e);
2337
+ warnings.push(
2338
+ `Q402_PRIVATE_KEY is set but does not parse as a 32-byte hex key: ${walletError}. Open ~/.q402/mcp.env in your editor and paste a real key (0x + 64 hex chars). Live calls will fail until this is fixed.`
2339
+ );
2340
+ }
2285
2341
  }
2286
2342
  const verifyTargets = [];
2287
2343
  if (CONFIG.trialApiKey) verifyTargets.push({ scope: "trial", envVar: "Q402_TRIAL_API_KEY", key: CONFIG.trialApiKey });
@@ -2328,6 +2384,38 @@ async function runDoctor() {
2328
2384
  );
2329
2385
  }
2330
2386
  const ready = warnings.length === 0 && keys.some((k) => k.valid);
2387
+ const activeModes = [];
2388
+ if (modes.modeA) activeModes.push({
2389
+ mode: "A",
2390
+ label: "Real EOA (Q402_PRIVATE_KEY)",
2391
+ rationale: "Your MetaMask wallet signs directly. EIP-7702 delegates it to Q402 for the call."
2392
+ });
2393
+ if (modes.modeB) activeModes.push({
2394
+ mode: "B",
2395
+ label: "Agent Wallet local (Q402_AGENTIC_PRIVATE_KEY)",
2396
+ rationale: "Your exported Agent Wallet PK signs locally. Your MetaMask stays untouched."
2397
+ });
2398
+ if (modes.modeC) activeModes.push({
2399
+ mode: "C",
2400
+ label: "Server-mediated Agent Wallet (apiKey only)",
2401
+ rationale: "No PK in your env. Q402's server holds the encrypted Agent Wallet key and signs for you."
2402
+ });
2403
+ const recommendedMode = modes.primary ?? (CONFIG.apiKey?.startsWith("q402_live_") ? "C" : "none");
2404
+ const walletModes = {
2405
+ available: activeModes,
2406
+ count: modes.count,
2407
+ primary: modes.primary,
2408
+ /** Plain-English picker that the AI should echo when the user asks
2409
+ * "which mode do I use?" or "do I need a private key?". */
2410
+ recommendation: recommendedMode === "C" ? "You're configured for Mode C \u2014 Q402's server signs with your Agent Wallet. No private key needed. Simplest path; recommended for most users." : recommendedMode === "B" ? "You're configured for Mode B \u2014 your exported Agent Wallet PK signs locally. Your MetaMask is never touched." : recommendedMode === "A" ? "You're configured for Mode A \u2014 your MetaMask EOA signs directly. EIP-7702 delegates it to Q402 for the call. (If the Smart-account banner in MetaMask is a concern, switch to Mode B or C.)" : "No signing path configured yet. Easiest: set Q402_MULTICHAIN_API_KEY (or Q402_TRIAL_API_KEY for free BNB) and let the server sign \u2014 that's Mode C, no PK needed.",
2411
+ /** All three modes documented so the AI can answer "what are my
2412
+ * options?" without re-deriving from envState. */
2413
+ catalog: [
2414
+ { mode: "A", label: "Real EOA", envVar: "Q402_PRIVATE_KEY", needsPk: true, bestFor: "Power users who want their MetaMask to be the on-chain signer." },
2415
+ { mode: "B", label: "Agent Wallet local", envVar: "Q402_AGENTIC_PRIVATE_KEY", needsPk: true, bestFor: "Users who want Agent Wallet automation but keep custody of the PK." },
2416
+ { mode: "C", label: "Server-mediated", envVar: "(none)", needsPk: false, bestFor: "Most users. No PK in env; Q402 holds the Agent Wallet key encrypted server-side." }
2417
+ ]
2418
+ };
2331
2419
  return {
2332
2420
  package: PACKAGE_NAME,
2333
2421
  version: PACKAGE_VERSION,
@@ -2335,6 +2423,7 @@ async function runDoctor() {
2335
2423
  ready,
2336
2424
  envFile,
2337
2425
  envState,
2426
+ walletModes,
2338
2427
  missing,
2339
2428
  wallet: walletAddress ? { address: walletAddress } : void 0,
2340
2429
  keys,
@@ -2346,10 +2435,10 @@ async function runDoctor() {
2346
2435
  nextStep: ready ? "Show userInstructions verbatim. Then offer to make a small test quote (q402_quote) to confirm everything works end-to-end." : "Walk the user through each warning in order. Show userInstructions verbatim for the cleanup steps.",
2347
2436
  agentInstructions: ready ? "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] Live mode is fully configured. Summarize the wallet address (mask middle), plan tier(s), remaining quota, and any non-zero delegation counts to the user as a checklist. Offer a tiny test (q402_quote, not q402_pay) to confirm. Don't echo the full keys array verbatim \u2014 pick the most useful 2-3 fields per scope." : "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] Walk the user through each warning IN ORDER, plain language. For slot-mismatch warnings, the fix is editing ~/.q402/mcp.env and restarting the client (Cursor / Cline: reload window; Claude / Codex: quit + relaunch). Surface body.error strings from any verify failure as the user-visible reason (e.g. 'your Trial expired 3 days ago', 'API key has been rotated') \u2014 don't generic-out to 'check the key value'.",
2348
2437
  userInstructions: ready ? [
2349
- `Your wallet: ${walletAddress ? walletAddress.slice(0, 6) + "\u2026" + walletAddress.slice(-4) : "(derive failed \u2014 check Q402_PRIVATE_KEY)"}`,
2438
+ walletAddress ? `Your wallet: ${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}` : modes.modeC && !modes.modeA && !modes.modeB ? "Wallet: server-managed (Mode C) \u2014 Q402 holds your Agent Wallet key. No local wallet to show." : "(wallet derive failed \u2014 check Q402_PRIVATE_KEY or Q402_AGENTIC_PRIVATE_KEY in ~/.q402/mcp.env)",
2350
2439
  "Q402 is live. You can now ask me to quote, pay, batch-pay, or check Trust Receipts.",
2351
2440
  "Want me to run a quick gas comparison across all 9 chains as a smoke test?",
2352
- "Need to chain-test against sandbox without changing keys? Set Q402_ENABLE_REAL_PAYMENTS=0 in ~/.q402/mcp.env and restart \u2014 every q402_pay returns a fake hash until you flip it back to 1."
2441
+ "Need to chain-test against sandbox without changing keys? Set Q402_ENABLE_REAL_PAYMENTS=0 in ~/.q402/mcp.env and restart - every q402_pay returns a fake hash until you flip it back to 1."
2353
2442
  ] : [
2354
2443
  `Q402 has ${warnings.length} issue${warnings.length === 1 ? "" : "s"} to fix:`,
2355
2444
  ...warnings.map((w) => `\u2022 ${w}`),
@@ -2480,6 +2569,306 @@ async function runAgenticInfo(input = {}) {
2480
2569
  };
2481
2570
  }
2482
2571
 
2572
+ // src/tools/recurring-list.ts
2573
+ import { z as z10 } from "zod";
2574
+ var RecurringListInputSchema = z10.object({
2575
+ walletId: z10.string().optional().describe(
2576
+ "Optional lowercased Agent Wallet address to list rules for when the user holds multiple wallets. Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
2577
+ )
2578
+ });
2579
+ var RECURRING_LIST_TOOL = {
2580
+ name: "q402_recurring_list",
2581
+ description: "Read the user's active recurring-payment rules on their Agent Wallet. Returns each rule's ruleId, label, frequency (hourly:N / daily / weekly:{day} / monthly:N / monthly:last), recipient + amount, chain, token, status (active / paused / paused-by-archive / fired-cap-exceeded / cancelled), when the next fire is scheduled, how many fires have completed, and the most recent error (if any). Use this when the user asks 'what scheduled payouts do I have?' or before authoring a new rule with q402_recurring_create. Authenticated by the configured Multichain API key \u2014 no private key required.",
2582
+ inputSchema: {
2583
+ type: "object",
2584
+ properties: {
2585
+ walletId: {
2586
+ type: "string",
2587
+ description: "Optional. Lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
2588
+ }
2589
+ },
2590
+ additionalProperties: false
2591
+ }
2592
+ };
2593
+ async function runRecurringList(input = {}) {
2594
+ const base = CONFIG.relayBaseUrl;
2595
+ const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
2596
+ if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
2597
+ return {
2598
+ configured: false,
2599
+ walletId: null,
2600
+ rules: [],
2601
+ count: 0,
2602
+ dashboardUrl,
2603
+ setupHint: "No live Q402 API key configured. Run q402_doctor to set one up, or open the dashboard to create rules from the UI."
2604
+ };
2605
+ }
2606
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
2607
+ try {
2608
+ const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
2609
+ method: "POST",
2610
+ headers: { "Content-Type": "application/json" },
2611
+ body: JSON.stringify({
2612
+ apiKey: CONFIG.apiKey,
2613
+ action: "list",
2614
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
2615
+ })
2616
+ });
2617
+ if (!res.ok) {
2618
+ const errBody = await res.json().catch(() => ({}));
2619
+ return {
2620
+ configured: true,
2621
+ walletId: explicitWalletId,
2622
+ rules: [],
2623
+ count: 0,
2624
+ dashboardUrl,
2625
+ setupHint: `recurring-list failed: ${errBody.error ?? `HTTP ${res.status}`}` + (errBody.message ? ` \u2014 ${errBody.message}` : "")
2626
+ };
2627
+ }
2628
+ const data = await res.json();
2629
+ return {
2630
+ configured: true,
2631
+ walletId: data.walletId,
2632
+ rules: data.rules,
2633
+ count: data.count,
2634
+ dashboardUrl
2635
+ };
2636
+ } catch (e) {
2637
+ return {
2638
+ configured: true,
2639
+ walletId: explicitWalletId,
2640
+ rules: [],
2641
+ count: 0,
2642
+ dashboardUrl,
2643
+ setupHint: `recurring-list network error: ${e instanceof Error ? e.message : String(e)}`
2644
+ };
2645
+ }
2646
+ }
2647
+
2648
+ // src/tools/recurring-create.ts
2649
+ import { z as z11 } from "zod";
2650
+ var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
2651
+ var AMOUNT_RE = /^\d+(\.\d{1,18})?$/;
2652
+ var RecurringCreateInputSchema = z11.object({
2653
+ confirm: z11.literal(true).describe(
2654
+ 'REQUIRED. Must be literally `true`. Authoring a recurring rule schedules future on-chain payments that the user does not click through one-by-one \u2014 the user has to explicitly say yes BEFORE this is called. Echo back the frequency + recipient + amount + chain + token + cancelWindow you intend to create, get a plain-language confirmation from the user (e.g. "yes, create the schedule"), and ONLY then call this with confirm: true. Mirrors the same guard q402_pay / q402_batch_pay use on one-shot sends.'
2655
+ ),
2656
+ frequency: z11.string().min(1).describe(
2657
+ 'Cadence string. One of: "hourly:N" (N=1..23), "daily", "weekly:{mon|tue|wed|thu|fri|sat|sun}", "monthly:N" (N=1..31), "monthly:last". Examples: "hourly:1" fires every hour, "weekly:fri" fires every Friday, "monthly:1" fires on the 1st of each month.'
2658
+ ),
2659
+ recipient: z11.string().regex(ADDRESS_RE).describe("0x-prefixed 20-byte recipient address. Required."),
2660
+ amount: z11.string().regex(AMOUNT_RE).describe(
2661
+ 'Amount per fire, as a decimal string (e.g. "1.5", "0.0001"). Counted in the same unit as `token` (USDC or USDT, both 1:1 USD).'
2662
+ ),
2663
+ chain: z11.enum(["bnb", "eth", "avax", "xlayer", "mantle", "injective", "monad", "scroll", "stable"]).default("bnb").describe(
2664
+ "Chain to fire the recurring TX on. Defaults to bnb (the only chain supported on Trial). Non-bnb requires the paid Multichain subscription."
2665
+ ),
2666
+ token: z11.enum(["USDC", "USDT"]).default("USDT").describe("Stablecoin to send. USDC or USDT. Both peg to USD-1."),
2667
+ label: z11.string().max(64).optional().describe("Optional human-readable label (\u226464 chars). Shows up in q402_recurring_list and the dashboard."),
2668
+ cancelWindowHours: z11.number().min(0).optional().describe(
2669
+ "Hours of advance notice before each fire during which the rule can be cancelled. 0 = fire immediately at the next slot, no alert. Capped at the cadence interval (e.g. \u2264 N-0.5h for hourly:N, \u226424 for daily). Defaults to 0."
2670
+ ),
2671
+ walletId: z11.string().optional().describe(
2672
+ "Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
2673
+ )
2674
+ });
2675
+ var RECURRING_CREATE_TOOL = {
2676
+ name: "q402_recurring_create",
2677
+ description: "Author a new recurring-payment rule on the user's Agent Wallet. Single-recipient (use the dashboard for multi-recipient payroll). Pick a cadence \u2014 hourly:N, daily, weekly:{day}, monthly:N, or monthly:last \u2014 and a recipient + amount + chain + token. Authenticated by the configured Multichain API key; no private key required. Non-bnb chains need the paid Multichain subscription. Each fire is bounded by the wallet's perTxMax (configured on the dashboard) \u2014 the dashboard's dailyLimit cap currently applies to manual sends only, NOT recurring fires, so an attacker with the apiKey could schedule N rules at perTxMax and drain the wallet's USDC balance over time. The user can stop a rule any time via q402_recurring_cancel.",
2678
+ inputSchema: {
2679
+ type: "object",
2680
+ properties: {
2681
+ confirm: {
2682
+ type: "boolean",
2683
+ const: true,
2684
+ description: "REQUIRED. Must be literally `true`. Recurring rules schedule future on-chain payments without per-fire user prompts, so the agent must get an explicit user yes BEFORE setting `confirm: true` and calling this. Same guard q402_pay / q402_batch_pay use on one-shot sends."
2685
+ },
2686
+ frequency: {
2687
+ type: "string",
2688
+ description: 'Required. "hourly:N" (N=1..23), "daily", "weekly:{day}", "monthly:N", or "monthly:last".'
2689
+ },
2690
+ recipient: {
2691
+ type: "string",
2692
+ pattern: "^0x[0-9a-fA-F]{40}$",
2693
+ description: "Required. 0x-prefixed 20-byte recipient address."
2694
+ },
2695
+ amount: {
2696
+ type: "string",
2697
+ pattern: "^\\d+(\\.\\d{1,18})?$",
2698
+ description: 'Required. Per-fire amount as decimal string (e.g. "1.5").'
2699
+ },
2700
+ chain: {
2701
+ type: "string",
2702
+ enum: ["bnb", "eth", "avax", "xlayer", "mantle", "injective", "monad", "scroll", "stable"],
2703
+ description: "Default 'bnb'. Non-bnb requires paid Multichain."
2704
+ },
2705
+ token: {
2706
+ type: "string",
2707
+ enum: ["USDC", "USDT"],
2708
+ description: "Default 'USDT'. Both peg USD-1."
2709
+ },
2710
+ label: {
2711
+ type: "string",
2712
+ maxLength: 64,
2713
+ description: "Optional human label (\u226464 chars)."
2714
+ },
2715
+ cancelWindowHours: {
2716
+ type: "number",
2717
+ minimum: 0,
2718
+ description: "Optional advance-notice window in hours. 0 = no alert, fires at the next slot. Defaults to 0."
2719
+ },
2720
+ walletId: {
2721
+ type: "string",
2722
+ description: "Optional. Defaults to default wallet on server."
2723
+ }
2724
+ },
2725
+ required: ["confirm", "frequency", "recipient", "amount"],
2726
+ additionalProperties: false
2727
+ }
2728
+ };
2729
+ async function runRecurringCreate(input) {
2730
+ const base = CONFIG.relayBaseUrl;
2731
+ const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
2732
+ if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
2733
+ return {
2734
+ ok: false,
2735
+ walletId: null,
2736
+ rule: null,
2737
+ error: "API_KEY_REQUIRED",
2738
+ message: "No live Q402 API key configured. Run q402_doctor to set one up.",
2739
+ dashboardUrl
2740
+ };
2741
+ }
2742
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
2743
+ try {
2744
+ const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
2745
+ method: "POST",
2746
+ headers: { "Content-Type": "application/json" },
2747
+ body: JSON.stringify({
2748
+ apiKey: CONFIG.apiKey,
2749
+ action: "create",
2750
+ frequency: input.frequency,
2751
+ recipient: input.recipient.toLowerCase(),
2752
+ amount: input.amount,
2753
+ chain: input.chain ?? "bnb",
2754
+ token: input.token ?? "USDT",
2755
+ ...input.label !== void 0 ? { label: input.label } : {},
2756
+ ...input.cancelWindowHours !== void 0 ? { cancelWindowHours: input.cancelWindowHours } : {},
2757
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
2758
+ })
2759
+ });
2760
+ const data = await res.json().catch(() => ({}));
2761
+ if (!res.ok) {
2762
+ return {
2763
+ ok: false,
2764
+ walletId: explicitWalletId,
2765
+ rule: null,
2766
+ error: data.error ?? `HTTP_${res.status}`,
2767
+ message: data.message ?? `Create failed with HTTP ${res.status}.`,
2768
+ dashboardUrl
2769
+ };
2770
+ }
2771
+ return {
2772
+ ok: true,
2773
+ walletId: data.walletId ?? explicitWalletId,
2774
+ rule: data.rule ?? null,
2775
+ dashboardUrl
2776
+ };
2777
+ } catch (e) {
2778
+ return {
2779
+ ok: false,
2780
+ walletId: explicitWalletId,
2781
+ rule: null,
2782
+ error: "NETWORK_ERROR",
2783
+ message: e instanceof Error ? e.message : String(e),
2784
+ dashboardUrl
2785
+ };
2786
+ }
2787
+ }
2788
+
2789
+ // src/tools/recurring-cancel.ts
2790
+ import { z as z12 } from "zod";
2791
+ var RecurringCancelInputSchema = z12.object({
2792
+ ruleId: z12.string().min(1).describe(
2793
+ "Rule id to cancel. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field. Cancelling is immediate."
2794
+ ),
2795
+ walletId: z12.string().optional().describe(
2796
+ "Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
2797
+ )
2798
+ });
2799
+ var RECURRING_CANCEL_TOOL = {
2800
+ name: "q402_recurring_cancel",
2801
+ description: "Cancel an active recurring-payment rule on the Agent Wallet. Takes a ruleId (from q402_recurring_list). Cancel is immediate \u2014 the rule will not fire again. Authenticated by the configured Multichain API key. Idempotent: cancelling an already-cancelled rule returns 409 with a clear message. Use this whenever the user says 'stop my recurring payment to X' \u2014 call q402_recurring_list first to find the matching ruleId, then call this with that id.",
2802
+ inputSchema: {
2803
+ type: "object",
2804
+ properties: {
2805
+ ruleId: {
2806
+ type: "string",
2807
+ description: "Rule id from q402_recurring_list. Required."
2808
+ },
2809
+ walletId: {
2810
+ type: "string",
2811
+ description: "Optional. Lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
2812
+ }
2813
+ },
2814
+ required: ["ruleId"],
2815
+ additionalProperties: false
2816
+ }
2817
+ };
2818
+ async function runRecurringCancel(input) {
2819
+ const base = CONFIG.relayBaseUrl;
2820
+ const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
2821
+ if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
2822
+ return {
2823
+ ok: false,
2824
+ walletId: null,
2825
+ rule: null,
2826
+ error: "API_KEY_REQUIRED",
2827
+ message: "No live Q402 API key configured. Run q402_doctor to set one up.",
2828
+ dashboardUrl
2829
+ };
2830
+ }
2831
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
2832
+ try {
2833
+ const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
2834
+ method: "POST",
2835
+ headers: { "Content-Type": "application/json" },
2836
+ body: JSON.stringify({
2837
+ apiKey: CONFIG.apiKey,
2838
+ action: "cancel",
2839
+ ruleId: input.ruleId,
2840
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
2841
+ })
2842
+ });
2843
+ const data = await res.json().catch(() => ({}));
2844
+ if (!res.ok) {
2845
+ return {
2846
+ ok: false,
2847
+ walletId: explicitWalletId,
2848
+ rule: null,
2849
+ error: data.error ?? `HTTP_${res.status}`,
2850
+ message: data.message ?? `Cancel failed with HTTP ${res.status}.`,
2851
+ dashboardUrl
2852
+ };
2853
+ }
2854
+ return {
2855
+ ok: true,
2856
+ walletId: data.walletId ?? explicitWalletId,
2857
+ rule: data.rule ?? null,
2858
+ dashboardUrl
2859
+ };
2860
+ } catch (e) {
2861
+ return {
2862
+ ok: false,
2863
+ walletId: explicitWalletId,
2864
+ rule: null,
2865
+ error: "NETWORK_ERROR",
2866
+ message: e instanceof Error ? e.message : String(e),
2867
+ dashboardUrl
2868
+ };
2869
+ }
2870
+ }
2871
+
2483
2872
  // src/index.ts
2484
2873
  function jsonText(value) {
2485
2874
  return { type: "text", text: JSON.stringify(value, null, 2) };
@@ -2501,6 +2890,9 @@ async function main() {
2501
2890
  RECEIPT_TOOL,
2502
2891
  WALLET_STATUS_TOOL,
2503
2892
  AGENTIC_INFO_TOOL,
2893
+ RECURRING_LIST_TOOL,
2894
+ RECURRING_CREATE_TOOL,
2895
+ RECURRING_CANCEL_TOOL,
2504
2896
  CLEAR_DELEGATION_TOOL
2505
2897
  ]
2506
2898
  }));
@@ -2544,6 +2936,18 @@ async function main() {
2544
2936
  AgenticInfoInputSchema.parse(args ?? {});
2545
2937
  return { content: [jsonText(await runAgenticInfo())] };
2546
2938
  }
2939
+ case "q402_recurring_list": {
2940
+ const parsed = RecurringListInputSchema.parse(args ?? {});
2941
+ return { content: [jsonText(await runRecurringList(parsed))] };
2942
+ }
2943
+ case "q402_recurring_create": {
2944
+ const parsed = RecurringCreateInputSchema.parse(args ?? {});
2945
+ return { content: [jsonText(await runRecurringCreate(parsed))] };
2946
+ }
2947
+ case "q402_recurring_cancel": {
2948
+ const parsed = RecurringCancelInputSchema.parse(args ?? {});
2949
+ return { content: [jsonText(await runRecurringCancel(parsed))] };
2950
+ }
2547
2951
  default:
2548
2952
  return {
2549
2953
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 9 EVM chains, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.",
5
5
  "mcpName": "io.github.bitgett/q402-mcp",
6
6
  "keywords": [
@@ -68,6 +68,7 @@
68
68
  "access": "public"
69
69
  },
70
70
  "overrides": {
71
- "ws": "^8.20.1"
71
+ "ws": "^8.20.1",
72
+ "qs": "^6.15.2"
72
73
  }
73
74
  }