@quackai/q402-mcp 0.6.2 → 0.6.4

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 +14 -0
  2. package/dist/index.js +120 -5
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -157,6 +157,20 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
157
157
 
158
158
  ---
159
159
 
160
+ ## Wallet modes — which signing path?
161
+
162
+ Three signing paths. Pick one when `q402_doctor` asks on first install. You can change later by editing `~/.q402/mcp.env` and restarting your client.
163
+
164
+ | Mode | Best for | Env to set | Private key in env? |
165
+ |---|---|---|---|
166
+ | **C — Server-managed** (recommended) | Most users. AI agents, automations, anyone who wants payments to "just work". Q402 holds an encrypted Agent Wallet for you; no MetaMask popup, no Smart-account marker. | `Q402_MULTICHAIN_API_KEY` (paid) **or** `Q402_TRIAL_API_KEY` (free BNB) | **No** |
167
+ | **B — Local Agent Wallet PK** | You want Agent Wallet automation but prefer to hold the PK yourself. Export from the dashboard once. MCP signs locally — key never leaves your machine. Your MetaMask is never touched. | `Q402_AGENTIC_PRIVATE_KEY` + an API key | Yes (Agent Wallet's exported PK) |
168
+ | **A — Your own EOA** | Power users who want their existing MetaMask address to be the on-chain payer. Your EOA signs directly via EIP-7702; the "Smart account" marker after first use is normal + reversible with `q402_clear_delegation`. **Use a fresh wallet.** | `Q402_PRIVATE_KEY` + an API key | Yes (your EOA's PK) |
169
+
170
+ `q402_doctor` on first install reads your env and recommends one of these based on what's already configured. The default for an empty install is Mode C — simplest path. If multiple modes are configured at once (e.g. both `Q402_PRIVATE_KEY` and `Q402_AGENTIC_PRIVATE_KEY` set), `q402_pay` will ask which one to use before sending so the agent never silently picks the wrong wallet.
171
+
172
+ ---
173
+
160
174
  ## Tools exposed
161
175
 
162
176
  | Tool | Auth | Purpose |
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.2";
214
+ var PACKAGE_VERSION = "0.6.4";
215
215
 
216
216
  // src/tools/quote.ts
217
217
  import { z } from "zod";
@@ -2049,7 +2049,7 @@ function envSlot(name, purpose) {
2049
2049
  }
2050
2050
  function mask2(key) {
2051
2051
  if (!key || key.length < 12) return key ?? "";
2052
- return `${key.slice(0, 12)}\u2026${key.slice(-4)}`;
2052
+ return `${key.slice(0, 12)}...${key.slice(-4)}`;
2053
2053
  }
2054
2054
  function detectPhase() {
2055
2055
  const anyKey = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
@@ -2435,10 +2435,10 @@ async function runDoctor() {
2435
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.",
2436
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'.",
2437
2437
  userInstructions: ready ? [
2438
- `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)",
2439
2439
  "Q402 is live. You can now ask me to quote, pay, batch-pay, or check Trust Receipts.",
2440
2440
  "Want me to run a quick gas comparison across all 9 chains as a smoke test?",
2441
- "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."
2442
2442
  ] : [
2443
2443
  `Q402 has ${warnings.length} issue${warnings.length === 1 ? "" : "s"} to fix:`,
2444
2444
  ...warnings.map((w) => `\u2022 ${w}`),
@@ -2650,6 +2650,9 @@ import { z as z11 } from "zod";
2650
2650
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
2651
2651
  var AMOUNT_RE = /^\d+(\.\d{1,18})?$/;
2652
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
+ ),
2653
2656
  frequency: z11.string().min(1).describe(
2654
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.'
2655
2658
  ),
@@ -2675,6 +2678,11 @@ var RECURRING_CREATE_TOOL = {
2675
2678
  inputSchema: {
2676
2679
  type: "object",
2677
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
+ },
2678
2686
  frequency: {
2679
2687
  type: "string",
2680
2688
  description: 'Required. "hourly:N" (N=1..23), "daily", "weekly:{day}", "monthly:N", or "monthly:last".'
@@ -2714,7 +2722,7 @@ var RECURRING_CREATE_TOOL = {
2714
2722
  description: "Optional. Defaults to default wallet on server."
2715
2723
  }
2716
2724
  },
2717
- required: ["frequency", "recipient", "amount"],
2725
+ required: ["confirm", "frequency", "recipient", "amount"],
2718
2726
  additionalProperties: false
2719
2727
  }
2720
2728
  };
@@ -2861,6 +2869,108 @@ async function runRecurringCancel(input) {
2861
2869
  }
2862
2870
  }
2863
2871
 
2872
+ // src/tools/recurring-fires.ts
2873
+ import { z as z13 } from "zod";
2874
+ var RecurringFiresInputSchema = z13.object({
2875
+ ruleId: z13.string().min(1).describe(
2876
+ "Rule id whose fire history to fetch. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field."
2877
+ ),
2878
+ limit: z13.number().int().min(1).max(50).optional().describe(
2879
+ "Max number of fires to return (newest first). Defaults to 50 (the server cap). Pass a smaller number when you only need the most recent few."
2880
+ ),
2881
+ walletId: z13.string().optional().describe(
2882
+ "Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
2883
+ )
2884
+ });
2885
+ var RECURRING_FIRES_TOOL = {
2886
+ name: "q402_recurring_fires",
2887
+ description: "Read the past-fire history of a specific recurring-payment rule. Returns up to 50 entries (newest first), each with the timestamp, scheduled slot, total USD amount that settled, on-chain tx hashes, and a partial-failure flag if some recipient rows didn't make it. Use this when the user asks 'when was the last fire?', 'did Friday's payout go out?', 'how much has rule X spent?', or before claiming a fire is missing. Authenticated by the configured Multichain API key. Read-only \u2014 does not trigger or modify anything. Call q402_recurring_list first to find the ruleId.",
2888
+ inputSchema: {
2889
+ type: "object",
2890
+ properties: {
2891
+ ruleId: {
2892
+ type: "string",
2893
+ description: "Rule id from q402_recurring_list. Required."
2894
+ },
2895
+ limit: {
2896
+ type: "number",
2897
+ description: "Max number of fires to return (1-50, newest first). Defaults to 50."
2898
+ },
2899
+ walletId: {
2900
+ type: "string",
2901
+ 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."
2902
+ }
2903
+ },
2904
+ required: ["ruleId"],
2905
+ additionalProperties: false
2906
+ }
2907
+ };
2908
+ async function runRecurringFires(input) {
2909
+ const base = CONFIG.relayBaseUrl;
2910
+ const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
2911
+ if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
2912
+ return {
2913
+ configured: false,
2914
+ walletId: null,
2915
+ ruleId: input.ruleId,
2916
+ rule: null,
2917
+ fires: [],
2918
+ count: 0,
2919
+ dashboardUrl,
2920
+ setupHint: "No live Q402 API key configured. Run q402_doctor to set one up, or open the dashboard to view fire history from the UI."
2921
+ };
2922
+ }
2923
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
2924
+ try {
2925
+ const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
2926
+ method: "POST",
2927
+ headers: { "Content-Type": "application/json" },
2928
+ body: JSON.stringify({
2929
+ apiKey: CONFIG.apiKey,
2930
+ action: "fires",
2931
+ ruleId: input.ruleId,
2932
+ ...typeof input.limit === "number" ? { limit: input.limit } : {},
2933
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
2934
+ })
2935
+ });
2936
+ const data = await res.json().catch(() => ({}));
2937
+ if (!res.ok) {
2938
+ return {
2939
+ configured: true,
2940
+ walletId: explicitWalletId,
2941
+ ruleId: input.ruleId,
2942
+ rule: null,
2943
+ fires: [],
2944
+ count: 0,
2945
+ dashboardUrl,
2946
+ error: data.error ?? `HTTP_${res.status}`,
2947
+ message: data.message ?? `Fires fetch failed with HTTP ${res.status}.`
2948
+ };
2949
+ }
2950
+ return {
2951
+ configured: true,
2952
+ walletId: data.walletId ?? explicitWalletId,
2953
+ ruleId: data.ruleId ?? input.ruleId,
2954
+ rule: data.rule ?? null,
2955
+ fires: Array.isArray(data.fires) ? data.fires : [],
2956
+ count: typeof data.count === "number" ? data.count : data.fires?.length ?? 0,
2957
+ dashboardUrl
2958
+ };
2959
+ } catch (e) {
2960
+ return {
2961
+ configured: true,
2962
+ walletId: explicitWalletId,
2963
+ ruleId: input.ruleId,
2964
+ rule: null,
2965
+ fires: [],
2966
+ count: 0,
2967
+ dashboardUrl,
2968
+ error: "NETWORK_ERROR",
2969
+ message: e instanceof Error ? e.message : String(e)
2970
+ };
2971
+ }
2972
+ }
2973
+
2864
2974
  // src/index.ts
2865
2975
  function jsonText(value) {
2866
2976
  return { type: "text", text: JSON.stringify(value, null, 2) };
@@ -2884,6 +2994,7 @@ async function main() {
2884
2994
  AGENTIC_INFO_TOOL,
2885
2995
  RECURRING_LIST_TOOL,
2886
2996
  RECURRING_CREATE_TOOL,
2997
+ RECURRING_FIRES_TOOL,
2887
2998
  RECURRING_CANCEL_TOOL,
2888
2999
  CLEAR_DELEGATION_TOOL
2889
3000
  ]
@@ -2940,6 +3051,10 @@ async function main() {
2940
3051
  const parsed = RecurringCancelInputSchema.parse(args ?? {});
2941
3052
  return { content: [jsonText(await runRecurringCancel(parsed))] };
2942
3053
  }
3054
+ case "q402_recurring_fires": {
3055
+ const parsed = RecurringFiresInputSchema.parse(args ?? {});
3056
+ return { content: [jsonText(await runRecurringFires(parsed))] };
3057
+ }
2943
3058
  default:
2944
3059
  return {
2945
3060
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
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
  }