@agentwonderland/mcp 0.1.24 → 0.1.26

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.
Files changed (99) hide show
  1. package/dist/core/__tests__/amount-utils.test.d.ts +1 -0
  2. package/dist/core/__tests__/amount-utils.test.js +11 -0
  3. package/dist/core/__tests__/api-client.test.d.ts +1 -0
  4. package/dist/core/__tests__/api-client.test.js +51 -0
  5. package/dist/core/__tests__/formatters.test.js +10 -0
  6. package/dist/core/__tests__/passes-api.test.d.ts +1 -0
  7. package/dist/core/__tests__/passes-api.test.js +27 -0
  8. package/dist/core/__tests__/payments.test.js +59 -6
  9. package/dist/core/__tests__/principal.test.js +41 -4
  10. package/dist/core/__tests__/solana-charge.test.d.ts +1 -0
  11. package/dist/core/__tests__/solana-charge.test.js +50 -0
  12. package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
  13. package/dist/core/__tests__/spend-policy.test.js +40 -0
  14. package/dist/core/amount-utils.d.ts +1 -0
  15. package/dist/core/amount-utils.js +4 -0
  16. package/dist/core/api-client.d.ts +1 -0
  17. package/dist/core/api-client.js +8 -3
  18. package/dist/core/balances.d.ts +1 -0
  19. package/dist/core/balances.js +56 -0
  20. package/dist/core/base-charge.js +16 -8
  21. package/dist/core/config.d.ts +19 -0
  22. package/dist/core/config.js +22 -0
  23. package/dist/core/formatters.d.ts +5 -5
  24. package/dist/core/formatters.js +12 -8
  25. package/dist/core/passes.d.ts +1 -1
  26. package/dist/core/passes.js +5 -2
  27. package/dist/core/payments.d.ts +1 -0
  28. package/dist/core/payments.js +32 -9
  29. package/dist/core/principal.d.ts +3 -0
  30. package/dist/core/principal.js +29 -1
  31. package/dist/core/settings.d.ts +20 -0
  32. package/dist/core/settings.js +19 -0
  33. package/dist/core/solana-charge.d.ts +5 -0
  34. package/dist/core/solana-charge.js +31 -8
  35. package/dist/core/spend-policy.d.ts +12 -0
  36. package/dist/core/spend-policy.js +53 -0
  37. package/dist/core/tempo-charge.d.ts +7 -0
  38. package/dist/core/tempo-charge.js +84 -0
  39. package/dist/core/types.d.ts +1 -2
  40. package/dist/index.js +9 -5
  41. package/dist/prompts/index.js +4 -2
  42. package/dist/resources/agents.js +1 -1
  43. package/dist/tools/__tests__/jobs.test.d.ts +1 -0
  44. package/dist/tools/__tests__/jobs.test.js +71 -0
  45. package/dist/tools/__tests__/run.test.d.ts +1 -0
  46. package/dist/tools/__tests__/run.test.js +149 -0
  47. package/dist/tools/__tests__/solve.test.d.ts +1 -0
  48. package/dist/tools/__tests__/solve.test.js +158 -0
  49. package/dist/tools/__tests__/wallet.test.d.ts +1 -0
  50. package/dist/tools/__tests__/wallet.test.js +230 -0
  51. package/dist/tools/_payment-confirmation.js +1 -1
  52. package/dist/tools/agent-info.js +2 -2
  53. package/dist/tools/favorites.js +1 -1
  54. package/dist/tools/jobs.js +8 -1
  55. package/dist/tools/observability.d.ts +2 -0
  56. package/dist/tools/observability.js +20 -0
  57. package/dist/tools/passes.js +11 -6
  58. package/dist/tools/run.js +45 -29
  59. package/dist/tools/solve.js +53 -40
  60. package/dist/tools/wallet.js +58 -22
  61. package/package.json +2 -2
  62. package/src/core/__tests__/amount-utils.test.ts +13 -0
  63. package/src/core/__tests__/api-client.test.ts +78 -0
  64. package/src/core/__tests__/formatters.test.ts +12 -0
  65. package/src/core/__tests__/passes-api.test.ts +33 -0
  66. package/src/core/__tests__/payments.test.ts +79 -6
  67. package/src/core/__tests__/principal.test.ts +49 -4
  68. package/src/core/__tests__/solana-charge.test.ts +59 -0
  69. package/src/core/__tests__/spend-policy.test.ts +58 -0
  70. package/src/core/amount-utils.ts +5 -0
  71. package/src/core/api-client.ts +16 -3
  72. package/src/core/balances.ts +63 -0
  73. package/src/core/base-charge.ts +16 -8
  74. package/src/core/config.ts +45 -0
  75. package/src/core/formatters.ts +16 -11
  76. package/src/core/passes.ts +5 -2
  77. package/src/core/payments.ts +37 -9
  78. package/src/core/principal.ts +42 -1
  79. package/src/core/settings.ts +36 -0
  80. package/src/core/solana-charge.ts +45 -10
  81. package/src/core/spend-policy.ts +69 -0
  82. package/src/core/tempo-charge.ts +104 -0
  83. package/src/core/types.ts +1 -2
  84. package/src/index.ts +9 -5
  85. package/src/prompts/index.ts +4 -2
  86. package/src/resources/agents.ts +1 -1
  87. package/src/tools/__tests__/jobs.test.ts +89 -0
  88. package/src/tools/__tests__/run.test.ts +176 -0
  89. package/src/tools/__tests__/solve.test.ts +186 -0
  90. package/src/tools/__tests__/wallet.test.ts +289 -0
  91. package/src/tools/_payment-confirmation.ts +1 -1
  92. package/src/tools/agent-info.ts +2 -2
  93. package/src/tools/favorites.ts +1 -4
  94. package/src/tools/jobs.ts +10 -1
  95. package/src/tools/observability.ts +43 -0
  96. package/src/tools/passes.ts +12 -12
  97. package/src/tools/run.ts +50 -41
  98. package/src/tools/solve.ts +58 -52
  99. package/src/tools/wallet.ts +60 -24
@@ -13,11 +13,11 @@ import {
13
13
  } from "../core/payments.js";
14
14
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
15
15
  import { agentList, formatRunResult } from "../core/formatters.js";
16
+ import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
16
17
  import { uploadLocalFiles } from "../core/file-upload.js";
17
18
  import type { AgentRecord } from "../core/types.js";
18
19
  import { storeFeedbackToken } from "./_token-cache.js";
19
20
  import {
20
- formatPaymentChoicePrompt,
21
21
  formatPaymentLabel,
22
22
  formatSolveConfirmationCommand,
23
23
  makeSolvePendingKey,
@@ -30,9 +30,10 @@ const POLL_MAX_MS = 120000;
30
30
 
31
31
  async function pollSolveJob(
32
32
  jobId: string,
33
+ paymentMethod?: string,
33
34
  ): Promise<{ status: string; output?: unknown; error_code?: string }> {
34
35
  const deadline = Date.now() + POLL_MAX_MS;
35
- const walletAddress = await getWalletAddress();
36
+ const walletAddress = await getWalletAddress(paymentMethod);
36
37
  const walletParam = walletAddress ? `?wallet=${walletAddress}` : "";
37
38
 
38
39
  while (Date.now() < deadline) {
@@ -137,6 +138,8 @@ export function registerSolveTools(server: McpServer): void {
137
138
  .describe("Maximum budget in USD"),
138
139
  pay_with: z
139
140
  .string()
141
+ .trim()
142
+ .min(1)
140
143
  .optional()
141
144
  .describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
142
145
  confirmed: z
@@ -199,11 +202,13 @@ export function registerSolveTools(server: McpServer): void {
199
202
  const tipMsg = result.feedback_token ? await autoTip(jobId, agentId, agentName, result.feedback_token as string) : "";
200
203
  return multiText(formatRunResult(result), feedbackAsk(jobId, agentId, usedCreditPack ? undefined : cost, tipMsg));
201
204
  } catch (err: unknown) {
202
- const isAuthError =
205
+ const status =
203
206
  err instanceof Error &&
204
- "status" in err &&
205
- (err as { status: number }).status === 401;
206
- if (!isAuthError || !hasWalletConfigured()) throw err;
207
+ "status" in err
208
+ ? (err as { status: number }).status
209
+ : undefined;
210
+ const isRecoverableDirectSolveError = status === 401 || status === 402;
211
+ if (!isRecoverableDirectSolveError || !hasWalletConfigured()) throw err;
207
212
  }
208
213
 
209
214
  const params = new URLSearchParams({ q: intent, limit: "5" });
@@ -220,17 +225,15 @@ export function registerSolveTools(server: McpServer): void {
220
225
  }
221
226
 
222
227
  const discovery = agentList(agents, intent);
223
- const inputTokens = Math.ceil(JSON.stringify(input).length / 4);
224
228
  const affordable = agents.filter((agent) => {
225
- const price = parseFloat(agent.pricePer1kTokens ?? "0.01");
226
- const cost = agent.pricingModel === "fixed" ? price : (inputTokens / 1000) * price;
227
- return cost <= budget;
229
+ const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
230
+ return price <= budget;
228
231
  });
229
232
  const selected = affordable[0] ?? agents[0];
230
- const activeCreditPack = await getCreditPackInventory(selected.id).then(getActiveCreditPack);
233
+ const activeCreditPack = await getCreditPackInventory(selected.id, requestedMethod).then(getActiveCreditPack);
231
234
  const compatibleMethods = getCompatiblePaymentMethods(selected, configuredMethods);
232
235
 
233
- if (!activeCreditPack && normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
236
+ if (normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
234
237
  return text(
235
238
  `The best matching agent cannot be paid with "${requestedMethod}".\n\n` +
236
239
  `Available payment methods for ${selected.name}: ${compatibleMethods.join(", ") || "none"}.\n` +
@@ -238,39 +241,21 @@ export function registerSolveTools(server: McpServer): void {
238
241
  );
239
242
  }
240
243
 
241
- if (!activeCreditPack && !requestedMethod && compatibleMethods.length === 0) {
244
+ if (!activeCreditPack && compatibleMethods.length === 0) {
242
245
  return text(
243
246
  `No compatible payment methods are configured for ${selected.name}.\n\n` +
244
- `Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
245
- `Agent accepts: ${selected.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
246
- "Use wallet_status to review your current setup.",
247
+ "Use wallet_status to review your current payment methods.",
247
248
  );
248
249
  }
249
250
 
250
- if (!activeCreditPack && !requestedMethod && compatibleMethods.length > 1) {
251
- return text([
252
- discovery,
253
- "",
254
- formatPaymentChoicePrompt(
255
- selected.name ?? selected.id,
256
- compatibleMethods,
257
- compatibleMethods.map((method) =>
258
- formatSolveConfirmationCommand(intent, budget, method).replace(", confirmed: true", ""),
259
- ),
260
- ),
261
- ].join("\n"));
262
- }
263
-
264
- const method = activeCreditPack
265
- ? undefined
266
- : resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
251
+ const method = resolveConfirmationMethod(requestedMethod, pending?.method, compatibleMethods);
252
+ const spendCheckMethod = method ?? normalizedRequestedMethod ?? compatibleMethods[0];
267
253
 
268
- const selectedPrice = parseFloat(selected.pricePer1kTokens ?? "0.01");
269
- const estimatedCost = selected.pricingModel === "fixed"
270
- ? selectedPrice
271
- : (inputTokens / 1000) * selectedPrice;
254
+ const selectedPrice = parseFloat(selected.pricePerRunUsd ?? "0.01");
255
+ const estimatedCost = selectedPrice;
272
256
 
273
- if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
257
+ const needsPolicyConfirmation = !activeCreditPack && requiresPolicyConfirmation(spendCheckMethod, estimatedCost);
258
+ if (!activeCreditPack && (requiresSpendConfirmation() || needsPolicyConfirmation) && !confirmed) {
274
259
  pendingSolves.set(pendingKey, { method });
275
260
  return text([
276
261
  discovery,
@@ -291,26 +276,47 @@ export function registerSolveTools(server: McpServer): void {
291
276
  }
292
277
 
293
278
  let result: Record<string, unknown>;
279
+ if (!activeCreditPack) {
280
+ const spendCheck = canSpend({
281
+ method: spendCheckMethod,
282
+ amountUsd: estimatedCost,
283
+ });
284
+ if (!spendCheck.ok) {
285
+ return text(spendCheck.message);
286
+ }
287
+ }
288
+
294
289
  try {
295
- result = activeCreditPack
296
- ? await apiPost<Record<string, unknown>>(
297
- `/agents/${selected.id}/run`,
298
- { input: processedInput },
299
- { ensureConsumerPrincipal: true },
300
- )
301
- : await apiPostWithPayment<Record<string, unknown>>(
290
+ let usedPaidMethod = !activeCreditPack;
291
+ if (activeCreditPack) {
292
+ try {
293
+ result = await apiPost<Record<string, unknown>>(
294
+ `/agents/${selected.id}/run`,
295
+ { input: processedInput },
296
+ { ensureConsumerPrincipal: true },
297
+ );
298
+ } catch (packErr) {
299
+ const packApiErr = packErr as { status?: number };
300
+ if (packApiErr?.status !== 402) throw packErr;
301
+ result = await apiPostWithPayment<Record<string, unknown>>(
302
+ `/agents/${selected.id}/run`,
303
+ { input: processedInput },
304
+ method,
305
+ );
306
+ usedPaidMethod = true;
307
+ }
308
+ } else {
309
+ result = await apiPostWithPayment<Record<string, unknown>>(
302
310
  `/agents/${selected.id}/run`,
303
311
  { input: processedInput },
304
312
  method,
305
313
  );
314
+ }
315
+ if (usedPaidMethod) {
316
+ recordSpend(spendCheckMethod, estimatedCost);
317
+ }
306
318
  } catch (err: unknown) {
307
319
  const apiErr = err as { status?: number; message?: string };
308
- if (activeCreditPack && apiErr?.status === 402) {
309
- return text(
310
- `Your available credit packs for ${selected.name} could not cover this run.\n\n` +
311
- "Use list_agent_credit_packs to inspect your balances, or retry with a payment method.",
312
- );
313
- }
314
320
  if (apiErr?.status === 402) {
315
321
  return text(
316
322
  [
@@ -339,7 +345,7 @@ export function registerSolveTools(server: McpServer): void {
339
345
  }
340
346
 
341
347
  if (status === "processing") {
342
- const pollResult = await pollSolveJob(jobId);
348
+ const pollResult = await pollSolveJob(jobId, method);
343
349
  if (pollResult.status === "completed") {
344
350
  const asyncFormatted = formatRunResult({
345
351
  ...result,
@@ -6,8 +6,12 @@ import {
6
6
  setCardConfig,
7
7
  addWallet,
8
8
  getPendingCardSetupToken,
9
+ getSpendPolicy,
10
+ setSpendPolicy,
9
11
  } from "../core/config.js";
10
- import { getWalletAddress } from "../core/payments.js";
12
+ import { getWalletAddress, isCardPaymentEnabled } from "../core/payments.js";
13
+ import { fetchUsdcBalance } from "../core/balances.js";
14
+ import { getSettings } from "../core/settings.js";
11
15
  import {
12
16
  getOrCreatePendingCardSetup,
13
17
  formatCardSetupBlocks,
@@ -45,25 +49,36 @@ export function registerWalletTools(server: McpServer): void {
45
49
  );
46
50
  }
47
51
 
48
- const lines = ["Payment methods:"];
52
+ const settings = await getSettings();
53
+ const networkLabel = settings ? ` (${settings.network})` : "";
54
+ const lines = [`Payment methods${networkLabel}:`];
49
55
 
50
56
  for (const w of wallets) {
51
57
  const label = w.label ? ` (${w.label})` : "";
52
58
  const storage =
53
59
  w.keyType === "ows" ? " [encrypted]" : " [plaintext]";
54
- const chainAddresses = await Promise.all(
60
+ const chainLines = await Promise.all(
55
61
  w.chains.map(async (chainName) => {
56
62
  const addr = await getWalletAddress(chainName);
57
- return `${chainName}: ${addr ?? "unknown"}`;
63
+ if (!addr) return `${chainName}: unknown`;
64
+ let balanceStr = "";
65
+ if (chainName === "tempo" || chainName === "base" || chainName === "solana") {
66
+ const balance = await fetchUsdcBalance(chainName, addr);
67
+ if (balance !== null) {
68
+ const num = Number(balance);
69
+ balanceStr = ` ${Number.isFinite(num) ? num.toFixed(4).replace(/\.?0+$/, "") : balance} USDC`;
70
+ }
71
+ }
72
+ return `${chainName}: ${addr}${balanceStr}`;
58
73
  }),
59
74
  );
60
- lines.push(
61
- ` ${w.id}${label}${storage}: ${chainAddresses.join(" | ")}`,
62
- );
75
+ lines.push(` ${w.id}${label}${storage}:`);
76
+ for (const line of chainLines) lines.push(` ${line}`);
63
77
  }
64
78
 
65
- if (card) {
66
- lines.push(` Card: ${card.brand} ****${card.last4}`);
79
+ if (card && isCardPaymentEnabled()) {
80
+ const stripeMode = settings?.stripe.mode === "test" ? " [Stripe test mode]" : "";
81
+ lines.push(` Card: ${card.brand} ****${card.last4}${stripeMode}`);
67
82
  const capabilities = await getCardCapabilities();
68
83
  if (capabilities.spt_status === "enabled") {
69
84
  lines.push(" Card MPP: ready");
@@ -89,7 +104,7 @@ export function registerWalletTools(server: McpServer): void {
89
104
  // ── wallet_setup (NEW) ──────────────────────────────────────────
90
105
  server.tool(
91
106
  "wallet_setup",
92
- "Set up or manage payment methods. Options: 'add-card' to connect a credit/debit card (recommended), 'remove-card' to disconnect a card, 'create' a crypto wallet, or 'import' an existing key. For crypto wallet changes (removal, key rotation), direct users to edit their config files manually — never handle private keys programmatically.",
107
+ "Set up or manage payment methods. Options: 'add-card' to connect a credit/debit card, 'remove-card' to disconnect a card, 'create' a crypto wallet, or 'import' an existing key. After card setup, use wallet_status to confirm whether card-backed MPP is ready. For crypto wallet changes (removal, key rotation), direct users to edit their config files manually — never handle private keys programmatically.",
93
108
  {
94
109
  action: z
95
110
  .enum(["create", "import", "add-card", "remove-card"])
@@ -110,6 +125,9 @@ export function registerWalletTools(server: McpServer): void {
110
125
  async ({ action, name, key, chain }) => {
111
126
  // ── Card setup flow ──────────────────────────────────────
112
127
  if (action === "add-card") {
128
+ if (!isCardPaymentEnabled()) {
129
+ return text("Card payments are temporarily unavailable. Use wallet_setup({ action: \"create\" }) for a crypto wallet instead.");
130
+ }
113
131
  const existing = getCardConfig();
114
132
  if (existing) {
115
133
  return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
@@ -339,9 +357,9 @@ export function registerWalletTools(server: McpServer): void {
339
357
  // ── wallet_set_policy (NEW) ─────────────────────────────────────
340
358
  server.tool(
341
359
  "wallet_set_policy",
342
- "Set spending limits on a wallet to control agent costs. Limits reset daily.",
360
+ "Set client-side spending limits on a wallet to control agent costs in this MCP client. Limits reset daily.",
343
361
  {
344
- wallet_id: z.string().describe("Wallet ID to set policy on"),
362
+ wallet_id: z.string().describe("Wallet ID to set local policy on"),
345
363
  max_per_tx: z
346
364
  .number()
347
365
  .positive()
@@ -352,8 +370,13 @@ export function registerWalletTools(server: McpServer): void {
352
370
  .positive()
353
371
  .optional()
354
372
  .describe("Maximum USD per day across all transactions"),
373
+ require_confirmation_above: z
374
+ .number()
375
+ .positive()
376
+ .optional()
377
+ .describe("Require manual confirmation above this USD amount even when auto-spend is enabled"),
355
378
  },
356
- async ({ wallet_id, max_per_tx, max_per_day }) => {
379
+ async ({ wallet_id, max_per_tx, max_per_day, require_confirmation_above }) => {
357
380
  const wallets = getWallets();
358
381
  const wallet = wallets.find((w) => w.id === wallet_id);
359
382
 
@@ -364,29 +387,42 @@ export function registerWalletTools(server: McpServer): void {
364
387
  );
365
388
  }
366
389
 
367
- if (max_per_tx == null && max_per_day == null) {
390
+ if (
391
+ max_per_tx == null &&
392
+ max_per_day == null &&
393
+ require_confirmation_above == null
394
+ ) {
368
395
  return text(
369
- "No policy changes specified. Provide max_per_tx and/or max_per_day.",
396
+ "No policy changes specified. Provide at least one policy field.",
370
397
  );
371
398
  }
372
399
 
400
+ const existing = getSpendPolicy(wallet_id) ?? {};
401
+ const nextPolicy = {
402
+ ...existing,
403
+ ...(max_per_tx != null ? { maxPerTxUsd: max_per_tx } : {}),
404
+ ...(max_per_day != null ? { maxPerDayUsd: max_per_day } : {}),
405
+ ...(require_confirmation_above != null ? { requireConfirmationAboveUsd: require_confirmation_above } : {}),
406
+ };
407
+ setSpendPolicy(wallet_id, nextPolicy);
408
+
373
409
  // Build policy summary
374
410
  const policies: string[] = [];
375
- if (max_per_tx != null) {
376
- policies.push(`Max per transaction: $${max_per_tx.toFixed(2)}`);
411
+ if (nextPolicy.maxPerTxUsd != null) {
412
+ policies.push(`Max per transaction: $${nextPolicy.maxPerTxUsd.toFixed(2)}`);
377
413
  }
378
- if (max_per_day != null) {
379
- policies.push(`Max per day: $${max_per_day.toFixed(2)}`);
414
+ if (nextPolicy.maxPerDayUsd != null) {
415
+ policies.push(`Max per day: $${nextPolicy.maxPerDayUsd.toFixed(2)}`);
416
+ }
417
+ if (nextPolicy.requireConfirmationAboveUsd != null) {
418
+ policies.push(`Require confirmation above: $${nextPolicy.requireConfirmationAboveUsd.toFixed(2)}`);
380
419
  }
381
-
382
- // Note: actual persistence depends on the config module supporting policy fields.
383
- // For now, we report what would be set. The core/config module will handle storage.
384
420
  return text(
385
421
  [
386
- `Spending policy set for wallet "${wallet_id}":`,
422
+ `Local spending policy set for wallet "${wallet_id}":`,
387
423
  ...policies.map((p) => ` ${p}`),
388
424
  "",
389
- "Policy will be enforced on all future transactions from this wallet.",
425
+ "Policy is stored in this MCP client's local config and enforced on future transactions from this wallet on this client.",
390
426
  ].join("\n"),
391
427
  );
392
428
  },