@quackai/q402-mcp 0.5.17 → 0.6.1

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 +455 -26
  2. package/package.json +73 -73
package/dist/index.js CHANGED
@@ -120,9 +120,18 @@ 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_AGENT_WALLET_ADDRESS ?? ENV.Q402_WALLET_ID;
125
+ if (!ENV.Q402_AGENT_WALLET_ADDRESS && ENV.Q402_WALLET_ID) {
126
+ process.stderr.write(
127
+ "[q402-mcp] Q402_WALLET_ID is deprecated \u2014 rename to Q402_AGENT_WALLET_ADDRESS. Old name will be removed in v0.7.0.\n"
128
+ );
129
+ }
130
+ const walletId = typeof walletIdRaw === "string" && walletIdRaw.length > 0 ? walletIdRaw.toLowerCase() : null;
123
131
  const realPaymentsRequested = ENV.Q402_ENABLE_REAL_PAYMENTS === "1";
124
132
  const anyLiveKey = classifyApiKey(trialApiKey) === "live" || classifyApiKey(multichainApiKey) === "live" || classifyApiKey(legacyApiKey) === "live";
125
- const live = realPaymentsRequested && anyLiveKey && typeof privateKey === "string" && privateKey.length > 0;
133
+ const hasAnySignerKey = typeof privateKey === "string" && privateKey.length > 0 || typeof agenticPrivateKey === "string" && agenticPrivateKey.length > 0;
134
+ const live = realPaymentsRequested && anyLiveKey && hasAnySignerKey;
126
135
  return {
127
136
  trialApiKey,
128
137
  multichainApiKey,
@@ -130,6 +139,8 @@ function loadConfig() {
130
139
  apiKey,
131
140
  apiKeyKind,
132
141
  privateKey,
142
+ agenticPrivateKey,
143
+ walletId,
133
144
  realPaymentsRequested,
134
145
  mode: live ? "live" : "sandbox",
135
146
  relayBaseUrl: (ENV.Q402_RELAY_BASE_URL ?? DEFAULT_RELAY_BASE).replace(/\/$/, ""),
@@ -137,6 +148,17 @@ function loadConfig() {
137
148
  allowedRecipients: parseAllowedRecipients(ENV.Q402_ALLOWED_RECIPIENTS)
138
149
  };
139
150
  }
151
+ function detectAgenticModes(c = CONFIG) {
152
+ const modeA = isValidPrivateKey(c.privateKey);
153
+ const modeB = isValidPrivateKey(c.agenticPrivateKey);
154
+ const modeC = c.apiKey !== null && c.apiKey.startsWith("q402_live_");
155
+ let count = 0;
156
+ if (modeA) count++;
157
+ if (modeB) count++;
158
+ if (modeC) count++;
159
+ const primary = modeB ? "B" : modeA ? "A" : modeC ? "C" : null;
160
+ return { modeA, modeB, modeC, count, primary };
161
+ }
140
162
  var CONFIG = loadConfig();
141
163
  function resolveApiKey(chain, scope = "auto") {
142
164
  const effectiveScope = scope === "auto" ? (
@@ -180,15 +202,16 @@ var PRIVATE_KEY_RE = /^0x[a-fA-F0-9]{64}$/;
180
202
  function isLiveModeFor(resolved) {
181
203
  if (!resolved.apiKey) return false;
182
204
  if (!CONFIG.realPaymentsRequested) return false;
183
- if (!CONFIG.privateKey) return false;
184
- if (!PRIVATE_KEY_RE.test(CONFIG.privateKey)) return false;
205
+ const hasMode_A_Key = typeof CONFIG.privateKey === "string" && PRIVATE_KEY_RE.test(CONFIG.privateKey);
206
+ const hasMode_B_Key = typeof CONFIG.agenticPrivateKey === "string" && PRIVATE_KEY_RE.test(CONFIG.agenticPrivateKey);
207
+ if (!hasMode_A_Key && !hasMode_B_Key) return false;
185
208
  return resolved.apiKey.startsWith("q402_live_");
186
209
  }
187
210
  var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
188
211
 
189
212
  // src/version.ts
190
213
  var PACKAGE_NAME = "@quackai/q402-mcp";
191
- var PACKAGE_VERSION = "0.5.17";
214
+ var PACKAGE_VERSION = "0.6.1";
192
215
 
193
216
  // src/tools/quote.ts
194
217
  import { z } from "zod";
@@ -822,6 +845,16 @@ var PayInputSchema = z2.object({
822
845
  keyScope: z2.enum(["auto", "trial", "multichain"]).optional().describe(
823
846
  '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
847
  ),
848
+ walletMode: z2.enum(["eoa", "agentic-local", "agentic-server"]).optional().describe(
849
+ `Which wallet to spend from:
850
+ "eoa" \u2014 the user's real MetaMask/OKX EOA, signed locally with Q402_PRIVATE_KEY
851
+ "agentic-local" \u2014 the Agent Wallet's exported private key (Q402_AGENTIC_PRIVATE_KEY)
852
+ "agentic-server" \u2014 the server-managed Agent Wallet (Q402 holds the key; you only need Q402_MULTICHAIN_API_KEY)
853
+ 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.`
854
+ ),
855
+ walletId: z2.string().optional().describe(
856
+ `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.`
857
+ ),
825
858
  confirm: z2.literal(true).describe(
826
859
  "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
860
  )
@@ -854,10 +887,82 @@ async function runPay(input) {
854
887
  );
855
888
  }
856
889
  const guardsApplied = [];
857
- let senderWallet;
858
- if (CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
890
+ function failureResult(method) {
891
+ return {
892
+ success: false,
893
+ sandbox: false,
894
+ txHash: "",
895
+ tokenAmount: input.amount,
896
+ token: input.token,
897
+ chain: chain.key,
898
+ method,
899
+ explorerUrl: null
900
+ };
901
+ }
902
+ const modes = detectAgenticModes(CONFIG);
903
+ const available = [];
904
+ if (modes.modeA && CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
859
905
  try {
860
906
  const addr = new Wallet2(CONFIG.privateKey).address;
907
+ available.push({
908
+ id: "eoa",
909
+ label: "Your real MetaMask / OKX EOA",
910
+ addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`,
911
+ note: "Signs locally with Q402_PRIVATE_KEY. Your wallet becomes EIP-7702-delegated after the first payment on each chain."
912
+ });
913
+ } catch {
914
+ }
915
+ }
916
+ if (modes.modeB && CONFIG.agenticPrivateKey && isValidPrivateKey(CONFIG.agenticPrivateKey)) {
917
+ try {
918
+ const addr = new Wallet2(CONFIG.agenticPrivateKey).address;
919
+ available.push({
920
+ id: "agentic-local",
921
+ label: "Agent Wallet (local signing with exported key)",
922
+ addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`,
923
+ note: "Signs locally with Q402_AGENTIC_PRIVATE_KEY. Your MetaMask is never touched."
924
+ });
925
+ } catch {
926
+ }
927
+ }
928
+ if (modes.modeC) {
929
+ available.push({
930
+ id: "agentic-server",
931
+ label: "Agent Wallet (server-managed)",
932
+ note: "Q402 holds the encrypted key; payment fires through /api/wallet/agentic/send. Caps you set in the dashboard bound the spend."
933
+ });
934
+ }
935
+ const requestedMode = input.walletMode;
936
+ const requestedAvailable = requestedMode ? available.some((w) => w.id === requestedMode) : false;
937
+ if (requestedMode && !requestedAvailable) {
938
+ return {
939
+ result: failureResult("wallet_mode_unavailable"),
940
+ guardsApplied: [
941
+ `wallet_modes_available=${available.length}`,
942
+ `requested=${requestedMode}`
943
+ ],
944
+ ambiguousWalletChoice: {
945
+ 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?`,
946
+ available
947
+ }
948
+ };
949
+ }
950
+ if (available.length > 1 && !requestedMode) {
951
+ return {
952
+ result: failureResult("needs_wallet_choice"),
953
+ guardsApplied: [`wallet_modes_available=${available.length}`],
954
+ ambiguousWalletChoice: {
955
+ 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?`,
956
+ available
957
+ }
958
+ };
959
+ }
960
+ const effectiveMode = requestedMode && requestedAvailable ? requestedMode : available.length === 1 && available[0] ? available[0].id : "eoa";
961
+ const signingPk = effectiveMode === "eoa" ? CONFIG.privateKey : effectiveMode === "agentic-local" ? CONFIG.agenticPrivateKey : null;
962
+ let senderWallet;
963
+ if (signingPk && isValidPrivateKey(signingPk)) {
964
+ try {
965
+ const addr = new Wallet2(signingPk).address;
861
966
  senderWallet = {
862
967
  address: addr,
863
968
  addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
@@ -874,6 +979,127 @@ async function runPay(input) {
874
979
  const scopeRequest = input.keyScope ?? "auto";
875
980
  const resolved = resolveApiKey(input.chain, scopeRequest);
876
981
  guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
982
+ if (effectiveMode === "agentic-server") {
983
+ if (input.token === "RLUSD") {
984
+ return {
985
+ result: failureResult("rlusd_not_supported_for_server_mode"),
986
+ guardsApplied: [
987
+ ...guardsApplied,
988
+ "wallet=agentic-server",
989
+ "token=RLUSD",
990
+ "rejected_pre_relay"
991
+ ],
992
+ senderWallet,
993
+ 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.'
994
+ };
995
+ }
996
+ if (!resolved.apiKey || !resolved.apiKey.startsWith("q402_live_")) {
997
+ const result2 = sandboxPay(chain, {
998
+ to: input.to,
999
+ amount: input.amount,
1000
+ token: input.token
1001
+ });
1002
+ guardsApplied.push("mode=sandbox", "wallet=agentic-server");
1003
+ return {
1004
+ result: result2,
1005
+ guardsApplied,
1006
+ senderWallet,
1007
+ 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."
1008
+ };
1009
+ }
1010
+ if (!CONFIG.realPaymentsRequested) {
1011
+ const result2 = sandboxPay(chain, {
1012
+ to: input.to,
1013
+ amount: input.amount,
1014
+ token: input.token
1015
+ });
1016
+ guardsApplied.push("mode=sandbox", "wallet=agentic-server");
1017
+ return {
1018
+ result: result2,
1019
+ guardsApplied,
1020
+ senderWallet,
1021
+ setupHint: "Set Q402_ENABLE_REAL_PAYMENTS=1 to fire a real server-mediated payment."
1022
+ };
1023
+ }
1024
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
1025
+ let resp;
1026
+ try {
1027
+ resp = await fetch(`${CONFIG.relayBaseUrl}/wallet/agentic/send`, {
1028
+ method: "POST",
1029
+ headers: { "Content-Type": "application/json" },
1030
+ body: JSON.stringify({
1031
+ apiKey: resolved.apiKey,
1032
+ chain: input.chain,
1033
+ token: input.token,
1034
+ to: input.to,
1035
+ amount: input.amount,
1036
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
1037
+ })
1038
+ });
1039
+ } catch (e) {
1040
+ const transportErr = failureResult("eip7702");
1041
+ return {
1042
+ result: transportErr,
1043
+ guardsApplied: [
1044
+ ...guardsApplied,
1045
+ "wallet=agentic-server",
1046
+ "mode=live",
1047
+ "transport=fetch_failed",
1048
+ `error=${e instanceof Error ? e.message : String(e)}`
1049
+ ],
1050
+ senderWallet
1051
+ };
1052
+ }
1053
+ const data = await resp.json().catch(() => ({}));
1054
+ const txHash = data.txHash ?? "";
1055
+ const isPending = resp.status === 202 || data.pending === true || data.status === "processing";
1056
+ if (isPending) {
1057
+ const retryAfter = typeof data.retryAfterSec === "number" ? data.retryAfterSec : 5;
1058
+ return {
1059
+ result: {
1060
+ success: false,
1061
+ sandbox: false,
1062
+ txHash: "",
1063
+ tokenAmount: input.amount,
1064
+ token: input.token,
1065
+ chain: chain.key,
1066
+ method: "eip7702",
1067
+ pending: true,
1068
+ retryAfterSec: retryAfter
1069
+ },
1070
+ guardsApplied: [
1071
+ ...guardsApplied,
1072
+ "wallet=agentic-server",
1073
+ "mode=live",
1074
+ "status=pending",
1075
+ `retry_after=${retryAfter}s`
1076
+ ],
1077
+ senderWallet,
1078
+ setupHint: `An identical send for this wallet is still in flight on the server. Wait ${retryAfter}s and retry \u2014 the cached result will come back, no double-spend.`
1079
+ };
1080
+ }
1081
+ const success = resp.ok && txHash.length > 0;
1082
+ const message = "message" in data ? data.message : "error" in data ? data.error : void 0;
1083
+ return {
1084
+ result: {
1085
+ success,
1086
+ sandbox: false,
1087
+ txHash,
1088
+ tokenAmount: input.amount,
1089
+ token: input.token,
1090
+ chain: chain.key,
1091
+ method: "eip7702",
1092
+ explorerUrl: txHash ? void 0 : null
1093
+ },
1094
+ guardsApplied: [
1095
+ ...guardsApplied,
1096
+ "wallet=agentic-server",
1097
+ "mode=live",
1098
+ ...message ? [`server_message=${message}`] : []
1099
+ ],
1100
+ senderWallet
1101
+ };
1102
+ }
877
1103
  const live = isLiveModeFor(resolved);
878
1104
  if (!live) {
879
1105
  const result2 = sandboxPay(chain, {
@@ -881,13 +1107,21 @@ async function runPay(input) {
881
1107
  amount: input.amount,
882
1108
  token: input.token
883
1109
  });
884
- guardsApplied.push("mode=sandbox");
1110
+ guardsApplied.push("mode=sandbox", `wallet=${effectiveMode}`);
885
1111
  const setupHint = resolved.sandboxReason ?? describeSandboxReason(resolved.apiKey ?? "", resolved.scope);
886
1112
  return { result: result2, guardsApplied, setupHint, senderWallet };
887
1113
  }
1114
+ if (!signingPk) {
1115
+ return {
1116
+ result: failureResult("missing_signing_key"),
1117
+ guardsApplied: [...guardsApplied, `wallet=${effectiveMode}`, "mode=sandbox"],
1118
+ senderWallet,
1119
+ 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."
1120
+ };
1121
+ }
888
1122
  const client = new Q402NodeClient({
889
1123
  apiKey: resolved.apiKey,
890
- privateKey: CONFIG.privateKey,
1124
+ privateKey: signingPk,
891
1125
  chain,
892
1126
  relayBaseUrl: CONFIG.relayBaseUrl
893
1127
  });
@@ -896,7 +1130,7 @@ async function runPay(input) {
896
1130
  amount: input.amount,
897
1131
  token: input.token
898
1132
  });
899
- guardsApplied.push("mode=live");
1133
+ guardsApplied.push("mode=live", `wallet=${effectiveMode}`);
900
1134
  return {
901
1135
  result,
902
1136
  guardsApplied,
@@ -928,7 +1162,7 @@ function describeSandboxReason(resolvedKey, scope) {
928
1162
  }
929
1163
  var PAY_TOOL = {
930
1164
  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.",
1165
+ 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
1166
  inputSchema: {
933
1167
  type: "object",
934
1168
  properties: {
@@ -955,6 +1189,11 @@ var PAY_TOOL = {
955
1189
  enum: ["auto", "trial", "multichain"],
956
1190
  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
1191
  },
1192
+ walletMode: {
1193
+ type: "string",
1194
+ enum: ["eoa", "agentic-local", "agentic-server"],
1195
+ 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.`
1196
+ },
958
1197
  confirm: {
959
1198
  type: "boolean",
960
1199
  const: true,
@@ -1676,13 +1915,76 @@ Q402_MULTICHAIN_API_KEY=
1676
1915
 
1677
1916
 
1678
1917
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1679
- # WALLET \u2014 paste your private key on the right of \`=\`
1918
+ # SIGNING MODE \u2014 pick ONE of A / B / C below
1919
+ # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1920
+ # Q402 pays in three ways, depending on which keys you set. Pick the
1921
+ # row that matches your situation and fill ONLY that row's variable(s).
1922
+ # Setting multiple rows is allowed \u2014 q402_pay will ask you which one to
1923
+ # use per call (the AI surfaces the question; you don't pre-pick a
1924
+ # default here).
1925
+ #
1926
+ # A. Real EOA (your MetaMask wallet)
1927
+ # Set: Q402_PRIVATE_KEY = 0x... (your MetaMask account's private key)
1928
+ # Pros: simplest mental model \u2014 same wallet you already use
1929
+ # Cons: after first payment, MetaMask will show this account as
1930
+ # "Smart account" (EIP-7702 delegation, reversible via
1931
+ # q402_clear_delegation but visually alarming on first sight)
1932
+ #
1933
+ # B. Agent Wallet \u2014 local signing (recommended for AI agents)
1934
+ # Set: Q402_AGENTIC_PRIVATE_KEY = 0x... (Agent Wallet pk from dashboard export)
1935
+ # Pros: your MetaMask stays untouched; agent has its own purse with
1936
+ # per-tx + daily caps you set on the dashboard
1937
+ # Cons: requires creating an Agent Wallet on the dashboard first
1938
+ # (https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Create)
1939
+ # then exporting its private key
1940
+ #
1941
+ # C. Agent Wallet \u2014 server-managed (no private key on your machine)
1942
+ # Set: (just the api key + optionally Q402_AGENT_WALLET_ADDRESS below)
1943
+ # Pros: zero private-key handling locally; the server holds the
1944
+ # encrypted Agent Wallet pk and signs on your behalf
1945
+ # Cons: requires a paid Multichain API key (Mode C is not available
1946
+ # on the free Trial). The server-side keystore is AES-256-GCM
1947
+ # encrypted but is a custodial path \u2014 pick A or B if that
1948
+ # posture doesn't fit your threat model.
1949
+ #
1950
+ # A user can have multiple wallets configured simultaneously (e.g. PK
1951
+ # for personal pays + apiKey-only for agent pays). When more than one
1952
+ # mode is set, q402_pay asks the user which to use rather than picking
1953
+ # silently.
1954
+
1955
+
1956
+ # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1957
+ # WALLET \u2014 Mode A: real EOA private key
1680
1958
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1681
1959
  # Hex EVM private key (0x + 64 hex chars). Signs payments LOCALLY on
1682
1960
  # your machine \u2014 never leaves your device, never sent to any server.
1961
+ # Leave blank if you're using Mode B or Mode C.
1683
1962
  Q402_PRIVATE_KEY=
1684
1963
 
1685
1964
 
1965
+ # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1966
+ # WALLET \u2014 Mode B: exported Agent Wallet private key
1967
+ # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1968
+ # Hex EVM private key (0x + 64 hex chars) exported from your Agent
1969
+ # Wallet on the dashboard. Signs payments LOCALLY just like Mode A,
1970
+ # but the wallet is your dedicated Agent Wallet \u2014 your MetaMask EOA
1971
+ # is never touched. Get the key at:
1972
+ # https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Export
1973
+ # Leave blank if you're using Mode A or Mode C.
1974
+ Q402_AGENTIC_PRIVATE_KEY=
1975
+
1976
+
1977
+ # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1978
+ # WALLET \u2014 Mode C: server-managed Agent Wallet picker (optional)
1979
+ # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1980
+ # Only set this when you're running Mode C (api key only, no private
1981
+ # keys above) AND you have more than one Agent Wallet on the account
1982
+ # (max 10). Pin which one Q402 should spend from. Format: lowercase
1983
+ # 0x... address of the Agent Wallet (visible on the dashboard's Agent
1984
+ # tab). Omit entirely to use your default Agent Wallet.
1985
+ # Q402_AGENT_WALLET_ADDRESS=0x...
1986
+
1987
+
1686
1988
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1687
1989
  # Live mode switch (safe even at 1 \u2014 see SAFE-BY-DEFAULT note at top)
1688
1990
  # \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
@@ -1735,13 +2037,16 @@ function mask2(key) {
1735
2037
  function detectPhase() {
1736
2038
  const anyKey = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
1737
2039
  const anyLiveKey = classifyApiKey(CONFIG.trialApiKey) === "live" || classifyApiKey(CONFIG.multichainApiKey) === "live" || classifyApiKey(CONFIG.legacyApiKey) === "live";
1738
- const hasValidPrivateKey = isValidPrivateKey(CONFIG.privateKey);
1739
- if (!Q402_ENV_FILE_PRESENT && !anyKey && !CONFIG.privateKey) {
2040
+ const modes = detectAgenticModes();
2041
+ const hasAnyValidSigningPath = modes.count > 0;
2042
+ if (!Q402_ENV_FILE_PRESENT && !anyKey && !CONFIG.privateKey && !CONFIG.agenticPrivateKey) {
1740
2043
  return "first-install";
1741
2044
  }
1742
- const allEssentials = anyKey && hasValidPrivateKey && CONFIG.realPaymentsRequested && anyLiveKey;
2045
+ const allEssentials = anyKey && hasAnyValidSigningPath && CONFIG.realPaymentsRequested && anyLiveKey;
1743
2046
  if (allEssentials) return "live-check";
1744
- if (anyKey || CONFIG.privateKey || Q402_ENV_FILE_PRESENT) return "needs-completion";
2047
+ if (anyKey || CONFIG.privateKey || CONFIG.agenticPrivateKey || Q402_ENV_FILE_PRESENT) {
2048
+ return "needs-completion";
2049
+ }
1745
2050
  return "first-install";
1746
2051
  }
1747
2052
  async function verifyOneKey(scope, envVar, apiKey) {
@@ -1862,23 +2167,33 @@ async function runDoctor() {
1862
2167
  "Must be '1' to allow real TX. Anything else = test response (fake hash)."
1863
2168
  )
1864
2169
  };
2170
+ const modes = detectAgenticModes();
1865
2171
  const missing = [];
1866
2172
  if (!CONFIG.trialApiKey && !CONFIG.multichainApiKey && !CONFIG.legacyApiKey) {
1867
2173
  missing.push(
1868
2174
  "An API key (Q402_TRIAL_API_KEY for free BNB OR Q402_MULTICHAIN_API_KEY for paid 9-chain)"
1869
2175
  );
1870
2176
  }
1871
- if (!CONFIG.privateKey) {
1872
- missing.push("Q402_PRIVATE_KEY");
1873
- } else if (!isValidPrivateKey(CONFIG.privateKey)) {
1874
- missing.push(
1875
- "Q402_PRIVATE_KEY is set but malformed (expected 0x + 64 hex chars). Looks like the placeholder '0x...' is still in ~/.q402/mcp.env \u2014 paste a real key in your editor."
1876
- );
2177
+ if (modes.count === 0) {
2178
+ const pkAPlaceholder = !!CONFIG.privateKey && !isValidPrivateKey(CONFIG.privateKey);
2179
+ const pkBPlaceholder = !!CONFIG.agenticPrivateKey && !isValidPrivateKey(CONFIG.agenticPrivateKey);
2180
+ if (pkAPlaceholder) {
2181
+ missing.push(
2182
+ "Q402_PRIVATE_KEY is set but malformed (expected 0x + 64 hex chars). Looks like the placeholder '0x...' is still in ~/.q402/mcp.env \u2014 paste a real key in your editor."
2183
+ );
2184
+ } else if (pkBPlaceholder) {
2185
+ missing.push(
2186
+ "Q402_AGENTIC_PRIVATE_KEY is set but malformed (expected 0x + 64 hex chars). Paste your exported Agent Wallet private key (https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Export)."
2187
+ );
2188
+ } else {
2189
+ missing.push(
2190
+ "A signing path. Pick ONE: (A) Q402_PRIVATE_KEY = your MetaMask EOA's private key; (B) Q402_AGENTIC_PRIVATE_KEY = your exported Agent Wallet private key from the dashboard (Mode B keeps your MetaMask untouched); or (C) just use a live paid Q402_MULTICHAIN_API_KEY and let Q402 sign with your server-managed Agent Wallet (no PK on your machine)."
2191
+ );
2192
+ }
1877
2193
  }
1878
2194
  if (!CONFIG.realPaymentsRequested) {
1879
2195
  const haveAnyApi = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
1880
- const havePk = isValidPrivateKey(CONFIG.privateKey);
1881
- if (haveAnyApi && havePk) {
2196
+ if (haveAnyApi && modes.count > 0) {
1882
2197
  missing.push(
1883
2198
  "Q402_ENABLE_REAL_PAYMENTS=1 \u2014 your other config looks fine, but your MCP client isn't passing the registry default through. Add the line Q402_ENABLE_REAL_PAYMENTS=1 to ~/.q402/mcp.env explicitly and restart."
1884
2199
  );
@@ -1933,12 +2248,13 @@ async function runDoctor() {
1933
2248
  recommendedActions,
1934
2249
  greeting: phase === "first-install" ? `Q402 MCP is installed (v${PACKAGE_VERSION}).` : `Q402 MCP is installed (v${PACKAGE_VERSION}) \u2014 partially configured.`,
1935
2250
  nextStep: phase === "first-install" ? "Show userInstructions verbatim to the user; do NOT show agentInstructions verbatim (it's prescription for you, the AI)." : "Tell the user which env vars are still missing (from the 'missing' list) and how to add them to ~/.q402/mcp.env. Show userInstructions for the human-readable steps.",
1936
- agentInstructions: phase === "first-install" ? "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] Multi-turn flow: (1) Briefly tell the user MCP is installed. (2) Ask one yes/no question \u2014 'Want me to create your Q402 settings file?'. (3) On yes, execute recommendedActions IN ORDER: first `ensure-q402-dir` shell action (bash on macOS/Linux, PowerShell on Windows via shellWindows variant), then `create-env-file` write_file action. (4) Open the file in the user's editor \u2014 `code` works for VS Code / Cursor / Cline (e.g. `code ~/.q402/mcp.env`); `open` on macOS, `start` on Windows, `xdg-open` on Linux as fallback. (5) Walk through filling in the API key (from /event for free Trial or /payment for paid Multichain) and private key one at a time. (6) Do NOT accept key values via chat \u2014 direct the user to edit the file in their editor. BEFORE they paste a private key, surface the `advisories` array: fresh wallet, Smart-account-in-MetaMask heads-up, hardware wallets unsupported, MetaMask key-export path. (7) After they save, tell them to restart the MCP client \u2014 per-client restart verb: Claude Desktop \u2192 quit + relaunch; Codex \u2192 exit + relaunch; Cursor \u2192 Cmd/Ctrl+Shift+P \u2192 'Developer: Reload Window'; Cline \u2192 reload VS Code window. (8) Have them re-invoke 'Set up Q402' to confirm. Keep the conversation tight: one decision per turn, plain language, never echo this paragraph." : "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] User has SOME env set. List the missing items (from `missing`) in plain language. Tell them to edit ~/.q402/mcp.env and uncomment / fill the relevant line, then restart the MCP client. Restart verb per client: Claude Desktop \u2192 quit + relaunch; Codex \u2192 exit + relaunch; Cursor \u2192 Cmd/Ctrl+Shift+P \u2192 'Developer: Reload Window'; Cline \u2192 reload VS Code window.",
2251
+ agentInstructions: phase === "first-install" ? "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] Multi-turn flow: (1) Briefly tell the user MCP is installed. (2) Ask one yes/no question \u2014 'Want me to create your Q402 settings file?'. (3) On yes, execute recommendedActions IN ORDER: first `ensure-q402-dir` shell action (bash on macOS/Linux, PowerShell on Windows via shellWindows variant), then `create-env-file` write_file action. (4) Open the file in the user's editor \u2014 `code` works for VS Code / Cursor / Cline (e.g. `code ~/.q402/mcp.env`); `open` on macOS, `start` on Windows, `xdg-open` on Linux as fallback. (5) Help the user pick a wallet mode (A=Q402_PRIVATE_KEY real EOA, B=Q402_AGENTIC_PRIVATE_KEY exported Agent Wallet PK, C=API key only with server-managed Agent Wallet). If they're an AI agent / automation user, gently default to B or C; if they're a power user who wants their existing EOA to be the signer, A is fine. Walk through filling in the chosen mode's variable + the API key one at a time. (6) Do NOT accept key values via chat \u2014 direct the user to edit the file in their editor. BEFORE they paste any private key (Mode A OR Mode B), surface the `advisories` array: fresh wallet, Smart-account-in-MetaMask heads-up (Mode A only), hardware wallets unsupported, MetaMask key-export path (Mode A) or dashboard Export button (Mode B). (7) After they save, tell them to restart the MCP client \u2014 per-client restart verb: Claude Desktop \u2192 quit + relaunch; Codex \u2192 exit + relaunch; Cursor \u2192 Cmd/Ctrl+Shift+P \u2192 'Developer: Reload Window'; Cline \u2192 reload VS Code window. (8) Have them re-invoke 'Set up Q402' to confirm. Keep the conversation tight: one decision per turn, plain language, never echo this paragraph." : "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] User has SOME env set. List the missing items (from `missing`) in plain language. Tell them to edit ~/.q402/mcp.env and uncomment / fill the relevant line, then restart the MCP client. Restart verb per client: Claude Desktop \u2192 quit + relaunch; Codex \u2192 exit + relaunch; Cursor \u2192 Cmd/Ctrl+Shift+P \u2192 'Developer: Reload Window'; Cline \u2192 reload VS Code window.",
1937
2252
  userInstructions: phase === "first-install" ? [
1938
- "Q402 is installed. To start sending payments you need an API key and a wallet.",
2253
+ "Q402 is installed. To start sending payments you need (1) an API key and (2) a wallet to sign with.",
1939
2254
  "I'll create a settings file for you \u2014 say yes and I'll set it up + open it in your editor.",
1940
2255
  "Get a free API key at https://q402.quackai.ai/event (BNB Chain only, 2,000 sponsored transactions).",
1941
- "Use a FRESH wallet for Q402 \u2014 don't use your main wallet. The wallet will be marked 'Smart account' in MetaMask after your first payment (that's normal \u2014 Q402 reverses it on demand).",
2256
+ "There are 3 wallet modes \u2014 pick one: (A) your MetaMask EOA's private key (simplest, but your account will be marked 'Smart account' after first payment); (B) export an Agent Wallet's private key from the dashboard (keeps your MetaMask untouched, recommended for AI agents); (C) on a paid plan, use the server-managed Agent Wallet \u2014 just set the API key, no private key needed.",
2257
+ "Use a FRESH wallet for Mode A \u2014 don't use the one holding your main funds. The 'Smart account' marker is normal (EIP-7702 delegation, reversible via q402_clear_delegation).",
1942
2258
  "Paste your key + wallet private key INTO THE FILE (in your editor) \u2014 never paste a private key into this chat.",
1943
2259
  "Save the file, restart your MCP client, then ask me 'Verify Q402' to confirm."
1944
2260
  ] : [
@@ -2056,6 +2372,114 @@ var DOCTOR_TOOL = {
2056
2372
  }
2057
2373
  };
2058
2374
 
2375
+ // src/tools/agentic-info.ts
2376
+ import { z as z9 } from "zod";
2377
+ var AgenticInfoInputSchema = z9.object({
2378
+ walletId: z9.string().optional().describe(
2379
+ "Optional lowercased Agent Wallet address to introspect when the user holds multiple (max 10 per owner). Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
2380
+ )
2381
+ });
2382
+ var AGENTIC_INFO_TOOL = {
2383
+ name: "q402_agentic_info",
2384
+ 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?'",
2385
+ inputSchema: {
2386
+ type: "object",
2387
+ properties: {
2388
+ walletId: {
2389
+ type: "string",
2390
+ 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."
2391
+ }
2392
+ },
2393
+ additionalProperties: false
2394
+ }
2395
+ };
2396
+ function scan8004UrlFor(tag) {
2397
+ if (!tag) return null;
2398
+ const [network, agentId] = tag.split(":");
2399
+ if (!network || !agentId) return null;
2400
+ 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;
2401
+ if (slug === null) return null;
2402
+ return `https://8004scan.io/agents/${slug}/${agentId}`;
2403
+ }
2404
+ async function runAgenticInfo(input = {}) {
2405
+ const base = CONFIG.relayBaseUrl;
2406
+ const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
2407
+ const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
2408
+ if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
2409
+ return {
2410
+ configured: false,
2411
+ address: null,
2412
+ walletId: null,
2413
+ limits: null,
2414
+ archivedAt: null,
2415
+ totalUsd: null,
2416
+ asOf: null,
2417
+ erc8004AgentId: null,
2418
+ scan8004Url: null,
2419
+ dashboardUrl,
2420
+ setupHint: "No live Q402 API key configured. Run q402_doctor to set one up, or open your dashboard to create an Agent Wallet."
2421
+ };
2422
+ }
2423
+ let wallet = null;
2424
+ let balance = null;
2425
+ let fetchError = null;
2426
+ try {
2427
+ const res = await fetch(`${base}/wallet/agentic/info-by-key`, {
2428
+ method: "POST",
2429
+ headers: { "Content-Type": "application/json" },
2430
+ body: JSON.stringify({
2431
+ apiKey: CONFIG.apiKey,
2432
+ ...explicitWalletId ? { walletId: explicitWalletId } : {}
2433
+ })
2434
+ });
2435
+ if (res.ok) {
2436
+ const data = await res.json();
2437
+ wallet = data.wallet ?? null;
2438
+ balance = data.balance ?? null;
2439
+ } else if (res.status === 404) {
2440
+ fetchError = "endpoint_not_deployed";
2441
+ } else if (res.status === 410) {
2442
+ fetchError = "wallet_archived";
2443
+ } else {
2444
+ fetchError = `http_${res.status}`;
2445
+ }
2446
+ } catch (e) {
2447
+ fetchError = e instanceof Error ? e.message : String(e);
2448
+ }
2449
+ if (!wallet) {
2450
+ return {
2451
+ configured: true,
2452
+ address: null,
2453
+ walletId: explicitWalletId,
2454
+ limits: null,
2455
+ archivedAt: null,
2456
+ totalUsd: null,
2457
+ asOf: null,
2458
+ erc8004AgentId: null,
2459
+ scan8004Url: null,
2460
+ dashboardUrl,
2461
+ 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."
2462
+ };
2463
+ }
2464
+ const resolvedWalletId = typeof wallet.walletId === "string" ? wallet.walletId : wallet.address.toLowerCase();
2465
+ return {
2466
+ configured: true,
2467
+ address: wallet.address,
2468
+ walletId: resolvedWalletId,
2469
+ limits: {
2470
+ perTxMaxUsd: wallet.perTxMaxUsd,
2471
+ dailyLimitUsd: wallet.dailyLimitUsd
2472
+ },
2473
+ archivedAt: wallet.deletedAt,
2474
+ totalUsd: balance ? Math.round(balance.totalUsd * 100) / 100 : null,
2475
+ asOf: balance ? new Date(balance.asOf).toISOString() : null,
2476
+ erc8004AgentId: wallet.erc8004AgentId,
2477
+ scan8004Url: scan8004UrlFor(wallet.erc8004AgentId),
2478
+ dashboardUrl,
2479
+ setupHint: wallet.deletedAt ? "This Agent Wallet is archived and pending hard-delete. Restore it from the dashboard before sending." : void 0
2480
+ };
2481
+ }
2482
+
2059
2483
  // src/index.ts
2060
2484
  function jsonText(value) {
2061
2485
  return { type: "text", text: JSON.stringify(value, null, 2) };
@@ -2076,6 +2500,7 @@ async function main() {
2076
2500
  BATCH_PAY_TOOL,
2077
2501
  RECEIPT_TOOL,
2078
2502
  WALLET_STATUS_TOOL,
2503
+ AGENTIC_INFO_TOOL,
2079
2504
  CLEAR_DELEGATION_TOOL
2080
2505
  ]
2081
2506
  }));
@@ -2115,6 +2540,10 @@ async function main() {
2115
2540
  const parsed = ClearDelegationInputSchema.parse(args ?? {});
2116
2541
  return { content: [jsonText(await runClearDelegation(parsed))] };
2117
2542
  }
2543
+ case "q402_agentic_info": {
2544
+ AgenticInfoInputSchema.parse(args ?? {});
2545
+ return { content: [jsonText(await runAgenticInfo())] };
2546
+ }
2118
2547
  default:
2119
2548
  return {
2120
2549
  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.1",
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
+ }