@arispay/payagent-mcp 2.8.0 → 3.1.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/dist/index.js +481 -386
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
launchAgent,
|
|
16
16
|
listAgents,
|
|
17
17
|
payFetchDelegated,
|
|
18
|
+
payFetchLocal,
|
|
18
19
|
renameStoredAgent,
|
|
19
20
|
syncAgents
|
|
20
21
|
} from "payagent";
|
|
@@ -37,22 +38,8 @@ function platformToolsEnabled(raw = process.env.PAYAGENT_MCP_PROFILE) {
|
|
|
37
38
|
var arispayUrl = getArispayUrl(process.env.ARISPAY_URL);
|
|
38
39
|
var legacyAgentKey = process.env.ARISPAY_AGENT_KEY;
|
|
39
40
|
var legacyWalletAddress = process.env.PAYAGENT_WALLET;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const agents = listAgents();
|
|
43
|
-
return agents[0];
|
|
44
|
-
}
|
|
45
|
-
function resolveAgentKey(name) {
|
|
46
|
-
if (name) {
|
|
47
|
-
const stored = getAgent(name);
|
|
48
|
-
if (stored) return { key: stored.apiKey, source: `agent \`${name}\`` };
|
|
49
|
-
return void 0;
|
|
50
|
-
}
|
|
51
|
-
if (legacyAgentKey) return { key: legacyAgentKey, source: "ARISPAY_AGENT_KEY" };
|
|
52
|
-
const first = listAgents()[0];
|
|
53
|
-
if (first) return { key: first.apiKey, source: `agent \`${first.name}\`` };
|
|
54
|
-
return void 0;
|
|
55
|
-
}
|
|
41
|
+
var SELF_ENDUSER_EXTERNAL_ID = "self";
|
|
42
|
+
var buyformeUrl = process.env.BUYFORME_URL?.replace(/\/$/, "") ?? "https://buyforme.arispay.app";
|
|
56
43
|
function requireDevKey() {
|
|
57
44
|
const key = getApiKey();
|
|
58
45
|
if (!key) {
|
|
@@ -68,34 +55,357 @@ function textResult(text, isError = false) {
|
|
|
68
55
|
...isError ? { isError } : {}
|
|
69
56
|
};
|
|
70
57
|
}
|
|
58
|
+
async function resolveWallet(client, name) {
|
|
59
|
+
const list = await client.listWallets();
|
|
60
|
+
return list.wallets.find((w) => w.name === name);
|
|
61
|
+
}
|
|
62
|
+
async function resolveSelfEndUserId(client) {
|
|
63
|
+
try {
|
|
64
|
+
const user = await client.getEndUserByExternalId(SELF_ENDUSER_EXTERNAL_ID);
|
|
65
|
+
return user.id;
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
71
70
|
var server = new McpServer({
|
|
72
71
|
name: "payagent",
|
|
73
|
-
version: "
|
|
72
|
+
version: "3.0.0"
|
|
74
73
|
});
|
|
74
|
+
server.tool(
|
|
75
|
+
"create_user",
|
|
76
|
+
"Cold-start in one call: create an ArisPay user account plus the master funding account. Safe to re-run \u2014 it recovers the existing account instead of creating duplicates.",
|
|
77
|
+
{
|
|
78
|
+
email: z.string().describe("Email for the new ArisPay account."),
|
|
79
|
+
name: z.string().optional().describe("Human name. Falls back to the email local-part.")
|
|
80
|
+
},
|
|
81
|
+
async ({ email, name }) => {
|
|
82
|
+
try {
|
|
83
|
+
const result = await bootstrapAgent({
|
|
84
|
+
email,
|
|
85
|
+
name,
|
|
86
|
+
arispayUrl: process.env.ARISPAY_URL,
|
|
87
|
+
clientId: "payagent-mcp-bootstrap"
|
|
88
|
+
});
|
|
89
|
+
const devKey = getApiKey();
|
|
90
|
+
if (devKey) {
|
|
91
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
92
|
+
try {
|
|
93
|
+
await client.createEndUser({
|
|
94
|
+
externalId: SELF_ENDUSER_EXTERNAL_ID,
|
|
95
|
+
email,
|
|
96
|
+
findOrCreate: true
|
|
97
|
+
});
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const lines = [
|
|
102
|
+
`\u2713 Account: ${result.email} (org \`${result.orgName}\`)`,
|
|
103
|
+
`\u2713 Master funding account ready.`,
|
|
104
|
+
"",
|
|
105
|
+
"Next steps:",
|
|
106
|
+
" 1. create_wallet({ name: 'Daily', perTx, daily, monthly })",
|
|
107
|
+
" 2. fund_wallet({ wallet: 'Daily', rail: 'europ' }) for SEPA / EUR\xD8P",
|
|
108
|
+
" or fund_wallet({ wallet: 'Daily', rail: 'card' }) for card top-up."
|
|
109
|
+
];
|
|
110
|
+
return textResult(lines.join("\n"));
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err instanceof BootstrapError) {
|
|
113
|
+
return textResult(`create_user failed (${err.code}): ${err.message}`, true);
|
|
114
|
+
}
|
|
115
|
+
return textResult(
|
|
116
|
+
`create_user failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
117
|
+
true
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
server.tool(
|
|
123
|
+
"create_wallet",
|
|
124
|
+
"Create a sub-wallet / spending profile. Sub-wallets draw from the master funding account and enforce per-transaction, daily, and monthly spend limits.",
|
|
125
|
+
{
|
|
126
|
+
name: z.string().describe("Human name for this sub-wallet, e.g. 'Daily' or 'Travel'."),
|
|
127
|
+
perTx: z.number().int().positive().describe("Per-transaction spend cap in cents."),
|
|
128
|
+
daily: z.number().int().positive().describe("Daily spend cap in cents."),
|
|
129
|
+
monthly: z.number().int().positive().describe("Monthly spend cap in cents."),
|
|
130
|
+
allowedDomains: z.array(z.string()).optional().describe("If provided, restrict this sub-wallet to paying only these domains.")
|
|
131
|
+
},
|
|
132
|
+
async ({ name, perTx, daily, monthly, allowedDomains }) => {
|
|
133
|
+
try {
|
|
134
|
+
const devKey = requireDevKey();
|
|
135
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
136
|
+
const wallet = await client.createWallet({
|
|
137
|
+
name,
|
|
138
|
+
perTx,
|
|
139
|
+
daily,
|
|
140
|
+
monthly,
|
|
141
|
+
allowedDomains
|
|
142
|
+
});
|
|
143
|
+
const lines = [
|
|
144
|
+
`\u2713 Sub-wallet \`${wallet.name}\` created.`,
|
|
145
|
+
` id: ${wallet.id}`,
|
|
146
|
+
` limits: ${wallet.limits.perTx ?? "\u2014"}\xA2 / tx, ${wallet.limits.daily ?? "\u2014"}\xA2 / day, ${wallet.limits.monthly ?? "\u2014"}\xA2 / month`,
|
|
147
|
+
"",
|
|
148
|
+
`Fund it with fund_wallet({ wallet: "${wallet.name}", rail: "europ" | "card" }).`
|
|
149
|
+
];
|
|
150
|
+
return textResult(lines.join("\n"));
|
|
151
|
+
} catch (err) {
|
|
152
|
+
return textResult(
|
|
153
|
+
`create_wallet failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
154
|
+
true
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
server.tool(
|
|
160
|
+
"list_wallets",
|
|
161
|
+
"List all sub-wallets under this account.",
|
|
162
|
+
{},
|
|
163
|
+
async () => {
|
|
164
|
+
try {
|
|
165
|
+
const devKey = requireDevKey();
|
|
166
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
167
|
+
const list = await client.listWallets();
|
|
168
|
+
if (!list.wallets.length) {
|
|
169
|
+
return textResult(
|
|
170
|
+
"No sub-wallets yet. Create one with create_wallet({ name, perTx, daily, monthly })."
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const lines = list.wallets.map(
|
|
174
|
+
(w) => [
|
|
175
|
+
`\`${w.name}\` (${w.id})`,
|
|
176
|
+
` status: ${w.status}`,
|
|
177
|
+
` limits: ${w.limits.perTx ?? "\u2014"}\xA2 / tx, ${w.limits.daily ?? "\u2014"}\xA2 / day, ${w.limits.monthly ?? "\u2014"}\xA2 / month`,
|
|
178
|
+
` balance: ${w.balance} ${w.balanceCurrency}`
|
|
179
|
+
].join("\n")
|
|
180
|
+
);
|
|
181
|
+
return textResult(lines.join("\n\n"));
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return textResult(
|
|
184
|
+
`list_wallets failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
185
|
+
true
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
server.tool(
|
|
191
|
+
"fund_wallet",
|
|
192
|
+
"Add money to the master funding account so a sub-wallet can spend. rail='europ' opens the Schuman KYC / vIBAN flow in a browser; rail='card' mints a hosted card-setup URL.",
|
|
193
|
+
{
|
|
194
|
+
wallet: z.string().describe("Name of a sub-wallet (used only for handoff context)."),
|
|
195
|
+
rail: z.enum(["europ", "card"]).describe("Funding rail: 'europ' for SEPA/EUR\xD8P, 'card' for debit/credit card."),
|
|
196
|
+
amount: z.number().positive().optional().describe("Optional preset amount in dollars.")
|
|
197
|
+
},
|
|
198
|
+
async ({ wallet, rail, amount }) => {
|
|
199
|
+
try {
|
|
200
|
+
const devKey = requireDevKey();
|
|
201
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
202
|
+
const resolved = await resolveWallet(client, wallet);
|
|
203
|
+
if (!resolved) {
|
|
204
|
+
return textResult(`No sub-wallet named \`${wallet}\`. Run list_wallets().`, true);
|
|
205
|
+
}
|
|
206
|
+
if (rail === "europ") {
|
|
207
|
+
const handoffUrl = `${buyformeUrl}/onboarding/fund-europ?agentId=${encodeURIComponent(
|
|
208
|
+
resolved.id
|
|
209
|
+
)}`;
|
|
210
|
+
const lines2 = [
|
|
211
|
+
`Fund the master wallet for \`${wallet}\` with EUR\xD8P via Schuman:`,
|
|
212
|
+
"",
|
|
213
|
+
` ${handoffUrl}`,
|
|
214
|
+
"",
|
|
215
|
+
"Open that link in a browser to:",
|
|
216
|
+
" 1. Verify your identity with Schuman Financial (one-time KYC).",
|
|
217
|
+
" 2. Receive a dedicated SEPA vIBAN.",
|
|
218
|
+
" 3. Send EUR by bank transfer; Schuman mints EUR\xD8P once it clears.",
|
|
219
|
+
"",
|
|
220
|
+
"After the deposit clears, the master funding account is credited and this sub-wallet can spend."
|
|
221
|
+
];
|
|
222
|
+
return textResult(lines2.join("\n"));
|
|
223
|
+
}
|
|
224
|
+
const endUserId = await resolveSelfEndUserId(client);
|
|
225
|
+
if (!endUserId) {
|
|
226
|
+
return textResult(
|
|
227
|
+
"No payer record found. Run create_user first, then retry fund_wallet with rail='card'.",
|
|
228
|
+
true
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
const session = await client.createCardSetupSession({ endUserId });
|
|
232
|
+
const lines = [
|
|
233
|
+
`Add a card to fund the master wallet for \`${wallet}\`:`,
|
|
234
|
+
"",
|
|
235
|
+
` ${session.setupUrl}`,
|
|
236
|
+
"",
|
|
237
|
+
` Expires at: ${session.expiresAt}`,
|
|
238
|
+
"",
|
|
239
|
+
"After the card is verified, you can top up. Card top-ups are not yet exposed via MCP; use the BuyForMe web app or the ArisPay dashboard for now."
|
|
240
|
+
];
|
|
241
|
+
return textResult(lines.join("\n"));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
return textResult(
|
|
244
|
+
`fund_wallet failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
245
|
+
true
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
server.tool(
|
|
251
|
+
"get_balance",
|
|
252
|
+
"Show the master funding-account balance(s) and a sub-wallet's spend limits. If no wallet is named, lists all sub-wallets.",
|
|
253
|
+
{
|
|
254
|
+
wallet: z.string().optional().describe("Optional sub-wallet name. Omit to list all sub-wallets.")
|
|
255
|
+
},
|
|
256
|
+
async ({ wallet }) => {
|
|
257
|
+
try {
|
|
258
|
+
const devKey = requireDevKey();
|
|
259
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
260
|
+
if (!wallet) {
|
|
261
|
+
const list = await client.listWallets();
|
|
262
|
+
if (!list.wallets.length) {
|
|
263
|
+
return textResult("No sub-wallets yet. Create one with create_wallet().");
|
|
264
|
+
}
|
|
265
|
+
const lines2 = list.wallets.map((w) => {
|
|
266
|
+
const lim = `limits: ${w.limits.perTx ?? "\u2014"}\xA2 / tx, ${w.limits.daily ?? "\u2014"}\xA2 / day, ${w.limits.monthly ?? "\u2014"}\xA2 / month`;
|
|
267
|
+
return `\`${w.name}\` \u2014 ${w.balance} ${w.balanceCurrency} \u2014 ${lim}`;
|
|
268
|
+
});
|
|
269
|
+
return textResult(lines2.join("\n"));
|
|
270
|
+
}
|
|
271
|
+
const resolved = await resolveWallet(client, wallet);
|
|
272
|
+
if (!resolved) {
|
|
273
|
+
return textResult(`No sub-wallet named \`${wallet}\`.`, true);
|
|
274
|
+
}
|
|
275
|
+
const bal = await client.getWalletBalance(resolved.id);
|
|
276
|
+
const lines = [
|
|
277
|
+
`Sub-wallet: \`${bal.wallet.name}\``,
|
|
278
|
+
` status: ${bal.wallet.status}`,
|
|
279
|
+
` limits: ${bal.wallet.limits.perTx ?? "\u2014"}\xA2 / tx, ${bal.wallet.limits.daily ?? "\u2014"}\xA2 / day, ${bal.wallet.limits.monthly ?? "\u2014"}\xA2 / month`,
|
|
280
|
+
"",
|
|
281
|
+
"Master funding accounts:"
|
|
282
|
+
];
|
|
283
|
+
if (!bal.fundingAccounts.length) {
|
|
284
|
+
lines.push(" No funded accounts yet. Use fund_wallet() to add money.");
|
|
285
|
+
} else {
|
|
286
|
+
for (const acct of bal.fundingAccounts) {
|
|
287
|
+
lines.push(` ${acct.currency}: ${acct.balance}\xA2 (${acct.provider ?? "internal"})`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return textResult(lines.join("\n"));
|
|
291
|
+
} catch (err) {
|
|
292
|
+
return textResult(
|
|
293
|
+
`get_balance failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
294
|
+
true
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
server.tool(
|
|
300
|
+
"pay_merchant",
|
|
301
|
+
"Pay a merchant from the master wallet through a sub-wallet. For balance-rail payments (from FundingAccount), merchantId is required. For card-rail payments, the user must have a verified card on file.",
|
|
302
|
+
{
|
|
303
|
+
wallet: z.string().describe("Name of the sub-wallet to spend through."),
|
|
304
|
+
merchantUrl: z.string().describe("Canonical merchant URL for this payment."),
|
|
305
|
+
amount: z.number().int().positive().describe("Amount in cents."),
|
|
306
|
+
memo: z.string().describe("Human-readable memo, e.g. 'T-shirt from getwatta.com'."),
|
|
307
|
+
merchantId: z.string().optional().describe("Required for balance-rail payments to a registered merchant."),
|
|
308
|
+
currency: z.string().optional().describe("Currency code. Default: USD."),
|
|
309
|
+
rail: z.enum(["balance", "card"]).optional().describe("Force balance or card rail. Default: server picks based on available funding.")
|
|
310
|
+
},
|
|
311
|
+
async ({ wallet, merchantUrl, amount, memo, merchantId, currency, rail }) => {
|
|
312
|
+
try {
|
|
313
|
+
const devKey = requireDevKey();
|
|
314
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
315
|
+
const resolved = await resolveWallet(client, wallet);
|
|
316
|
+
if (!resolved) {
|
|
317
|
+
return textResult(`No sub-wallet named \`${wallet}\`.`, true);
|
|
318
|
+
}
|
|
319
|
+
const endUserId = await resolveSelfEndUserId(client);
|
|
320
|
+
if (!endUserId) {
|
|
321
|
+
return textResult(
|
|
322
|
+
"No payer record found. Run create_user first, then retry the payment.",
|
|
323
|
+
true
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
const payment = await client.createPayment(resolved.id, {
|
|
327
|
+
userId: endUserId,
|
|
328
|
+
amount,
|
|
329
|
+
currency: currency ?? "USD",
|
|
330
|
+
memo,
|
|
331
|
+
merchantUrl,
|
|
332
|
+
merchantId,
|
|
333
|
+
rail
|
|
334
|
+
});
|
|
335
|
+
const lines = [
|
|
336
|
+
`Payment ${payment.id}: ${payment.status}`,
|
|
337
|
+
` rail: ${payment.rail}`,
|
|
338
|
+
` amount: ${payment.amount}\xA2 ${payment.currency}`
|
|
339
|
+
];
|
|
340
|
+
if (payment.merchantName) lines.push(` merchant: ${payment.merchantName}`);
|
|
341
|
+
if (payment.txHash) lines.push(` tx hash: ${payment.txHash}`);
|
|
342
|
+
if (payment.spend) {
|
|
343
|
+
const fmtCap = (v) => v == null ? "no cap" : `${v}\xA2`;
|
|
344
|
+
lines.push(
|
|
345
|
+
` budget: ${fmtCap(payment.spend.remainingDaily)} left today, ${fmtCap(payment.spend.remainingMonthly)} this month`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (payment.nextAction?.challengeUrl) {
|
|
349
|
+
lines.push(` 3DS challenge URL: ${payment.nextAction.challengeUrl}`);
|
|
350
|
+
}
|
|
351
|
+
if (payment.error) {
|
|
352
|
+
lines.push(` error: ${payment.error.code} \u2014 ${payment.error.message}`);
|
|
353
|
+
}
|
|
354
|
+
return textResult(lines.join("\n"), payment.status === "failed");
|
|
355
|
+
} catch (err) {
|
|
356
|
+
return textResult(
|
|
357
|
+
`pay_merchant failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
358
|
+
true
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
);
|
|
75
363
|
server.tool(
|
|
76
364
|
"pay_api",
|
|
77
|
-
"
|
|
365
|
+
"Advanced: make an HTTP request to an x402-protected API, paying with USDC on Base via delegated signing. This is a separate rail from the wallet-centric balance/card flows.",
|
|
78
366
|
{
|
|
79
|
-
url: z.string().describe("The full URL of the API endpoint to call"),
|
|
80
|
-
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).default("GET")
|
|
81
|
-
headers: z.record(z.string(), z.string()).optional()
|
|
82
|
-
body: z.string().optional()
|
|
83
|
-
agent: z.string().optional().describe(
|
|
84
|
-
"Optional: name of a locally-stored agent to pay with. Defaults to ARISPAY_AGENT_KEY or the first stored agent."
|
|
85
|
-
)
|
|
367
|
+
url: z.string().describe("The full URL of the API endpoint to call."),
|
|
368
|
+
method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).default("GET"),
|
|
369
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
370
|
+
body: z.string().optional(),
|
|
371
|
+
agent: z.string().optional().describe("Optional name of a stored x402 payer agent.")
|
|
86
372
|
},
|
|
87
373
|
async ({ url, method, headers, body, agent }) => {
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
374
|
+
const localKey = process.env.PAYAGENT_PRIVATE_KEY;
|
|
375
|
+
if (localKey) {
|
|
376
|
+
try {
|
|
377
|
+
const fetch402 = payFetchLocal({ privateKey: localKey });
|
|
378
|
+
const response = await fetch402(url, { method, headers, body });
|
|
379
|
+
const responseBody = await response.text();
|
|
380
|
+
return textResult(
|
|
381
|
+
[
|
|
382
|
+
`HTTP ${response.status} (paid locally \u2014 permissionless mode, no server-enforced limits)`,
|
|
383
|
+
"",
|
|
384
|
+
responseBody
|
|
385
|
+
].join("\n")
|
|
386
|
+
);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
389
|
+
if (looksLikeInsufficientFunds(message)) {
|
|
390
|
+
return textResult(
|
|
391
|
+
[
|
|
392
|
+
`Error: ${message}`,
|
|
393
|
+
"",
|
|
394
|
+
"The local wallet is out of USDC. Fund it on Base, or unset PAYAGENT_PRIVATE_KEY to use delegated custody."
|
|
395
|
+
].join("\n"),
|
|
396
|
+
true
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return textResult(`Error: ${message}`, true);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const stored = agent ? getAgent(agent) : listAgents()[0];
|
|
403
|
+
if (!stored?.apiKey) {
|
|
90
404
|
return textResult(
|
|
91
405
|
[
|
|
92
|
-
"No agent available.
|
|
93
|
-
|
|
94
|
-
""
|
|
95
|
-
"Or, if a developer key is already paired (`npx payagent init`):",
|
|
96
|
-
" create_agent({ name, perTx, daily, monthly })",
|
|
97
|
-
"",
|
|
98
|
-
"Or set ARISPAY_AGENT_KEY for legacy single-agent mode."
|
|
406
|
+
"No x402 payer agent available. For wallet-centric payments use pay_merchant.",
|
|
407
|
+
"To create an x402 agent, use create_agent({ name, perTx, daily, monthly }).",
|
|
408
|
+
"Or set PAYAGENT_PRIVATE_KEY to pay with a local key (permissionless mode \u2014 no ArisPay account)."
|
|
99
409
|
].join("\n"),
|
|
100
410
|
true
|
|
101
411
|
);
|
|
@@ -104,14 +414,14 @@ server.tool(
|
|
|
104
414
|
let paymentInfo;
|
|
105
415
|
const fetch402 = payFetchDelegated({
|
|
106
416
|
arispayUrl,
|
|
107
|
-
apiKey:
|
|
417
|
+
apiKey: stored.apiKey,
|
|
108
418
|
onPayment: (info) => {
|
|
109
419
|
paymentInfo = info;
|
|
110
420
|
}
|
|
111
421
|
});
|
|
112
422
|
const response = await fetch402(url, { method, headers, body });
|
|
113
423
|
const responseBody = await response.text();
|
|
114
|
-
const statusLine = paymentInfo ? `HTTP ${response.status} (paid ${paymentInfo.amountCents}\xA2 via
|
|
424
|
+
const statusLine = paymentInfo ? `HTTP ${response.status} (paid ${paymentInfo.amountCents}\xA2 via \`${stored.name}\` \u2014 budget left: ${paymentInfo.remainingDaily}\xA2 today, ${paymentInfo.remainingMonthly}\xA2 this month)` : `HTTP ${response.status} (paid via \`${stored.name}\`)`;
|
|
115
425
|
return textResult([statusLine, "", responseBody].join("\n"));
|
|
116
426
|
} catch (err) {
|
|
117
427
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -120,9 +430,7 @@ server.tool(
|
|
|
120
430
|
[
|
|
121
431
|
`Error: ${message}`,
|
|
122
432
|
"",
|
|
123
|
-
"
|
|
124
|
-
" Use the `fund_agent` tool (rail='usdc' for the wallet address,",
|
|
125
|
-
" or rail='europ' to fund via SEPA/EUR\xD8P in the browser)."
|
|
433
|
+
"The x402 agent wallet is out of USDC. Fund it with fund_agent({ name }) or switch to wallet-centric pay_merchant."
|
|
126
434
|
].join("\n"),
|
|
127
435
|
true
|
|
128
436
|
);
|
|
@@ -131,49 +439,17 @@ server.tool(
|
|
|
131
439
|
}
|
|
132
440
|
}
|
|
133
441
|
);
|
|
134
|
-
server.tool(
|
|
135
|
-
"check_wallet",
|
|
136
|
-
`Show the current agent's credentials, wallet address, and on-chain USDC balance \u2014 the quick "what state am I in" probe.`,
|
|
137
|
-
{
|
|
138
|
-
agent: z.string().optional().describe(
|
|
139
|
-
"Optional: name of a locally-stored agent. Defaults to legacy ARISPAY_WALLET mode."
|
|
140
|
-
)
|
|
141
|
-
},
|
|
142
|
-
async ({ agent }) => {
|
|
143
|
-
const resolved = resolveAgent(agent);
|
|
144
|
-
const walletAddress = resolved?.walletAddress ?? legacyWalletAddress;
|
|
145
|
-
const keyTail = resolved?.apiKey.slice(-4) ?? (legacyAgentKey ? legacyAgentKey.slice(-4) : void 0);
|
|
146
|
-
const lines = [`ArisPay URL: ${arispayUrl}`];
|
|
147
|
-
if (resolved) lines.push(`Agent: ${resolved.name} (${resolved.agentId})`);
|
|
148
|
-
if (keyTail) lines.push(`Agent key: ap_\u2026${keyTail}`);
|
|
149
|
-
if (walletAddress) {
|
|
150
|
-
lines.push(`Wallet: ${walletAddress}`);
|
|
151
|
-
try {
|
|
152
|
-
const raw = await getUSDCBalance(walletAddress, resolved?.network ?? "base");
|
|
153
|
-
lines.push(`Balance: ${formatUSDC(raw)} USDC on ${resolved?.network ?? "base"}`);
|
|
154
|
-
} catch (err) {
|
|
155
|
-
lines.push(
|
|
156
|
-
`Balance: unavailable (${err instanceof Error ? err.message : String(err)})`
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
lines.push("", "No wallet known. Use `create_agent` or set PAYAGENT_WALLET for legacy mode.");
|
|
161
|
-
}
|
|
162
|
-
lines.push("", "Manage limits + full history: https://arispay.app/dashboard");
|
|
163
|
-
return textResult(lines.join("\n"));
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
442
|
server.tool(
|
|
167
443
|
"create_agent",
|
|
168
|
-
"
|
|
444
|
+
"Advanced: provision an x402 payer agent with its own Base USDC wallet. Use create_wallet instead unless you specifically need to pay x402-protected HTTP resources.",
|
|
169
445
|
{
|
|
170
|
-
name: z.string().describe("Local identifier for this agent.
|
|
446
|
+
name: z.string().describe("Local identifier for this x402 agent."),
|
|
171
447
|
perTx: z.number().int().positive().describe("Per-transaction spend cap in cents."),
|
|
172
448
|
daily: z.number().int().positive().describe("Daily spend cap in cents."),
|
|
173
449
|
monthly: z.number().int().positive().describe("Monthly spend cap in cents."),
|
|
174
|
-
allowedDomains: z.array(z.string()).optional()
|
|
175
|
-
network: z.enum(["base", "base-sepolia", "ethereum", "polygon"]).default("base")
|
|
176
|
-
agentType: z.string().optional()
|
|
450
|
+
allowedDomains: z.array(z.string()).optional(),
|
|
451
|
+
network: z.enum(["base", "base-sepolia", "ethereum", "polygon"]).default("base"),
|
|
452
|
+
agentType: z.string().optional()
|
|
177
453
|
},
|
|
178
454
|
async ({ name, perTx, daily, monthly, allowedDomains, network, agentType }) => {
|
|
179
455
|
try {
|
|
@@ -186,13 +462,13 @@ server.tool(
|
|
|
186
462
|
agentType
|
|
187
463
|
});
|
|
188
464
|
const lines = [
|
|
189
|
-
`\u2713
|
|
465
|
+
`\u2713 x402 agent \`${name}\` created.`,
|
|
190
466
|
` Agent ID: ${agent.agentId}`,
|
|
191
467
|
` Wallet: ${agent.walletAddress}`,
|
|
192
468
|
` Network: ${agent.network}`,
|
|
193
469
|
` Limits: ${perTx}\xA2 / tx, ${daily}\xA2 / day, ${monthly}\xA2 / month`,
|
|
194
470
|
"",
|
|
195
|
-
` Fund the wallet with USDC on ${agent.network}, then call
|
|
471
|
+
` Fund the wallet with USDC on ${agent.network}, then call get_balance_agent with name="${name}".`
|
|
196
472
|
];
|
|
197
473
|
return textResult(lines.join("\n"));
|
|
198
474
|
} catch (err) {
|
|
@@ -203,57 +479,35 @@ server.tool(
|
|
|
203
479
|
}
|
|
204
480
|
}
|
|
205
481
|
);
|
|
206
|
-
var buyformeUrl = process.env.BUYFORME_URL?.replace(/\/$/, "") ?? "https://buyforme.arispay.app";
|
|
207
482
|
server.tool(
|
|
208
483
|
"fund_agent",
|
|
209
|
-
"
|
|
484
|
+
"Advanced: get the USDC deposit address for an x402 payer agent.",
|
|
210
485
|
{
|
|
211
|
-
name: z.string().describe("Name of a locally-stored agent.")
|
|
212
|
-
rail: z.enum(["usdc", "europ"]).default("usdc").describe(
|
|
213
|
-
"Funding rail: 'usdc' sends stablecoin directly to the agent wallet; 'europ' opens the BuyForMe EUR\xD8P onboarding flow in a browser to verify identity and get a vIBAN."
|
|
214
|
-
)
|
|
486
|
+
name: z.string().describe("Name of a locally-stored x402 agent.")
|
|
215
487
|
},
|
|
216
|
-
async ({ name
|
|
488
|
+
async ({ name }) => {
|
|
217
489
|
const stored = getAgent(name);
|
|
218
|
-
if (!stored) return textResult(`No
|
|
219
|
-
if (rail === "europ") {
|
|
220
|
-
const handoffUrl = `${buyformeUrl}/onboarding/fund-europ?agentId=${encodeURIComponent(
|
|
221
|
-
stored.agentId
|
|
222
|
-
)}`;
|
|
223
|
-
const lines2 = [
|
|
224
|
-
`Fund \`${name}\` with EUR\xD8P via Schuman:`,
|
|
225
|
-
"",
|
|
226
|
-
` ${handoffUrl}`,
|
|
227
|
-
"",
|
|
228
|
-
"Open that link in a browser to:",
|
|
229
|
-
" 1. Verify your identity with Schuman Financial (one-time KYC).",
|
|
230
|
-
" 2. Receive a dedicated SEPA vIBAN.",
|
|
231
|
-
" 3. Send EUR by bank transfer; Schuman mints EUR\xD8P to your wallet once it clears.",
|
|
232
|
-
"",
|
|
233
|
-
"After the deposit clears, call `get_balance` to confirm the agent's USDC balance."
|
|
234
|
-
];
|
|
235
|
-
return textResult(lines2.join("\n"));
|
|
236
|
-
}
|
|
490
|
+
if (!stored) return textResult(`No x402 agent named \`${name}\`.`, true);
|
|
237
491
|
const lines = [
|
|
238
|
-
`Fund \`${name}\` by sending USDC on ${stored.network ?? "base"} to:`,
|
|
492
|
+
`Fund x402 agent \`${name}\` by sending USDC on ${stored.network ?? "base"} to:`,
|
|
239
493
|
"",
|
|
240
494
|
` ${stored.walletAddress}`,
|
|
241
495
|
"",
|
|
242
496
|
"Only send USDC on the network shown above. Other tokens or networks may be lost.",
|
|
243
|
-
"Call
|
|
497
|
+
"Call get_balance_agent when the deposit lands."
|
|
244
498
|
];
|
|
245
499
|
return textResult(lines.join("\n"));
|
|
246
500
|
}
|
|
247
501
|
);
|
|
248
502
|
server.tool(
|
|
249
|
-
"
|
|
250
|
-
"
|
|
503
|
+
"get_balance_agent",
|
|
504
|
+
"Advanced: on-chain USDC balance for an x402 payer agent.",
|
|
251
505
|
{
|
|
252
|
-
name: z.string().describe("Name of a locally-stored agent.")
|
|
506
|
+
name: z.string().describe("Name of a locally-stored x402 agent.")
|
|
253
507
|
},
|
|
254
508
|
async ({ name }) => {
|
|
255
509
|
const stored = getAgent(name);
|
|
256
|
-
if (!stored) return textResult(`No
|
|
510
|
+
if (!stored) return textResult(`No x402 agent named \`${name}\`.`, true);
|
|
257
511
|
try {
|
|
258
512
|
const devKey = requireDevKey();
|
|
259
513
|
const client = new DelegationClient(arispayUrl, devKey);
|
|
@@ -267,7 +521,72 @@ server.tool(
|
|
|
267
521
|
return textResult(lines.join("\n"));
|
|
268
522
|
} catch (err) {
|
|
269
523
|
return textResult(
|
|
270
|
-
`
|
|
524
|
+
`get_balance_agent failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
525
|
+
true
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
server.tool(
|
|
531
|
+
"list_agents",
|
|
532
|
+
"Advanced: list x402 payer agents with on-chain wallets.",
|
|
533
|
+
{
|
|
534
|
+
withBalance: z.boolean().optional().describe("Fetch on-chain USDC balance per agent.")
|
|
535
|
+
},
|
|
536
|
+
async ({ withBalance }) => {
|
|
537
|
+
const devKey = getApiKey();
|
|
538
|
+
if (!devKey) {
|
|
539
|
+
return textResult("No developer key found. Run create_user or npx payagent init.");
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const result = await syncAgents({ includeBalance: withBalance === true });
|
|
543
|
+
if (!result.agents.length) {
|
|
544
|
+
return textResult("No x402 agents found. Use create_agent to provision one.");
|
|
545
|
+
}
|
|
546
|
+
const lines = result.agents.map((a) => {
|
|
547
|
+
const balanceLine = a.usdcBalance !== void 0 ? ` balance: ${formatUSDC(BigInt(a.usdcBalance || "0"))} USDC` : null;
|
|
548
|
+
return [
|
|
549
|
+
a.name,
|
|
550
|
+
` agent id: ${a.agentId}`,
|
|
551
|
+
` wallet: ${a.walletAddress}`,
|
|
552
|
+
` network: ${a.network}`,
|
|
553
|
+
` limits: ${a.limits.maxPerTx}\xA2 / tx, ${a.limits.maxDaily}\xA2 / day, ${a.limits.maxMonthly}\xA2 / month`,
|
|
554
|
+
...balanceLine ? [balanceLine] : []
|
|
555
|
+
].join("\n");
|
|
556
|
+
});
|
|
557
|
+
return textResult(lines.join("\n\n"));
|
|
558
|
+
} catch (err) {
|
|
559
|
+
return textResult(
|
|
560
|
+
`list_agents failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
561
|
+
true
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
server.tool(
|
|
567
|
+
"rename_agent",
|
|
568
|
+
"Advanced: rename an x402 payer agent.",
|
|
569
|
+
{
|
|
570
|
+
name: z.string().describe("Current local name of the x402 agent."),
|
|
571
|
+
newName: z.string().describe("New name.")
|
|
572
|
+
},
|
|
573
|
+
async ({ name, newName }) => {
|
|
574
|
+
if (name === newName) {
|
|
575
|
+
return textResult(`Agent \`${name}\` already has that name.`);
|
|
576
|
+
}
|
|
577
|
+
const stored = getAgent(name);
|
|
578
|
+
if (!stored) {
|
|
579
|
+
return textResult(`No x402 agent named \`${name}\`.`, true);
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const devKey = requireDevKey();
|
|
583
|
+
const client = new DelegationClient(arispayUrl, devKey);
|
|
584
|
+
const result = await client.renameAgent(stored.agentId, newName);
|
|
585
|
+
renameStoredAgent(name, result.name);
|
|
586
|
+
return textResult(`\u2713 Renamed \`${name}\` \u2192 \`${result.name}\`.`);
|
|
587
|
+
} catch (err) {
|
|
588
|
+
return textResult(
|
|
589
|
+
`rename_agent failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
271
590
|
true
|
|
272
591
|
);
|
|
273
592
|
}
|
|
@@ -276,24 +595,22 @@ server.tool(
|
|
|
276
595
|
if (platformToolsEnabled()) {
|
|
277
596
|
server.tool(
|
|
278
597
|
"create_enduser",
|
|
279
|
-
"Platform mode: register an end-customer
|
|
598
|
+
"Platform mode: register an end-customer under your account.",
|
|
280
599
|
{
|
|
281
|
-
externalId: z.string().describe("Your own stable id for this customer
|
|
282
|
-
email: z.string().optional()
|
|
283
|
-
findOrCreate: z.boolean().optional()
|
|
600
|
+
externalId: z.string().describe("Your own stable id for this customer."),
|
|
601
|
+
email: z.string().optional(),
|
|
602
|
+
findOrCreate: z.boolean().optional()
|
|
284
603
|
},
|
|
285
604
|
async ({ externalId, email, findOrCreate }) => {
|
|
286
605
|
try {
|
|
287
606
|
const devKey = requireDevKey();
|
|
288
607
|
const client = new DelegationClient(arispayUrl, devKey);
|
|
289
608
|
const user = await client.createEndUser({ externalId, email, findOrCreate });
|
|
290
|
-
|
|
291
|
-
`\u2713 End-user \`${externalId}\` ready
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
];
|
|
296
|
-
return textResult(lines.join("\n"));
|
|
609
|
+
return textResult(
|
|
610
|
+
`\u2713 End-user \`${externalId}\` ready.
|
|
611
|
+
ArisPay id: ${user.id}
|
|
612
|
+
Has card: ${user.hasPaymentMethod ? "yes" : "no"}`
|
|
613
|
+
);
|
|
297
614
|
} catch (err) {
|
|
298
615
|
return textResult(
|
|
299
616
|
`create_enduser failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -304,10 +621,10 @@ if (platformToolsEnabled()) {
|
|
|
304
621
|
);
|
|
305
622
|
server.tool(
|
|
306
623
|
"attach_card_for_user",
|
|
307
|
-
"Platform mode: mint a hosted card-entry URL for an end-user
|
|
624
|
+
"Platform mode: mint a hosted card-entry URL for an end-user.",
|
|
308
625
|
{
|
|
309
|
-
userId: z.string().describe("
|
|
310
|
-
agentName: z.string().optional().describe("Optional
|
|
626
|
+
userId: z.string().describe("ArisPay-internal end-user id."),
|
|
627
|
+
agentName: z.string().optional().describe("Optional x402 agent name to scope the session to.")
|
|
311
628
|
},
|
|
312
629
|
async ({ userId, agentName }) => {
|
|
313
630
|
try {
|
|
@@ -315,17 +632,11 @@ if (platformToolsEnabled()) {
|
|
|
315
632
|
const client = new DelegationClient(arispayUrl, devKey);
|
|
316
633
|
const agentId = agentName ? getAgent(agentName)?.agentId : void 0;
|
|
317
634
|
const session = await client.createCardSetupSession({ endUserId: userId, agentId });
|
|
318
|
-
|
|
319
|
-
`Card-entry URL
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
` Expires at: ${session.expiresAt}`,
|
|
324
|
-
"",
|
|
325
|
-
"Share this URL with the end-user. They enter the card on ArisPay's hosted page; we handle tokenization + 3DS.",
|
|
326
|
-
"Call `get_user_status` afterwards to confirm the card is attached."
|
|
327
|
-
];
|
|
328
|
-
return textResult(lines.join("\n"));
|
|
635
|
+
return textResult(
|
|
636
|
+
[`Card-entry URL:`, ` ${session.setupUrl}`, ` Expires at: ${session.expiresAt}`].join(
|
|
637
|
+
"\n"
|
|
638
|
+
)
|
|
639
|
+
);
|
|
329
640
|
} catch (err) {
|
|
330
641
|
return textResult(
|
|
331
642
|
`attach_card_for_user failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -336,22 +647,20 @@ if (platformToolsEnabled()) {
|
|
|
336
647
|
);
|
|
337
648
|
server.tool(
|
|
338
649
|
"set_user_limits",
|
|
339
|
-
"Platform mode: set per-(user, agent) spend caps
|
|
650
|
+
"Platform mode: set per-(user, agent) spend caps.",
|
|
340
651
|
{
|
|
341
|
-
userId: z.string()
|
|
342
|
-
agentName: z.string()
|
|
343
|
-
perTx: z.number().int().optional()
|
|
344
|
-
daily: z.number().int().optional()
|
|
345
|
-
monthly: z.number().int().optional()
|
|
346
|
-
allowedMcc: z.array(z.string()).optional()
|
|
347
|
-
blockedMcc: z.array(z.string()).optional()
|
|
652
|
+
userId: z.string(),
|
|
653
|
+
agentName: z.string(),
|
|
654
|
+
perTx: z.number().int().optional(),
|
|
655
|
+
daily: z.number().int().optional(),
|
|
656
|
+
monthly: z.number().int().optional(),
|
|
657
|
+
allowedMcc: z.array(z.string()).optional(),
|
|
658
|
+
blockedMcc: z.array(z.string()).optional()
|
|
348
659
|
},
|
|
349
660
|
async ({ userId, agentName, perTx, daily, monthly, allowedMcc, blockedMcc }) => {
|
|
350
661
|
try {
|
|
351
662
|
const stored = getAgent(agentName);
|
|
352
|
-
if (!stored) {
|
|
353
|
-
return textResult(`No locally-stored agent named \`${agentName}\`.`, true);
|
|
354
|
-
}
|
|
663
|
+
if (!stored) return textResult(`No x402 agent named \`${agentName}\`.`, true);
|
|
355
664
|
const devKey = requireDevKey();
|
|
356
665
|
const client = new DelegationClient(arispayUrl, devKey);
|
|
357
666
|
const limit = await client.setUserLimits(userId, {
|
|
@@ -363,10 +672,7 @@ if (platformToolsEnabled()) {
|
|
|
363
672
|
blockedMerchantCategories: blockedMcc
|
|
364
673
|
});
|
|
365
674
|
return textResult(
|
|
366
|
-
|
|
367
|
-
`\u2713 Limits set for \`${userId}\` on agent \`${agentName}\`.`,
|
|
368
|
-
` per-tx ${limit.maxPerTransaction ?? "inherit"}\xA2, daily ${limit.maxDaily ?? "inherit"}\xA2, monthly ${limit.maxMonthly ?? "inherit"}\xA2`
|
|
369
|
-
].join("\n")
|
|
675
|
+
`\u2713 Limits set. per-tx ${limit.maxPerTransaction ?? "inherit"}\xA2, daily ${limit.maxDaily ?? "inherit"}\xA2, monthly ${limit.maxMonthly ?? "inherit"}\xA2`
|
|
370
676
|
);
|
|
371
677
|
} catch (err) {
|
|
372
678
|
return textResult(
|
|
@@ -378,9 +684,9 @@ if (platformToolsEnabled()) {
|
|
|
378
684
|
);
|
|
379
685
|
server.tool(
|
|
380
686
|
"get_user_status",
|
|
381
|
-
"Platform mode: report an end-user's card
|
|
687
|
+
"Platform mode: report an end-user's card and wallet readiness.",
|
|
382
688
|
{
|
|
383
|
-
userId: z.string()
|
|
689
|
+
userId: z.string()
|
|
384
690
|
},
|
|
385
691
|
async ({ userId }) => {
|
|
386
692
|
try {
|
|
@@ -389,21 +695,9 @@ if (platformToolsEnabled()) {
|
|
|
389
695
|
const user = await client.getEndUser(userId);
|
|
390
696
|
const lines = [
|
|
391
697
|
`${user.externalId} (${user.id})`,
|
|
392
|
-
`
|
|
393
|
-
`
|
|
394
|
-
` wallet: ${user.walletAddress ?? "\u2014"}${user.walletChain ? ` on ${user.walletChain}` : ""}`
|
|
698
|
+
` card: ${user.hasPaymentMethod ? `${user.cardBrand ?? "card"} \u2026${user.cardLast4 ?? "????"}` : "\u2014"}`,
|
|
699
|
+
` wallet: ${user.walletAddress ?? "\u2014"}${user.walletChain ? ` on ${user.walletChain}` : ""}`
|
|
395
700
|
];
|
|
396
|
-
if (user.hasWallet) {
|
|
397
|
-
try {
|
|
398
|
-
const w = await client.getWalletStatus(userId);
|
|
399
|
-
lines.push(` balance: ${w.usdcBalance} USDC base units`);
|
|
400
|
-
lines.push(
|
|
401
|
-
` allowance: ${w.usdcAllowance} (${w.allowanceSufficient ? "ok" : "insufficient"})`
|
|
402
|
-
);
|
|
403
|
-
lines.push(` ready: ${w.ready}`);
|
|
404
|
-
} catch {
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
701
|
return textResult(lines.join("\n"));
|
|
408
702
|
} catch (err) {
|
|
409
703
|
return textResult(
|
|
@@ -413,231 +707,32 @@ if (platformToolsEnabled()) {
|
|
|
413
707
|
}
|
|
414
708
|
}
|
|
415
709
|
);
|
|
416
|
-
server.tool(
|
|
417
|
-
"pay_merchant",
|
|
418
|
-
"Direct payment to a known merchant via ArisPay rails (card / crypto / balance / MPP) when you already know the amount. For x402-protected HTTP resources use `pay_api` instead.",
|
|
419
|
-
{
|
|
420
|
-
agentName: z.string().describe("Name of the locally-stored agent making the payment."),
|
|
421
|
-
merchantUrl: z.string().describe("Canonical merchant URL (stored on the payment record)."),
|
|
422
|
-
amount: z.number().int().positive().describe("Amount in cents."),
|
|
423
|
-
memo: z.string().describe("Human-readable memo attached to the payment."),
|
|
424
|
-
userId: z.string().optional().describe("Optional ArisPay end-user id. Required when the agent is in platform mode."),
|
|
425
|
-
rail: z.enum(["card", "crypto", "balance", "mpp"]).optional().describe("Force a specific rail. Default: server-picked based on agent mode + userId."),
|
|
426
|
-
merchantName: z.string().optional().describe("Optional human-readable merchant name."),
|
|
427
|
-
merchantCategoryCode: z.string().optional().describe("MCC for spend-limit enforcement.")
|
|
428
|
-
},
|
|
429
|
-
async ({
|
|
430
|
-
agentName,
|
|
431
|
-
merchantUrl,
|
|
432
|
-
amount,
|
|
433
|
-
memo,
|
|
434
|
-
userId,
|
|
435
|
-
rail,
|
|
436
|
-
merchantName,
|
|
437
|
-
merchantCategoryCode
|
|
438
|
-
}) => {
|
|
439
|
-
try {
|
|
440
|
-
const stored = getAgent(agentName);
|
|
441
|
-
if (!stored) {
|
|
442
|
-
return textResult(`No locally-stored agent named \`${agentName}\`.`, true);
|
|
443
|
-
}
|
|
444
|
-
const devKey = requireDevKey();
|
|
445
|
-
const client = new DelegationClient(arispayUrl, devKey);
|
|
446
|
-
const payment = await client.createPayment(stored.agentId, {
|
|
447
|
-
amount,
|
|
448
|
-
memo,
|
|
449
|
-
merchantUrl,
|
|
450
|
-
merchantName,
|
|
451
|
-
merchantCategoryCode,
|
|
452
|
-
userId,
|
|
453
|
-
rail
|
|
454
|
-
});
|
|
455
|
-
const lines = [
|
|
456
|
-
`Payment ${payment.id}: ${payment.status}`,
|
|
457
|
-
` rail: ${payment.rail}`,
|
|
458
|
-
` amount: ${payment.amount}\xA2 ${payment.currency}`
|
|
459
|
-
];
|
|
460
|
-
if (payment.merchantName) lines.push(` merchant: ${payment.merchantName}`);
|
|
461
|
-
if (payment.txHash) lines.push(` tx hash: ${payment.txHash}`);
|
|
462
|
-
if (payment.spend) {
|
|
463
|
-
const fmtCap = (v) => v == null ? "no cap" : `${v}\xA2`;
|
|
464
|
-
lines.push(
|
|
465
|
-
` budget: ${fmtCap(payment.spend.remainingDaily)} left today, ${fmtCap(payment.spend.remainingMonthly)} this month`
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
if (payment.nextAction) {
|
|
469
|
-
lines.push(` next action: ${payment.nextAction.type}`);
|
|
470
|
-
if (payment.nextAction.challengeUrl) {
|
|
471
|
-
lines.push(` 3DS challenge URL: ${payment.nextAction.challengeUrl}`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (payment.error) {
|
|
475
|
-
lines.push(` error: ${payment.error.code} \u2014 ${payment.error.message}`);
|
|
476
|
-
}
|
|
477
|
-
return textResult(lines.join("\n"), payment.status === "failed");
|
|
478
|
-
} catch (err) {
|
|
479
|
-
return textResult(
|
|
480
|
-
`pay_merchant failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
481
|
-
true
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
);
|
|
486
710
|
}
|
|
487
711
|
server.tool(
|
|
488
|
-
"
|
|
489
|
-
"
|
|
490
|
-
{
|
|
491
|
-
email: z.string().describe("Email for the new ArisPay developer account."),
|
|
492
|
-
name: z.string().optional().describe("Human name. Falls back to the email local-part."),
|
|
493
|
-
orgName: z.string().optional().describe(`Workspace / org name. Falls back to "<name>'s Team".`),
|
|
494
|
-
agentName: z.string().optional().describe('Local name for the provisioned agent. Default: "default".'),
|
|
495
|
-
perTx: z.number().int().positive().optional().describe("Per-tx cap in cents. Default: 50."),
|
|
496
|
-
daily: z.number().int().positive().optional().describe("Daily cap in cents. Default: 1000."),
|
|
497
|
-
monthly: z.number().int().positive().optional().describe("Monthly cap in cents. Default: 10000."),
|
|
498
|
-
allowedDomains: z.array(z.string()).optional().describe("Domains the agent may pay. Empty = unrestricted.")
|
|
499
|
-
},
|
|
500
|
-
async ({ email, name, orgName, agentName, perTx, daily, monthly, allowedDomains }) => {
|
|
501
|
-
try {
|
|
502
|
-
const result = await bootstrapAgent({
|
|
503
|
-
email,
|
|
504
|
-
name,
|
|
505
|
-
orgName,
|
|
506
|
-
agentName,
|
|
507
|
-
limits: { perTx, daily, monthly },
|
|
508
|
-
allowedDomains,
|
|
509
|
-
arispayUrl: process.env.ARISPAY_URL,
|
|
510
|
-
clientId: "payagent-mcp-bootstrap"
|
|
511
|
-
});
|
|
512
|
-
const lines = [
|
|
513
|
-
`\u2713 Account: ${result.email} (org \`${result.orgName}\`)`,
|
|
514
|
-
`\u2713 Agent: ${result.agentName} (${result.agentId})`,
|
|
515
|
-
` Wallet: ${result.walletAddress} (${result.network})`,
|
|
516
|
-
` Limits: ${result.limits.perTx}\xA2 / tx, ${result.limits.daily}\xA2 / day, ${result.limits.monthly}\xA2 / month`,
|
|
517
|
-
"",
|
|
518
|
-
`Fund the wallet by sending USDC on ${result.network} to ${result.walletAddress}.`,
|
|
519
|
-
`Or call \`fund_agent\` with name="${result.agentName}" and rail="usdc" or rail="europ".`
|
|
520
|
-
];
|
|
521
|
-
if (result.pairing) {
|
|
522
|
-
lines.push("");
|
|
523
|
-
lines.push("\u{1F4F1} Open BuyForMe on your phone (signs you into the same account):");
|
|
524
|
-
lines.push(` ${result.pairing.url}`);
|
|
525
|
-
lines.push(` Link expires: ${result.pairing.expiresAt}`);
|
|
526
|
-
}
|
|
527
|
-
return textResult(lines.join("\n"));
|
|
528
|
-
} catch (err) {
|
|
529
|
-
if (err instanceof BootstrapError) {
|
|
530
|
-
return textResult(`bootstrap_agent failed (${err.code}): ${err.message}`, true);
|
|
531
|
-
}
|
|
532
|
-
return textResult(
|
|
533
|
-
`bootstrap_agent failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
534
|
-
true
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
);
|
|
539
|
-
server.tool(
|
|
540
|
-
"list_agents",
|
|
541
|
-
"List all wallets under this account (server-authoritative; rehydrates the local cache).",
|
|
712
|
+
"check_wallet",
|
|
713
|
+
"Legacy diagnostic for x402 payer agents. Use get_balance for wallet-centric balances.",
|
|
542
714
|
{
|
|
543
|
-
|
|
544
|
-
"When true, fetch on-chain USDC balance per agent (adds one round-trip per wallet)."
|
|
545
|
-
)
|
|
715
|
+
agent: z.string().optional().describe("Optional name of a locally-stored x402 agent.")
|
|
546
716
|
},
|
|
547
|
-
async ({
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
` network: ${a.network ?? "base"}`,
|
|
562
|
-
` limits: ${a.limits.perTx}\xA2 / tx, ${a.limits.daily}\xA2 / day, ${a.limits.monthly}\xA2 / month`,
|
|
563
|
-
` created: ${a.createdAt}`,
|
|
564
|
-
" (local cache only \u2014 set ARISPAY_API_KEY to sync from the server)"
|
|
565
|
-
].join("\n")
|
|
566
|
-
);
|
|
567
|
-
return textResult(lines.join("\n\n"));
|
|
568
|
-
}
|
|
569
|
-
try {
|
|
570
|
-
const result = await syncAgents({ includeBalance: withBalance === true });
|
|
571
|
-
if (!result.agents.length) {
|
|
572
|
-
return textResult(
|
|
573
|
-
"No wallets found under this developer key. Create one with `create_agent` or `bootstrap_agent`."
|
|
574
|
-
);
|
|
717
|
+
async ({ agent }) => {
|
|
718
|
+
const stored = agent ? getAgent(agent) : listAgents()[0];
|
|
719
|
+
const walletAddress = stored?.walletAddress ?? legacyWalletAddress;
|
|
720
|
+
const keyTail = stored?.apiKey.slice(-4) ?? (legacyAgentKey ? legacyAgentKey.slice(-4) : void 0);
|
|
721
|
+
const lines = [`ArisPay URL: ${arispayUrl}`];
|
|
722
|
+
if (stored) lines.push(`Agent: ${stored.name} (${stored.agentId})`);
|
|
723
|
+
if (keyTail) lines.push(`Agent key: ap_\u2026${keyTail}`);
|
|
724
|
+
if (walletAddress) {
|
|
725
|
+
lines.push(`Wallet: ${walletAddress}`);
|
|
726
|
+
try {
|
|
727
|
+
const raw = await getUSDCBalance(walletAddress, stored?.network ?? "base");
|
|
728
|
+
lines.push(`Balance: ${formatUSDC(raw)} USDC on ${stored?.network ?? "base"}`);
|
|
729
|
+
} catch (err) {
|
|
730
|
+
lines.push(`Balance: unavailable (${err instanceof Error ? err.message : String(err)})`);
|
|
575
731
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const fundedLine = a.fundedAt ? ` funded: ${a.fundedAt}` : " funded: not yet";
|
|
579
|
-
const domainsLine = a.allowedDomains.length ? ` domains: ${a.allowedDomains.join(", ")}` : " domains: (unrestricted)";
|
|
580
|
-
const suspendedLine = a.suspended ? " status: SUSPENDED" : null;
|
|
581
|
-
return [
|
|
582
|
-
a.name,
|
|
583
|
-
` agent id: ${a.agentId}`,
|
|
584
|
-
` wallet: ${a.walletAddress}`,
|
|
585
|
-
` network: ${a.network}`,
|
|
586
|
-
` limits: ${a.limits.maxPerTx}\xA2 / tx, ${a.limits.maxDaily}\xA2 / day, ${a.limits.maxMonthly}\xA2 / month`,
|
|
587
|
-
domainsLine,
|
|
588
|
-
fundedLine,
|
|
589
|
-
...balanceLine ? [balanceLine] : [],
|
|
590
|
-
...suspendedLine ? [suspendedLine] : [],
|
|
591
|
-
` created: ${a.createdAt}`
|
|
592
|
-
].join("\n");
|
|
593
|
-
});
|
|
594
|
-
return textResult(
|
|
595
|
-
`${result.agents.length} wallet${result.agents.length === 1 ? "" : "s"} under this account (synced from server):
|
|
596
|
-
|
|
597
|
-
${lines.join("\n\n")}`
|
|
598
|
-
);
|
|
599
|
-
} catch (err) {
|
|
600
|
-
return textResult(
|
|
601
|
-
`list_agents failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
602
|
-
true
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
);
|
|
607
|
-
server.tool(
|
|
608
|
-
"rename_agent",
|
|
609
|
-
"Rename an agent on the server and in the local cache (names are unique per account).",
|
|
610
|
-
{
|
|
611
|
-
name: z.string().describe("Current local name of the agent to rename."),
|
|
612
|
-
newName: z.string().describe(
|
|
613
|
-
"New name. Letters, numbers, spaces, hyphens, underscores, dots. 1-64 chars. Must be unique per account."
|
|
614
|
-
)
|
|
615
|
-
},
|
|
616
|
-
async ({ name, newName }) => {
|
|
617
|
-
if (name === newName) {
|
|
618
|
-
return textResult(`Agent \`${name}\` already has that name \u2014 nothing to do.`);
|
|
619
|
-
}
|
|
620
|
-
const stored = getAgent(name);
|
|
621
|
-
if (!stored) {
|
|
622
|
-
return textResult(
|
|
623
|
-
`No locally-cached agent named \`${name}\`. Run \`list_agents\` first to sync, or check the spelling.`,
|
|
624
|
-
true
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
try {
|
|
628
|
-
const devKey = requireDevKey();
|
|
629
|
-
const client = new DelegationClient(arispayUrl, devKey);
|
|
630
|
-
const result = await client.renameAgent(stored.agentId, newName);
|
|
631
|
-
renameStoredAgent(name, result.name);
|
|
632
|
-
return textResult(
|
|
633
|
-
`\u2713 Renamed \`${name}\` \u2192 \`${result.name}\`. Wallet ${result.walletAddress ?? stored.walletAddress} is unchanged.`
|
|
634
|
-
);
|
|
635
|
-
} catch (err) {
|
|
636
|
-
return textResult(
|
|
637
|
-
`rename_agent failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
638
|
-
true
|
|
639
|
-
);
|
|
732
|
+
} else {
|
|
733
|
+
lines.push("", "No wallet known.");
|
|
640
734
|
}
|
|
735
|
+
return textResult(lines.join("\n"));
|
|
641
736
|
}
|
|
642
737
|
);
|
|
643
738
|
async function main() {
|