@quackai/q402-mcp 0.4.3 → 0.4.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 +10 -7
  2. package/dist/index.js +33 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -76,10 +76,13 @@ command = "npx"
76
76
  args = ["-y", "@quackai/q402-mcp"]
77
77
  startup_timeout_sec = 20.0
78
78
  env = {
79
- # Two-key model (v0.4.3+): set whichever applies — both is best.
79
+ # Two-key model (v0.4.4+): set whichever applies — both is best.
80
80
  # The server auto-routes by chain: BNB → trial key, else multichain key.
81
- Q402_TRIAL_API_KEY = "q402_live_trial_...", # BNB-only sponsored
82
- Q402_MULTICHAIN_API_KEY = "q402_live_...", # paid 8-chain
81
+ # Both keys use the same q402_live_ prefix — the env var name is what
82
+ # carries the scope, not the key string. Get the values from the
83
+ # dashboard (each key has its own copy button per view).
84
+ Q402_TRIAL_API_KEY = "q402_live_...", # BNB-only sponsored (from /event)
85
+ Q402_MULTICHAIN_API_KEY = "q402_live_...", # paid 8-chain (from /payment)
83
86
  # Legacy fallback — used if neither scoped key above is set.
84
87
  Q402_API_KEY = "q402_live_...",
85
88
  Q402_PRIVATE_KEY = "0xabc...",
@@ -130,14 +133,14 @@ By default the MCP server operates in **sandbox mode**: `q402_pay` returns a det
130
133
  To enable real on-chain transactions, the resolved API key must be live (`q402_live_*`), `Q402_PRIVATE_KEY` must be set, and `Q402_ENABLE_REAL_PAYMENTS=1`:
131
134
 
132
135
  ```bash
133
- # Two-key model (v0.4.3+) — set whichever applies. Both is best.
136
+ # Two-key model (v0.4.4+) — set whichever applies. Both is best.
134
137
  # Auto-routing: chain="bnb" → trial key (if set), otherwise multichain key.
135
138
  # Override per call with keyScope: "auto" | "trial" | "multichain".
136
- Q402_TRIAL_API_KEY=q402_live_trial_... # BNB-only sponsored Trial key
139
+ Q402_TRIAL_API_KEY=q402_live_... # BNB-only sponsored Trial key (from /event)
137
140
  Q402_MULTICHAIN_API_KEY=q402_live_... # paid 8-chain key (per-chain Gas Tank)
138
141
 
139
142
  # Legacy fallback. Used for both scopes when the two above are unset —
140
- # pre-v0.4.3 users keep working without any config change.
143
+ # pre-v0.4.4 users keep working without any config change.
141
144
  Q402_API_KEY=q402_live_...
142
145
 
143
146
  Q402_PRIVATE_KEY=0xabc... # signer for the payer EOA
@@ -165,7 +168,7 @@ Combined with the `confirm: true` argument the tool requires, this means the mod
165
168
  |---|---|---|
166
169
  | `Q402_TRIAL_API_KEY` | live-pay (BNB) | BNB-only sponsored Trial key. Free at https://q402.quackai.ai/event. Used automatically for `chain="bnb"` when set. |
167
170
  | `Q402_MULTICHAIN_API_KEY` | live-pay (8-chain) | Paid 8-chain key. Get one at https://q402.quackai.ai/payment. Used for all non-BNB chains and for BNB when no Trial key is set. |
168
- | `Q402_API_KEY` | legacy fallback | Pre-v0.4.3 single-env path. Used for both scopes when the two above are unset. Keep set if you only have one key. |
171
+ | `Q402_API_KEY` | legacy fallback | Pre-v0.4.4 single-env path. Used for both scopes when the two above are unset. Keep set if you only have one key. |
169
172
  | `Q402_PRIVATE_KEY` | live-pay | Signer for the payer EOA. **Never share. Never paste in chat.** |
170
173
  | `Q402_ENABLE_REAL_PAYMENTS` | live-pay | Set to `1` to opt in. Any other value (or unset) → sandbox. |
171
174
  | `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `5`. |
package/dist/index.js CHANGED
@@ -52,31 +52,47 @@ function loadConfig() {
52
52
  };
53
53
  }
54
54
  var CONFIG = loadConfig();
55
- function resolveApiKey(chain, scope = "auto") {
56
- const effectiveScope = scope === "auto" ? chain === "bnb" && CONFIG.trialApiKey ? "trial" : "multichain" : scope;
55
+ function resolveApiKey(chain, scope = "auto", intent = "single") {
56
+ const effectiveScope = scope === "auto" ? (
57
+ // Smart routing: batches default to multichain (trial cap=5 would
58
+ // silently fail any 6+ recipient batch). Single payments default to
59
+ // trial on BNB when a trial key is set, so the free sponsored
60
+ // allotment gets used naturally.
61
+ intent === "batch" ? "multichain" : chain === "bnb" && CONFIG.trialApiKey ? "trial" : "multichain"
62
+ ) : scope;
57
63
  if (effectiveScope === "trial") {
58
64
  if (chain !== "bnb") {
59
- throw new Error(
60
- `Trial API Key supports BNB Chain only \u2014 got "${chain}". Use a Multichain API Key (set Q402_MULTICHAIN_API_KEY) for ${chain} and other paid chains, or omit keyScope to let the server auto-pick.`
61
- );
65
+ return {
66
+ apiKey: null,
67
+ scope: "trial",
68
+ fromLegacyFallback: false,
69
+ sandboxReason: `keyScope="trial" requested but chain="${chain}" \u2014 Trial keys support BNB Chain only. Drop keyScope (or set keyScope="multichain") to use the paid Multichain key on ${chain}.`
70
+ };
62
71
  }
63
72
  const key2 = CONFIG.trialApiKey ?? CONFIG.legacyApiKey;
64
73
  if (!key2) {
65
- throw new Error(
66
- "keyScope='trial' was requested but neither Q402_TRIAL_API_KEY nor Q402_API_KEY is set. Get a Trial key at https://q402.quackai.ai/event."
67
- );
74
+ return {
75
+ apiKey: null,
76
+ scope: "trial",
77
+ fromLegacyFallback: false,
78
+ sandboxReason: "keyScope='trial' requested but neither Q402_TRIAL_API_KEY nor Q402_API_KEY is set. Get a free Trial key at https://q402.quackai.ai/event."
79
+ };
68
80
  }
69
81
  return { apiKey: key2, scope: "trial", fromLegacyFallback: !CONFIG.trialApiKey };
70
82
  }
71
83
  const key = CONFIG.multichainApiKey ?? CONFIG.legacyApiKey;
72
84
  if (!key) {
73
- throw new Error(
74
- "keyScope='multichain' was requested but neither Q402_MULTICHAIN_API_KEY nor Q402_API_KEY is set. Activate a paid plan at https://q402.quackai.ai/payment to get one."
75
- );
85
+ return {
86
+ apiKey: null,
87
+ scope: "multichain",
88
+ fromLegacyFallback: false,
89
+ sandboxReason: (scope === "multichain" ? "keyScope='multichain' requested but neither Q402_MULTICHAIN_API_KEY" : `chain="${chain}" routes to the Multichain scope but neither Q402_MULTICHAIN_API_KEY`) + " nor Q402_API_KEY is set. Activate a paid plan at https://q402.quackai.ai/payment to get one."
90
+ };
76
91
  }
77
92
  return { apiKey: key, scope: "multichain", fromLegacyFallback: !CONFIG.multichainApiKey };
78
93
  }
79
94
  function isLiveModeFor(resolved) {
95
+ if (!resolved.apiKey) return false;
80
96
  if (!CONFIG.realPaymentsRequested) return false;
81
97
  if (!CONFIG.privateKey) return false;
82
98
  return resolved.apiKey.startsWith("q402_live_");
@@ -290,7 +306,7 @@ function runQuote(input) {
290
306
  }
291
307
  var QUOTE_TOOL = {
292
308
  name: "q402_quote",
293
- description: "Compare gas costs and supported tokens across the 8 chains Q402 relays for (avax, bnb, eth, xlayer, stable, mantle, injective, monad). Trial-tier API keys see BNB-only quotes (q402_pay enforces the same scope server-side); paid-tier keys see the full matrix including RLUSD on Ethereum and Injective USDT-only. Read-only \u2014 no API key needed, no funds move. Use this before q402_pay so the user sees what's currently routable on their tier.",
309
+ description: "Compare gas costs and supported tokens across the 8 chains Q402 relays for (avax, bnb, eth, xlayer, stable, mantle, injective, monad). Returns the full chain \xD7 token matrix unconditionally \u2014 this tool does not read any API key, so it can't filter by trial vs multichain scope. When the caller intends to settle with a Trial API Key, treat any non-BNB row as informational only (q402_pay will return 403 TRIAL_BNB_ONLY for those). Includes RLUSD on Ethereum and Injective USDT-only. Read-only \u2014 no API key needed, no funds move. Use this before q402_pay so the user can see what's available and pick a chain.",
294
310
  // Plain JSON schema mirroring the Zod schema above; MCP servers receive parameters as JSON.
295
311
  inputSchema: {
296
312
  type: "object",
@@ -705,7 +721,7 @@ async function runPay(input) {
705
721
  guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
706
722
  }
707
723
  const scopeRequest = input.keyScope ?? "auto";
708
- const resolved = resolveApiKey(input.chain, scopeRequest);
724
+ const resolved = resolveApiKey(input.chain, scopeRequest, "single");
709
725
  guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
710
726
  const live = isLiveModeFor(resolved);
711
727
  if (!live) {
@@ -715,7 +731,7 @@ async function runPay(input) {
715
731
  token: input.token
716
732
  });
717
733
  guardsApplied.push("mode=sandbox");
718
- const setupHint = describeSandboxReason(resolved.apiKey);
734
+ const setupHint = resolved.sandboxReason ?? describeSandboxReason(resolved.apiKey ?? "");
719
735
  return { result: result2, guardsApplied, setupHint };
720
736
  }
721
737
  const client = new Q402NodeClient({
@@ -847,7 +863,7 @@ async function runBatchPay(input) {
847
863
  guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
848
864
  }
849
865
  const scopeRequest = input.keyScope ?? "auto";
850
- const resolved = resolveApiKey(input.chain, scopeRequest);
866
+ const resolved = resolveApiKey(input.chain, scopeRequest, "batch");
851
867
  guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
852
868
  const live = isLiveModeFor(resolved);
853
869
  if (!live) {
@@ -855,7 +871,7 @@ async function runBatchPay(input) {
855
871
  (r) => sandboxPay(chain, { to: r.to, amount: r.amount, token: input.token })
856
872
  );
857
873
  guardsApplied.push("mode=sandbox");
858
- const reason = describeSandboxReason2(resolved.apiKey);
874
+ const reason = resolved.sandboxReason ?? describeSandboxReason2(resolved.apiKey ?? "");
859
875
  return {
860
876
  mode: "sandbox",
861
877
  status: "sandbox",
@@ -1205,7 +1221,7 @@ var RECEIPT_TOOL = {
1205
1221
 
1206
1222
  // src/index.ts
1207
1223
  var PACKAGE_NAME = "@quackai/q402-mcp";
1208
- var PACKAGE_VERSION = "0.4.2";
1224
+ var PACKAGE_VERSION = "0.4.4";
1209
1225
  function jsonText(value) {
1210
1226
  return { type: "text", text: JSON.stringify(value, null, 2) };
1211
1227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 8 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": [