@quackai/q402-mcp 0.8.15 → 0.8.17
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/dist/index.js +145 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -209,9 +209,86 @@ function isLiveModeFor(resolved) {
|
|
|
209
209
|
}
|
|
210
210
|
var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
|
|
211
211
|
|
|
212
|
+
// package.json
|
|
213
|
+
var package_default = {
|
|
214
|
+
name: "@quackai/q402-mcp",
|
|
215
|
+
version: "0.8.17",
|
|
216
|
+
description: "MCP server for Q402 \u2014 gasless USDC/USDT/RLUSD payments on 10 EVM chains + Chainlink CCIP USDC bridge on the eth/avax/arbitrum triangle, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.",
|
|
217
|
+
mcpName: "io.github.bitgett/q402-mcp",
|
|
218
|
+
keywords: [
|
|
219
|
+
"mcp",
|
|
220
|
+
"model-context-protocol",
|
|
221
|
+
"claude",
|
|
222
|
+
"claude-desktop",
|
|
223
|
+
"claude-code",
|
|
224
|
+
"codex",
|
|
225
|
+
"openai-codex",
|
|
226
|
+
"cline",
|
|
227
|
+
"q402",
|
|
228
|
+
"x402",
|
|
229
|
+
"stablecoin",
|
|
230
|
+
"usdc",
|
|
231
|
+
"usdt",
|
|
232
|
+
"rlusd",
|
|
233
|
+
"ripple",
|
|
234
|
+
"gasless",
|
|
235
|
+
"eip-7702",
|
|
236
|
+
"payments",
|
|
237
|
+
"ai-agents"
|
|
238
|
+
],
|
|
239
|
+
type: "module",
|
|
240
|
+
main: "dist/index.js",
|
|
241
|
+
bin: {
|
|
242
|
+
"q402-mcp": "dist/index.js"
|
|
243
|
+
},
|
|
244
|
+
files: [
|
|
245
|
+
"dist",
|
|
246
|
+
"README.md",
|
|
247
|
+
"LICENSE"
|
|
248
|
+
],
|
|
249
|
+
engines: {
|
|
250
|
+
node: ">=18.18"
|
|
251
|
+
},
|
|
252
|
+
scripts: {
|
|
253
|
+
build: "tsup",
|
|
254
|
+
dev: "tsup --watch",
|
|
255
|
+
lint: "tsc --noEmit",
|
|
256
|
+
prepublishOnly: "npm run lint && npm run build",
|
|
257
|
+
start: "node dist/index.js"
|
|
258
|
+
},
|
|
259
|
+
dependencies: {
|
|
260
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
261
|
+
ethers: "^6.16.0",
|
|
262
|
+
zod: "^3.23.8"
|
|
263
|
+
},
|
|
264
|
+
devDependencies: {
|
|
265
|
+
"@types/node": "^20.11.0",
|
|
266
|
+
tsup: "^8.3.0",
|
|
267
|
+
typescript: "^5.5.0"
|
|
268
|
+
},
|
|
269
|
+
repository: {
|
|
270
|
+
type: "git",
|
|
271
|
+
url: "git+https://github.com/bitgett/q402-mcp.git"
|
|
272
|
+
},
|
|
273
|
+
homepage: "https://q402.quackai.ai/claude",
|
|
274
|
+
bugs: {
|
|
275
|
+
url: "https://github.com/bitgett/q402-mcp/issues"
|
|
276
|
+
},
|
|
277
|
+
license: "Apache-2.0",
|
|
278
|
+
author: "David Lee <davidlee@quackai.ai>",
|
|
279
|
+
publishConfig: {
|
|
280
|
+
access: "public"
|
|
281
|
+
},
|
|
282
|
+
overrides: {
|
|
283
|
+
ws: "^8.20.1",
|
|
284
|
+
qs: "^6.15.2",
|
|
285
|
+
hono: "^4.12.21"
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
212
289
|
// src/version.ts
|
|
213
|
-
var PACKAGE_NAME =
|
|
214
|
-
var PACKAGE_VERSION =
|
|
290
|
+
var PACKAGE_NAME = package_default.name;
|
|
291
|
+
var PACKAGE_VERSION = package_default.version;
|
|
215
292
|
|
|
216
293
|
// src/tools/quote.ts
|
|
217
294
|
import { z } from "zod";
|
|
@@ -1092,6 +1169,53 @@ async function runPay(input) {
|
|
|
1092
1169
|
}
|
|
1093
1170
|
const data = await resp.json().catch(() => ({}));
|
|
1094
1171
|
const txHash = data.txHash ?? "";
|
|
1172
|
+
const isSplit = data.split === true || Array.isArray(data.legs);
|
|
1173
|
+
if (isSplit) {
|
|
1174
|
+
const legs = Array.isArray(data.legs) ? data.legs : [];
|
|
1175
|
+
const status = data.status;
|
|
1176
|
+
const replayed = data.replayed === true;
|
|
1177
|
+
const settledCount = typeof data.settled === "number" ? data.settled : legs.filter((l) => typeof l.txHash === "string" && l.txHash.length > 0).length;
|
|
1178
|
+
const failedCount = typeof data.failed === "number" ? data.failed : legs.filter((l) => !l.txHash).length;
|
|
1179
|
+
const isComplete = status === "complete" && failedCount === 0;
|
|
1180
|
+
const isPartial = status === "partial" || resp.status === 207;
|
|
1181
|
+
const success2 = isComplete;
|
|
1182
|
+
const message2 = "message" in data ? data.message : "error" in data ? data.error : void 0;
|
|
1183
|
+
return {
|
|
1184
|
+
result: {
|
|
1185
|
+
success: success2,
|
|
1186
|
+
sandbox: false,
|
|
1187
|
+
// Top-level txHash mirrors the server's (first settled leg). Per-leg
|
|
1188
|
+
// hashes in `legs` remain authoritative.
|
|
1189
|
+
txHash,
|
|
1190
|
+
tokenAmount: input.amount,
|
|
1191
|
+
token: input.token,
|
|
1192
|
+
chain: chain.key,
|
|
1193
|
+
method: "eip7702",
|
|
1194
|
+
split: true,
|
|
1195
|
+
legs,
|
|
1196
|
+
settledLegs: settledCount,
|
|
1197
|
+
failedLegs: failedCount,
|
|
1198
|
+
...isPartial && !isComplete ? { partial: true } : {},
|
|
1199
|
+
...replayed ? { replayed: true } : {},
|
|
1200
|
+
explorerUrl: txHash ? void 0 : null
|
|
1201
|
+
},
|
|
1202
|
+
guardsApplied: [
|
|
1203
|
+
...guardsApplied,
|
|
1204
|
+
"wallet=agentic-server",
|
|
1205
|
+
"mode=live",
|
|
1206
|
+
"settlement=split",
|
|
1207
|
+
`split_settled=${settledCount}`,
|
|
1208
|
+
`split_failed=${failedCount}`,
|
|
1209
|
+
`split_status=${status ?? "unknown"}`,
|
|
1210
|
+
...replayed ? ["replayed=true"] : [],
|
|
1211
|
+
...message2 ? [`server_message=${message2}`] : []
|
|
1212
|
+
],
|
|
1213
|
+
senderWallet,
|
|
1214
|
+
...isPartial && !isComplete ? {
|
|
1215
|
+
setupHint: `Split PARTIALLY settled: ${settledCount} leg(s) landed on-chain, ${failedCount} did NOT. The settled legs already moved funds \u2014 do NOT blindly retry the whole payment (a retry replays only the unsettled intent, it will not double-pay the settled legs). Inspect legs[] for which recipients received funds and which still need handling.`
|
|
1216
|
+
} : {}
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1095
1219
|
const isPending = resp.status === 202 || data.pending === true || data.status === "processing";
|
|
1096
1220
|
if (isPending) {
|
|
1097
1221
|
const retryAfter = typeof data.retryAfterSec === "number" ? data.retryAfterSec : 5;
|
|
@@ -2950,7 +3074,10 @@ var BridgeSendInputSchema = z11.object({
|
|
|
2950
3074
|
walletId: z11.string().optional().describe("Agentic Wallet ID (from q402_agentic_info). Optional \u2014 defaults to the owner's default Agent Wallet."),
|
|
2951
3075
|
feeToken: z11.enum(["LINK", "native"]).optional().describe("Fee token. Defaults to LINK (~10% cheaper than native)."),
|
|
2952
3076
|
sandbox: z11.boolean().optional().describe("Sandbox mode (default true). Set to false for a live on-chain bridge."),
|
|
2953
|
-
maxFeeRaw: z11.string().regex(/^\d+$/).optional().describe("Optional client-side fee cap in raw 18-dec wei. Server still clamps to its 10% slippage ceiling; clients may LOWER but not RAISE.")
|
|
3077
|
+
maxFeeRaw: z11.string().regex(/^\d+$/).optional().describe("Optional client-side fee cap in raw 18-dec wei. Server still clamps to its 10% slippage ceiling; clients may LOWER but not RAISE."),
|
|
3078
|
+
confirm: z11.boolean().optional().describe(
|
|
3079
|
+
"MUST be true to fire a LIVE bridge (ignored in sandbox). Set this only after the user has explicitly approved this exact bridge (src, dst, amount, feeToken) in the conversation. When omitted or false on a live call the tool previews the action and does NOT move any funds. Never set confirm:true on the user's behalf without approval."
|
|
3080
|
+
)
|
|
2954
3081
|
}).refine((d) => d.src !== d.dst, {
|
|
2955
3082
|
// Local Zod rejection saves a network round-trip + a Q402 backend log
|
|
2956
3083
|
// entry. The bridge route also rejects same-chain bridges but the
|
|
@@ -2960,7 +3087,7 @@ var BridgeSendInputSchema = z11.object({
|
|
|
2960
3087
|
});
|
|
2961
3088
|
var BRIDGE_SEND_TOOL = {
|
|
2962
3089
|
name: "q402_bridge_send",
|
|
2963
|
-
description: "Execute a Chainlink CCIP USDC bridge across the 3-chain triangle (eth/avax/arbitrum) on behalf of the user's server-managed Agentic Wallet (Mode C). Sandbox-by-default \u2014 returns a synthetic messageId unless `sandbox: false` is passed AND Q402_ENABLE_REAL_PAYMENTS=1 AND a live Multichain API key is configured. The server signs ccipSend with the Agent Wallet's encrypted PK, auto-funds source-chain gas from the user's Gas Tank, and debits both the auto- fund cost and the CCIP fee per the bridge's settled receipt. Recommended flow: q402_bridge_quote first \u2192 confirm cost with the user \u2192 q402_bridge_send with sandbox: false. Live mode needs a Multichain subscription; trial keys are rejected. If the bridge returns AGENT_WALLET_DELEGATED, run q402_clear_delegation on the source chain first.",
|
|
3090
|
+
description: "Execute a Chainlink CCIP USDC bridge across the 3-chain triangle (eth/avax/arbitrum) on behalf of the user's server-managed Agentic Wallet (Mode C). Sandbox-by-default \u2014 returns a synthetic messageId unless `sandbox: false` is passed AND Q402_ENABLE_REAL_PAYMENTS=1 AND a live Multichain API key is configured. The server signs ccipSend with the Agent Wallet's encrypted PK, auto-funds source-chain gas from the user's Gas Tank, and debits both the auto- fund cost and the CCIP fee per the bridge's settled receipt. REQUIRES CONFIRMATION \u2014 like q402_pay and q402_yield_deposit, a LIVE bridge (sandbox: false) refuses to execute unless confirm: true is set. Call it first WITHOUT confirm to get a one-line preview (src, dst, amount, fee token); show that to the user, get explicit approval, THEN re-call with sandbox: false AND confirm: true. Never set confirm: true on the user's behalf. Recommended flow: q402_bridge_quote first \u2192 preview + confirm cost with the user \u2192 q402_bridge_send with sandbox: false, confirm: true. Live mode needs a Multichain subscription; trial keys are rejected. If the bridge returns AGENT_WALLET_DELEGATED, run q402_clear_delegation on the source chain first.",
|
|
2964
3091
|
inputSchema: {
|
|
2965
3092
|
type: "object",
|
|
2966
3093
|
properties: {
|
|
@@ -2996,6 +3123,10 @@ var BRIDGE_SEND_TOOL = {
|
|
|
2996
3123
|
type: "string",
|
|
2997
3124
|
pattern: "^[0-9]+$",
|
|
2998
3125
|
description: "Optional client-side fee cap in raw 18-dec wei."
|
|
3126
|
+
},
|
|
3127
|
+
confirm: {
|
|
3128
|
+
type: "boolean",
|
|
3129
|
+
description: "MUST be true to fire a LIVE bridge (ignored in sandbox) \u2014 set only after the user explicitly approved this exact bridge in chat. Omit (or false) on a live call to preview without moving funds."
|
|
2999
3130
|
}
|
|
3000
3131
|
},
|
|
3001
3132
|
required: ["src", "dst", "amount"]
|
|
@@ -3025,6 +3156,16 @@ async function runBridgeSend(input) {
|
|
|
3025
3156
|
}]
|
|
3026
3157
|
};
|
|
3027
3158
|
}
|
|
3159
|
+
if (input.confirm !== true) {
|
|
3160
|
+
const walletDesc = typeof input.walletId === "string" && input.walletId.length > 0 ? `wallet ${input.walletId.toLowerCase()}` : "your default Agent Wallet";
|
|
3161
|
+
const fee = input.feeToken === "native" ? "native" : "LINK";
|
|
3162
|
+
return {
|
|
3163
|
+
content: [{
|
|
3164
|
+
type: "text",
|
|
3165
|
+
text: `Will bridge ${input.amount} raw USDC units from ${input.src} \u2192 ${input.dst} via Chainlink CCIP from ${walletDesc} (fee paid in ${fee}). This MOVES FUNDS on-chain. Re-call with confirm:true to execute.`
|
|
3166
|
+
}]
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3028
3169
|
const resolved = resolveApiKey(input.src, "multichain");
|
|
3029
3170
|
if (!resolved.apiKey) {
|
|
3030
3171
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.17",
|
|
4
4
|
"description": "MCP server for Q402 — gasless USDC/USDT/RLUSD payments on 10 EVM chains + Chainlink CCIP USDC bridge on the eth/avax/arbitrum triangle, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.",
|
|
5
5
|
"mcpName": "io.github.bitgett/q402-mcp",
|
|
6
6
|
"keywords": [
|