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