@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 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
- function resolveAgent(name) {
41
- if (name) return getAgent(name);
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: "2.1.0"
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
- "Make an HTTP request to a paid API, transparently paying any x402 (HTTP 402) challenge with USDC under the agent's spend limits. THE tool for fetching paid resources \u2014 never bypass a 402 or hand-roll payment.",
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").describe("HTTP method"),
81
- headers: z.record(z.string(), z.string()).optional().describe("Additional HTTP headers"),
82
- body: z.string().optional().describe("Request body (for POST/PUT/PATCH)"),
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 resolved = resolveAgentKey(agent);
89
- if (!resolved) {
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. Cold-start in one call:",
93
- ' bootstrap_agent({ email: "you@example.com", name: "<your-name>" })',
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: resolved.key,
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 ${resolved.source} \u2014 budget left: ${paymentInfo.remainingDaily}\xA2 today, ${paymentInfo.remainingMonthly}\xA2 this month)` : `HTTP ${response.status} (paid via ${resolved.source})`;
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
- "This looks like the agent wallet is out of USDC. Fund it and retry:",
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
- "Provision an additional x402 payment agent (wallet + spend limits) under the already-paired developer account.",
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. Used by later tools."),
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().describe("If provided, restrict this agent to paying only these domains."),
175
- network: z.enum(["base", "base-sepolia", "ethereum", "polygon"]).default("base").describe("EVM network the agent pays on."),
176
- agentType: z.string().optional().describe('Optional metadata label (e.g. "hermes").')
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 Agent \`${name}\` created.`,
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 \`get_balance\` with name="${name}".`
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
- "Get funding instructions for an agent's wallet. Use rail='usdc' for the direct USDC deposit address on Base, or rail='europ' for a browser handoff to complete Schuman identity verification and receive a SEPA vIBAN that credits EUR\xD8P.",
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, rail }) => {
488
+ async ({ name }) => {
217
489
  const stored = getAgent(name);
218
- if (!stored) return textResult(`No locally-stored agent named \`${name}\`.`, true);
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 `get_balance` when the deposit lands."
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
- "get_balance",
250
- "Server-side USDC balance + funding status for one named agent.",
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 locally-stored agent named \`${name}\`.`, true);
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
- `get_balance failed: ${err instanceof Error ? err.message : String(err)}`,
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 (EndUser) under your account, keyed by your own externalId.",
598
+ "Platform mode: register an end-customer under your account.",
280
599
  {
281
- externalId: z.string().describe("Your own stable id for this customer (e.g. `tg:12345`)."),
282
- email: z.string().optional().describe("Optional email for compliance / receipts."),
283
- findOrCreate: z.boolean().optional().describe("If true, return the existing user instead of erroring on externalId collision.")
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
- const lines = [
291
- `\u2713 End-user \`${externalId}\` ready.`,
292
- ` ArisPay id: ${user.id}`,
293
- ` Has card: ${user.hasPaymentMethod ? "yes" : "no"}`,
294
- ` Has wallet: ${user.hasWallet ? "yes" : "no"}`
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 (ArisPay handles tokenization + 3DS).",
624
+ "Platform mode: mint a hosted card-entry URL for an end-user.",
308
625
  {
309
- userId: z.string().describe("The ArisPay-internal end-user id (not externalId)."),
310
- agentName: z.string().optional().describe("Optional locally-stored agent name to scope the card-setup session to.")
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
- const lines = [
319
- `Card-entry URL for user \`${userId}\`:`,
320
- "",
321
- ` ${session.setupUrl}`,
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 in integer cents plus MCC allow/block rules.",
650
+ "Platform mode: set per-(user, agent) spend caps.",
340
651
  {
341
- userId: z.string().describe("ArisPay-internal end-user id."),
342
- agentName: z.string().describe("Locally-stored agent name."),
343
- perTx: z.number().int().optional().describe("Per-transaction cap in cents. Omit to inherit agent defaults."),
344
- daily: z.number().int().optional().describe("Daily cap in cents."),
345
- monthly: z.number().int().optional().describe("Monthly cap in cents."),
346
- allowedMcc: z.array(z.string()).optional().describe("Allowed Merchant Category Codes."),
347
- blockedMcc: z.array(z.string()).optional().describe("Blocked MCCs.")
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, wallet, and payment readiness.",
687
+ "Platform mode: report an end-user's card and wallet readiness.",
382
688
  {
383
- userId: z.string().describe("ArisPay-internal end-user id.")
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
- ` email: ${user.email ?? "\u2014"}`,
393
- ` card: ${user.hasPaymentMethod ? `${user.cardBrand ?? "card"} \u2026${user.cardLast4 ?? "????"}` : "\u2014"}`,
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
- "bootstrap_agent",
489
- "Cold-start in one call: create (or recover) an ArisPay account plus a payment agent from just an email. Run this first on a fresh machine; safe to re-run \u2014 it recovers the existing agent and wallet set instead of creating duplicates.",
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
- withBalance: z.boolean().optional().describe(
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 ({ withBalance }) => {
548
- const devKey = getApiKey();
549
- if (!devKey) {
550
- const local = listAgents();
551
- if (!local.length) {
552
- return textResult(
553
- "No developer key set and no agents cached locally. Run `bootstrap_agent` or set ARISPAY_API_KEY."
554
- );
555
- }
556
- const lines = local.map(
557
- (a) => [
558
- a.name,
559
- ` agent id: ${a.agentId}`,
560
- ` wallet: ${a.walletAddress}`,
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
- const lines = result.agents.map((a) => {
577
- const balanceLine = a.usdcBalance !== void 0 ? ` balance: ${formatUSDC(BigInt(a.usdcBalance || "0"))} USDC` : null;
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() {