@agentwonderland/mcp 0.1.23 → 0.1.25

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 (95) 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__/card-setup.test.d.ts +1 -0
  4. package/dist/core/__tests__/card-setup.test.js +99 -0
  5. package/dist/core/__tests__/formatters.test.d.ts +1 -0
  6. package/dist/core/__tests__/formatters.test.js +15 -0
  7. package/dist/core/__tests__/passes.test.d.ts +1 -0
  8. package/dist/core/__tests__/passes.test.js +82 -0
  9. package/dist/core/__tests__/payments.test.d.ts +1 -0
  10. package/dist/core/__tests__/payments.test.js +101 -0
  11. package/dist/core/__tests__/principal.test.d.ts +1 -0
  12. package/dist/core/__tests__/principal.test.js +67 -0
  13. package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
  14. package/dist/core/__tests__/spend-policy.test.js +40 -0
  15. package/dist/core/amount-utils.d.ts +1 -0
  16. package/dist/core/amount-utils.js +4 -0
  17. package/dist/core/api-client.d.ts +9 -4
  18. package/dist/core/api-client.js +52 -22
  19. package/dist/core/base-charge.js +3 -2
  20. package/dist/core/card-setup.d.ts +20 -13
  21. package/dist/core/card-setup.js +85 -29
  22. package/dist/core/config.d.ts +22 -0
  23. package/dist/core/config.js +46 -2
  24. package/dist/core/formatters.d.ts +4 -3
  25. package/dist/core/formatters.js +10 -8
  26. package/dist/core/index.d.ts +2 -0
  27. package/dist/core/index.js +2 -0
  28. package/dist/core/ows-adapter.d.ts +10 -2
  29. package/dist/core/ows-adapter.js +54 -10
  30. package/dist/core/passes.d.ts +40 -0
  31. package/dist/core/passes.js +32 -0
  32. package/dist/core/payments.d.ts +8 -0
  33. package/dist/core/payments.js +111 -17
  34. package/dist/core/principal.d.ts +2 -0
  35. package/dist/core/principal.js +109 -0
  36. package/dist/core/solana-charge.d.ts +9 -0
  37. package/dist/core/solana-charge.js +96 -0
  38. package/dist/core/spend-policy.d.ts +12 -0
  39. package/dist/core/spend-policy.js +53 -0
  40. package/dist/core/types.d.ts +11 -2
  41. package/dist/index.js +11 -3
  42. package/dist/prompts/index.js +4 -2
  43. package/dist/resources/agents.js +1 -1
  44. package/dist/resources/wallet.js +8 -1
  45. package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
  46. package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
  47. package/dist/tools/_payment-confirmation.d.ts +6 -0
  48. package/dist/tools/_payment-confirmation.js +28 -0
  49. package/dist/tools/agent-info.js +16 -2
  50. package/dist/tools/favorites.js +1 -1
  51. package/dist/tools/index.d.ts +1 -0
  52. package/dist/tools/index.js +1 -0
  53. package/dist/tools/observability.d.ts +2 -0
  54. package/dist/tools/observability.js +20 -0
  55. package/dist/tools/passes.d.ts +2 -0
  56. package/dist/tools/passes.js +157 -0
  57. package/dist/tools/run.js +127 -53
  58. package/dist/tools/solve.js +115 -51
  59. package/dist/tools/wallet.js +110 -59
  60. package/package.json +3 -1
  61. package/src/core/__tests__/amount-utils.test.ts +13 -0
  62. package/src/core/__tests__/card-setup.test.ts +118 -0
  63. package/src/core/__tests__/formatters.test.ts +17 -0
  64. package/src/core/__tests__/passes.test.ts +94 -0
  65. package/src/core/__tests__/payments.test.ts +122 -0
  66. package/src/core/__tests__/principal.test.ts +87 -0
  67. package/src/core/__tests__/spend-policy.test.ts +58 -0
  68. package/src/core/amount-utils.ts +5 -0
  69. package/src/core/api-client.ts +70 -23
  70. package/src/core/base-charge.ts +3 -2
  71. package/src/core/card-setup.ts +109 -34
  72. package/src/core/config.ts +74 -3
  73. package/src/core/formatters.ts +13 -9
  74. package/src/core/index.ts +2 -0
  75. package/src/core/ows-adapter.ts +74 -8
  76. package/src/core/passes.ts +74 -0
  77. package/src/core/payments.ts +130 -17
  78. package/src/core/principal.ts +128 -0
  79. package/src/core/solana-charge.ts +150 -0
  80. package/src/core/spend-policy.ts +69 -0
  81. package/src/core/types.ts +11 -2
  82. package/src/index.ts +11 -3
  83. package/src/prompts/index.ts +4 -2
  84. package/src/resources/agents.ts +1 -1
  85. package/src/resources/wallet.ts +8 -1
  86. package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
  87. package/src/tools/_payment-confirmation.ts +52 -0
  88. package/src/tools/agent-info.ts +25 -2
  89. package/src/tools/favorites.ts +1 -4
  90. package/src/tools/index.ts +1 -0
  91. package/src/tools/observability.ts +43 -0
  92. package/src/tools/passes.ts +228 -0
  93. package/src/tools/run.ts +174 -57
  94. package/src/tools/solve.ts +147 -59
  95. package/src/tools/wallet.ts +132 -62
@@ -1,22 +1,34 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
- import { getWallets, getCardConfig, setCardConfig, addWallet } from "../core/config.js";
3
+ import {
4
+ getWallets,
5
+ getCardConfig,
6
+ setCardConfig,
7
+ addWallet,
8
+ getPendingCardSetupToken,
9
+ getSpendPolicy,
10
+ setSpendPolicy,
11
+ } from "../core/config.js";
4
12
  import { getWalletAddress } from "../core/payments.js";
5
- import { initiateCardSetup, pollCardSetup } from "../core/card-setup.js";
13
+ import {
14
+ getOrCreatePendingCardSetup,
15
+ formatCardSetupBlocks,
16
+ getCardCapabilities,
17
+ pollCardSetup,
18
+ } from "../core/card-setup.js";
6
19
  import {
7
20
  isOwsAvailable,
8
21
  createOwsWallet,
9
22
  importKeyToOws,
10
23
  listOwsWallets,
24
+ listOwsWalletsByChain,
11
25
  } from "../core/ows-adapter.js";
26
+ import { ensureConsumerPrincipal, getConsumerPrincipal } from "../core/principal.js";
12
27
 
13
28
  function text(t: string) {
14
29
  return { content: [{ type: "text" as const, text: t }] };
15
30
  }
16
31
 
17
- // Pending card setup — stored between the QR display call and the confirmation poll
18
- let pendingCardSetup: { token: string } | null = null;
19
-
20
32
  export function registerWalletTools(server: McpServer): void {
21
33
  // ── wallet_status (extracted from check_wallet) ─────────────────
22
34
  server.tool(
@@ -26,8 +38,10 @@ export function registerWalletTools(server: McpServer): void {
26
38
  async () => {
27
39
  const wallets = getWallets();
28
40
  const card = getCardConfig();
41
+ const pendingCardSetupToken = getPendingCardSetupToken();
42
+ const consumerPrincipal = await getConsumerPrincipal();
29
43
 
30
- if (wallets.length === 0 && !card) {
44
+ if (wallets.length === 0 && !card && !pendingCardSetupToken) {
31
45
  return text(
32
46
  "No payment methods configured.\nUse wallet_setup to create or import a wallet.",
33
47
  );
@@ -36,17 +50,38 @@ export function registerWalletTools(server: McpServer): void {
36
50
  const lines = ["Payment methods:"];
37
51
 
38
52
  for (const w of wallets) {
39
- const addr = await getWalletAddress(w.id);
40
53
  const label = w.label ? ` (${w.label})` : "";
41
54
  const storage =
42
55
  w.keyType === "ows" ? " [encrypted]" : " [plaintext]";
56
+ const chainAddresses = await Promise.all(
57
+ w.chains.map(async (chainName) => {
58
+ const addr = await getWalletAddress(chainName);
59
+ return `${chainName}: ${addr ?? "unknown"}`;
60
+ }),
61
+ );
43
62
  lines.push(
44
- ` ${w.id}${label}${storage}: ${w.chains.join(", ")} \u2014 ${addr ?? "unknown"}`,
63
+ ` ${w.id}${label}${storage}: ${chainAddresses.join(" | ")}`,
45
64
  );
46
65
  }
47
66
 
48
67
  if (card) {
49
68
  lines.push(` Card: ${card.brand} ****${card.last4}`);
69
+ const capabilities = await getCardCapabilities();
70
+ if (capabilities.spt_status === "enabled") {
71
+ lines.push(" Card MPP: ready");
72
+ } else if (capabilities.spt_status === "unavailable") {
73
+ lines.push(` Card MPP: unavailable — ${capabilities.message ?? "Stripe Shared Payment Token access is not enabled."}`);
74
+ } else {
75
+ lines.push(` Card MPP: unknown — ${capabilities.message ?? "Could not determine card payment readiness."}`);
76
+ }
77
+ }
78
+
79
+ if (pendingCardSetupToken) {
80
+ lines.push(" Card setup: pending confirmation");
81
+ }
82
+
83
+ if (consumerPrincipal) {
84
+ lines.push("", `Consumer principal: ${consumerPrincipal}`);
50
85
  }
51
86
 
52
87
  return text(lines.join("\n"));
@@ -72,7 +107,7 @@ export function registerWalletTools(server: McpServer): void {
72
107
  "Private key hex string (required for 'import', ignored for 'create')",
73
108
  ),
74
109
  chain: z.enum(["tempo", "base", "solana"]).optional()
75
- .describe("Primary chain (default: tempo). Solana support is via Stripe deposit mode."),
110
+ .describe("Primary chain (default: tempo). Solana uses an OWS wallet plus Stripe deposit-mode USDC."),
76
111
  },
77
112
  async ({ action, name, key, chain }) => {
78
113
  // ── Card setup flow ──────────────────────────────────────
@@ -82,53 +117,37 @@ export function registerWalletTools(server: McpServer): void {
82
117
  return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
83
118
  }
84
119
 
120
+ const pendingToken = getPendingCardSetupToken();
121
+
85
122
  // Check if there's a pending setup to complete (user said they're done)
86
- if (pendingCardSetup) {
87
- const pendingToken = pendingCardSetup.token;
88
- const result = await pollCardSetup(pendingToken, 120_000);
89
- pendingCardSetup = null;
123
+ if (pendingToken) {
124
+ const result = await pollCardSetup(pendingToken, 250);
90
125
 
91
126
  if (result) {
92
- return text(`Connected! ${result.brand} ****${result.last4} is ready for payments.`);
127
+ const principal = await ensureConsumerPrincipal();
128
+ return text(
129
+ `Connected! ${result.brand} ****${result.last4} is ready for payments.\n\n` +
130
+ `Consumer principal: ${principal}`,
131
+ );
93
132
  }
94
- return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
95
- }
96
-
97
- // Create new card setup session with QR code, then auto-poll
98
- try {
99
- const { qr, url, token } = await initiateCardSetup();
100
-
101
- // Auto-poll for up to 90 seconds — if the user enters their card
102
- // quickly, the tool returns success without a second call
103
- const result = await pollCardSetup(token, 90_000);
104
133
 
105
- if (result) {
134
+ try {
135
+ const { url } = await getOrCreatePendingCardSetup();
136
+ const blocks = formatCardSetupBlocks(url);
106
137
  return {
107
- content: [
108
- { type: "text" as const, text: "\n" + qr.trim() },
109
- { type: "text" as const, text: [
110
- `Open to connect a card: ${url}`,
111
- "",
112
- `Connected! ${result.brand} ****${result.last4} is ready for payments.`,
113
- ].join("\n") },
114
- ],
138
+ content: blocks.map((t) => ({ type: "text" as const, text: t })),
115
139
  };
140
+ } catch {
141
+ return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
116
142
  }
143
+ }
117
144
 
118
- // Timed out save token for manual follow-up
119
- pendingCardSetup = { token };
145
+ // Create new card setup session and return immediately so clients
146
+ // don't appear hung while the user is still in the browser flow.
147
+ try {
148
+ const { url } = await getOrCreatePendingCardSetup();
120
149
  return {
121
- content: [
122
- { type: "text" as const, text: "\n" + qr.trim() },
123
- { type: "text" as const, text: [
124
- `IMPORTANT: Present this link to the user to connect a payment card:`,
125
- "",
126
- url,
127
- "",
128
- `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
129
- `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
130
- ].join("\n") },
131
- ],
150
+ content: formatCardSetupBlocks(url).map((t) => ({ type: "text" as const, text: t })),
132
151
  };
133
152
  } catch {
134
153
  return text("Error: Could not create card setup session. Try again later.");
@@ -155,18 +174,23 @@ export function registerWalletTools(server: McpServer): void {
155
174
 
156
175
  const selectedChains = chain === "solana" ? ["solana"] : ["tempo", "base"];
157
176
  const defaultCh = chain ?? "tempo";
177
+ const preferredOwsChain = defaultCh === "solana" ? "solana" : "evm";
158
178
 
159
179
  const owsReady = await isOwsAvailable();
160
180
 
161
181
  if (action === "create") {
162
182
  // Check for existing OWS wallets first
163
183
  if (owsReady) {
164
- const existing = await listOwsWallets();
184
+ const existing = preferredOwsChain === "solana"
185
+ ? await listOwsWalletsByChain("solana")
186
+ : await listOwsWallets();
165
187
  if (existing.length > 0) {
166
188
  const w = existing[0];
167
189
  // Check if already in config
168
190
  const wallets = getWallets();
169
- const alreadyLinked = wallets.find(wl => wl.owsWalletId === w.id);
191
+ const alreadyLinked = wallets.find((wl) => wl.owsWalletId === w.id);
192
+ let chainStatus = "Linked to Agent Wonderland.";
193
+
170
194
  if (!alreadyLinked) {
171
195
  addWallet({
172
196
  id: w.name,
@@ -176,15 +200,28 @@ export function registerWalletTools(server: McpServer): void {
176
200
  defaultChain: defaultCh,
177
201
  label: w.name,
178
202
  });
203
+ } else {
204
+ const mergedChains = [...new Set([...alreadyLinked.chains, ...selectedChains])];
205
+ if (mergedChains.length !== alreadyLinked.chains.length) {
206
+ addWallet({
207
+ ...alreadyLinked,
208
+ chains: mergedChains,
209
+ });
210
+ chainStatus = `Updated linked chains: ${mergedChains.join(", ")}.`;
211
+ } else {
212
+ chainStatus = "Already linked to Agent Wonderland.";
213
+ }
179
214
  }
215
+ const principal = await ensureConsumerPrincipal();
180
216
  return text([
181
217
  "Found existing OWS wallet:",
182
218
  ` Name: ${w.name}`,
183
219
  ` Address: ${w.address}`,
184
220
  ` Chains: ${selectedChains.join(", ")}`,
185
221
  ` Storage: ~/.ows/ (encrypted)`,
222
+ ` Consumer principal: ${principal}`,
186
223
  "",
187
- alreadyLinked ? "Already linked to Agent Wonderland." : "Linked to Agent Wonderland.",
224
+ chainStatus,
188
225
  "",
189
226
  `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
190
227
  ].join("\n"));
@@ -195,12 +232,14 @@ export function registerWalletTools(server: McpServer): void {
195
232
  return text(
196
233
  "Cannot create wallet: OWS (Open Wallet Standard) is not installed.\n" +
197
234
  "Install with: npm install -g @open-wallet-standard/core\n\n" +
198
- "Alternatively, use action 'import' with an existing private key.",
235
+ (defaultCh === "solana"
236
+ ? "Solana wallets currently require OWS-managed encrypted storage."
237
+ : "Alternatively, use action 'import' with an existing private key."),
199
238
  );
200
239
  }
201
240
 
202
241
  const walletName = name ?? `aw-${Date.now()}`;
203
- const result = await createOwsWallet(walletName);
242
+ const result = await createOwsWallet(walletName, preferredOwsChain);
204
243
 
205
244
  // Persist to config so payment flows can find it
206
245
  addWallet({
@@ -211,6 +250,7 @@ export function registerWalletTools(server: McpServer): void {
211
250
  defaultChain: defaultCh,
212
251
  label: walletName,
213
252
  });
253
+ const principal = await ensureConsumerPrincipal();
214
254
 
215
255
  return text(
216
256
  [
@@ -220,6 +260,7 @@ export function registerWalletTools(server: McpServer): void {
220
260
  ` Name: ${walletName}`,
221
261
  ` Chains: ${selectedChains.join(", ")}`,
222
262
  ` Storage: ~/.ows/ (AES-256-GCM encrypted)`,
263
+ ` Consumer principal: ${principal}`,
223
264
  "",
224
265
  `Fund this address with USDC on ${defaultCh === "solana" ? "Solana" : "Tempo"} to start using agents.`,
225
266
  "For testnet: npx mppx account fund",
@@ -230,7 +271,7 @@ export function registerWalletTools(server: McpServer): void {
230
271
  // action === "import"
231
272
  if (owsReady) {
232
273
  const walletName = name ?? `imported-${Date.now()}`;
233
- const result = await importKeyToOws(key!, walletName);
274
+ const result = await importKeyToOws(key!, walletName, preferredOwsChain);
234
275
 
235
276
  addWallet({
236
277
  id: walletName,
@@ -240,6 +281,7 @@ export function registerWalletTools(server: McpServer): void {
240
281
  defaultChain: defaultCh,
241
282
  label: walletName,
242
283
  });
284
+ const principal = await ensureConsumerPrincipal();
243
285
 
244
286
  return text(
245
287
  [
@@ -248,10 +290,18 @@ export function registerWalletTools(server: McpServer): void {
248
290
  ` Address: ${result.address}`,
249
291
  ` Name: ${walletName}`,
250
292
  ` Chains: ${selectedChains.join(", ")}`,
293
+ ` Consumer principal: ${principal}`,
251
294
  ].join("\n"),
252
295
  );
253
296
  }
254
297
 
298
+ if (defaultCh === "solana") {
299
+ return text(
300
+ "Solana key import currently requires OWS (Open Wallet Standard) so the ed25519 key can be stored safely.\n" +
301
+ "Install with: npm install -g @open-wallet-standard/core",
302
+ );
303
+ }
304
+
255
305
  // No OWS — store plaintext
256
306
  try {
257
307
  const { privateKeyToAccount } = await import("viem/accounts");
@@ -269,6 +319,7 @@ export function registerWalletTools(server: McpServer): void {
269
319
  defaultChain: defaultCh,
270
320
  label: walletName,
271
321
  });
322
+ const principal = await ensureConsumerPrincipal();
272
323
 
273
324
  return text(
274
325
  [
@@ -276,6 +327,7 @@ export function registerWalletTools(server: McpServer): void {
276
327
  ` Address: ${account.address}`,
277
328
  ` Name: ${walletName}`,
278
329
  ` Chains: ${selectedChains.join(", ")}`,
330
+ ` Consumer principal: ${principal}`,
279
331
  "",
280
332
  "Install OWS for encrypted storage: npm install -g @open-wallet-standard/core",
281
333
  ].join("\n"),
@@ -302,8 +354,13 @@ export function registerWalletTools(server: McpServer): void {
302
354
  .positive()
303
355
  .optional()
304
356
  .describe("Maximum USD per day across all transactions"),
357
+ require_confirmation_above: z
358
+ .number()
359
+ .positive()
360
+ .optional()
361
+ .describe("Require manual confirmation above this USD amount even when auto-spend is enabled"),
305
362
  },
306
- async ({ wallet_id, max_per_tx, max_per_day }) => {
363
+ async ({ wallet_id, max_per_tx, max_per_day, require_confirmation_above }) => {
307
364
  const wallets = getWallets();
308
365
  const wallet = wallets.find((w) => w.id === wallet_id);
309
366
 
@@ -314,23 +371,36 @@ export function registerWalletTools(server: McpServer): void {
314
371
  );
315
372
  }
316
373
 
317
- if (max_per_tx == null && max_per_day == null) {
374
+ if (
375
+ max_per_tx == null &&
376
+ max_per_day == null &&
377
+ require_confirmation_above == null
378
+ ) {
318
379
  return text(
319
- "No policy changes specified. Provide max_per_tx and/or max_per_day.",
380
+ "No policy changes specified. Provide at least one policy field.",
320
381
  );
321
382
  }
322
383
 
384
+ const existing = getSpendPolicy(wallet_id) ?? {};
385
+ const nextPolicy = {
386
+ ...existing,
387
+ ...(max_per_tx != null ? { maxPerTxUsd: max_per_tx } : {}),
388
+ ...(max_per_day != null ? { maxPerDayUsd: max_per_day } : {}),
389
+ ...(require_confirmation_above != null ? { requireConfirmationAboveUsd: require_confirmation_above } : {}),
390
+ };
391
+ setSpendPolicy(wallet_id, nextPolicy);
392
+
323
393
  // Build policy summary
324
394
  const policies: string[] = [];
325
- if (max_per_tx != null) {
326
- policies.push(`Max per transaction: $${max_per_tx.toFixed(2)}`);
395
+ if (nextPolicy.maxPerTxUsd != null) {
396
+ policies.push(`Max per transaction: $${nextPolicy.maxPerTxUsd.toFixed(2)}`);
327
397
  }
328
- if (max_per_day != null) {
329
- policies.push(`Max per day: $${max_per_day.toFixed(2)}`);
398
+ if (nextPolicy.maxPerDayUsd != null) {
399
+ policies.push(`Max per day: $${nextPolicy.maxPerDayUsd.toFixed(2)}`);
400
+ }
401
+ if (nextPolicy.requireConfirmationAboveUsd != null) {
402
+ policies.push(`Require confirmation above: $${nextPolicy.requireConfirmationAboveUsd.toFixed(2)}`);
330
403
  }
331
-
332
- // Note: actual persistence depends on the config module supporting policy fields.
333
- // For now, we report what would be set. The core/config module will handle storage.
334
404
  return text(
335
405
  [
336
406
  `Spending policy set for wallet "${wallet_id}":`,