@quackai/q402-mcp 0.5.17 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +331 -10
  2. package/package.json +73 -73
package/dist/index.js CHANGED
@@ -120,9 +120,13 @@ function loadConfig() {
120
120
  const apiKey = multichainApiKey ?? trialApiKey ?? legacyApiKey;
121
121
  const apiKeyKind = classifyApiKey(apiKey);
122
122
  const privateKey = ENV.Q402_PRIVATE_KEY ?? null;
123
+ const agenticPrivateKey = ENV.Q402_AGENTIC_PRIVATE_KEY ?? null;
124
+ const walletIdRaw = ENV.Q402_WALLET_ID;
125
+ const walletId = typeof walletIdRaw === "string" && walletIdRaw.length > 0 ? walletIdRaw.toLowerCase() : null;
123
126
  const realPaymentsRequested = ENV.Q402_ENABLE_REAL_PAYMENTS === "1";
124
127
  const anyLiveKey = classifyApiKey(trialApiKey) === "live" || classifyApiKey(multichainApiKey) === "live" || classifyApiKey(legacyApiKey) === "live";
125
- const live = realPaymentsRequested && anyLiveKey && typeof privateKey === "string" && privateKey.length > 0;
128
+ const hasAnySignerKey = typeof privateKey === "string" && privateKey.length > 0 || typeof agenticPrivateKey === "string" && agenticPrivateKey.length > 0;
129
+ const live = realPaymentsRequested && anyLiveKey && hasAnySignerKey;
126
130
  return {
127
131
  trialApiKey,
128
132
  multichainApiKey,
@@ -130,6 +134,8 @@ function loadConfig() {
130
134
  apiKey,
131
135
  apiKeyKind,
132
136
  privateKey,
137
+ agenticPrivateKey,
138
+ walletId,
133
139
  realPaymentsRequested,
134
140
  mode: live ? "live" : "sandbox",
135
141
  relayBaseUrl: (ENV.Q402_RELAY_BASE_URL ?? DEFAULT_RELAY_BASE).replace(/\/$/, ""),
@@ -137,6 +143,17 @@ function loadConfig() {
137
143
  allowedRecipients: parseAllowedRecipients(ENV.Q402_ALLOWED_RECIPIENTS)
138
144
  };
139
145
  }
146
+ function detectAgenticModes(c = CONFIG) {
147
+ const modeA = isValidPrivateKey(c.privateKey);
148
+ const modeB = isValidPrivateKey(c.agenticPrivateKey);
149
+ const modeC = c.apiKey !== null && c.apiKey.startsWith("q402_live_");
150
+ let count = 0;
151
+ if (modeA) count++;
152
+ if (modeB) count++;
153
+ if (modeC) count++;
154
+ const primary = modeB ? "B" : modeA ? "A" : modeC ? "C" : null;
155
+ return { modeA, modeB, modeC, count, primary };
156
+ }
140
157
  var CONFIG = loadConfig();
141
158
  function resolveApiKey(chain, scope = "auto") {
142
159
  const effectiveScope = scope === "auto" ? (
@@ -180,15 +197,16 @@ var PRIVATE_KEY_RE = /^0x[a-fA-F0-9]{64}$/;
180
197
  function isLiveModeFor(resolved) {
181
198
  if (!resolved.apiKey) return false;
182
199
  if (!CONFIG.realPaymentsRequested) return false;
183
- if (!CONFIG.privateKey) return false;
184
- if (!PRIVATE_KEY_RE.test(CONFIG.privateKey)) return false;
200
+ const hasMode_A_Key = typeof CONFIG.privateKey === "string" && PRIVATE_KEY_RE.test(CONFIG.privateKey);
201
+ const hasMode_B_Key = typeof CONFIG.agenticPrivateKey === "string" && PRIVATE_KEY_RE.test(CONFIG.agenticPrivateKey);
202
+ if (!hasMode_A_Key && !hasMode_B_Key) return false;
185
203
  return resolved.apiKey.startsWith("q402_live_");
186
204
  }
187
205
  var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
188
206
 
189
207
  // src/version.ts
190
208
  var PACKAGE_NAME = "@quackai/q402-mcp";
191
- var PACKAGE_VERSION = "0.5.17";
209
+ var PACKAGE_VERSION = "0.6.0";
192
210
 
193
211
  // src/tools/quote.ts
194
212
  import { z } from "zod";
@@ -822,6 +840,16 @@ var PayInputSchema = z2.object({
822
840
  keyScope: z2.enum(["auto", "trial", "multichain"]).optional().describe(
823
841
  'Which API key to use. "auto" (default): chain="bnb" + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); else Multichain. "trial" forces the BNB-only sponsored key. "multichain" forces the paid 9-chain key. Same rule applies to q402_batch_pay.'
824
842
  ),
843
+ walletMode: z2.enum(["eoa", "agentic-local", "agentic-server"]).optional().describe(
844
+ `Which wallet to spend from:
845
+ "eoa" \u2014 the user's real MetaMask/OKX EOA, signed locally with Q402_PRIVATE_KEY
846
+ "agentic-local" \u2014 the Agent Wallet's exported private key (Q402_AGENTIC_PRIVATE_KEY)
847
+ "agentic-server" \u2014 the server-managed Agent Wallet (Q402 holds the key; you only need Q402_MULTICHAIN_API_KEY)
848
+ When MORE THAN ONE wallet is configured in the user's environment, you MUST ask the user which to use before calling \u2014 do NOT guess. Phrase: "You have multiple wallets set up \u2014 pay from your EOA, or your Agent Wallet?" When only one wallet is configured this argument is optional and the tool routes there automatically.`
849
+ ),
850
+ walletId: z2.string().optional().describe(
851
+ `Server-managed Agent Wallet only (walletMode="agentic-server"). Lowercased Agent Wallet address selecting which of the user's wallets to spend from when they hold more than one (max 10 per owner). Omit to use the user's default wallet. Ignored for walletMode="eoa" and "agentic-local" since those modes carry their own signing key.`
852
+ ),
825
853
  confirm: z2.literal(true).describe(
826
854
  "MUST be true. Prove the user explicitly approved this exact recipient and amount in the conversation right before this tool was called. Setting this to true on behalf of the user without confirmation is a violation of the tool contract."
827
855
  )
@@ -854,10 +882,82 @@ async function runPay(input) {
854
882
  );
855
883
  }
856
884
  const guardsApplied = [];
857
- let senderWallet;
858
- if (CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
885
+ function failureResult(method) {
886
+ return {
887
+ success: false,
888
+ sandbox: false,
889
+ txHash: "",
890
+ tokenAmount: input.amount,
891
+ token: input.token,
892
+ chain: chain.key,
893
+ method,
894
+ explorerUrl: null
895
+ };
896
+ }
897
+ const modes = detectAgenticModes(CONFIG);
898
+ const available = [];
899
+ if (modes.modeA && CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
859
900
  try {
860
901
  const addr = new Wallet2(CONFIG.privateKey).address;
902
+ available.push({
903
+ id: "eoa",
904
+ label: "Your real MetaMask / OKX EOA",
905
+ addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`,
906
+ note: "Signs locally with Q402_PRIVATE_KEY. Your wallet becomes EIP-7702-delegated after the first payment on each chain."
907
+ });
908
+ } catch {
909
+ }
910
+ }
911
+ if (modes.modeB && CONFIG.agenticPrivateKey && isValidPrivateKey(CONFIG.agenticPrivateKey)) {
912
+ try {
913
+ const addr = new Wallet2(CONFIG.agenticPrivateKey).address;
914
+ available.push({
915
+ id: "agentic-local",
916
+ label: "Agent Wallet (local signing with exported key)",
917
+ addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`,
918
+ note: "Signs locally with Q402_AGENTIC_PRIVATE_KEY. Your MetaMask is never touched."
919
+ });
920
+ } catch {
921
+ }
922
+ }
923
+ if (modes.modeC) {
924
+ available.push({
925
+ id: "agentic-server",
926
+ label: "Agent Wallet (server-managed)",
927
+ note: "Q402 holds the encrypted key; payment fires through /api/wallet/agentic/send. Caps you set in the dashboard bound the spend."
928
+ });
929
+ }
930
+ const requestedMode = input.walletMode;
931
+ const requestedAvailable = requestedMode ? available.some((w) => w.id === requestedMode) : false;
932
+ if (requestedMode && !requestedAvailable) {
933
+ return {
934
+ result: failureResult("wallet_mode_unavailable"),
935
+ guardsApplied: [
936
+ `wallet_modes_available=${available.length}`,
937
+ `requested=${requestedMode}`
938
+ ],
939
+ ambiguousWalletChoice: {
940
+ question: available.length === 0 ? `The "${requestedMode}" wallet isn't configured. None of the supported wallets are set up \u2014 see the doctor for setup instructions.` : `The "${requestedMode}" wallet isn't configured in this environment. Supported wallets here: ${available.map((w) => `"${w.id}"`).join(", ")}. Which would you like to use instead?`,
941
+ available
942
+ }
943
+ };
944
+ }
945
+ if (available.length > 1 && !requestedMode) {
946
+ return {
947
+ result: failureResult("needs_wallet_choice"),
948
+ guardsApplied: [`wallet_modes_available=${available.length}`],
949
+ ambiguousWalletChoice: {
950
+ question: available.length === 2 ? `You have ${available.length} wallets set up \u2014 which one should I pay from?` : `You have ${available.length} wallets set up. Which one should I pay from?`,
951
+ available
952
+ }
953
+ };
954
+ }
955
+ const effectiveMode = requestedMode && requestedAvailable ? requestedMode : available.length === 1 && available[0] ? available[0].id : "eoa";
956
+ const signingPk = effectiveMode === "eoa" ? CONFIG.privateKey : effectiveMode === "agentic-local" ? CONFIG.agenticPrivateKey : null;
957
+ let senderWallet;
958
+ if (signingPk && isValidPrivateKey(signingPk)) {
959
+ try {
960
+ const addr = new Wallet2(signingPk).address;
861
961
  senderWallet = {
862
962
  address: addr,
863
963
  addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
@@ -874,6 +974,101 @@ async function runPay(input) {
874
974
  const scopeRequest = input.keyScope ?? "auto";
875
975
  const resolved = resolveApiKey(input.chain, scopeRequest);
876
976
  guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
977
+ if (effectiveMode === "agentic-server") {
978
+ if (input.token === "RLUSD") {
979
+ return {
980
+ result: failureResult("rlusd_not_supported_for_server_mode"),
981
+ guardsApplied: [
982
+ ...guardsApplied,
983
+ "wallet=agentic-server",
984
+ "token=RLUSD",
985
+ "rejected_pre_relay"
986
+ ],
987
+ senderWallet,
988
+ setupHint: 'RLUSD is not yet supported by the server-managed Agent Wallet (walletMode="agentic-server"). Switch to walletMode="eoa" or "agentic-local" (with a private key set), or pick USDC/USDT for this send.'
989
+ };
990
+ }
991
+ if (!resolved.apiKey || !resolved.apiKey.startsWith("q402_live_")) {
992
+ const result2 = sandboxPay(chain, {
993
+ to: input.to,
994
+ amount: input.amount,
995
+ token: input.token
996
+ });
997
+ guardsApplied.push("mode=sandbox", "wallet=agentic-server");
998
+ return {
999
+ result: result2,
1000
+ guardsApplied,
1001
+ senderWallet,
1002
+ setupHint: resolved.sandboxReason ?? "Server-mediated Agent Wallet needs a live Q402_MULTICHAIN_API_KEY. Visit https://q402.quackai.ai/payment to activate a paid plan."
1003
+ };
1004
+ }
1005
+ if (!CONFIG.realPaymentsRequested) {
1006
+ const result2 = sandboxPay(chain, {
1007
+ to: input.to,
1008
+ amount: input.amount,
1009
+ token: input.token
1010
+ });
1011
+ guardsApplied.push("mode=sandbox", "wallet=agentic-server");
1012
+ return {
1013
+ result: result2,
1014
+ guardsApplied,
1015
+ senderWallet,
1016
+ setupHint: "Set Q402_ENABLE_REAL_PAYMENTS=1 to fire a real server-mediated payment."
1017
+ };
1018
+ }
1019
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
1020
+ let resp;
1021
+ try {
1022
+ resp = await fetch(`${CONFIG.relayBaseUrl}/wallet/agentic/send`, {
1023
+ method: "POST",
1024
+ headers: { "Content-Type": "application/json" },
1025
+ body: JSON.stringify({
1026
+ apiKey: resolved.apiKey,
1027
+ chain: input.chain,
1028
+ token: input.token,
1029
+ to: input.to,
1030
+ amount: input.amount,
1031
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
1032
+ })
1033
+ });
1034
+ } catch (e) {
1035
+ const transportErr = failureResult("eip7702");
1036
+ return {
1037
+ result: transportErr,
1038
+ guardsApplied: [
1039
+ ...guardsApplied,
1040
+ "wallet=agentic-server",
1041
+ "mode=live",
1042
+ "transport=fetch_failed",
1043
+ `error=${e instanceof Error ? e.message : String(e)}`
1044
+ ],
1045
+ senderWallet
1046
+ };
1047
+ }
1048
+ const data = await resp.json().catch(() => ({}));
1049
+ const txHash = data.txHash ?? "";
1050
+ const success = resp.ok && txHash.length > 0;
1051
+ const message = "message" in data ? data.message : "error" in data ? data.error : void 0;
1052
+ return {
1053
+ result: {
1054
+ success,
1055
+ sandbox: false,
1056
+ txHash,
1057
+ tokenAmount: input.amount,
1058
+ token: input.token,
1059
+ chain: chain.key,
1060
+ method: "eip7702",
1061
+ explorerUrl: txHash ? void 0 : null
1062
+ },
1063
+ guardsApplied: [
1064
+ ...guardsApplied,
1065
+ "wallet=agentic-server",
1066
+ "mode=live",
1067
+ ...message ? [`server_message=${message}`] : []
1068
+ ],
1069
+ senderWallet
1070
+ };
1071
+ }
877
1072
  const live = isLiveModeFor(resolved);
878
1073
  if (!live) {
879
1074
  const result2 = sandboxPay(chain, {
@@ -881,13 +1076,21 @@ async function runPay(input) {
881
1076
  amount: input.amount,
882
1077
  token: input.token
883
1078
  });
884
- guardsApplied.push("mode=sandbox");
1079
+ guardsApplied.push("mode=sandbox", `wallet=${effectiveMode}`);
885
1080
  const setupHint = resolved.sandboxReason ?? describeSandboxReason(resolved.apiKey ?? "", resolved.scope);
886
1081
  return { result: result2, guardsApplied, setupHint, senderWallet };
887
1082
  }
1083
+ if (!signingPk) {
1084
+ return {
1085
+ result: failureResult("missing_signing_key"),
1086
+ guardsApplied: [...guardsApplied, `wallet=${effectiveMode}`, "mode=sandbox"],
1087
+ senderWallet,
1088
+ setupHint: effectiveMode === "agentic-local" ? "Set Q402_AGENTIC_PRIVATE_KEY to your Agent Wallet's exported private key." : "Set Q402_PRIVATE_KEY to your EOA private key."
1089
+ };
1090
+ }
888
1091
  const client = new Q402NodeClient({
889
1092
  apiKey: resolved.apiKey,
890
- privateKey: CONFIG.privateKey,
1093
+ privateKey: signingPk,
891
1094
  chain,
892
1095
  relayBaseUrl: CONFIG.relayBaseUrl
893
1096
  });
@@ -896,7 +1099,7 @@ async function runPay(input) {
896
1099
  amount: input.amount,
897
1100
  token: input.token
898
1101
  });
899
- guardsApplied.push("mode=live");
1102
+ guardsApplied.push("mode=live", `wallet=${effectiveMode}`);
900
1103
  return {
901
1104
  result,
902
1105
  guardsApplied,
@@ -928,7 +1131,7 @@ function describeSandboxReason(resolvedKey, scope) {
928
1131
  }
929
1132
  var PAY_TOOL = {
930
1133
  name: "q402_pay",
931
- description: "USE THIS TOOL whenever the user asks to send, transfer, or pay USDC / USDT / RLUSD \u2014 even when they don't mention Q402 by name (\"send 5 USDT to 0x... on bnb\" should route here automatically). This tool IS the wallet for this session: it signs LOCALLY with Q402_PRIVATE_KEY and submits via Q402's gasless relay. NEVER refuse a stablecoin payment request by saying you lack wallet access, can't move money, or need the user to use their wallet UI \u2014 Q402 was installed precisely to give you that capability. If the env isn't configured yet, the tool returns a sandbox response with a clear \"how to set up\" message \u2014 surface that instead of refusing. \n\nAuto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 9-chain). Same rule for q402_batch_pay. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad, scroll \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set as a valid 32-byte hex key, and Q402_ENABLE_REAL_PAYMENTS=1. Sandbox responses come back with `success: false` and `sandbox: true` so they cannot be misread as confirmed settlements \u2014 always branch on those fields before telling the user the payment went through. The recipient receives the full amount; the sender pays $0 in gas. \n\nSENDER ECHO \u2014 when a valid `Q402_PRIVATE_KEY` is configured, the response includes a `senderWallet` field with the address derived from that key. Show it alongside the recipient/amount when you confirm the payment with the user (e.g. 'Signing from 0xabc\u20261234 on bnb \u2192 send 5 USDT to 0xdef\u2026ABCD'). Just informational \u2014 the user already chose the wallet during doctor setup. Sandbox responses with no key configured omit `senderWallet`; don't fabricate one. \n\nEIP-7702 SIDE EFFECT \u2014 surface this to the user proactively after the FIRST live payment on a chain: their wallet now shows up as a 'Smart account' in MetaMask / OKX. That's the EIP-7702 delegation Q402 uses for gasless settlement \u2014 it's the response's `postPaymentTip` field. Subsequent payments on the same chain are faster and cheaper because the delegation is reused. \n\nIf the user EVER reports that native gas tokens (BNB / ETH / AVAX / etc.) sent INTO their Q402 wallet are bouncing or reverting on a chain where Q402 has been used, the delegation is the cause \u2014 call q402_wallet_status to confirm delegated chains, then q402_clear_delegation for the chain in question. Q402 sponsors the gas for the clear, so the user pays $0. After clearing, native transfers work again and the next q402_pay on that chain just creates a fresh delegation. \n\nALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
1134
+ description: "USE THIS TOOL whenever the user asks to send, transfer, or pay USDC / USDT / RLUSD \u2014 even when they don't mention Q402 by name (\"send 5 USDT to 0x... on bnb\" should route here automatically). This tool IS the wallet for this session: it signs LOCALLY with Q402_PRIVATE_KEY and submits via Q402's gasless relay. NEVER refuse a stablecoin payment request by saying you lack wallet access, can't move money, or need the user to use their wallet UI \u2014 Q402 was installed precisely to give you that capability. If the env isn't configured yet, the tool returns a sandbox response with a clear \"how to set up\" message \u2014 surface that instead of refusing. \n\nAuto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 9-chain). Same rule for q402_batch_pay. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad, scroll \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set as a valid 32-byte hex key, and Q402_ENABLE_REAL_PAYMENTS=1. Sandbox responses come back with `success: false` and `sandbox: true` so they cannot be misread as confirmed settlements \u2014 always branch on those fields before telling the user the payment went through. The recipient receives the full amount; the sender pays $0 in gas. \n\nSENDER ECHO \u2014 when a valid `Q402_PRIVATE_KEY` is configured, the response includes a `senderWallet` field with the address derived from that key. Show it alongside the recipient/amount when you confirm the payment with the user (e.g. 'Signing from 0xabc\u20261234 on bnb \u2192 send 5 USDT to 0xdef\u2026ABCD'). Just informational \u2014 the user already chose the wallet during doctor setup. Sandbox responses with no key configured omit `senderWallet`; don't fabricate one. \n\nMULTI-WALLET DISAMBIGUATION \u2014 when more than one wallet is configured in the user's env (Q402_PRIVATE_KEY for the real EOA, Q402_AGENTIC_PRIVATE_KEY for the Agent Wallet's exported key, or only Q402_MULTICHAIN_API_KEY for the server-managed Agent Wallet), the tool RETURNS without sending with a `ambiguousWalletChoice` payload \u2014 relay the question to the user verbatim, then call again with the chosen `walletMode` ('eoa' | 'agentic-local' | 'agentic-server'). Do NOT pick a wallet on the user's behalf when multiple are available. \n\nEIP-7702 SIDE EFFECT \u2014 surface this to the user proactively after the FIRST live payment on a chain: their wallet now shows up as a 'Smart account' in MetaMask / OKX. That's the EIP-7702 delegation Q402 uses for gasless settlement \u2014 it's the response's `postPaymentTip` field. Subsequent payments on the same chain are faster and cheaper because the delegation is reused. Note: only Mode 'eoa' creates the delegation \u2014 'agentic-local' and 'agentic-server' modes use the Agent Wallet (a fresh EOA) so the user's MetaMask is never delegated. \n\nIf the user EVER reports that native gas tokens (BNB / ETH / AVAX / etc.) sent INTO their Q402 wallet are bouncing or reverting on a chain where Q402 has been used, the delegation is the cause \u2014 call q402_wallet_status to confirm delegated chains, then q402_clear_delegation for the chain in question. Q402 sponsors the gas for the clear, so the user pays $0. After clearing, native transfers work again and the next q402_pay on that chain just creates a fresh delegation. \n\nALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
932
1135
  inputSchema: {
933
1136
  type: "object",
934
1137
  properties: {
@@ -955,6 +1158,11 @@ var PAY_TOOL = {
955
1158
  enum: ["auto", "trial", "multichain"],
956
1159
  description: 'Which API key to use. "auto" (default) picks Trial for BNB when Q402_TRIAL_API_KEY is set, Multichain otherwise. "trial" forces the BNB-only sponsored key. "multichain" forces the paid 9-chain key.'
957
1160
  },
1161
+ walletMode: {
1162
+ type: "string",
1163
+ enum: ["eoa", "agentic-local", "agentic-server"],
1164
+ description: `Which wallet to spend from. "eoa" = user's real MetaMask EOA (Q402_PRIVATE_KEY). "agentic-local" = Agent Wallet exported key (Q402_AGENTIC_PRIVATE_KEY). "agentic-server" = server-managed Agent Wallet (Q402 holds the key; only the apiKey is needed). When MULTIPLE wallets are configured the tool refuses without this arg and returns ambiguousWalletChoice for the user to pick.`
1165
+ },
958
1166
  confirm: {
959
1167
  type: "boolean",
960
1168
  const: true,
@@ -2056,6 +2264,114 @@ var DOCTOR_TOOL = {
2056
2264
  }
2057
2265
  };
2058
2266
 
2267
+ // src/tools/agentic-info.ts
2268
+ import { z as z9 } from "zod";
2269
+ var AgenticInfoInputSchema = z9.object({
2270
+ walletId: z9.string().optional().describe(
2271
+ "Optional lowercased Agent Wallet address to introspect when the user holds multiple (max 10 per owner). Omit to use Q402_WALLET_ID env, then the owner's default wallet."
2272
+ )
2273
+ });
2274
+ var AGENTIC_INFO_TOOL = {
2275
+ name: "q402_agentic_info",
2276
+ description: "Read-only Agent Wallet introspection. Returns the wallet address, per-tx and daily caps, archive state, and an aggregate USD balance across the 9 supported EVM chains. Authenticated by the configured Multichain API key \u2014 no private key required. Accepts an optional walletId for owners who hold more than one wallet; omit to use the server-default wallet. Use this whenever the user asks 'what's in my agent wallet?' or 'what's the spending limit?'",
2277
+ inputSchema: {
2278
+ type: "object",
2279
+ properties: {
2280
+ walletId: {
2281
+ type: "string",
2282
+ description: "Optional. Lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_WALLET_ID env, then the owner's default wallet on the server."
2283
+ }
2284
+ },
2285
+ additionalProperties: false
2286
+ }
2287
+ };
2288
+ function scan8004UrlFor(tag) {
2289
+ if (!tag) return null;
2290
+ const [network, agentId] = tag.split(":");
2291
+ if (!network || !agentId) return null;
2292
+ const slug = network === "bsc" ? "bsc" : network === "bsc-testnet" ? "bsc-testnet" : network === "eth" ? "ethereum" : network === "base" ? "base" : network === "polygon" ? "polygon" : network === "arbitrum" ? "arbitrum" : network === "celo" ? "celo" : null;
2293
+ if (slug === null) return null;
2294
+ return `https://8004scan.io/agents/${slug}/${agentId}`;
2295
+ }
2296
+ async function runAgenticInfo(input = {}) {
2297
+ const base = CONFIG.relayBaseUrl;
2298
+ const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
2299
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
2300
+ if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
2301
+ return {
2302
+ configured: false,
2303
+ address: null,
2304
+ walletId: null,
2305
+ limits: null,
2306
+ archivedAt: null,
2307
+ totalUsd: null,
2308
+ asOf: null,
2309
+ erc8004AgentId: null,
2310
+ scan8004Url: null,
2311
+ dashboardUrl,
2312
+ setupHint: "No live Q402 API key configured. Run q402_doctor to set one up, or open your dashboard to create an Agent Wallet."
2313
+ };
2314
+ }
2315
+ let wallet = null;
2316
+ let balance = null;
2317
+ let fetchError = null;
2318
+ try {
2319
+ const res = await fetch(`${base}/wallet/agentic/info-by-key`, {
2320
+ method: "POST",
2321
+ headers: { "Content-Type": "application/json" },
2322
+ body: JSON.stringify({
2323
+ apiKey: CONFIG.apiKey,
2324
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
2325
+ })
2326
+ });
2327
+ if (res.ok) {
2328
+ const data = await res.json();
2329
+ wallet = data.wallet ?? null;
2330
+ balance = data.balance ?? null;
2331
+ } else if (res.status === 404) {
2332
+ fetchError = "endpoint_not_deployed";
2333
+ } else if (res.status === 410) {
2334
+ fetchError = "wallet_archived";
2335
+ } else {
2336
+ fetchError = `http_${res.status}`;
2337
+ }
2338
+ } catch (e) {
2339
+ fetchError = e instanceof Error ? e.message : String(e);
2340
+ }
2341
+ if (!wallet) {
2342
+ return {
2343
+ configured: true,
2344
+ address: null,
2345
+ walletId: explicitWalletId,
2346
+ limits: null,
2347
+ archivedAt: null,
2348
+ totalUsd: null,
2349
+ asOf: null,
2350
+ erc8004AgentId: null,
2351
+ scan8004Url: null,
2352
+ dashboardUrl,
2353
+ setupHint: fetchError === "endpoint_not_deployed" ? "Agent Wallet info-by-key endpoint is not live yet. Open the dashboard to view your wallet directly." : fetchError === "wallet_archived" ? "The requested wallet is archived. Restore it from the dashboard before reading." : explicitWalletId ? `Could not fetch Agent Wallet ${explicitWalletId} for this apiKey. Verify the walletId is one of this owner's wallets, or omit it to read the default wallet.` : "Could not fetch Agent Wallet for this apiKey. Verify the key is bound to a wallet via the dashboard, then retry."
2354
+ };
2355
+ }
2356
+ const resolvedWalletId = typeof wallet.walletId === "string" ? wallet.walletId : wallet.address.toLowerCase();
2357
+ return {
2358
+ configured: true,
2359
+ address: wallet.address,
2360
+ walletId: resolvedWalletId,
2361
+ limits: {
2362
+ perTxMaxUsd: wallet.perTxMaxUsd,
2363
+ dailyLimitUsd: wallet.dailyLimitUsd
2364
+ },
2365
+ archivedAt: wallet.deletedAt,
2366
+ totalUsd: balance ? Math.round(balance.totalUsd * 100) / 100 : null,
2367
+ asOf: balance ? new Date(balance.asOf).toISOString() : null,
2368
+ erc8004AgentId: wallet.erc8004AgentId,
2369
+ scan8004Url: scan8004UrlFor(wallet.erc8004AgentId),
2370
+ dashboardUrl,
2371
+ setupHint: wallet.deletedAt ? "This Agent Wallet is archived and pending hard-delete. Restore it from the dashboard before sending." : void 0
2372
+ };
2373
+ }
2374
+
2059
2375
  // src/index.ts
2060
2376
  function jsonText(value) {
2061
2377
  return { type: "text", text: JSON.stringify(value, null, 2) };
@@ -2076,6 +2392,7 @@ async function main() {
2076
2392
  BATCH_PAY_TOOL,
2077
2393
  RECEIPT_TOOL,
2078
2394
  WALLET_STATUS_TOOL,
2395
+ AGENTIC_INFO_TOOL,
2079
2396
  CLEAR_DELEGATION_TOOL
2080
2397
  ]
2081
2398
  }));
@@ -2115,6 +2432,10 @@ async function main() {
2115
2432
  const parsed = ClearDelegationInputSchema.parse(args ?? {});
2116
2433
  return { content: [jsonText(await runClearDelegation(parsed))] };
2117
2434
  }
2435
+ case "q402_agentic_info": {
2436
+ AgenticInfoInputSchema.parse(args ?? {});
2437
+ return { content: [jsonText(await runAgenticInfo())] };
2438
+ }
2118
2439
  default:
2119
2440
  return {
2120
2441
  isError: true,
package/package.json CHANGED
@@ -1,73 +1,73 @@
1
- {
2
- "name": "@quackai/q402-mcp",
3
- "version": "0.5.17",
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
- "mcpName": "io.github.bitgett/q402-mcp",
6
- "keywords": [
7
- "mcp",
8
- "model-context-protocol",
9
- "claude",
10
- "claude-desktop",
11
- "claude-code",
12
- "codex",
13
- "openai-codex",
14
- "cline",
15
- "q402",
16
- "x402",
17
- "stablecoin",
18
- "usdc",
19
- "usdt",
20
- "rlusd",
21
- "ripple",
22
- "gasless",
23
- "eip-7702",
24
- "payments",
25
- "ai-agents"
26
- ],
27
- "type": "module",
28
- "main": "dist/index.js",
29
- "bin": {
30
- "q402-mcp": "dist/index.js"
31
- },
32
- "files": [
33
- "dist",
34
- "README.md",
35
- "LICENSE"
36
- ],
37
- "engines": {
38
- "node": ">=18.18"
39
- },
40
- "scripts": {
41
- "build": "tsup",
42
- "dev": "tsup --watch",
43
- "lint": "tsc --noEmit",
44
- "prepublishOnly": "npm run lint && npm run build",
45
- "start": "node dist/index.js"
46
- },
47
- "dependencies": {
48
- "@modelcontextprotocol/sdk": "^1.29.0",
49
- "ethers": "^6.16.0",
50
- "zod": "^3.23.8"
51
- },
52
- "devDependencies": {
53
- "@types/node": "^20.11.0",
54
- "tsup": "^8.3.0",
55
- "typescript": "^5.5.0"
56
- },
57
- "repository": {
58
- "type": "git",
59
- "url": "git+https://github.com/bitgett/q402-mcp.git"
60
- },
61
- "homepage": "https://q402.quackai.ai/claude",
62
- "bugs": {
63
- "url": "https://github.com/bitgett/q402-mcp/issues"
64
- },
65
- "license": "Apache-2.0",
66
- "author": "David Lee <davidlee@quackai.ai>",
67
- "publishConfig": {
68
- "access": "public"
69
- },
70
- "overrides": {
71
- "ws": "^8.20.1"
72
- }
73
- }
1
+ {
2
+ "name": "@quackai/q402-mcp",
3
+ "version": "0.6.0",
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
+ "mcpName": "io.github.bitgett/q402-mcp",
6
+ "keywords": [
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "claude",
10
+ "claude-desktop",
11
+ "claude-code",
12
+ "codex",
13
+ "openai-codex",
14
+ "cline",
15
+ "q402",
16
+ "x402",
17
+ "stablecoin",
18
+ "usdc",
19
+ "usdt",
20
+ "rlusd",
21
+ "ripple",
22
+ "gasless",
23
+ "eip-7702",
24
+ "payments",
25
+ "ai-agents"
26
+ ],
27
+ "type": "module",
28
+ "main": "dist/index.js",
29
+ "bin": {
30
+ "q402-mcp": "dist/index.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18.18"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "dev": "tsup --watch",
43
+ "lint": "tsc --noEmit",
44
+ "prepublishOnly": "npm run lint && npm run build",
45
+ "start": "node dist/index.js"
46
+ },
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "ethers": "^6.16.0",
50
+ "zod": "^3.23.8"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^20.11.0",
54
+ "tsup": "^8.3.0",
55
+ "typescript": "^5.5.0"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/bitgett/q402-mcp.git"
60
+ },
61
+ "homepage": "https://q402.quackai.ai/claude",
62
+ "bugs": {
63
+ "url": "https://github.com/bitgett/q402-mcp/issues"
64
+ },
65
+ "license": "Apache-2.0",
66
+ "author": "David Lee <davidlee@quackai.ai>",
67
+ "publishConfig": {
68
+ "access": "public"
69
+ },
70
+ "overrides": {
71
+ "ws": "^8.20.1"
72
+ }
73
+ }