@quackai/q402-mcp 0.5.16 → 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.
- package/README.md +4 -4
- package/dist/index.js +337 -15
- package/package.json +73 -73
package/README.md
CHANGED
|
@@ -91,8 +91,8 @@ Q402_ENABLE_REAL_PAYMENTS=1
|
|
|
91
91
|
# Default Q402 deployment. Only change for self-hosted.
|
|
92
92
|
Q402_RELAY_BASE_URL=https://q402.quackai.ai/api
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
|
|
94
|
+
# Safety guards (max-amount ships uncommented at $200; lower for tighter caps):
|
|
95
|
+
Q402_MAX_AMOUNT_PER_CALL=200
|
|
96
96
|
# Q402_ALLOWED_RECIPIENTS=0xabc...,0xdef...
|
|
97
97
|
```
|
|
98
98
|
|
|
@@ -202,7 +202,7 @@ Two additional guards run before every payment regardless of mode:
|
|
|
202
202
|
|
|
203
203
|
| Env var | Default | Effect |
|
|
204
204
|
|---|---|---|
|
|
205
|
-
| `Q402_MAX_AMOUNT_PER_CALL` | `
|
|
205
|
+
| `Q402_MAX_AMOUNT_PER_CALL` | `200` | Reject any single call where `amount > N` USD-equivalent. |
|
|
206
206
|
| `Q402_ALLOWED_RECIPIENTS` | (empty = off) | Comma-separated address allowlist. When set, all other recipients are rejected. |
|
|
207
207
|
|
|
208
208
|
Combined with the `confirm: true` argument the tool requires, this means the model needs (a) explicit user OK in chat, (b) amount ≤ cap, (c) recipient on allowlist if one exists, (d) all three live-mode env vars set, before a single wei moves.
|
|
@@ -217,7 +217,7 @@ Combined with the `confirm: true` argument the tool requires, this means the mod
|
|
|
217
217
|
| `Q402_MULTICHAIN_API_KEY` | live-pay (9-chain) | Paid 9-chain key. Get one at https://q402.quackai.ai/payment. Auto-routed for non-BNB chains AND for BNB when no Trial key is set. Cap: 20 recipients per batch. |
|
|
218
218
|
| `Q402_PRIVATE_KEY` | live-pay | Signer for the payer EOA. **Never share. Never paste in chat.** |
|
|
219
219
|
| `Q402_ENABLE_REAL_PAYMENTS` | live-pay | Set to `1` to opt in. Any other value (or unset) → sandbox. |
|
|
220
|
-
| `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `
|
|
220
|
+
| `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `200`. Lower for tighter agent blast-radius. |
|
|
221
221
|
| `Q402_ALLOWED_RECIPIENTS` | optional | Comma-separated lowercase addresses. Defaults to no allowlist. |
|
|
222
222
|
| `Q402_RELAY_BASE_URL` | optional | Defaults to `https://q402.quackai.ai/api`. Override for self-hosted Q402. |
|
|
223
223
|
|
package/dist/index.js
CHANGED
|
@@ -96,7 +96,7 @@ var Q402_ENV_FILE_KEYS_ALL = Object.freeze(
|
|
|
96
96
|
new Set(Object.keys(FILE_ENV))
|
|
97
97
|
);
|
|
98
98
|
var DEFAULT_RELAY_BASE = "https://q402.quackai.ai/api";
|
|
99
|
-
var DEFAULT_MAX_AMOUNT =
|
|
99
|
+
var DEFAULT_MAX_AMOUNT = 200;
|
|
100
100
|
function classifyApiKey(k) {
|
|
101
101
|
if (!k) return "missing";
|
|
102
102
|
if (k.startsWith("q402_live_")) return "live";
|
|
@@ -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
|
|
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
|
-
|
|
184
|
-
|
|
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.
|
|
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
|
-
|
|
858
|
-
|
|
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:
|
|
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,
|
|
@@ -1702,11 +1910,12 @@ Q402_RELAY_BASE_URL=https://q402.quackai.ai/api
|
|
|
1702
1910
|
|
|
1703
1911
|
|
|
1704
1912
|
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1705
|
-
#
|
|
1913
|
+
# Safety guards
|
|
1706
1914
|
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1707
|
-
# Max USD per single q402_pay call
|
|
1708
|
-
#
|
|
1709
|
-
|
|
1915
|
+
# Max USD per single q402_pay call. Any request above this is rejected
|
|
1916
|
+
# before signing. Lower this if you want a tighter agent blast-radius.
|
|
1917
|
+
Q402_MAX_AMOUNT_PER_CALL=200
|
|
1918
|
+
|
|
1710
1919
|
# Comma-separated lowercase recipient allowlist (unset = any address OK)
|
|
1711
1920
|
# Q402_ALLOWED_RECIPIENTS=0xabc...,0xdef...
|
|
1712
1921
|
`;
|
|
@@ -2055,6 +2264,114 @@ var DOCTOR_TOOL = {
|
|
|
2055
2264
|
}
|
|
2056
2265
|
};
|
|
2057
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
|
+
|
|
2058
2375
|
// src/index.ts
|
|
2059
2376
|
function jsonText(value) {
|
|
2060
2377
|
return { type: "text", text: JSON.stringify(value, null, 2) };
|
|
@@ -2075,6 +2392,7 @@ async function main() {
|
|
|
2075
2392
|
BATCH_PAY_TOOL,
|
|
2076
2393
|
RECEIPT_TOOL,
|
|
2077
2394
|
WALLET_STATUS_TOOL,
|
|
2395
|
+
AGENTIC_INFO_TOOL,
|
|
2078
2396
|
CLEAR_DELEGATION_TOOL
|
|
2079
2397
|
]
|
|
2080
2398
|
}));
|
|
@@ -2114,6 +2432,10 @@ async function main() {
|
|
|
2114
2432
|
const parsed = ClearDelegationInputSchema.parse(args ?? {});
|
|
2115
2433
|
return { content: [jsonText(await runClearDelegation(parsed))] };
|
|
2116
2434
|
}
|
|
2435
|
+
case "q402_agentic_info": {
|
|
2436
|
+
AgenticInfoInputSchema.parse(args ?? {});
|
|
2437
|
+
return { content: [jsonText(await runAgenticInfo())] };
|
|
2438
|
+
}
|
|
2117
2439
|
default:
|
|
2118
2440
|
return {
|
|
2119
2441
|
isError: true,
|
package/package.json
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "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
|
-
}
|
|
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
|
+
}
|